Quatro fases num plano so (escala-dry-pos-e10): leitura unica da tabela na lista, memo de processo dos potenciais por municipio, TooltipProvider unico no layout e multi-filtros do mobile saindo das definitions. A F1 nao e achado novo: o A11 ja tinha adiado a "hidratacao unica do sort derivado" com gatilho "3o path derivado no loader" — com classe o loader chegou ao 4o (votos, deficit, frescor, classe), entao o adiado do A11 fica riscado e apontando para o item novo. Reforcada tambem a nota de gatilho do disclosure em conta-da-cadeira.md (a classe entrou acima da dl, nao como 5a metrica). Descartados no triage, com racional no plano: formatador de percentual proprio do TerritoryListColumns, os tres caches do classificador e o ruido do knip com payload.config.ts (ja ledgerado P3, e a evidencia da sessao contradiz).
28 KiB
E8 — Conta da cadeira (metas derivadas, potencial por município, cobertura)
Status: entregue em código (2026-07-24; ver "Entregue (as-built)" abaixo)
Atualizado em: 2026-07-24 (auditoria de implementação — ver "Achados da auditoria" abaixo; escopo completo confirmado com o usuário; appetite revisado ~2,5–3d; entrega registrada)
Item do roadmap: docs/roadmap.md (seção "Inteligência de campanha", E8; plano-mestre inteligencia-campanha.md; A10 ✓)
Impeccable: B — cobertura/metas encaixadas no dashboard, na lista de municípios e no detalhe (sem rota nova)
Appetite: ~2,5–3 dias eng (revisado — degrau 3 da caching ladder para teto do campo/share intracampo/roll-off, ver auditoria); migration pequena (global campaignGoals) + artefato TSE estendido + utilities derivadas + encaixes de UI
Responsável: —
Achados da auditoria de implementação (2026-07-24)
Confirmado contra o repositório antes do craft: dependências duras satisfeitas (A10 vivo, remodelagem em produção, voteGoals de fato removido); o artefato commitado bahia-federal-baseline.json já tem votesByYear/validVotesByYear por município para 2014/2018/2022; o banco local seedado para esta sessão confirma TSE completo nos três anos (DF T1 2014/2018/2022 + ticket 2022 completo, incluindo presidente/governador por zona) — o rabbit hole "roll-off por zona sem tally do majoritário" fica fechado por evidência para 2022 (não para 2014/2018, que só têm deputado federal).
Ajustes ao plano abaixo, travados nesta sessão:
- Escala. Teto do campo / share intracampo / roll-off NÃO usam
unstable_cachesobre as collections como a seção "Abordagem proposta" sugeria —electionCandidateVotetem ~100k/73k/52k linhas por ano (2022/2018/2014); varrer isso por município na lista/dashboard é exatamente o padrão que o hardening de 2026-07-23 baniu do runtime. Vai para o degrau 3 da caching ladder: estender o artefato commitado viapnpm build:election-aggregates(campoFederalVotesByYear,federalTallyByYear,majoritarian2022), não um loader novo com tag. - Meta × comprometido (semântica). A seção "Objetivos" abaixo (linha da cobertura) e o rabbit hole sobre
expectedVotes ?? Σ pledgesestavam ambíguos: hojeresolveMunicipalityStaffVoteTotalfaz a expectativa substituir a soma dos pledges — usar isso como denominador daria 100% de cobertura nos ~189 municípios com planilha (E4R). Travado:meta = expectedVotes[cenário] ?? suggestedGoal;comprometido = aggregate.effectiveByScenario[cenário](só pledges, nunca a expectativa da mesa). Cobertura = "quanto da expectativa está lastreado em compromisso auditável" — mesma regra do plano do E9. - Global sem hook de revalidação.
revalidateGlobalcitado nos "Componentes" abaixo é cerimônia morta:/campanhaé dinâmico com auth (nada cacheia o global) e a tag não entraria no allowlist derevalidateRequest.ts. O globalcampaignGoalsnão teráafterChangehook (comentário no código justificando). campoParties.tsnão é uma segunda fonte de verdade de partido. Já existe electionPartySpectrum.ts (buckets esquerda/centro/direita).campoParties.tsé a curadoria de campo por ano (2014/2018/2022/2026); um unit test prova que toda sigla do campo está no bucketesquerda, com exclusões documentadas.- Access de global é terreno novo. Os 4 globals existentes (
SiteSettings,HomePage,Metadata,PrivacyPolicy) usamread: () => true/update: Boolean(user); nenhum está no grupoCampanhanem nega leitura.campaignGoalsprecisa de access explícito (JWT decampaignUseralcança/api/globals/*).
Decisões de produto travadas na mesma sessão (detalhes na seção "Decisões travadas" e "Questões em aberto" abaixo, já atualizadas): meta estadual inicial 150.000 (editável); válidos projetados = média ponderada (v2014 + v2018 + 2×v2022) / 4; decomposição proporcional ao teto do campo projetado (não fórmula composta) — revista no mesmo dia, ver "Revisão da fórmula da meta sintética" abaixo: a meta passou a ser ancorada na votação própria de 2022, por cenário; teto do campo = presidencial do campo T1 2022 (#13) como primário, governador como leitura secundária (assumido, validar com produto); roll-off = (brancos+nulos DF) − (brancos+nulos majoritária), 2022-only.
Entregue (as-built, 2026-07-24)
Todas as 6 fases implementadas conforme o plano de execução, com os ajustes da auditoria já incorporados:
- Artefato v2 (
pnpm build:election-aggregates):campoFederalVotesByYear,federalTallyByYearemajoritarian2022(presidente/governador T1 2022) adicionados por município abahia-federal-baseline.json, ao lado devotesByYear/validVotesByYear. Curadoriasrc/lib/campoParties.ts(2014/2018/2022/2026), validada por unit test contra o bucketesquerdadeelectionPartySpectrum.ts. Teto de bytes do artefato revisado de 400 KB para 700 KB (tests/unit/bahiaElectionAggregates.unit.spec.ts). - Global
campaignGoals(src/globals/CampaignGoals.ts, migration20260724_180000_add_campaign_goals_global):stateGoal(default 150.000),margin,baseYear,note;read= admin/staff,update= admin/isCampaignUnrestricted; sem hookafterChange(achado #4 da auditoria — cerimônia morta,/campanhaé dinâmico com auth). Loadersrc/utilities/campaignGoals.tscom Reactcache(). - Derivados puros:
src/utilities/municipalityPotential.ts(projectedValidVotes,fieldCeiling,projectedFieldCeiling,captureRate,intraFieldShare,rollOff,deriveSuggestedGoalsByScenario— originalmentedecomposeStateGoal, revista no mesmo dia (ver "Revisão da fórmula da meta sintética" abaixo) —,sanityCheckSuggestedGoalsByTerritory) esrc/utilities/goalCoverage.ts(meta = expectedVotes[cenário] ?? suggestedGoal;comprometido = aggregate.effectiveByScenario[cenário], nunca a expectativa da mesa — achado #2). Orquestração server-scoped emsrc/utilities/municipalityGoalAccount.ts(cache()-deduplicado entre dashboard/lista/detalhe). - UI (Impeccable classe B, shape → craft → critique → harden): linha "Cobertura da meta" na
CampaignMetricStripdo dashboard (CampaignDashboard.tsx) e do overview da lista (MunicipalityListOverview.tsx); nova coluna "Cobertura da meta" emMunicipalityList.tsx(a coluna antiga "Cobertura" foi renomeada para "Assessoria" para eliminar a ambiguidade), com célula compartilhadaMunicipalityListGoalCoverageCell.tsx(variantecompactpara a tabela desktop,defaultpara os cards mobile); card novo "Conta da cadeira" (MunicipalityGoalAccountCard.tsx) na aba Visão geral do detalhe do município, fixo no cenáriocentral(sem seletor de cenário nessa página) — substitui o bloco "Votos estimados" que antes vivia emMunicipalityStrategyCard.tsx. - Verificação:
tsc --noEmit,pnpm lint,pnpm exec knip, unit + int (incl.tests/int/campaignGoalsAccess.int.spec.tscobrindo a matriz de acesso por papel) epnpm buildverdes contra o banco local;pnpm test:e2everde quando executado isolado (flakiness de timeout/ECONNRESETobservada só sob contenção de recursos do sandbox com múltiplos dev servers em paralelo — não é regressão de código; 1 assertion ajustada para um seletor Playwright não-ambíguo pós-mudança de UI). - Divergências do plano original: nenhuma decisão de produto mudou; os 6 achados da auditoria (seção acima) e as decisões travadas na sessão foram seguidos à risca.
territoryWarnings(sanity check por TI) ficam computados e disponíveis emmunicipalityPotential.ts, mas não ganharam superfície de UI nesta entrega — o brief de shape aprovado (Fase 4a) cobriu só as 4 integrações (dashboard, overview, lista, detalhe); exibir os avisos por TI é candidato natural para E12 (camada TI) ou um fill-in futuro.
Revisão da fórmula da meta sintética (2026-07-24, durante o E9)
A auditoria de implementação do E9 (fila-de-alocacao.md) encontrou um erro material nesta entrega e o corrigiu antes de construir a fila. decomposeStateGoal repartia os 150 mil proporcionalmente só ao teto do campo (voto de Lula), ignorando a votação própria do candidato: Vitória da Conquista, com 5.005 votos dele em 2022, recebia meta 2.911; Campo Formoso, com 47 votos, recebia 813. Como o E9 ordena a fila por déficit descoberto, essa meta colocaria desertos no topo — contra o padrão P6 do relatório ("se nunca tive voto, é deserto… despriorizar por escrito").
A meta sugerida passou a ser derivada por cenário e ancorada na votação própria de 2022 (deriveSuggestedGoalsByScenario substitui decomposeStateGoal, que foi deletada):
base_i = votação própria de 2022 no município
pessimista_i = base_i × (1 − margin) Σ = 116.071
central_i = base_i Σ = 128.968 ("repetir 2022")
otimista_i = base_i × (stateGoal ÷ Σ base) Σ = 150.000 (exato, por construção)
Consequências para este plano: computeGoalCoverage/computeGoalCoverageByScenario recebem SuggestedGoalByScenario no lugar do escalar (a semântica travada meta = expectedVotes[S] ?? suggestedGoal[S] fica simétrica dos dois lados); stateGoal deixa de ser "piso decomposto" e vira o cenário otimista (o fator 1,163 traduz "meta de 150 mil" em "2022 mais 16% em todo lugar"), enquanto margin deixa de ser leitura da mesa e vira o corte pessimista (fallback 10%) — só admin.description do global mudou, sem migration. Se stateGoal < Σ base, o otimista clampa em max(central, otimista). sanityCheckSuggestedGoalsByTerritory fica praticamente inerte com a meta ancorada no histórico; o teto de plausibilidade que a literatura pede (soma de pledges acima do teto do campo) é sinal de pledge, não de meta, e foi adiado para o E11.
Racional na literatura do projeto: docs/research/relatorio-entrevista-persona-campanha.md define potencial como "voto histórico próprio mais fração do campo não capturado" (linhas 323/331), com pesos por classe operacional — e classe operacional é escopo do E10, não do E8.
Follow-up pós-entrega (mesmo dia, feedback do usuário)
- Tooltips nos 4 números de diagnóstico. O card
MunicipalityGoalAccountCard.tsxsó tinha oCampaignInfoHint("?") no título; os 4 números do bloco de diagnóstico (teto do campo, captura, share intracampo, roll-off) ficaram sem explicação inline. AdicionadoGoalAccountMetric(Tooltip do shadcn/ui, hover + foco por teclado, sem ícone de interrogação — pedido explícito do usuário) envolvendo cada pardt/dd. - Majoritária 2014/2018: útil, seed expandido. A auditoria (linha 12 acima) tinha fechado o rabbit hole do roll-off "só para 2022" citando que 2014/2018 só tinham deputado federal seedado. Decisão: essa lacuna é útil de fechar (diagnóstico histórico futuro), então
scripts/seed-tse-results.mjspassou a importar presidente + governador também para 2014/2018 (HISTORICAL_BASELINE_OFFICESemelectionResultsBuild.ts), carregando sempre os arquivos BR (nacionais) de voto/apuração — os votos presidenciais por município da Bahia vivem nesses arquivos, não nos BA. Isso expôs duplicatas de "voto em trânsito" (só existem no cargo presidente) que colidiam com o índice único deElectionCandidateVote/electionTallyem 2014 (BA, zona 154, e outras); corrigido somando os splits (mergeDuplicateVoteRows/mergeDuplicateTallyRowsemelectionResultsParse.ts). Escopo mantido: só o banco local ganhou os dados; o artefato commitado (bahia-federal-baseline.json) e as fórmulas demunicipalityPotential.ts(teto do campo, roll-off) continuam 2022-only por design — estender isso é trabalho futuro, não pedido nesta sessão. Textos na UI/comentários que diziam "não seedado" foram corrigidos para não ficarem factualmente errados. /impeccable critiqueno card + hardening completo (mesmo dia). Critique sobreMunicipalityGoalAccountCard.tsx/CampaignInfoHint.tsx/MunicipalityHoverTooltip.tsx(persistido em.impeccable/critique/2026-07-25T01-04-34Z__omponents-campaign-municipalitygoalaccountcard-tsx.md, 26/40) achou 2×P1 confirmados com evidência de browser real (Playwright, 1280×720 e 390×844): (a) as 4 tooltips de métrica eram inalcançáveis por toque —@radix-ui/react-tooltipignorapointerType: 'touch'de propósito, e o card é usado "no campo"; (b) a bolha colidia (sideflip do Radix) e cobria a linha "Cobertura da meta" logo abaixo do título. Mais 2×P2 (idiomas de disclosure incompatíveis no mesmo card — Popover clica-e-fica-aberto vs. Tooltip hover/foco que nunca abre no clique; cópia densa com referência cruzada entre "Captura" e "Share intracampo" que não podem estar abertas ao mesmo tempo) e 1×P3 (barra de progresso a 0% quase invisível,h-1). Todos endereçados na mesma sessão:MunicipalityHoverTooltip.tsxganhou estado controlado com umonPointerUpque só reage apointerType === 'touch'(mouse/teclado continuam 100% no hover/foco nativo do Radix — sem o bug simétrico de "clique fecha o que o hover acabou de abrir") e um listener depointerdownfora do gatilho que fecha a bolha (Radix não tem dismiss-layer para Tooltip, só para Popover) — abrir a métrica B fecha a métrica A "de graça", já que é só um "toque fora" do ponto de vista de A.GoalAccountMetrictrocou odiv tabIndexpor um<button>real comaria-label(toque confiável + nome acessível anunciado antes de abrir, achado da persona Sam) eside="right" align="start"(a bolha nunca mais colide no eixo vertical com "Cobertura da meta" — cresce para o lado e para baixo, não para cima). Cópia das 4 métricas dividida em frase-líder (termo em negrito) + linha de fórmula secundária, sem mais cross-reference (Share intracampo agora reexplica o denominador inline). Barra de progresso local (className="h-2", só nesta instância —Progressé compartilhado com planos/lista/dashboard, não alterado globalmente). Verificado com toque sintético via Playwright (PointerEventcompointerType: 'touch') em 1280×720 e 390×844: abre no toque, troca de métrica fecha a anterior, toque fora fecha, nunca cobre a linha de cobertura.tsc/lint/knip/unit (323 testes)/pnpm build/Aikido verdes; só os 2 arquivos de componente mudaram.
Design (Impeccable)
Âncoras: PRODUCT.md (princípio 5 — métricas relativas e locais; anti-goal % estadual absoluto) / DESIGN.md (register product) · tema data-theme='campaign'.
Na implementação (implement-roadmap-item): craft compacto → critique → polish.
- Persona / contexto: coordenador-geral na reunião semanal; assessor conferindo "quanto falta" nas seus municípios.
- Job principal: responder "a conta fecha?" em uma linha — meta, comprometido, delta da semana, por município e no agregado.
- Estratégia de cor: Restrained; delta negativo usa o vermelho de campanha com parcimônia (não hero-metric).
- Edit where you see: sim — meta do município editável em contexto (Popover reusando
municipalityStaffFormActions), como B9. - Anti-goals: KPI de % estadual absoluto; contagem bruta de cadastros; gauge/velocímetro SaaS.
Contexto
O relatório de discovery (§5.1) fixou a OMTM da campanha até 16/08: cobertura da meta por compromissos auditáveis (Σ pledges ÷ meta, com delta semanal como número da reunião), decomposta por município. Hoje municipality.expectedVotes (pessimista/média/otimista) é a única série manual por cenário (o grupo voteGoals foi removido em 2026-07-24), mas: não há meta estadual nem decomposição de cima para baixo; não há noção de potencial derivado (válidos projetados, captura do campo, roll-off); denominadores baseados em aptos inflariam municípios de abstenção estrutural (relatório §6.6/I-B); e não existe "campo" definido por ano para share intracampo. Com E4R (import-planilha-projecao.md), expectedVotes/priority chegam seedados da planilha da coordenação (~189 municípios com estimativas em 3 cenários): a decomposição meta→município nasce reconciliando a sugestão derivada com as estimativas reais da mesa — o valor manual continua vencendo o sugerido, e divergências grandes viram aviso, não sobrescrita. Os dados TSE necessários já estão em electionTally (válidos/brancos/nulos/comparecimento por cargo×ano×cidade×zona) e electionCandidateVote; desde o hardening 2026-07-23, a série de válidos federais T1 + votos Solla por município já existe pré-agregada no artefato commitado src/lib/bahiaElectionAggregates.ts. Teto do campo, share intracampo e roll-off também vão para esse artefato (não para um loader novo com unstable_cache): electionCandidateVote tem ~100k/73k/52k linhas por ano (2022/2018/2014) e a lista/dashboard varreriam isso por município a cada carregamento — exatamente o padrão de runtime que o hardening de 2026-07-23 baniu. pnpm build:election-aggregates passa a escrever campoFederalVotesByYear, federalTallyByYear e majoritarian2022 por município, ao lado de votesByYear/validVotesByYear já existentes.
Objetivos
- Global
campaignGoals(admin groupCampanha, staff-only): meta estadual de votos, margem, ano-base, nota. - Utilities derivadas por município (sem persistir o derivado): válidos projetados do cargo (série 2014/2018/2022), teto do campo (majoritário 1º turno), taxa de captura, share intracampo (via
campoParties.ts), roll-off DF×majoritária, potencial e meta sugerida (na entrega: ancorada na votação de 2022 por cenário — ver "Revisão da fórmula da meta sintética"). - Cobertura por município e agregada, para os três cenários:
meta = expectedVotes[cenário] ?? suggestedGoal(derivado);comprometido = aggregate.effectiveByScenario[cenário](só Σ pledges — declarado/estimado por leadership, nunca a expectativa da mesa);coverageRatio = comprometido ÷ meta. Cenário ativo escolhido no cliente (MunicipalityEstimateScenarioProvider, defaultcentral). Delta semanal (exige C12 para série; até lá, delta vs. snapshot em memória do último load é aceitável mostrar como "—"). - Encaixes: linha de cobertura no dashboard (
campaignDashboardData.ts), colunas meta/cobertura na lista (MunicipalityList/MunicipalityListOverview), card no detalhe do município. - Access: tudo staff-only (
coordinator/advisor);leadernão vê metas — mesmo padrão deexpectedVotes. - Sanity check por TI: Σ metas dos municípios do TI vs. participação histórica do TI no voto (aviso, não bloqueio).
Decisões travadas
- Denominadores usam válidos projetados do cargo, nunca
aptos(relatório §6.6; abstenção estrutural >35% em municípios pequenos infla potencial). Rejeitado:aptos(viés pró-município-vazio); válidos da majoritária (cargo errado); eleitorado IBGE (não é cadastro eleitoral). - Meta estadual mora num global editável, decomposição é derivada e a estimativa por município continua editável (
expectedVotesmanual vence a sugerida quando preenchida). Mantém julgamento humano no loop e evita reescrever E1. Rejeitado: meta por município 100% automática (K-C: "o erro é a conta" precisa de dono humano); collection de metas versionadas (C12 cobre auditoria via decisões). - "Campo" por ano é lib estática curada
src/lib/campoParties.ts(ano → partidos/números). Rejeitado: collection administrável (curadoria política rara, não CRUD); inferir por coligação do TSE (federações ≠ campo político real). - i18n e naming:
campaignGoals,computeMunicipalityPotential,computeGoalCoverage,projectedValidVotes,captureRate,intraFieldShare,rollOff; labels pt-BR.
Questões em aberto
- Meta estadual inicial? Resolvido em campo (2026-07-23 — CUSTOMER.md): piso projetado do coordenador = 150 mil (2022: 129 mil) — topo da faixa da cadeira. Valor inicial do global
campaignGoals= 150 mil, editável (margem continua dele); o QE cheio segue rejeitado como default. - Projeção de válidos: média simples da série ou tendência? Resolvido (auditoria 2026-07-24): média ponderada com peso 2 para 2022 —
(v2014 + v2018 + 2×v2022) / 4. Barata, estável, documentada como não-previsão (fora de escopo regressão/tendência). - Teto do campo: presidencial ou governador? Assumido (validar com produto): presidencial do campo T1 2022 (#13) como série primária; governador (também #13, mesmo campo) como leitura secundária de diagnóstico — presidencial tem participação mais homogênea entre municípios pequenos e grandes.
- Forma da decomposição meta→município? Resolvido: proporcional ao teto do campo projetado —
suggestedGoal_i = metaEfetiva × tetoProjetado_i / Σ tetoProjetado— não uma fórmula composta com captura/share/roll-off como pesos (isso viraria um modelo preditivo multifatorial, rejeitado). Captura, share intracampo e roll-off ficam como colunas de diagnóstico, não como entradas da conta. - Roll-off: quais anos? Resolvido: só 2022 por design (artefato/fórmulas não cortam a fatia majoritária para outros anos); nota na UI explicando a limitação em vez de calcular para 2014/2018. O seed local passou a importar majoritária 2014/2018 também (ver "Follow-up pós-entrega" acima), mas isso não muda esta decisão — é dado disponível para uso futuro, não uma extensão da conta atual.
Abordagem proposta
flowchart LR
Seed["electionTally + electionCandidateVote<br/>(local, já seedado)"]
Campo["campoParties.ts<br/>(curadoria ano → siglas)"]
Build["pnpm build:election-aggregates"]
Artifact["bahia-federal-baseline.json v2<br/>(Solla, válidos, campo DF,<br/>teto majoritário 2022, roll-off 2022)"]
Goals["global campaignGoals"]
Pot["computeMunicipalityPotential<br/>(válidos proj., teto, captura, roll-off)"]
Cov["computeGoalCoverage<br/>(meta vs. comprometido)"]
Pledges["aggregatePledgesByMunicipality<br/>(votePledgeData.ts)"]
UI["dashboard + MunicipalityList/Overview + detalhe"]
Seed --> Build
Campo --> Build
Build --> Artifact
Artifact --> Pot
Goals --> Pot
Pot --> Cov
Pledges --> Cov
Cov --> UI
Componentes:
src/lib/campoParties.ts: mapa ano→partidos do campo (2014/2018/2022/2026), validado contraelectionPartySpectrum.ts(unit test), com exclusões documentadas.scripts/build-election-aggregates.mjs+src/lib/bahiaElectionAggregates.ts: artefato estendido (v2) — por município, além devotesByYear/validVotesByYear, agoracampoFederalVotesByYear(3 anos),federalTallyByYear(comparecimento/brancos/nulos) emajoritarian2022(presidente/governador T1: votos + brancos/nulos/válidos).src/utilities/municipalityPotential.ts: deriva por município a partir do artefato (nenhuma query no runtime); expõeprojectedValidVotes,fieldCeiling,captureRate,intraFieldShare,rollOff,suggestedGoal.src/utilities/goalCoverage.ts:meta = expectedVotes[cenário] ?? suggestedGoal;comprometido = aggregate.effectiveByScenario[cenário](só Σ pledges, via A10 /votePledgeData.ts, defaultcentral) — a expectativa da mesa nunca substitui os pledges no denominador de cobertura; sanity por TI viabahiaTerritories.ts.- Global
CampaignGoals(src/globals/CampaignGoals.ts): camposstateGoal,margin,baseYear,note; access staff-read/coordinator-write; sem hook de revalidação (/campanhaé dinâmico com auth, nada cacheia este global — ver "Achados da auditoria"). - UI: linha nova em
CampaignMetricStripdo dashboard; coluna/ordenar por cobertura emMunicipalityList(reusa padrão B9 para editar meta viamunicipalityStaffFormActions); card "Conta da cadeira" no detalhe. - Migration:
pnpm migrate:create add_campaign_goals_global(tabela do global). Sem mudança em collections.
Dependências
- Duras: nenhuma pendente — A10 entregue (plano) e remodelagem em produção desde 2026-07-23 (schema
municipality/votePledgevivo). Suave: C12 (delta semanal real da cobertura usa trajetória de pledges; até lá, sem histórico). - Reusa:
bahiaElectionAggregates.ts(artefato v2),municipalityElectionGeography.ts(loaders do artefato v2),votePledgeData.ts(agregação por cenário pós-A10),campaignMunicipalityScope.ts(loadMunicipalityScope),campaignDashboardData.ts,municipalityStaffFormActions.ts,campaignAccess.ts.
Não escopo
- Fila ordenada por déficit (E9 — fila-de-alocacao.md); histórico/trajetória (C12); recalibração de classes (E10); rollups TI além do sanity check (E12); previsão estatística (Fora de escopo do roadmap).
Rabbit holes
- Projeção virar modelo preditivo. Se alguém "melhorar" com regressão/ML: viola decisão de escopo do ciclo. Mitigação: média ponderada fixa, documentada como não-previsão.
- Decomposição automática sobrescrever
expectedVotes. Mitigação: meta sugerida é coluna separada; manual sempre vence; nunca escrever emexpectedVotespor job. - Roll-off por zona de Salvador sem tally do majoritário na mesma malha. Fechado por evidência (auditoria 2026-07-24): o banco local seedado tem presidente/governador T1 e T2 por zona para 2022 (450 células por escopo, cobrindo as 19 zonas de Salvador). Mais tarde no mesmo dia o seed passou a cobrir presidente/governador para 2014/2018 também (ver "Follow-up pós-entrega"), mas roll-off continua 2022-only — decisão de escopo da fórmula, não limitação de dado.
Adiado com gatilho
- Delta semanal persistido. Revisitar quando C12 entregar trajetória (gatilho: primeira semana com 2 snapshots).
- Meta por cenário (pessimista/central/otimista) × cobertura tripla. Resolvido nesta sessão como escopo do próprio E8 (não mais adiado): cobertura é computada para os três cenários no servidor, seletor de cenário fica no cliente (
MunicipalityEstimateScenarioProvider, defaultcentral). - Modelo de disclosure do grid de diagnóstico (
GoalAccountMetric) ainda não "assentado" para mais métricas. O/impeccable critiquepós-hardening (26/40; ver Follow-up #3) levantou, em "Questions to Consider", se hover/foco continua o mecanismo certo de explicação conforme E9/E10/B13/E11/E12/E13/E14 adicionam métricas a este mesmo card, e se "Captura"/"Share intracampo" deveriam virar um único número com toggle de denominador em vez de dois números cada um explicando o outro por tooltip. Nenhum achado adicional confirmado nesta sessão (é pergunta de design, não bug) — não registrado como item; gatilho: revisitar quando a próxima entrega de E9–E14 for adicionar uma 5ª métrica a estadl/grid (nesse ponto, decidir layout/idioma de disclosure antes de replicarGoalAccountMetricde novo). Largura tablet/foldable intermediária também não testada — mesmo gatilho. Reforço 2026-07-25 (E10): a classe territorial entrou neste card como uma linha acima dadl(TerritorialClassRow), não como 5ª métrica dentro dela — o gatilho continua não disparado, mas o/simplifydo E10 registrou que essa linha já reimplementa o estilo do trigger deGoalAccountMetric(o comportamento de toque/dismiss é compartilhado viaCampaignHoverTooltip, então só o botão duplica). Quando o gatilho disparar, decidir o idioma antes de replicar o componente pela terceira vez — ver "Adiado com gatilho" de escala-dry-pos-e10.md.
Referências
docs/roadmap.md(seção Inteligência de campanha) · plano-mestre (gaps G5/G6)docs/research/relatorio-entrevista-persona-campanha.md§5.1 (OMTM), §6.6 (denominadores/roll-off), FU2 (numerador/denominador)src/collections/Municipality.ts(expectedVotes/access),src/collections/ElectionTally.ts(campos por cargo)src/utilities/municipalityElectoralBaseline.ts,src/utilities/municipalityElectionGeography.ts,src/utilities/votePledgeData.ts,src/utilities/campaignDashboardData.ts- AGENTS.md — migrations, access staff-only, naming, transações