ADR-015 — Manual Override de Situação por Aluno (situacao_origem)
Status: Aceito · Data: 2026-06-11 · Categoria: Backend / Resultados Estende: ADR-014 — Premiação por Nível
1. Contexto
O coordenador escolar precisa, em casos reais de produção, ajustar a classificação final de um aluno independentemente do cálculo automático derivado de pontuacao + configuracoes_fase_nivel:
- Aluno em polo de aplicação (vaga extra concedida pela coordenação geral mesmo abaixo da nota de corte);
- Desempate manual (mesma pontuação no limite da faixa, decisão pedagógica de quem ascende);
- Correção pós-fato (revisão de prova) que muda nota sem mudar a classificação previamente comunicada.
Até esta decisão, a coluna situacao_origem em resultados_aluno existia apenas como flag interna da action set_premiacoes_manual (escopo: premiação na última fase), conforme ADR-014 §"Manual override". Qualquer recálculo subsequente (set_nota_corte, set_premiacao, upsert_batch) sobrescrevia a situação manual silenciosamente nas fases intermediárias.
2. Decisão
situacao_origem passa a ser contrato de primeira classe do domínio Resultados, com semântica única em todas as fases (não só a última) e em todos os tipos de situação terminal (medalhas + classificação + sem_premiacao):
Set canônico de situações manuais válidas:
medalha_ouro,medalha_prata,medalha_bronze,mencao_honrosa,sem_premiacao,classificado,nao_classificado. Exposto em_shared/resultados-compute.ts → SITUACOES_TERMINAIS_MANUAIS.Preservação fail-safe no recompute:
recomputeFaseForOlimpiadamantémsituacao_origem='manual'somente quando a situação ainda é válida para a configuração vigente da fase/nível (isSituacaoValidaParaConfig(situacao, cfg)). Se a config mudou de tal forma que a situação manual deixou de ter sentido (ex.: faixas removidas,medalha_ouromanual com cfg só-nota-corte), o sistema rebaixa paraautosilenciosamente — preferimos consistência a um override órfão.Action única para edição individual:
set_situacao_alunoaceita{ inscricaoId, faseId, situacao?, pontuacao?, origem }:origem='manual'→ exigesituacao∈ set canônico e válida para cfg da fase;origem='auto'→situacaoé ignorada (deriva depontuacao); reset do override.
Observabilidade: toda mutation em lote (
upsert_batch,set_premiacao_batch,set_nota_corte) retornamanualPreservados: number. O frontend exibe aviso quando> 0para que o coordenador revise.
3. Alternativas consideradas
| Alternativa | Por que não |
|---|---|
| A. Limitar override à última fase (mantém ADR-014 original) | Não cobre polo/desempate em fases intermediárias — caso real bloqueado em 2026-06. |
B. Coluna bloqueado_para_recompute boolean | Booleano não distingue manual de auto-validado; perde semântica para observabilidade e logs. |
C. Tabela separada resultados_aluno_overrides | Duplicação; força JOIN em todo render; sem ganho. |
D. (escolhida) situacao_origem text com validação dinâmica | Reaproveita coluna existente; uma única ação backend; recompute auto-consistente. |
4. Consequências
Positivas
- Override sobrevive ao recompute em qualquer fase, desde que coerente com cfg.
- Coordenador tem reset explícito (
origem='auto') — não há "estado preso". - Logs auditáveis:
resultado.set_situacao_alunoregistra{ antes, depois }comorigem.
Negativas / aceitas
- Mudar
configuracoes_fase_nivelpode invalidar silenciosamente overrides (rebaixados paraauto). Aceito pelo princípio fail-safe; o avisomanualPreservadosna UI orienta o coordenador. - Action
set_premiacoes_manual(legada) continua existindo para compatibilidade do fluxo de premiação em lote pós-Mural; deve migrar paraset_situacao_alunoem iteração futura.
5. Trade-offs
| Eixo | Escolha | Custo |
|---|---|---|
| Consistência vs. teimosia do override | Fail-safe: rebaixa quando inválido | Coordenador precisa reaplicar se config mudou drasticamente |
| Single-row action vs. batch | set_situacao_aluno é single | Edição em massa continua via set_premiacoes_manual |
| Validação no backend vs. no banco | Backend (helper puro) | Trigger SQL seria mais à prova de bypass — débito |
6. Conformidade
- [ ] Toda nova action que escreva em
resultados_aluno.situacaoDEVE setarsituacao_origemexplicitamente ('manual'ou'auto'). Default implícito é proibido. - [ ] Toda função de recompute DEVE consultar
situacao_origeme gatear a sobrescrita porisSituacaoValidaParaConfig. - [ ] Toda mutation em lote DEVE retornar
manualPreservados. - [ ] Frontend DEVE renderizar badge "Manual" quando
situacaoOrigem === 'manual'.
Como verificar:
rg "situacao_origem" supabase/functions/_shared/
rg "isSituacaoValidaParaConfig" supabase/functions/_shared/
rg "manualPreservados" src/hooks/7. Débito técnico
set_premiacoes_manualainda existe em paralelo; consolidar emset_situacao_aluno(batch mode) — backlog.- Validação
SITUACOES_TERMINAIS_MANUAISestá em código TS; replicar comoCHECK constraintem SQL é desejável (defesa em profundidade) — backlog.
8. Referências
- ADR-014 — Premiação por Nível
supabase/functions/_shared/resultados-compute.ts(SITUACOES_TERMINAIS_MANUAIS,isSituacaoValidaParaConfig,recomputeFaseForOlimpiada)supabase/functions/_shared/resultados-config.ts(handleSetSituacaoAluno)src/components/coordenador/resultados/editar-resultado-aluno-dialog.tsxdocs/features/RESULTADOS.md§"Override manual"