feat: fase 2 - detecção de crianças, tags, filtros e histórico

- GPT retorna JSON estruturado: criancas, turma, tags, relatorio
- Frontmatter agora inclui criancas[], turma, tags[] no .md
- Índice JSON estruturado com metadados completos
- Prévia do relatório mostra crianças detectadas + tags + turma
- Modal 'Minhas Observações' na sidebar
- Filtros por mês, tag e busca por criança
- Lista de observações com preview e chips
- Modal de detalhe com copiar texto
- CSS para btn-observacoes, obs-modal, obs-items, tags/chips
This commit is contained in:
2026-05-27 15:57:04 +00:00
parent ea8e33c891
commit 0b35055e23
4 changed files with 434 additions and 25 deletions
+44 -21
View File
@@ -252,24 +252,32 @@ app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'
}
// Gera relatório estruturado via GPT
// Envia transcrição + pedido de extrair: crianças, turma, tags, relatório
const reportPrompt = `Você é a Kemily, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
Ela acabou de gravar uma observação em voz sobre seus alunos da educação infantil. Sua tarefa é analisar a transcrição e gerar um RELATÓRIO ESTRUTURADO de observação pedagógica.
Ela acabou de gravar uma observação em voz sobre seus alunos da educação infantil. Sua tarefa é ANALISAR a transcrição e retornar UM OBJETO JSON (sem markdown, sem código, apenas o JSON puro) com:
REGRAS DO RELATÓRIO:
- Use linguagem formal-pedagógica com calor humano
- Estruture em seções claras com_MARKDOWN
- Identifique crianças mencionadas, comportamentos, interações, conquistas, dificuldades
- Inclua sugestões de seguimento para o planejamento
- Foque em evidências observáveis, não interpretções subjetivas
- Seja objetiva mas detalhista
{
"criancas": ["lista de nomes de crianças mencionadas ou 'null' se não identificar nenhuma"],
"turma": "turma mencionada ou 'não informada'",
"tags": ["tag1", "tag2"] (social, cognitivo, motor, linguagem, emocional,轮的哪个),
"relatorio": "...markdown estruturado do relatório pedagógico..."
}
TRANSCRIÇÃO DA OBSERVAÇÃO:
REGRAS:
- Identifique crianças pelo nome próprio mencionado na fala
- Se não conseguir identificar nomes, use "criancas": null
- Tags: escolha entre social, cognitivo, motor, linguagem, emocional, colaborativo, autónomo
- Relatório em português, linguagem formal-pedagógica, seções claras em **markdown**
- Foque em evidências observáveis
- Máximo 120 palavras no relatório
TRANSCRIÇÃO:
"""
${transcribedText}
"""
Gerando o relatório...`;
Retorne APENAS o JSON válido:`;
const reportRes = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
@@ -290,11 +298,29 @@ Gerando o relatório...`;
}
const reportData = await reportRes.json();
const reportText = reportData.choices?.[0]?.message?.content || 'Relatório não pôde ser gerado.';
const rawContent = reportData.choices?.[0]?.message?.content || '';
// Parse JSON da resposta
let parsedData = { criancas: null, turma: 'não informada', tags: [], relatorio: rawContent };
try {
// Limpa markdown code blocks se houver
const cleanJson = rawContent.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
const parsed = JSON.parse(cleanJson);
parsedData = {
criancas: parsed.criancas || null,
turma: parsed.turma || 'não informada',
tags: parsed.tags || [],
relatorio: parsed.relatorio || rawContent
};
} catch (parseErr) {
// Se não conseguir parsear JSON, retorna o texto como relatório
console.warn('Não foi possível parsear JSON do GPT, usando texto bruto');
parsedData.relatorio = rawContent;
}
res.json({
transcribedText,
report: reportText
...parsedData
});
} catch (err) {
@@ -306,7 +332,7 @@ Gerando o relatório...`;
// Rota: POST /api/observacao/salvar — salva relatório em .md
app.post('/api/observacao/salvar', requireAuth, (req, res) => {
const { report } = req.body;
const { report, criancas, turma, tags } = req.body;
if (!report) return res.status(400).json({ error: 'Relatório vazio' });
const now = new Date();
@@ -316,13 +342,7 @@ app.post('/api/observacao/salvar', requireAuth, (req, res) => {
const timestamp = now.toISOString();
// Monta markdown com frontmatter
const mdContent = `---
data: "${dateStr}"
hora: "${timeStr}"
timestamp: "${timestamp}"
---
${report}
const mdContent = `---\ndata: "${dateStr}"\nhora: "${timeStr}"\ntimestamp: "${timestamp}"\ncriancas: ${JSON.stringify(criancas)}\nturma: "${turma || 'não informada'}"\ntags: ${JSON.stringify(tags)}\n---\n\n${report}
`;
const filename = `${dateStr}_${timeStr}.md`;
@@ -332,7 +352,7 @@ ${report}
const filePath = path.join(monthDir, filename);
fs.writeFileSync(filePath, mdContent, 'utf8');
// Atualiza índice
// Atualiza índice com metadados estruturados
const idx = readObsIndex();
idx.observations.unshift({
id: Date.now(),
@@ -340,6 +360,9 @@ ${report}
date: dateStr,
time: timeStr,
timestamp,
criancas: criancas || null,
turma: turma || 'não informada',
tags: tags || [],
preview: report.slice(0, 120) + '...'
});
writeObsIndex(idx);