# norteia — metodologia (regras vigentes) Referência atual do motor de estudo. Só o que vale **hoje** (revisão de 19/10/2026); o que foi substituído está em [HISTORICO.md](HISTORICO.md), com a data e o link para a regra que o substituiu. Cada regra cita o módulo/função que a implementa; os testes que a cobrem estão em `local/test_*.py` (ver [CONTRIBUINDO.md](CONTRIBUINDO.md)). Código: `rules.py` (pool, evidência e domínio), `engine.py` (prioridade, tempo, calendário), `server.py` (validação dos registros), `simulado.py`, `tracking.py`, `tec_sync.py`, `calculo.py`, `fundamentos.py`, `edital/dificuldade.py`. Conteúdo (teoria, fichas, análises, IA) está em [CONTEUDO.md](CONTEUDO.md); telas em [DESIGN_SYSTEM.md](DESIGN_SYSTEM.md); operação do TEC em [OPERACAO_TEC.md](OPERACAO_TEC.md). Princípio de base (decisão de 17/10/2026, [`planejamento/estrategia_reversa_v2.md`](../../planejamento/estrategia_reversa_v2.md)): **a questão real é a unidade de partida**. Prova real → questão válida → objeto cobrado → aderência ao edital → dificuldade observada → fundamento mínimo → explicação → outra questão. Não se preenche a falta de evidência com questões inventadas; minutos registrados nunca comprovam domínio. --- ## 1. Pool de questões e prioridade **Questões que contam (`rules.study_pool`, `rules.pool_index`).** O *study_pool* de um subitem reúne questões reais (`kind: real`, com URL, não anuladas nem desatualizadas — `rules.real`), de aderência `direta` (`rules.eligible`) e **com gabarito**, selecionadas por faixa de proximidade da banca alvo (`rules.tier`/`rules._select`): 1 = CEBRASPE 2022+ em TI/Dados/Controle-TI; 2 = outras áreas da mesma banca; 3 = CEBRASPE antes de 2022; 4 = outras bancas. A faixa seguinte é incorporada enquanto houver menos de oito questões. Questões sem gabarito não entram no pool nem nas contagens por nível. Questões de aderência `parcial` existem no banco, mas são sempre prática. **Prioridade única (`engine.relevance_tiers`).** Cada subitem recebe uma faixa pela contagem de questões CEBRASPE 2022–2026 nos assuntos TEC pareados (`edital/pareamento/relevancia.json`): tiers 0 = Muito alta, 1 = Alta, 2 = Média, 3 = Baixa. Os cortes são **por valor** — quartis das contagens maiores que zero dentro de cada prova (`cut=[vals[ceil(n·f)−1]]`), então a mesma contagem cai sempre na mesma faixa; sem contagem = Baixa. A faixa ordena o plano e é a mesma mostrada na interface. Desempate: massa histórica normalizada do TCU e, na falta dela, questões ainda não acertadas por minuto (`raw_return`). **Ordem da fila (`engine.priorities`, `out.sort`).** `manual_order` (ordem manual, Meu material) → `prio_tier` → relevância → `score` → `raw_return` → id. Esse `rank` (`topic.rank`) é a ordem do plano, das baterias, do lote de dificuldade **e da tabela de Matérias** (19/10/2026; a tela só ordena por `rank`, sem regra própria): adiados aparecem depois da sua faixa e Baixa adiada no fim (ver Adiamento abaixo). **Adiamento ("Pular por enquanto", `deferred`; `engine.priorities`).** Nunca antecipa: `prio_tier = max(1.5, tier + 0.5)` → Muito alta/Alta vão para logo depois do bloco alto (1,5), Média para depois das médias (2,5), Baixa para depois de tudo (3,5). A faixa de origem (`prio_base`) continua valendo para a leitura do resumo base. Pode ser desfeito a qualquer momento. **Data preferida e ordem manual (`state.topics[id].pin` / `.order`; `engine.candidates_for`, `engine.next_piece`).** "Trazer para hoje" / "Trazer para " grava `pin` (AAAA-MM-DD). Antes da data o lote não entra; **no dia do pin** o lote vai para a frente da fila da sua prova (chave de ordenação 0) e, a partir dele, volta à ordem normal. As **bases pendentes do subitem com pin vencido sobem junto** (`pinned_bases`). A ordem manual (`order`) vem antes da faixa na ordenação. `backlog` tira o subitem do plano até "Reativar". **Fim da coleta.** Não há vagas de "coleta" no calendário: as questões vêm da importação do TEC. Sessões antigas com nível `coleta` continuam legíveis (inclusive em backups), mas o servidor não aceita novos registros desse nível (`server.SESSION_LEVELS` × `LEGACY_SESSION_LEVELS`). ## 2. Estudo reverso, leitura e modo transição **Questões primeiro, leitura opcional (18/10/2026, decisão do usuário; `engine.topic_time(read_first)`, `engine.plan`).** Por padrão (`settings.read_first = false`) o tópico com questões começa pelas questões: o plano **não** gera a tarefa "Ler o resumo" e o tempo de leitura **não** entra no planejado (`time.total_estimated`/`total_adjusted`, demanda, custo da ordenação); `time.reading` continua exposto com `time.reading_optional = true`. O resumo fica na aba Teoria (ver [CONTEUDO.md](CONTEUDO.md), "Teoria única") e é chamado sob demanda: ao errar ou consultar o gabarito antes de responder, o Gabarito comentado mostra o link para a seção da teoria cujo `question_ids` contém a questão. Ao abrir um tópico, a aba padrão é "Resolver questões"; sem questões reais, a Teoria (ou, sem resumo, os exercícios autorais). **Modo transição (`settings.read_first = true`, booleano validado em `server.validate_settings`).** Restaura a leitura obrigatória: subitem com questões e resumo recebe "Ler o resumo" (`leitura:`, nível `leitura`, inteira, nunca fatiada) **antes** das questões — os lotes de questões só entram depois de a leitura ter sido registrada ou agendada (`engine.candidates_for`); o tempo de leitura volta ao total e o tópico abre na aba Teoria. **Leitura do resumo base (`engine.plan`, lista `readers`).** Subitem **sem questões diretas** com gabarito recebe uma única tarefa "Ler o resumo base" (nível `leitura`) nos dois modos, somente se existir resumo `kind: base` (`data/materials/.json`) e `prio_base ≤ Média`. Subitem só com questões parciais recebe a leitura com título próprio ("só há questões de aderência parcial: praticar em Questões"). Sem resumo base, não recebe tarefa. **Histórico de leituras.** Sessões `leitura` já registradas (`task_id` `leitura:`) continuam válidas em qualquer modo: aparecem no dia e em `history_days`, contam para `read` e para a calibragem. **Aguardando questões (18/10/2026; `engine.priorities`).** Subitem com `n_questions == 0` tem `awaiting_questions: true` e `awaiting_status` "Aguardando questões" ou, depois da leitura, "Lido · aguardando questões". Não conta como concluído e não gera tarefas de estudo (a leitura base é a única e não se repete). Quando as questões chegam, o subitem volta sozinho à fila na posição de prioridade, sem nova leitura. `summary.topics_awaiting` / `topics_awaiting_read`. **Bases (fundamentos; `catalog.foundations`, `engine.plan` "foundation_tasks").** Bases compartilhadas vêm de `planejamento/bases_reversas_tcu_cgu.json`, ligadas a questões TCU; `build_data.py` fixa **15 min** cada. Cada base entra **uma vez**, como tarefa `base`, só quando ligada ao lote ativo (primeiro subitem não concluído que tem questões dela, por pin e rank); os lotes de questões do subitem esperam as bases pendentes. "Já compreendo esta base" (`POST /api/foundation`, `state.foundations[id] = true`) retira a base da fila e dispensa seu tempo, sem comprovar domínio. Subitem concluído não puxa bases. ## 3. Tempo, calibragem e calendário **Tempo de estudo (18/10/2026; `engine.topic_time`, `engine.level_minutes`, `engine.reading_minutes`).** Calculado por subitem e exposto em `plan.topics[].time` (`reading`, `questions` por nível, `total_estimated`, `factor`, `total_adjusted`); `topic.minutes` é sobrescrito com os valores ajustados. - **Leitura:** ⌈palavras de `sections[].body` + `review` ÷ 120⌉ min, mínimo 10 (`READ_WPM`, `READ_MIN`). - **Questões:** por nível com questões, **base fixa + 3 min por questão** do study_pool (`MIN_PER_Q`), teto de 40 questões por nível por ciclo (`Q_CAP`; o excedente, `time.over_cap`, fica para revisões e prática). Base pela extensão do item do edital (`build_data.py`): fácil = 20 + 5 × partes, média = 20 + 3 × partes (até 6 partes), difícil = 25, diagnóstico = 20 (`DIAG_BASE`). Não há piso de 15 min por nível. Nível sem questões não gera tempo. - **Total** = fácil + média + diagnóstico (+ difícil com `settings.hard`) e, no modo transição ou em subitem sem questões, a leitura. **Diagnóstico escalado.** Questões de dificuldade desconhecida formam o nível `diagnostico` (mesma regra de tempo); conta como custo na ordenação e, concluído, ancora revisões como o nível fácil. **Calibragem pessoal (`engine.time_factor`).** O cliente envia `planned` (1–600) em `POST /api/session`; o servidor grava `planned_factor` (fator vigente). Fator = mediana de minutos registrados ÷ (planejado ÷ `planned_factor`) nas sessões de estudo/leitura com `task_id` e `planned`; sessões cujo `planned_factor` esteja fora de [0,6; 2,0] são descartadas. Vale com ≥ 3 sessões (`FACTOR_MIN_N`), limitado a [0,6; 2,0] (`FACTOR_LO/HI`), uma casa. Aplica-se a leitura e questões, não a revisões nem treinos. `plan.summary.time_factor` / `time_factor_n`. **Calendário (`engine.plan`).** Começa em max(início configurado, hoje em America/Sao_Paulo). Dias passados não são simulados: o que não foi feito volta à fila a partir de hoje; dias anteriores mostram o histórico das sessões (`plan.history_days`). Capacidade do dia = disponibilidade do dia da semana − margem (`settings.reserve`, %) − minutos já registrados. Tarefa de estudo tem id único por dia (`::`). Não são emitidas tarefas de zero minuto. **Blocos e alternância (`engine.plan`, `next_piece`; `BLOCK_MIN=25`, `BLOCK_MAX=60`, `SHARES`).** Depois das revisões e dos treinos, o saldo do dia vai por **cotas acumuladas**: P1 15 %, P2 25 %, P3 60 % do orçamento acumulado desde o primeiro dia; a cada passo, a prova com maior déficit recebe um bloco de 25–60 min do seu próximo lote. Blocos menores que 25 min só para terminar um lote (leituras e bases vão inteiras); sobra que ficaria abaixo de 25 encurta o bloco (70 → 45 + 25). Um bloco por lote por dia. Prova sem lote disponível não acumula déficit. **Dia com saldo < 25 min** (`small`): blocos menores que 25 passam a ser permitidos e o dia recebe, em regra, um bloco com todo o saldo — se o lote escolhido acabar antes do saldo, um segundo pedaço pode entrar. Cada tarefa expõe `part`/`parts` e `total_minutes`. **Progressão temporal no cenário (`engine.candidates_for`).** O lote de **médias** só entra depois de o lote fácil do subitem terminar **e em dia posterior** (`prev[prereq] >= ds` → espera); difíceis idem em relação às médias. Fora do cenário, o que libera o nível é o gate (seção 4). **Fim de semana (`engine.plan`, tarefa `treino-`).** Sábado: "Discursivas P3 e P2 + correção", nível `discursiva`, **50 min**. Domingo: "Minissimulado P1/P2/P3 (22 itens, 40 min) + correção", nível `simulado`, **40 min**. Minutos já registrados no dia nesses níveis são descontados; cabe no saldo do dia, antes das cotas de estudo. **Véspera (`engine.plan`, dia `end`).** No último dia a disponibilidade é limitada a 120 min e todo o saldo vira a única tarefa `vespera` ("Revisão leve do caderno de erros e logística"); nenhum estudo novo é agendado nesse dia. O Acompanhamento usa a mesma conta ("véspera limitada a 2h"). **Demanda estimada (`plan.summary`).** `demand_content` = minutos restantes dos lotes; `demand_with_reviews` = isso + revisões pendentes + **30 min por lote fácil/diagnóstico ainda na fila** (estimativa das revisões D+1/7/21 de 10 min que cada lote vai gerar ao terminar). `fits` compara com a capacidade até a prova. ## 4. Domínio, progressão e conclusão **Modos de tentativa (`server.validate_attempt`, `rules.is_study`).** - **Estudo**: primeira resposta do dia a uma questão do study_pool, em nível liberado, sem consulta. Só ela conta para domínio. - **Prática (`mode: pratica`, `eligible_mastery: false`)**: área Questões, questão fora do pool (aderência parcial), nível bloqueado, marcada pelo cliente como prática, minissimulado (`origin: simulado`). Fica no histórico; não entra na evidência nem libera níveis. - **Consultada (`consulted: true`, 18/10/2026)**: gabarito comentado, comentário do professor ou estatística com distribuição por alternativa abertos **antes de responder naquele dia** → a resposta vai com `consulted` e é gravada como prática. O servidor também marca consultada a resposta dada no mesmo dia depois de uma prática da mesma questão. A marca do cliente fica em `sessionStorage` por data. O gabarito marcado nas alternativas só aparece para a resposta de hoje. - **Refeita (`redo: true`, 18/10/2026)**: qualquer resposta a questão que já tem resposta na mesma data é prática com `redo` (e `consulted` se a anterior do dia foi prática). Em outro dia volta a contar. - **Validação de `POST /api/attempt` (19/10/2026):** `mode`, se enviado, só `estudo` ou `pratica` (`server.ATTEMPT_MODES`); `note` texto de até 3.000 caracteres (`NOTE_MAX`); fora disso, 400. O cliente só sugere o modo: a interface prevê "estudo" pela lista `topic.study_open_ids` (questões do pool com o nível liberado), mas quem decide é o servidor pelas regras acima. **Reresposta (`rules.study_latest`).** Para domínio vale, por questão, a **primeira** resposta de estudo da data mais recente. Responder de novo no mesmo dia não melhora a evidência; em outro dia substitui a anterior. Repetir uma questão não aumenta a amostra. **Aprovação de nível (`rules.approved`, `rules.evidence`).** Nível aprovado com ≥ min(8, acervo do nível) questões distintas em modo estudo, ≥ 80 % de acerto e, quando o acervo do nível tem ≥ 8, respostas em duas datas. Com acervo < 8, a progressão é provisória (`provisional`). **Gates (`rules.gate`).** Médias abrem com a fácil aprovada; sem fáceis classificadas, abrem como diagnóstico provisório. Difíceis exigem a média aprovada e são opcionais (`settings.hard`). Não se inventam fáceis para liberar médias. **Concluído e frágil (`engine.priorities`).** Subitem **concluído pela regra** (`done`, `closed_mode: regra`) quando todos os níveis fácil, média e desconhecida (`rules.DONE_LEVELS`) que têm questões no pool estão aprovados. **Frágil** com ≥ 3 respostas de estudo e < 60 %. Concluído pela regra não tem "reabrir". **Conclusão manual ("Concluir tópico"; `server.m_topic`, `validate_closed`).** Grava `state.topics[id].closed = {date, mode: "manual"}` com a data de hoje; "Reabrir tópico" remove. **Concluir exige questões (18/10/2026):** só com **min(8, acervo)** questões reais distintas respondidas em estudo (`close_min`, `close_done`, `close_ready`); subitem só com questões fora do pool usa `min(8, n_questions)` em qualquer modo, menos consultadas. A interface esconde o botão até lá e o servidor devolve 400 com a mesma mensagem. Conclusões anteriores à regra permanecem. **Exercícios autorais e cobertura (18/10/2026; `data/exercises/.json`, `engine.exercise_stats`, `server.m_exercise`).** Exceção explícita à regra de usar só questões reais: itens C/E escritos a partir do edital, sempre rotulados "Autoral". Ficam fora de `catalog.questions`, do pool, das contagens, da prioridade, do domínio e do banco. `POST /api/exercise {id, chosen: C|E|branco, date ≤ hoje}` → `state.exercises[id]` (vale a última; `correct` sempre recalculado pelo gabarito). **Cobertura:** subitem **sem questões reais** com todos os exercícios respondidos (`cobertura_ready`) pode ser concluído com `mode: "cobertura"` → selo "Coberto por leitura e exercícios", revisões de manutenção D+1/7/21 de 10 min (`review_kind: cobertura`), conta em `summary.topics_covered`, **não** em `topics_closed`. A cobertura caduca quando chegam questões reais (`coverage_lapsed`), e "Desfazer cobertura" remove o registro. ## 5. Revisões **Revisões de subitem em estudo (`engine._review_anchors`).** D+1/D+7/D+21 (`REVIEW_GAPS`), 10 min, motivo `estudo`, ancoradas na data em que o primeiro nível do subitem atingiu seus minutos (antes disso, na primeira data de estudo), sem reiniciar a cada fatia. O cenário antecipa revisões condicionais dos lotes futuros. **Revisões de subitem concluído (`engine.CLOSED_GAPS`, `REVIEW_MINUTES`).** `review_status` = `manutencao` (cumpre a regra: D+1/7/21, 10 min) ou `reforco` (concluído manualmente sem cumpri-la: D+1/3/7/21, 15 min). Âncora = data manual ou, pela regra, a data em que a regra passou a valer (`engine.rule_since`: início da sequência atual de dias em que a regra se cumpre). Quando o reforço passa a cumprir a regra vira manutenção, mantendo a âncora; o D+3 pendente sai. Subitem concluído usa **só** o ciclo da conclusão; revisões feitas de ciclos anteriores ficam no histórico. **Identificação e registro (`server.validate_session`, `m_session`).** Id `:review:<âncora>:`; feita quando existe sessão `revisao` com esse `task_id`. "Concluir revisão" (mesmo rótulo no Início, em Revisões e na aba Revisão) registra os minutos da revisão com a data de hoje; o servidor exige que o id pertença ao próprio subitem e não registra a mesma revisão duas vezes. **Orçamento e atraso (`REVIEW_SHARE=0,18`, `REVIEW_SHARE_LATE=0,25`).** Vencida e não feita continua pendente (`overdue`) e entra a partir de hoje em ordem de vencimento (reforço antes em empate). Revisões usam até 18 % do saldo do dia, 25 % enquanto houver reforço atrasado; o que não cabe até a prova vai para `summary.review_debt`. `plan.reviews` lista todas (feitas e pendentes) com `scheduled_date`. **Registros (`server.not_future`).** Sessões não podem ter data futura; a mesma tarefa na mesma data não é registrada duas vezes; `planned` opcional (1–600). `POST /api/topic` aceita só `id`, `backlog`, `pin`, `order`, `deferred` e `closed`; qualquer outra chave é recusada (400). **Backup e restauração (19/10/2026; `server.validate_restore`, `m_restore`, `backup_state`).** O backup restaurado passa pela **mesma regra das rotas** e precisa gerar um plano antes de substituir o estado; inválido → 400 sem alterar nada. - **Tentativas** (`validate_legacy_attempt`): `question_id` e `correct` obrigatórios, data válida, `chosen` válido; `eligible_mastery`, `consulted` e `redo` só booleanos (as strings `'true'`/`'false'` são normalizadas; outro valor é recusado); `mode` só `estudo`/`pratica` e, ausente, recebe o padrão coerente (`pratica` se `eligible_mastery: false`, senão `estudo`; `eligible_mastery: true` com `pratica` vira `false`); `topic_id` obrigatório como subitem oficial, derivado da questão quando ausente; `origin` ausente, `tec` ou `simulado`; `difficulty` e `note` (≤ 3.000) como na rota. - **Tópicos** (`validate_topic_fields`, a mesma da rota `/api/topic`): só `backlog`/`deferred` (booleanos), `pin` (data ou nulo), `order` (número em ±100.000) e `closed` (`validate_closed`); chave extra recusa. **Notas**: texto por subitem oficial. **Bases** (`foundations`): booleano por base existente. Exercícios, cálculo e campos do TEC também são revalidados. - **Cópia antes de restaurar:** o estado que será substituído é gravado em `data/backups/state-AAAAMMDD-HHMMSS.json` (`server.backup_state`; ficam as **10 últimas**, `BACKUPS_KEEP`). `state.bak` é outra coisa: a cópia da **gravação anterior**, sobrescrita a cada POST. ## 6. Placar do dia e refazer **Placar (Estudar › Resolver questões; calculado no servidor por `engine.day_scores` → `topic.day_score` `{n, c, study, rn, rc, wrong}`, 19/10/2026; regra de modo em `validate_attempt`).** "Hoje neste tópico": (1) **1ª tentativa do dia** X de Y (Z %) — é o que conta; as que viraram prática por consulta/aderência aparecem como "não contam" (`n − study`); (2) **Refeitas hoje** A de B — preliminar, vale a última refeita, não conta (`rn`, `rc`); (3) **% definitivo** do tópico (`topic.acc`). A interface só formata `day_score` (`study.js dayScore`). **Refazer erradas de hoje.** Abre as questões cuja última resposta de hoje está errada (`day_score.wrong`), limpas (sem gabarito marcado, painel fechado), às cegas; a resposta é gravada como refeita e o placar preliminar atualiza. Repetir até zerar é o protocolo de fixação; o domínio de amanhã vem de responder em outro dia. **Erradas pela regra de estudo.** A seção "Refazer erradas" da aba Revisão usa `topic.wrong_study_ids`: questões cuja resposta de estudo que vale (`rules.study_latest`) está errada, calculadas no servidor. ## 7. Minissimulado (18/10/2026; `simulado.py`, rotas `/api/simulado/*`) Regra CEBRASPE da CGU 2026 (`engine.MINIMUM`): P1 40 e P2 40 itens de 1 ponto, P3 90 itens de 2 pontos; certo +valor, errado −valor, branco 0; eliminação com NP1 < 8, NP2 < 12, NP3 < 54 ou total < 78. - **Composição:** 22 itens (5 P1, 5 P2, 12 P3) em 40 min. `POST /api/simulado/novo {date ≤ hoje}` cria `state.simulados["sim-"]` (determinístico, semente = data) ou devolve o existente. - **Seleção (`simulado.montar`):** só questões reais C/E com gabarito, não anuladas; CEBRASPE primeiro (outras bancas completam com aviso). Por prova ~2/3 de tópicos já estudados e ~1/3 de prioridade alta (`prio_tier ≤ 1`) não estudados; excluídas as respondidas nos últimos 7 dias e as de simulados anteriores; ≤ 2 itens por tópico. - **Respostas:** `POST /api/simulado/responder {sim_id, question_id, chosen, flag?}`; sem gabarito na resposta; branco = sem resposta; cronômetro a partir de `started_at`; finaliza sozinho ao zerar. - **Pontuação (`simulado.score`, `finalizar`):** nota líquida por prova (P1/P2 c − e; P3 2c − 2e), aproveitamento, projeção = nota × itens da prova ÷ itens no simulado (×8 em P1/P2, ×7,5 em P3), mínimos sobre a projeção. Amostra pequena: tendência, não previsão. - **Registros (idempotentes):** cada item C/E vira tentativa `mode: pratica`, `origin: simulado` (não conta para domínio); registro em `state.simulations`; erros vão ao caderno (`state.notes[tópico]`: "Simulado : errei — revisar"); se existe a tarefa `treino-` de domingo, registra a sessão `simulado` com `min(duração, 600)`. ## 8. Baterias, rodadas e recuperação (18/10/2026) **Cadernos de bateria (`edital/gerar_baterias.py`; `catalog.baterias`).** Um caderno TEC por **tópico macro** (três primeiros segmentos do id: `P3.D01.2.1` → `P3.D01.2`), até 50 questões, nome `BAT `. Composição: assuntos TEC centrais dos subitens (parciais só para subitem sem central), ano ≥ 2022, sem anuladas/desatualizadas, mais recentes primeiro; CEBRASPE ≥ 30 → só CEBRASPE; senão todas as bancas se ≥ 15; senão não cria. Ordem de criação = `rank` do plano (`--ordem plano`). Como gerar: [OPERACAO_TEC.md](OPERACAO_TEC.md). **Rodadas (`tec_sync._apply_resumo`, `status_of`).** Cada resumo do caderno atualiza `state.tec_cadernos[id].rodadas`. A rodada **continua** enquanto as resolvidas não diminuem; **nova rodada** quando (a) as resolvidas caem (caderno reiniciado) ou (b) a anterior estava completa e, em outro dia, acertos/erros mudaram. Caderno "Resolvidas: " é a rodada seguinte do original. Rodada 1 em ordem; 2+ embaralhadas. % = acertos ÷ (acertos + erros). **Status** pela última rodada com ≥ 80 % resolvido: **Liberado ≥ 85 %**, **Revisar 70–85 %**, **Refazer < 70 %** (`tec_sync.LIBERADO/REVISAR/COBERTURA_MIN`); senão Em andamento / Não iniciado. O domínio dos micros (80 %) continua vindo das tentativas por questão. O servidor entrega cada rodada já com o % (`rodadas[].pct`) e os cortes em `thresholds` (`tec_sync.THRESHOLDS` = `{liberado: 85, revisar: 70, cobertura: 80}`), que a interface usa nas legendas e cores sem repetir números. **Plano de recuperação (`tec_sync.bateria_recuperacao`, `fundamentos.fichas_sugeridas`).** Só para Revisar/Refazer. Fonte: tentativas `origin: tec` do caderno e dos "Resolvidas: …"; por questão vale a **última resposta no TEC** (refazer no norteia não tira da lista). (a) Erros por subitem (erradas/total distintas, maior % primeiro) com **"Revise primeiro"**: até 3 fichas contadas pela frequência em `analise.base.fichas` das erradas (sem ponte: as 3 primeiras do subitem) e **uma** ação "Estudar a teoria" (aba Teoria na primeira ficha sugerida; rótulos pelo princípio 8 do [DESIGN_SYSTEM.md](DESIGN_SYSTEM.md)). (b) **Refazer as N erradas** às cegas no norteia, na ordem do caderno; respostas pelo `POST /api/attempt` normal (mesmo dia = refeita; outro dia = estudo). (c) Rodada seguinte no TEC (embaralhar ou "Resolvidas: …", "Enviar resumo" no fim). ## 9. Respostas do TEC — favorito "norteia: gravar" (18/10/2026; `tec_bookmarklet.js`, `tec_sync.py`) Decisão do usuário: o TEC aceita uma sessão por vez, então a captura roda **dentro da aba do TEC do próprio usuário**, por escuta passiva — **zero requisições extras** ao TEC (exceto "Enviar resumo", 1 GET a pedido). Operação, limites e plano B: [OPERACAO_TEC.md](OPERACAO_TEC.md) › Favorito. - **Lote (`tec_sync.validate_payload`):** `{caderno:{id, nome?, resumo?}, eventos:[…]}`, ≤ 200 eventos e 2 MB; tipos `resposta`, `questao`, `comentario`, `resumo`, `estatistica`, `desconhecido`; campo fora da lista recusa o lote (400). CORS só para `https://www.tecconcursos.com.br` e só em `/api/tec/sync` (`server.cors`, `do_OPTIONS`); a resposta traz só o resumo do gravado. - **Como contam (`tec_sync.attempt_from`):** tentativa com `origin: "tec"`, `tec_caderno_id`, `tec_key` (caderno:questão:instante; reenviar não duplica), `tec_tempo`, data local do instante; `correct` = `correcaoQuestao` do TEC (vale mesmo se o acervo divergir; a divergência vira aviso). Rótulo C/E: 1 = C, 2 = E; múltipla escolha 1 = A … 5 = E. Modo pelas regras da seção 4 (primeira do dia no pool e nível liberado = estudo; repetição = `redo`; comentário aberto antes = `consulted`). - **Mapeamento para o subitem (`tec_sync.map_topic`):** (1) questão no acervo → `topic_id`; (2) caderno que é bateria → `assunto_subitem`; (3) escolha anterior (`state.tec_assunto_map`); (4) pareamento assunto → subitem (`edital/pareamento/P*.json`) subindo a árvore do TEC (`catalogo/taxonomia_pareamento.json`), central antes de parcial, depois maior relevância. Sem pareamento → `state.tec_unmapped`, escolha manual em "Conectar ao TEC" (`POST /api/tec/map`, `tec_sync.map_unmapped`, que usa o **hoje em America/Sao_Paulo do servidor** ao aplicar as estatísticas pendentes da questão importada). - **Importação da questão (`atena_io.questao_base`, `mesclar_questoes`):** questão fora do acervo (ou sem gabarito) é montada do próprio payload; ids novos entram; gabarito e comentário existentes **nunca** são sobrescritos. Recompilação em segundo plano 2 s depois do último lote (`server.schedule_recompile`, roda `localizar_imagens.py` antes do `build_data`). A interface não baixa o catálogo de novo sozinha: avisa e oferece "Recarregar acervo" ([DESIGN_SYSTEM.md](DESIGN_SYSTEM.md) › Conectar ao TEC). - **Estatística (`tec_sync.apply_stats`):** eventos de `/desempenho` sanitizados na aba (só números/booleanos/rótulos ≤ 60 caracteres; chaves pessoais ou de gabarito removidas) → `tec_stats` na questão e dificuldade derivada (seção 10); questão fora do acervo → `state.tec_stats_pendentes` (≤ 5000); caminhos não reconhecidos → `state.tec_desconhecidos` (≤ 50). Abrir só o % geral antes de responder não marca consultada; distribuição por alternativa marca. - **Backup:** `tec_cadernos`, `tec_unmapped`, `tec_assunto_map`, `tec_stats_pendentes`, `tec_desconhecidos` entram no estado e no `/api/restore`. ## 10. Dificuldade derivada (18/10/2026; `edital/dificuldade.py`) **Pipeline único (19/10/2026).** Uma só regra, em `edital/dificuldade.py` (`atualizacao`, `dificuldade_por_estatistica`), sobre `tec_stats` com **≥ 50 respostas** e faixas **80/60/40/20**. Os três pontos de entrada usam a mesma função: a sincronização do favorito (`tec_sync.apply_stats`), a compilação do catálogo (`build_data.difficulty_fields`) e a importação manual (`rules.normalize_difficulty`, `POST /api/questions`). O campo antigo `empirical` foi **aposentado**: registro que o traga é recusado com a orientação de enviar `tec_stats {acertos, erros}` (histórico em [HISTORICO.md](HISTORICO.md) § 2.3). **Exibição (decisão do usuário, 19/10/2026).** A dificuldade derivada da estatística do TEC só pode ser apresentada ao usuário como **"estimada"** — nunca como "base externa" ou com o nome da fonte; a origem completa fica no JSON (`difficulty_source`). Hoje a interface mostra só o nível. Questão TEC sem rótulo TEC recebe a dificuldade do % de acerto geral (`desempenhoGeral`: acertos ÷ (acertos + erros)) pelas faixas publicadas pelo TEC (`FAIXAS`): > 80 % Muito Fácil, > 60 % Fácil, > 40 % Médio, > 20 % Difícil, senão Muito Difícil → nível por `atena_io.nivel` (muito fácil/fácil = `facil`, médio = `media`, difícil/muito difícil = `dificil`). **Amostra mínima: 50 respostas** (`AMOSTRA_MIN`); abaixo disso fica "não medida", mas `tec_stats` é guardado. - **Calibração (18/10/2026, `dificuldade.calibrar`; `python edital/dificuldade.py` imprime a matriz):** nas 174 questões CEBRASPE com rótulo TEC e ≥ 30 respostas, a busca em grade dá o platô 80–81 / 60–61 / 40–44 %, que contém as faixas publicadas; com elas o rótulo bate em 137/174 (79 %) e o nível em 148/174 (85 %); com ≥ 100 respostas, 90 % / 95 %. O critério do TEC é ≥ 50 alunos; aqui contam-se respostas. - **Regras:** rótulo TEC existente (`difficulty_original`) nunca é sobrescrito; rótulo vindo no payload vira `difficulty_original`; dificuldade de outra fonte não é trocada; a derivada grava `difficulty`, `difficulty_derivada` e `difficulty_source` = "derivada do % de acerto TEC (n respostas), faixas calibradas em 2026-10-18" e é refeita quando chegam números novos. - **Duas rotas trazem a estatística:** captura passiva do favorito (seção 9) e o lote lento `edital/medir_dificuldade.py` ([OPERACAO_TEC.md](OPERACAO_TEC.md)); ambas gravam `tec_stats` (em `local/data/private/tec_stats.json`, fora do git e do catálogo; [OPERACAO_TEC.md](OPERACAO_TEC.md) § 7) e passam pelo pipeline único acima; o rótulo derivado fica no acervo. ## 11. Acompanhamento (18/10/2026; `tracking.py`, `GET /api/data?parts=tracking`) Somente leitura, calculado de dados existentes; a interface só formata. Tela em [DESIGN_SYSTEM.md](DESIGN_SYSTEM.md) › Acompanhamento. - **Resumo:** questões resolvidas (todas as origens), hoje, média diária (7 dias ÷ 7 e total ÷ dias desde o início), sequência de dias com registro, % de acerto de estudo (primeira resposta de estudo por questão e dia) e geral, tópicos aprovados, em reforço e para revisar. - **Previsto × realizado × dívida:** previsto = disponibilidade do dia − margem (véspera limitada a 2 h), de `settings.start` até ontem (semanas passadas usam a disponibilidade atual); realizado = minutos de todas as sessões; dívida = previsto − realizado dos dias encerrados, acumulada (negativa = crédito). Últimas 6 semanas + a atual. - **Acerto por disciplina × semana (mapa de calor):** 13 disciplinas; filtro Estudo / Prática / Tudo; cortes **80 %** (aprovação) e **60 %** (frágil) em `tracking.HEAT_OK`/`HEAT_MID`. Cada célula já vem do servidor com `n`, `correct`, `pct` e `nivel` (`ok`/`mid`/`ko`); `heat.tudo` (estudo + prática) é somado no servidor e os cortes vão em `heat_cuts` (19/10/2026). - **Volume diário:** 30 dias; **meta implícita** = questões cobertas por dia pelos blocos dos próximos 7 dias (bloco de m min cobre m ÷ total do lote das min(questões, 40)). - **Cadernos de bateria:** uma linha por bateria com as rodadas (seção 8: `rodadas[].pct`, `thresholds`), na ordem do plano; não iniciadas recolhidas. - **Simulados:** último, diferença para o anterior, média dos últimos 3. - **Evidência e diagnósticos (19/10/2026):** `history` (corpus de evidência), `coverage_by_level` (cobertura por nível) e `diagnosticos` (últimos 5 diagnósticos manuais de `state.simulations`, com projeção e mínimos de `simulado.diagnostico`) vêm só em `parts=tracking` (os dois primeiros saíram do plano servido; ver [../README.md](../README.md) › API, plano enxuto). ## 12. Cálculo na mão (19/10/2026; `calculo.py`, `data/calculo/`) A CEBRASPE não permite calculadora. Três partes: **fichas de técnica** (`data/calculo/.json`, lidas em tempo de execução por `calculo.carregar_fichas`, sem recompilar; formato em [CONTEUDO.md](CONTEUDO.md)); **treino gerado** (`GET /api/calculo/serie?tecnica=&n=&seed=`: 10 itens determinísticos pela semente, 4/3/3 por nível, números "de prova"; `tecnica=misto` = série C/E com cronômetro; entrada tolerante por `calculo.interpretar`); **registro** (`POST /api/calculo/resultado` → `state.calculo`, últimas 300 séries). **Não conta** para domínio, evidência, sessões, tempo nem plano: o motor não lê `state.calculo`. Ligação com as questões: `GET /api/generated/analise/` acrescenta `treino_calculo` (`calculo.tecnica_para_questao`, heurística sobre enunciado/resposta curta/ponte, só com número no enunciado) e a resposta curta mostra "Treinar o cálculo: →".