Cross-checked by parallel code-quality/performance/reuse reviewers against the E9 commit. Behavior-preserving: - laterTimestamp/resolveMunicipalityLastSignalAt were the same "latest of two ISO timestamps" written twice; both now delegate to a new latestIsoTimestamp in campaignTime.ts, which documents the fixed-width UTC invariant that makes the string compare sound. - Hoisted the frescor comparator's Date.now() out of the per-comparison accessor so the sort key stops drifting mid-sort. - Deleted the unread baseTotal/growthFactor/belowBase passthrough from the statewide goal entry point and fixed a comment citing a symbol (goalDecompositionBelowBase) that never existed; renamed computeStatewideGoalDecomposition -> computeStatewideSuggestedGoals now that nothing is proportionally decomposed. - Collapsed the advisor badge's two copies (desktop table vs mobile card) into one AdvisorStatusBadge; they had quietly diverged on which icon renders for the "no advisor" state. - Renamed the sort-menu's stale "Cobertura" label for advisor coverage to "Assessoria" (E8 already renamed the list column for the same reason); derived the sort-key set from the label record instead of a third parallel list. - Consolidated three zeroed-scenario-record literals into one zeroByVoteEstimateScenario() factory in voteEstimate.ts. - Routed three separate votesByYear[2022] reads through one votesInYear helper in municipalityPotential.ts. - Gated the goal card's empty-state copy on !usesMesaEstimate so it stops telling a município with an existing mesa estimate to enter one. - Swapped a test assertion pinned to a raw Tailwind token for a stable data-signal="cold" hook, and tightened the "desert" test's bound to check against the actual growth factor (the old bound was true for every slug regardless of the formula). Left alone (roadmap decision, not cleanup): sanityCheckSuggestedGoalsByTerritory has no production caller yet (deferred to E11/E12); comment shortened instead of deleting the dead-but-documented code. Verified: tsc --noEmit, pnpm lint (0 warnings), 337 unit + 366 integration tests, pnpm build all green.
10 KiB
E16 — Dossiê do município (pré-agenda)
Status: entregue em código (2026-07-25)
Atualizado em: 2026-07-25 (as-built: aba ?tab=dossie staff-only com MunicipalityDossier.tsx + municipalityDossierData.ts compondo os loaders existentes — baseline TSE/rank A11, conta da cadeira E8, rede com frescor updatedAt, agenda actionPlan, sinais, perfil A8 com caveat para zonas; caps 8/5/3+2 com "ver tudo"; municipality.budgetNotes via migration 20260725_022155_add_municipality_budget_notes — o .json também sara a cadeia de snapshots pós-campaign_goals; print CSS: chrome print:hidden, shells print:h-auto print:overflow-visible, tipografia A4; int test com mock do unstable_cache do baseline. Revisão pré-implementação 2026-07-24: budgetNotes ganhou migration própria — o veículo C12 caducou; registrado o estado zero-consumidor de A8, resolvido pela seção "Perfil")
Item do roadmap: docs/roadmap.md (Inteligência de campanha, E16; plano-mestre inteligencia-campanha.md, gap G11)
Impeccable: B — visão nova dentro do detalhe do município existente (tab/rota dossie + print), sem sistema novo
Appetite: ~1 dia eng; uma migration pequena (budgetNotes — ver questão resolvida abaixo); o resto compõe dados existentes (fonte estruturada de emendas segue G11 adiado)
Responsável: —
Design (Impeccable)
Âncoras: PRODUCT.md (princípios 2 "clarity under pressure" e 6) / DESIGN.md (register product; Flat desk, print sóbrio) · detalhe do município existente (tabs, cards).
Na implementação: craft compacto → critique → polish.
- Persona / contexto: CG/assessor a caminho de uma agenda: "chego no território sem dossiê" (O6 — prefeito aliado?, economia, disputa local, emendas aportadas; caso Nazaré das Farinhas). Hoje esses dados existem espalhados em tabs.
- Job principal: uma tela (e um print/PDF de 1–2 páginas) com tudo que se lê antes de entrar na cidade.
- Estratégia de cor: Restrained; dossiê é leitura, não dashboard — tipografia e hierarquia, zero heat.
- Anti-goals: formulário novo para o assessor preencher (anti-goal E1 do relatório: extração sem devolução); duplicar os cards das tabs em vez de compô-los; virar relatório de 10 páginas.
Contexto
A sessão real de 2026-07-23 (CUSTOMER.md) trouxe O6 como o único pedido explícito de produto: dossiê de território pré-agenda com conjuntura local (prefeito aliado/oposição, disputa local "Jacu × Beija-Flor"), economia e emendas aportadas por município. O produto já tem quase tudo: série/captura (MunicipalityBaselineCard + artefato), pledges/estimativas (MunicipalityPledgesPanel, A10), lideranças (MunicipalityLeadershipsPanel), estratégia/conjuntura (MunicipalityStrategyCard: politicalTrend, strengths/risks, dobradinhaNotes, nextSteps, dobradinhas M4), sinais/atualizações (municipalityUpdate), demografia IBGE (A8 — bahiaMunicipalityDemographics.ts: população, faixas etárias). O que falta é a composição em visão única imprimível — e o único dado realmente novo é emendas (gap G11 do plano-mestre: manual-first; fonte estruturada Câmara/Transparência adiada com gatilho).
Objetivos
- Tab/rota "Dossiê" no detalhe do município (staff-only): seções na ordem de leitura de campo — capa (nome, TI, prioridade, nível quando existir), conta local (série 2014/2018/2022, % da própria votação via A11, captura/cobertura quando E8 existir), rede (lideranças ativas + responsável + frescor), conjuntura (
politicalTrend+ forças/riscos + disputa local), dobradinhas (M4 + notas), agenda/planos recentes e sinais recentes (últimosmunicipalityUpdate), perfil (demografia A8), emendas (quando houver dado — v1 mostra a seção só se preenchida). - Visão print:
print:-friendly (CSS print da mesma rota; sem lib de PDF) — 1–2 páginas A4, cabeçalho com data de geração ("dossiê de " — dado envelhece). - Emendas manual-first (G11): campo
budgetNotesstaff-only novo emmunicipality(migration pequena própria — decisão registrada na questão resolvida abaixo), editado no formulário de estratégia; a seção "Emendas aportadas" do dossiê só aparece quando preenchido. - Access: tudo staff (coordinator/advisor/candidate); leader não acessa (lockdown); campos staff-only seguem a redaction dos view models.
Decisões travadas
- Dossiê compõe, não duplica. As seções renderizam os mesmos loaders/componentes das tabs (ou variantes compactas), nunca uma segunda fonte. Rejeitado: collection
dossierou snapshot persistido (o dossiê é leitura derivada; snapshot ex-ante é papel doallocationDecision/C12). - Print via CSS, não gerador de PDF. 1 rota,
@media print, sem dependência. Rejeitado: lib de PDF server-side (peso e manutenção por um ganho nulo — o uso real é imprimir/compartilhar do navegador). - Emendas entram manual-first (G11). Fonte estruturada (Câmara/Portal da Transparência) só com gatilho (E16 em uso + pedido de dado sistemático). Rejeitado: import automático de emendas na v1 (pipeline novo por uma seção que começa vazia).
- i18n e naming: rota/tab
dossie(valor de URL em pt, como os demais),MunicipalityDossier*,municipalityDossierData.ts; labels pt-BR ("Dossiê", "Conjuntura", "Emendas aportadas").
Questões em aberto
Onde mora o dado de emendas na v1?Resolvida (2026-07-24): campo staff-only novobudgetNotesemmunicipality, com migration própria pequena (add_municipality_budget_notes). A opção "carona na migration de C12" caducou — C12 foi entregue com20260724_180000_add_campaign_foundation_recordsantes de E16 começar. Rejeitada: convenção dentro denextSteps(polui a semântica do campo e quebra a regra "seção Emendas só aparece se preenchida").- Dossiê mostra nível N0–N4 (E14)? Recomendação: sim quando existir, staff-only — mas nunca no print destinado a circulação além da coordenação (vocabulário duplo). v1: print omite nível por default.
Abordagem proposta
flowchart LR
Data["municipalityDossierData.ts<br/>(compõe loaders existentes)"]
Baseline["baseline/A11<br/>(série, rank, share)"]
Rede["lideranças + frescor"]
Conj["politicalTrend + forças/riscos<br/>+ dobradinhas + emendas (G11)"]
Sinais["municipalityUpdate recentes"]
Perfil["demografia A8"]
View["tab Dossiê + @media print"]
Baseline --> Data
Rede --> Data
Conj --> Data
Sinais --> Data
Perfil --> Data
Data --> View
Componentes: src/utilities/municipalityDossierData.ts (novo — orquestra loaders existentes em uma passada), src/components/campaign/MunicipalityDossier.tsx (+ seções compactas reusando cards atuais), entrada na MunicipalityTabNav (dossie), CSS print na própria rota.
Dependências
- Nenhuma dura — compõe o que existe (baseline, pledges, lideranças, estratégia, updates, A8). Melhora com: A11 (rank/share), E8 (captura/cobertura/meta na "conta local"), C12 (sinais tipados na seção de sinais; migration compartilhada para
budgetNotes), E14 (nível), A6 (dobradinha 2026 real). - Alimenta: E13 (o compositor de giros linka o dossiê de cada município do giro — preparação de visita).
Não escopo
- Import estruturado de emendas (G11 adiado com gatilho); dossiê por TI (leitura regional é E17/E12); snapshot histórico de dossiê (C12/E15 cobrem o ex-ante); dossiê para
leader(lockdown).
Rabbit holes
- Virar relatório infinito. 1–2 páginas A4; cada seção tem teto de itens (ex.: 5 sinais recentes, 8 lideranças) com link "ver tudo" na tab correspondente.
- Economia local virar pesquisa. v1 usa o que A8 tem (demografia); "economia" além disso é nota de conjuntura manual — não construir base socioeconômica nova.
- Print pixel-perfect. CSS print razoável basta; não é material de campanha, é documento de trabalho.
Já resolvido no simplify (não reabrir)
- Dados mortos
votePosition2022/pledgeCoverageremovidos deMunicipalityDossierData(rank já vem do baseline card). - Agenda reusa
buildActionPlanListWhere+actionPlanListSelect+toActionPlanListViewModel+pagination: false. loadFreshestMunicipalityLeaderships(sort-updatedAt+ limit no Postgres) em vez de fetch-all-and-slice.- Conta da cadeira encadeada no
Promise.all(pledges → goal), concorrente com baseline/rede/sinais/agenda. - Datas/números via
formatBahiaDateTimeLabel/formatElectionNumber; Prettier emmigrations/index.tse fontes novas.
Explicitamente fora (triage pós-simplify 2026-07-25)
- Memoização em module-scope de
computeStatewideSuggestedGoals— pré-existente E8, fora do diff do dossiê (revisitar no plano conta-da-cadeira.md se o hot path do dashboard doer). - Encolher o page size do feed de sinais de 10 → 5 — o loader compartilhado precisa de
totalDocs; micro-otimização sem evidência. - Critique do
MunicipalityGoalAccountCard(tooltips touch) — sessão E8, já hardenido no follow-up #3 daquele plano; não é dívida do E16.
Adiado com gatilho
- G11 fonte estruturada de emendas (Câmara/Portal da Transparência → valor por município/ano). Gatilho: E16 em uso real + coordenação pedindo o dado sistemático.
- Compartilhamento do dossiê via WhatsApp (kit). Gatilho: pedido de campo; hoje o print resolve.
- Helper compartilhado da proveniência da meta (
usesMesaEstimate/ rótulo "estimativa da mesa" vs "meta sugerida"). Hoje 2 call sites (MunicipalityGoalAccountCard+ dossiê) abaixo do limiar de abstração do repo. Gatilho: 3º consumidor da mesma derivação/cópia.
Referências
docs/roadmap.md(E16) · plano-mestre (G11) · planejador-de-giros.md (E13 — consome)docs/CUSTOMER.md— O6 (pedido explícito; Nazaré das Farinhas), restrição "perna" (dossiê barateia a preparação da agenda)src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx(tabs),src/components/campaign/MunicipalityBaselineCard.tsx,MunicipalityStrategyCard.tsx,MunicipalityLeadershipsPanel.tsx,MunicipalityPledgesPanel.tsxsrc/lib/bahiaMunicipalityDemographics.ts(A8),src/lib/bahiaElectionAggregates.ts, ranking-votos-municipio.md (A11)PRODUCT.md/DESIGN.md— clareza sob pressão; print sóbrio; AGENTS.md — access, naming