Code Review

Inteligência Artificial
no i-Diário

Guia para Code Review — 4 PRs em 2 repositórios.
Abra cada PR, entenda o contexto e revise.

4 PRs em 2 repos
56 arquivos
+5.150 linhas
15 commits
Rolar para explorar

Por que IA no i-Diário e como se integra

O i-Diário é usado por milhares de professores em todo o Brasil. A IA vem para automatizar tarefas repetitivas, economizar tempo e melhorar a qualidade dos registros educacionais.

!

Decisão Arquitetural Importante

As funcionalidades de IA não vão para a comunidade open source agora. Elas vivem exclusivamente no engine i-diario-plus, que é proprietário da Portabilis. O i-diario-portabilis recebe apenas pontos de extensão vazios — partials com um comentário e nada mais — e esse código é replicado para o repositório open source i-diario.

i-diario-portabilis
  • Repo privado da Portabilis
  • Tudo que entra aqui vai para o open source (i-diario)
  • Funcionalidades básicas completas
  • Pontos de extensão para plugins
  • Nenhuma lógica de IA aqui
i-diario-plus (engine)
  • Repo privado da Portabilis
  • Nunca vai para o open source
  • Chat de frequência com IA
  • Assistente de voz para notas
  • Painel de configurações de IA

Padrão "Partial Slots"

Este é o padrão central. O app principal cria partials vazias como "slots" que o engine preenche com conteúdo real.

Quando o engine NÃO está instalado
Rails App
i-diario-portabilis
_plugin_widget.html.erb
<!-- vazio -->
Renderiza nada
Zero impacto
Quando o engine ESTÁ instalado
Rails App
i-diario-portabilis
_plugin_widget.html.erb
Chat Widget completo
OpenAI API
GPT-4o mini + Whisper
💡

Como funciona o prepend_view_path

O engine usa prepend_view_path para que suas views tenham prioridade sobre as do app principal. Quando o Rails procura shared/_plugin_widget, verifica primeiro o path do engine. Se encontra lá, usa aquela versão. Caso contrário, usa a partial vazia do app principal.

Camadas da Arquitetura

Frontend
Layout application.html.erb
Chat Widget (JavaScript)
Voice Assistant (JavaScript)
Vue.js / jQuery (existente)
Controllers
ChatController
DescriptiveExamAssistantCtrl
AiConfigurationsController
Controllers existentes
Services
Chat::Orchestrator
Chat::ContextBuilder
Chat::AttendanceRegistrar
NoteFormatter
PromptRenderer
LLM / APIs
OpenAI GPT-4o mini
OpenAI Whisper
Llm::ProviderFactory
Llm::OpenaiProvider
Dados
ai_configurations
chat_sessions
chat_messages
daily_frequencies (existente)
App principal (vai para o open source)
Engine i-diario-plus (privado)
APIs externas
Banco de dados

Ordem de merge das PRs

PR #4894 — Plugin Slots
i-diario-portabilis · 2 arquivos · +5 linhas
independente
PR #346 — Infraestrutura + Config
i-diario-plus · 22 arquivos · +623 linhas
depende da #346
PR #347 — Chat de Frequência
i-diario-plus · 23 arquivos · +3.176 linhas
PR #348 — Assistente de Voz
i-diario-plus · 9 arquivos · +1.346 linhas

Plugin Slots no App Principal

Apenas 5 linhas de código. Nenhuma lógica de IA. Apenas "slots" vazios para o engine preencher.

Arquivos alterados

app/views/
  layouts/
    application.html.erb +2 linhas (render do plugin_widget)
  shared/
    _plugin_widget.html.erb novo — slot para o widget de chat
  descriptive_exams/
    edit.html.erb +1 linha (render do plugin_scripts)
    _plugin_scripts.html.erb novo — slot para scripts do assistente de voz

Diffs detalhados

app/views/shared/_plugin_widget.html.erb Novo
<%# Plugin extension point - can be overridden by engine plugins %>
app/views/descriptive_exams/_plugin_scripts.html.erb Novo
<%# Plugin extension point - can be overridden by engine plugins %>
app/views/layouts/application.html.erb Modificado
<%= stylesheet_pack_tag 'app' %> <%= yield :js %>++ <%= render 'shared/plugin_widget' %> </body></html>
app/views/descriptive_exams/edit.html.erb Modificado
<% content_for :js do %> <%= javascript_include_tag 'views/descriptive_exams/functions' %> <%= javascript_include_tag 'views/descriptive_exams/student_fields' %>+ <%= render 'descriptive_exams/plugin_scripts' %><% end %>
🔍

Por que nomes genéricos?

As partials usam nomes como _plugin_widget e _plugin_scripts em vez de _ai_chat_widget. Isso é intencional: não acopla o ponto de extensão a uma funcionalidade específica (IA) e permite que qualquer plugin/engine use esses slots no futuro.

Pronto para revisar esta PR?

4 arquivos · +5 linhas · Risco extremamente baixo

Abrir PR #4894 →

Infraestrutura + Configurações de IA

A base de tudo: model de configuração, CRUD admin, provider de LLM, sistema de prompts e migrations. Deve ser mergeada primeiro.

Painel de Configurações de IA

Acessível apenas para administradores, no menu Administrativo > Configurações de IA. Permite ligar/desligar cada feature de IA independentemente.

Configuração Tipo Padrão
chat_enabled Boolean (toggle) Desativado
descriptive_ai_enabled Boolean (toggle) Desativado
descriptive_ai_prompt Texto livre Template padrão

Tudo é opt-in

As features de IA vêm desabilitadas por padrão. O administrador precisa explicitamente ativar cada uma. Sem chave de API do OpenAI configurada nos secrets, nada funciona.

Abstração de LLM Provider

O sistema usa um Factory Pattern para desacoplar a implementação do provider de LLM. Hoje usa OpenAI, mas a arquitetura permite adicionar Claude, Gemini ou outros facilmente.

app/services/chat/llm/provider_factory.rb Ruby
class Chat::Llm::ProviderFactory PROVIDERS = { 'openai' => 'Chat::Llm::OpenaiProvider' }.freeze def self.build(provider_name = nil) provider_name ||= Rails.application.secrets[:LLM_PROVIDER] || 'openai' PROVIDERS[provider_name].constantize.new end end

Sistema de Prompts

Os prompts são templates ERB armazenados em app/prompts/, renderizados pelo PromptRenderer. Isso separa o conteúdo do prompt da lógica de código.

Schema: ai_configurations

Tabela Campos Principais Propósito
ai_configurations chat_enabled, descriptive_ai_enabled, descriptive_ai_prompt Singleton com feature flags e prompts customizáveis

Esta é a base — deve ser mergeada primeiro

22 arquivos · +623 linhas · As PRs #347 e #348 dependem desta

Abrir PR #346 →

Chat de Frequência por IA

O professor registra frequência por texto ou voz, usando linguagem natural. A feature mais complexa, com orchestrator, context builder e integração nativa com o i-Diário.

Como funciona na prática

Em vez de marcar cada aluno individualmente em uma tabela, o professor diz algo como:

PROFESSOR DIZ:
"Na turma 4A de hoje, faltaram a Ana e o João Pedro"
IA RESPONDE:
Entendi! Vou registrar a frequência da Turma 4A para hoje:
❌ Ana Silva — Ausente
❌ João Pedro — Ausente
✅ Demais 28 alunos — Presentes

Posso confirmar o registro?

Como funciona por baixo

📝

Contexto Rico

A IA recebe o nome do professor, turmas, lista de alunos com IDs, datas e o histórico da conversa. Tudo isso no system prompt.

🧠

Interpretação Inteligente

A IA faz fuzzy matching de nomes (ex: "a Ana" vira "Ana Silva"), infere datas (ex: "hoje"), e seleciona turma automaticamente quando o professor tem só uma.

Confirmação Obrigatória

Nenhum registro é feito sem confirmação explícita do professor. A IA apresenta um resumo e espera o "sim" antes de persistir no banco.

💾

Integração Nativa

Usa os mesmos services existentes do i-Diário (DailyFrequenciesCreator) para gravar a frequência, respeitando calendário e deadlines.

Fluxo completo

💬 Fluxo do Chat de Frequência
1

Professor envia mensagem

Texto ou áudio pelo widget de chat no canto da tela. Vai para ChatController#create_message.

2

Transcrição (se áudio)

Se for áudio, o WhisperTranscriber converte para texto via API OpenAI Whisper (whisper-1, pt-BR).

3

Construção do contexto

ContextBuilder monta o system prompt com: nome do professor, turmas, lista de alunos com IDs, data atual, e sessão anterior.

4

Chamada ao LLM

OpenaiProvider envia para GPT-4o mini com response_format JSON. O modelo retorna uma ação estruturada.

5

Roteamento da resposta

O Orchestrator analisa a ação retornada: register_attendance (mostrar resumo e pedir confirmação), ask_clarification (pedir mais info), ou general_response (conversa livre).

6

Confirmação e registro

Professor confirma → AttendanceRegistrar usa DailyFrequenciesCreator do i-Diário para persistir a frequência. Valida calendário, deadline e dia letivo.

System Prompt do Chat

📝

System Prompt — Chat de Frequência

Personalidade: Amigável e conversacional, não robótico

Contexto injetado:
• Nome do professor: <%= teacher_name %>
• Escola: <%= unity_name %>
• Ano letivo: <%= school_year %>
• Turmas e alunos com IDs
• Data atual e última turma usada

Ações possíveis (JSON):
register_attendance — registrar frequência
ask_clarification — pedir mais informações
general_response — conversa geral

Regras especiais:
• Fuzzy matching para nomes de alunos
• Default para data de hoje
• Auto-select turma se só tem uma
• Markdown na resposta
• 6 exemplos de cenários no prompt

Schema do banco

Tabela Campos Principais Propósito
chat_sessions user_id, teacher_id, entity_id, conversation_state, pending_action Sessão de chat por professor, com estado da conversa e ação pendente (JSONB)
chat_messages chat_session_id, role, content, audio, metadata Histórico de mensagens (user/assistant/system) com suporte a áudio

Limites e Proteções

🔒

Rate Limiting

Máximo de 10 mensagens por minuto por sessão. Protege contra uso excessivo da API.

💾

Tamanho de Áudio

Limite de 10 MB por arquivo de áudio (base64). Previne uploads gigantescos.

📐

Histórico de Contexto

O LLM recebe apenas as últimas 10 mensagens da conversa, evitando excesso de tokens.

📅

Validações de Data

O registrador valida: não é data futura, está no calendário escolar, dentro do prazo de lançamento, e é dia letivo.

Depende da PR #346

23 arquivos · +3.176 linhas · A feature mais complexa

Abrir PR #347 →

Assistente de Voz para Notas Descritivas

O professor dita a nota do aluno por voz. A IA transcreve e formata em linguagem formal educacional, seguindo as normas do MEC.

Como funciona na prática

Na tela de avaliações descritivas, o professor pode ditar a nota do aluno por voz.

PROFESSOR DITA:
"O aluno ta se desenvolvendo bem na leitura, ainda tem dificuldade com escrita cursiva mas ta melhorando bastante"
IA FORMATA:
"O estudante apresenta desenvolvimento satisfatório na leitura, demonstrando progresso consistente na compreensão textual. No que se refere à escrita cursiva, ainda encontra dificuldades, porém evidencia avanço significativo ao longo do período avaliado."
🎤

Transcrição com Whisper

Usa a API OpenAI Whisper (modelo whisper-1) para converter áudio em texto, com suporte nativo a português brasileiro.

✍️

Formatação com GPT-4o mini

O texto transcrito passa por um segundo processamento que o transforma em linguagem formal, pedagogicamente adequada para boletins escolares.

🔄

Fallback Inteligente

Se a formatação falhar, o professor recebe a transcrição bruta. Nunca perde o que ditou.

⚙️

Prompt Customizável

O administrador pode personalizar o prompt de formatação via painel de configurações, sem alterar código.

Fluxo completo

🎤 Fluxo do Assistente de Voz (Notas Descritivas)
1

Professor clica no microfone

Na tela de edição de avaliações descritivas, o botão de gravação aparece ao lado do campo de texto do aluno.

2

Gravação do áudio

JavaScript captura áudio pelo navegador, converte para WAV (8kHz), e envia como base64 para o backend.

3

Transcrição com Whisper

WhisperTranscriber recebe o áudio e retorna texto em português.

4

Formatação com GPT

NoteFormatter envia a transcrição + contexto (aluno, turma, disciplina) para o GPT-4o mini, que reescreve em linguagem formal educacional.

5

Resultado no editor

O texto formatado é inserido no campo SummerNote do aluno. O professor revisa, edita se quiser, e salva.

System Prompt de Notas Descritivas

✍️

System Prompt — Notas Descritivas

Papel: Especialista em redação de notas descritivas para boletins escolares

Diretrizes MEC:
• Linguagem formal e pedagógica
• Terceira pessoa ("O/A estudante...")
• Tom positivo e construtivo
• Não inventar informação
• Texto corrido, sem formatação

User Prompt (contexto):
• Turma: <%= classroom_name %>
• Disciplina: <%= discipline_name %>
• Estudante: <%= student_name %>

• Professor ditou: "<%= transcription %>"

Reescreva como nota formal para boletim.

Depende da PR #346

9 arquivos · +1.346 linhas · Feature independente do Chat

Abrir PR #348 →

Como protegemos os dados

Decisões arquiteturais que garantem a segurança e privacidade dos dados educacionais.

🔒 Multi-tenancy e isolamento de dados

Cada entidade (rede escolar) tem seu próprio banco de dados no i-Diário. Os arquivos de áudio são armazenados em diretórios separados por entity_id: development/entity-{id}/chat_audio/{session_id}/. As sessões de chat incluem entity_id como chave estrangeira obrigatória.

🔐 Chaves de API seguras

As chaves da API OpenAI são armazenadas em Rails.application.secrets, nunca hardcoded no código. Sem a chave configurada, as features simplesmente não funcionam — sem erros visíveis para o usuário.

👥 Controle de acesso por role

Chat: Apenas professores com registro ativo podem usar. O controller verifica require_teacher em todas as ações.

Configurações: Apenas administradores podem acessar. O decorator verifica admin? para as features de IA.

Assistente de Voz: Apenas professores com acesso a tela de avaliações descritivas.

📑 Auditoria e monitoramento

O model AiConfiguration usa a gem audited, registrando todas as mudanças de configuração com quem, quando e o que mudou.

Erros de API são reportados ao Honeybadger para monitoramento em tempo real. Todas as mensagens do chat ficam persistidas no banco para auditoria posterior.

🗃️ Limpeza de arquivos temporários

O WhisperTranscriber cria arquivos temporários durante a transcrição. Esses arquivos são sempre removidos no bloco ensure, mesmo em caso de erro. Nenhum dado de áudio temporário permanece no servidor.

LGPD & Proteção de Dados

Análise jurídica em andamento sobre conformidade com a Lei Geral de Proteção de Dados para uso de IA em contexto educacional.

⚠️
Consulta jurídica em andamento — O time jurídico da Portabilis está avaliando os pontos abaixo com advogados especializados em LGPD. As funcionalidades de IA só serão habilitadas em produção após parecer favorável.

Contexto

As features de IA utilizam provedores externos (OpenAI, Anthropic, Groq) para duas funcionalidades: speech-to-text (transcrição de áudio) e LLM (processamento de linguagem natural). Os dados processados incluem informações sobre frequência de alunos e avaliações descritivas em contexto educacional público (redes municipais e estaduais).

🌐 Transferência Internacional de Dados (Art. 33 LGPD)

Os provedores de IA (OpenAI, Anthropic, Groq) processam dados em servidores fora do Brasil. A LGPD permite transferência internacional quando há garantias adequadas:

  • Todos os provedores oferecem Data Processing Agreements (DPA)
  • Cláusulas contratuais padrão (SCCs) estão incluídas nos termos
  • Os dados enviados são contextuais (nomes de alunos, datas de aula, frequência) — não incluem documentos sensíveis como CPF, endereço ou dados de saúde
👶 Tratamento de Dados de Crianças e Adolescentes (Art. 14 LGPD)

O i-Diário gerencia dados de alunos da educação básica, incluindo crianças e adolescentes. A LGPD exige que o tratamento desses dados seja realizado no melhor interesse do menor e com consentimento específico dos responsáveis.

  • A IA processa apenas dados já existentes no sistema (frequência, avaliações)
  • Não há coleta adicional de dados dos alunos para fins de IA
  • O processamento visa auxiliar o professor, não perfilar o aluno
  • Necessidade de avaliar se o consentimento existente cobre o uso por IA
🔒 Dados Sensíveis (Art. 11 LGPD)

Dados sobre crianças em contexto educacional podem ser considerados sensíveis. A análise jurídica está avaliando:

  • Se dados de frequência/avaliação escolar configuram dados sensíveis
  • Qual base legal é mais adequada (consentimento vs. execução de políticas públicas)
  • Se o uso por IA altera a finalidade original do tratamento
  • Necessidade de atualização dos termos de uso e política de privacidade
📋 Relatório de Impacto (RIPD)

A ANPD pode exigir um Relatório de Impacto à Proteção de Dados Pessoais (RIPD) quando há tratamento de dados sensíveis ou de menores. O relatório deve conter:

  • Descrição dos processos de tratamento que envolvem IA
  • Medidas de segurança e mitigação de riscos implementadas
  • Análise de proporcionalidade e necessidade do tratamento
  • Avaliação dos riscos aos titulares (alunos, professores)
📄 Provedores e Acordos de Processamento (DPAs)

Todos os provedores considerados possuem DPAs e políticas de dados empresariais:

🤖

OpenAI

Whisper (transcrição) + GPT-4o mini (LLM). DPA disponível. Dados da API não usados para treino.

Groq

Alternativa de alta performance para LLM. DPA disponível. Processamento em hardware dedicado.

💬

Anthropic

Claude como alternativa de LLM. DPA disponível. Foco em segurança de IA.

💡
Importante: Nas APIs empresariais de todos esses provedores, os dados enviados não são utilizados para treinamento dos modelos. Isso está previsto contratualmente nos respectivos DPAs.
📣 Avisos e Consentimento do Titular

Independente da base legal escolhida, os titulares (ou responsáveis, no caso de menores) precisam ser informados sobre o uso de IA. Pontos em análise:

  • Atualização da Política de Privacidade mencionando processamento por IA
  • Atualização dos Termos de Uso com cláusulas específicas
  • Necessidade (ou não) de consentimento específico para features de IA
  • Comunicação às Secretarias de Educação sobre a funcionalidade

FAQ

Respostas para as dúvidas mais comuns sobre IA no i-Diário.

🤔 Por que dividir em 4 PRs?

O trabalho original era uma única PR monolítica com 56 arquivos e +5.150 linhas. Dividimos em 4 PRs para facilitar o review:

PR #4894 (App Principal) — Apenas 5 linhas, pontos de extensão vazios. Review rápido.
PR #346 (Infra) — Toda a base compartilhada: config, providers, prompts, migrations. Merge primeiro.
PR #347 (Chat) — Feature completa do chat de frequência. Pode ser revisada independente da #348.
PR #348 (Voz) — Feature completa do assistente de voz. Pode ser revisada independente da #347.

Cada PR é autocontida e pode ser entendida sem ler as outras. A única dependência é que #347 e #348 precisam da #346 mergeada primeiro.

🤔 A comunidade open source vai ter acesso às features de IA?

Não neste momento. Toda a lógica de IA vive no engine i-diario-plus, que é um repositório privado da Portabilis e nunca vai para o open source. O i-diario-portabilis recebe apenas pontos de extensão vazios (que depois vão para o i-diario open source). Sem o engine instalado, a experiência do i-Diário é exatamente a mesma de antes.

🤔 Qual o custo das APIs de IA?

Os preços atuais da OpenAI (fev/2026):

Serviço Preço Equivalente
GPT-4o mini (input) US$ 0,15 / 1M tokens ~2.500 páginas por US$ 0,15
GPT-4o mini (output) US$ 0,60 / 1M tokens 16x mais barato que o GPT-4o
Whisper (transcrição) US$ 0,006 / minuto US$ 0,36 por hora de áudio

Na prática: Uma mensagem de chat (prompt + resposta) custa frações de centavo. Uma transcrição de 30 segundos de áudio custa US$ 0,003. O rate limiting (10 msgs/min por sessão) ajuda a controlar uso. A chave de API é configurada pelo administrador do sistema.

Fonte: openai.com/api/pricing

🤔 Posso trocar o OpenAI por outro provider (Claude, Gemini)?

Sim! A arquitetura já foi pensada para isso. O ProviderFactory aceita diferentes providers no hash PROVIDERS. Para adicionar um novo, basta criar uma classe que herda de BaseProvider e implementar chat_completion. O provider ativo é definido pelo secret LLM_PROVIDER.

🤔 O que acontece se a API da OpenAI cair?

O sistema tem fallback em vários níveis:

Chat: Retorna uma mensagem amigável de erro ao professor. O Honeybadger é notificado. A sessão permanece intacta para retentativa.

Assistente de Voz: Se a formatação falhar, o professor recebe a transcrição bruta. Se a transcrição também falhar, recebe erro com explicação.

Em nenhum caso dados são perdidos.

🤔 Como a IA sabe quem são os alunos de cada turma?

O ContextBuilder consulta o banco do i-Diário em tempo real. Ele busca todas as turmas de frequência geral do professor, e para cada turma lista os alunos ativamente matriculados na data corrente. Essas informações (com nomes e IDs) são injetadas no system prompt, permitindo que a IA identifique alunos por nome, apelido ou parte do nome.

🤔 O professor pode ditar frequência por WhatsApp?

O schema já suporta o canal whatsapp na tabela chat_sessions, mas a implementação atual é apenas via web. A infraestrutura está pronta para expandir para WhatsApp no futuro.

🤔 Essas PRs quebram alguma coisa no app principal?

Não. A PR #4894 adiciona apenas partials com um comentário ERB — renderizam literalmente nada. As PRs #346, #347 e #348 ficam no engine i-diario-plus, que só é carregado quando instalado. Sem o engine, essas linhas de código são invisíveis e inofensivas. O risco é extremamente baixo.