Review Guiado

Inteligência Artificial
no i-Diário

Um guia completo sobre como funciona a integração de IA no i-Diário, a arquitetura engine + app principal, e como tudo se encaixa.

4 PRs em 2 repos
78 arquivos
+6.734 linhas
23 commits
Rolar para explorar

Por que IA no i-Diário?

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
4
Pull Requests
78
Arquivos alterados
+6.734
Linhas adicionadas
2
Repositorios

Como o Engine se integra ao App

O i-diario-plus é um Rails Engine que estende o app principal usando o padrão Decorator e Partial Slots. Quando instalado, ele sobrescreve comportamentos sem modificar o código-base.

Padrão "Partial Slots"

Este é o padrão central usado na PR #4894. 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

O que a IA faz no i-Diário

Duas funcionalidades distintas, cada uma resolvendo uma dor real dos professores.

Chat de Frequência por IA

O professor pode registrar a frequência dos alunos por texto ou voz, usando linguagem natural. Em vez de marcar cada aluno individualmente em uma tabela, ele 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.

Assistente de Voz para Notas Descritivas

Na tela de avaliações descritivas, o professor pode ditar a nota do aluno por voz. A IA transcreve e depois formata o texto em linguagem formal educacional, seguindo as normas do MEC.

PROFESSOR DITA:
"O aluno tá se desenvolvendo bem na leitura, ainda tem dificuldade com escrita cursiva mas tá 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.

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.

O que muda 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.

PRs relacionadas (engine i-diario-plus)

#342

Chatbot de Frequência

Chat controller, orchestrator, context builder, attendance registrar, LLM provider, whisper transcriber, modelos, migrations, testes.

#343

Assistente de Voz

Voice assistant JavaScript, controller de transcrição, NoteFormatter service, prompt templates, integração com SummerNote.

#344

Configurações de IA

AiConfiguration model, admin controller/views, feature flags, decorators (features, user, navigation), migrations.

Passo a passo de cada feature

Acompanhe o fluxo completo desde a ação do professor até a persistência no banco de dados.

💬 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.

🎤 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.

Anatomia das peças-chave

Mergulho nos componentes mais importantes da implementação.

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.

📝

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
✍️

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.

Schema do Banco de Dados

Tabela Campos Principais Propósito
ai_configurations chat_enabled, descriptive_ai_enabled, descriptive_ai_prompt Singleton com feature flags e prompts customizáveis
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.

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 à 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.

FAQ

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

🤔 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.

🤔 Essa PR quebra alguma coisa no app principal?

Não. As partials adicionadas contêm apenas um comentário ERB. Renderizam literalmente nada. O risco é extremamente baixo. Sem o engine, essas 5 linhas de código são invisíveis e inofensivas.