Skip to content

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):

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

  2. Preservação fail-safe no recompute:recomputeFaseForOlimpiada mantém situacao_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_ouro manual com cfg só-nota-corte), o sistema rebaixa para auto silenciosamente — preferimos consistência a um override órfão.

  3. Action única para edição individual: set_situacao_aluno aceita { inscricaoId, faseId, situacao?, pontuacao?, origem }:

    • origem='manual' → exige situacao ∈ set canônico e válida para cfg da fase;
    • origem='auto'situacao é ignorada (deriva de pontuacao); reset do override.
  4. Observabilidade: toda mutation em lote (upsert_batch, set_premiacao_batch, set_nota_corte) retorna manualPreservados: number. O frontend exibe aviso quando > 0 para que o coordenador revise.

3. Alternativas consideradas

AlternativaPor 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 booleanBooleano não distingue manual de auto-validado; perde semântica para observabilidade e logs.
C. Tabela separada resultados_aluno_overridesDuplicação; força JOIN em todo render; sem ganho.
D. (escolhida) situacao_origem text com validação dinâmicaReaproveita 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_aluno registra { antes, depois } com origem.

Negativas / aceitas

  • Mudar configuracoes_fase_nivel pode invalidar silenciosamente overrides (rebaixados para auto). Aceito pelo princípio fail-safe; o aviso manualPreservados na 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 para set_situacao_aluno em iteração futura.

5. Trade-offs

EixoEscolhaCusto
Consistência vs. teimosia do overrideFail-safe: rebaixa quando inválidoCoordenador precisa reaplicar se config mudou drasticamente
Single-row action vs. batchset_situacao_aluno é singleEdição em massa continua via set_premiacoes_manual
Validação no backend vs. no bancoBackend (helper puro)Trigger SQL seria mais à prova de bypass — débito

6. Conformidade

  • [ ] Toda nova action que escreva em resultados_aluno.situacao DEVE setar situacao_origem explicitamente ('manual' ou 'auto'). Default implícito é proibido.
  • [ ] Toda função de recompute DEVE consultar situacao_origem e gatear a sobrescrita por isSituacaoValidaParaConfig.
  • [ ] Toda mutation em lote DEVE retornar manualPreservados.
  • [ ] Frontend DEVE renderizar badge "Manual" quando situacaoOrigem === 'manual'.

Como verificar:

bash
rg "situacao_origem" supabase/functions/_shared/
rg "isSituacaoValidaParaConfig" supabase/functions/_shared/
rg "manualPreservados" src/hooks/

7. Débito técnico

  • set_premiacoes_manual ainda existe em paralelo; consolidar em set_situacao_aluno (batch mode) — backlog.
  • Validação SITUACOES_TERMINAIS_MANUAIS está em código TS; replicar como CHECK constraint em 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.tsx
  • docs/features/RESULTADOS.md §"Override manual"