teqo-backup/docs/plans/mapa-projecao-municipios.md
Francisco Ignácio Fontoura Solla e8a440b837 Pass 2 W5: close-out de documentacao
- AGENTS.md: Known Gap #2 resolvido (D3), arvore de estrutura (+AllocationDecision, PrivacyPolicy, CampaignGoals; components por dominio), URLs novas (/campanha/assessores|conceitos|perfil), entrada 'Recently resolved' do Pass 2
- tracker: todos os workstreams done; secao de achados de drift de docs de produto (PRODUCT/DESIGN/CUSTOMER - reportados, nao aplicados)
- TESTING.md: 46/48/7 + linhas do safety net (formActions wrapper, voteTrend, submitWhatsapp)
- TECH-DEBT: smells fechados (municipalityUi split, detail RSCs, formActions, components subpastas, electionInsights)
- planos: status entregue nos 5 do Pass 2; escala-dry-pos-b16 absorvido; C8 F3/F4 fechados em escala-dry-pos-c6; notas de costura em B17/B18/cenario-junto; nota de conflito E4R em mapa-projecao
- roadmap: entrada Pass 2 no 'Ja entregue', B17/B18 marcados como triviais, B16+ riscado, header atualizado
- notebook do projeto: entrada Pass 2
- DESIGN.md: secao do sistema de listas (unico drift aplicado, conforme plano)
2026-07-25 16:08:38 -03:00

118 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mapa de projeção de votos por município (equiparar e superar as planilhas 2026)
Status: E1, E2 e E3 implementados e mesclados em `main` (2026-07-19); **E4 cortado** (2026-07-19) e **reaberto/entregue como E4R em 2026-07-24** — **atenção (nota 2026-07-25):** o E4R fase 2 importa TAMBÉM as colunas de estratégia (SITUAÇÃO/DOBRADINHAS/LIDERANÇAS/ASSESSOR/…), superando o que este plano descrevia como import zero-PII ([import-planilha-projecao.md](import-planilha-projecao.md) — `pnpm db:seed:projecao`); E5 pendente
Atualizado em: 2026-07-24 (E4R entregue — seed overwrite-always)
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha E — itens E1–E5, "Ciclo 3")
Responsável: —
## Contexto
A campanha opera hoje sobre duas planilhas Excel versionadas em `docs/sheets/` ("Mapa projeção de votos Solla 2026.xlsx" e `Mapa_projecao_votos_Solla_2026.xlsx` — a segunda é a rica). Elas cobrem os 417 municípios da Bahia com: série histórica de votos de Solla (2014/2018/2022), classificação de tendência (Queda/Mantém/Aumento), metas de votos em três cenários (Bom/Regular/Mínimo), lista de ~50 municípios prioritários com meta estadual de 71.000 votos, rede política por município (assessor responsável, lideranças, dobradinhas negociadas), encaminhamentos operacionais e um drill de Salvador por bairro (reconciliação não-oficial local↔bairro).
A vertical `/campanha` já cobre ou supera parte disso: baseline TSE 2022 por geografia do núcleo (A3/A4, mais rico que a planilha), CRM de lideranças (`leadership`↔`Contact`), responsável por território (`coordinators`), overview agregado do conjunto filtrado (B1) e Salvador por zona (baseline município×zona). Os gaps que este plano fecha: metas em cenários, prioridade, série histórica além de 2022 com tendência automática, dobradinhas/encaminhamentos manuais e o import único da planilha. Decisão de produto de 2026-07-19: equiparar e superar as planilhas dentro do produto, sem manter a planilha como fonte paralela.
## Objetivos
- Metas de votos em três cenários e flag de prioridade por núcleo, com escrita staff e leitura pelos três papéis (E1).
- Série histórica TSE 2014/2018/2022 (deputado federal, turno 1, BA) com classificação automática de tendência por núcleo (E2) — dado público, sem `Consent`.
- Campos manuais staff-only de dobradinhas e encaminhamentos na inteligência do núcleo (E3), excluídos dos view models de `lideranca`.
- ~~Import único e idempotente da planilha rica para núcleos (E4)~~ — **cortado 2026-07-19** (preenchimento manual via UI E1+E3; ver [roadmap](../roadmap.md) § Itens cortados).
- Registrar Salvador por bairro como item futuro (E5), sem implementar.
- Migrations somente em E1+E3 (campos novos em `electoralNucleus`); E2 e E4 não alteram schema.
## Decisões travadas
- **Sem entidade nova por município** — `electoralNucleus` absorve a camada estratégica (metas, prioridade, dobradinhas). A visão município-a-município é derivada: núcleos + baseline TSE agregados por `cities[]`. _(decisão de produto 2026-07-19; evita cadastro paralelo e reusa access/UI de núcleos)_
- **Série histórica 2014/2018 vem do TSE oficial**, estendendo o seed A3 (`pnpm db:seed:tse` parametrizado por ano) — não dos números da planilha. _(decisão de produto 2026-07-19; mesma proveniência e idempotência por escopo `(year, office, turn)` já existentes)_
- **Lideranças e assessores da planilha entram como texto staff-only, nunca como `Contact`/`leadership`.** Não há telefone nem consent na planilha; o CRM real é alimentado depois pelo fluxo normal com `Consent`. _(AGENTS.md — pessoa = `Contact` + junção com consent; Onda 0)_
- **Tendência é derivada, não persistida** — `computeVoteTrend` calcula em leitura a partir das collections A3, com limiares versionados como constantes (mesmo padrão de `computeGapVs2022` em `src/lib/electionInsights.ts`).
- **i18n e naming** seguem o AGENTS.md: identificadores em inglês (`voteGoals.good/regular/minimum`, `priority`, `dobradinhaNotes`, `nextSteps`, `computeVoteTrend`), strings visíveis e valores de enum de dados em pt-BR (`alta|normal`, `queda|mantem|aumento`).
## Questões em aberto
- **Banda de estabilidade da tendência?** **Resolvido (2026-07-19):** ±10% (`VOTE_TREND_STABLE_BAND = 0.1` em `electionInsights.ts`); par preferido 2018→2022.
- **E4 cria núcleo para todo município com dado estratégico (≈193) ou só para os ~50 prioritários?** **Cortado (2026-07-19)** — item E4 não será implementado; dados estratégicos entram manualmente via E1+E3.
- **Meta estadual (71.000) fica onde?** **Recomendação:** derivada — soma das metas dos núcleos no dashboard; sem campo global novo. Se produto quiser meta editável independente da soma, vira campo em global de campanha (fase posterior).
## Abordagem proposta
```mermaid
flowchart LR
subgraph E ["Trilha E — mapa de projeção"]
E1["E1 Metas em cenários + prioridade<br/>(migration)"]
E2["E2 Série TSE 2014/2018<br/>+ tendência derivada"]
E3["E3 Dobradinhas + encaminhamentos<br/>manuais (staff-only)"]
E5["E5 Salvador por bairro<br/>(registrado; futuro)"]
end
A3["A3/A4 baseline TSE 2022 ✓"] --> E2
E1 -.metas no coroplético.-> B3["B3 Mapa Leaflet"]
E2 -.classe por cor.-> B3
E2 -.dataset votacao_secao.-> E5
```
> **E4 (import único `pnpm db:seed:mapa`) — cortado 2026-07-19.** Decisão de produto: preencher metas, prioridade, dobradinhas e encaminhamentos manualmente na UI (E1+E3). O desenho original (script CLI idempotente, sem `Contact`/`leadership`) permanece documentado abaixo como referência histórica.
### E1 — Metas em cenários + prioridade no Núcleo (migration)
- **`electoralNucleus`** (`src/collections/ElectoralNucleus.ts`): grupo `voteGoals` (`good`/`regular`/`minimum`, numbers opcionais, validação `good ≥ regular ≥ minimum`) e `priority` (select `alta|normal`, default `normal`, indexado). Escrita staff via `canUpdateElectoralNucleus` (`geral` ou coordenador designado no núcleo) — **sem** `canSetDerivedNucleusField` (esse helper é só Payload-admin, usado nas estimativas derivadas). Leitura para os três papéis; E3 (`dobradinhaNotes`/`nextSteps`) staff-only via select/VM como `strengths`/`risks`.
- **UI:** card "Metas 2026" na aba Visão geral (3 cenários + `Progress` da estimativa confirmada vs. meta regular, ao lado de `NucleusElectoralBaseline`); filtro `priority` na lista (padrão dos filtros existentes em `src/utilities/nucleusUi.ts` / `buildNucleusListWhere`); bloco no overview B1 (`src/components/campaign/NucleusListOverview.tsx`): soma das metas por cenário no conjunto filtrado + contagem de prioritários; dashboard: soma estadual das metas (equivalente à "META: 71.000").
- **Migration:** `pnpm migrate:create add_nucleus_goals_strategy` (uma migration só para E1+E3), `pnpm generate:types` (skill `payload-migrations`).
### E2 — Série histórica TSE 2014/2018 + tendência automática
- **Seed:** `scripts/seed-tse-results.mjs` parametrizado por `--year=2014|2018|2022` (URLs `*_YYYY.zip` das mesmas famílias TSE; SHA-256 logado, pins opcionais em `EXPECTED_SHA256`). Escopo v1 histórico: **só `deputado_federal`, turno 1, BA**. Parser em `electionResultsCsv.ts` + build em `electionResultsBuild.ts` (`offices` opcional via `federalOnlyElectionOffices()`). Número do candidato: `CANDIDATE_NUMBER_BY_YEAR` (1313 nos três pleitos). Idempotência por `(year, office, turn)` — **sem migration**.
- **Insight:** `computeVoteTrend` + `VOTE_TREND_STABLE_BAND = 0.1` (±10%) em `src/lib/electionInsights.ts` → `queda|mantem|aumento|semBaseline`; par preferido 2018→2022.
- **Loader:** `loadCandidateSeriesByGeography` (query candidate-only multi-ano, espelha overview Gap) em `nucleusElectoralBaseline.ts`; `loadNucleusTrendOverview` para distribuição na lista.
- **UI:** série 2014→2018→2022 + badge no `NucleusElectoralBaseline`; Alert `vote-trend` no `NucleusInsights`; card "Tendência histórica" no `NucleusListOverview`.
- **Custo de I/O:** série usa query estreita (não multiplica o path caro do ranking federal — A7 F1 permanece backlog separado). Passagem `/simplify` no branch; débitos maiores → [escala-dry-pos-e2.md](escala-dry-pos-e2.md) (E7).
### E3 — Inteligência estratégica manual: dobradinhas + encaminhamentos
- Campos staff-only no bloco de inteligência do núcleo (mesma redação/access dos campos estratégicos existentes — `strengths`, `risks`): `dobradinhaNotes` (textarea — quem dobra ali hoje, estado da negociação) e `nextSteps` (textarea — encaminhamentos). Editados via `NucleusIntelligenceDialog` (`src/components/campaign/NucleusIntelligenceDialog.tsx` + `nucleusIntelligenceUi.ts`).
- Excluídos dos view models de `lideranca` (`src/utilities/nucleusViewModels.ts`, padrão já existente de campos sensíveis).
- Não conflita com A6 (dobradinha automática pós-TSE-2026) nem C4 (Demandas estruturadas): E3 é a versão manual imediata; os planos futuros complementam sem substituição forçada.
- Migration compartilhada com E1 (acima).
### E4 — Import único da planilha (`pnpm db:seed:mapa`) — **CORTADO**
**Status:** cortado em 2026-07-19. Motivo: decisão de produto — preferência por recolocar dados manualmente na UI em vez de bootstrap via planilha. Alternativa: criar/editar núcleos e preencher `voteGoals`, `priority`, `dobradinhaNotes` e `nextSteps` via E1+E3 em `/campanha/nucleos`.
Desenho original (não implementar):
- Script `scripts/seed-mapa-projecao.mjs` que lê `docs/sheets/Mapa_projecao_votos_Solla_2026.xlsx` (a rica) e cria/atualiza núcleos por município via Local API em transação: `voteGoals` (parse "Bom: X | Regular: Y | Mínimo: Z"), `priority` (`alta` quando flagado ou presente na aba PRIORITÁRIAS), `dobradinhaNotes`, `nextSteps` (encaminhamentos + observações). Município → nome canônico via `canonicalizeMunicipalityName` (`src/lib/bahiaMunicipalityCodes.ts`); região derivada no servidor (A1).
- Nomes de assessores/lideranças da planilha entram como texto nos campos staff-only — **nunca criam `Contact`/`leadership`** (decisão travada acima).
- Guard de banco local (mesma família dos outros seeds: `pnpm db:seed:posts` / `db:seed:tse`; override `ALLOW_REMOTE_DB=true` com o runbook documentado); idempotente por slug de núcleo; municípios sem nenhum dado estratégico não geram núcleo. Sem revalidate (páginas de campanha são dinâmicas).
### E5 — Salvador por bairro (registrar, não implementar)
- A aba SALVADOR da planilha usa uma reconciliação não-oficial local-TSE→bairro (DE-PARA). Reproduzir exigiria o dataset TSE `votacao_secao` + tabela de mapeamento versionada — custo alto para valor incerto. Fica registrado no roadmap como item futuro condicionado a decisão de produto; a visão por **zona** de Salvador já é coberta pelo baseline município×zona atual.
## Dependências
- **E2 ← A3/A4** (entregues): collections `electionTally`/`electionCandidateVote`/`electionCandidate`, seed `db:seed:tse`, `getNucleusElectoralBaseline`, `computeGapVs2022`.
- ~~**E4 ← E1 + E3** (duras)~~ — E4 cortado; E1+E3 atendem operação manual.
- **E1/E3** não dependem de nada novo — paralelizáveis com o restante da Janela 2.
- Suaves para B3 (mapa Leaflet): metas e classe de tendência como métricas do coroplético.
- Reusa: `withPayloadTransaction`, locks advisory, padrões de filtro/overview B1, skill `payload-migrations`.
## Não escopo
- Criar `Contact`/`leadership` a partir da planilha — vai para o fluxo normal com `Consent` (Onda 0 / [cadastro-nominal-apoiadores.md](cadastro-nominal-apoiadores.md)).
- Dobradinha automática por dados TSE 2026 — [insight-dobradinha-2026.md](insight-dobradinha-2026.md) (A6).
- Encaminhamentos estruturados com status/responsável — [demandas-campanha.md](demandas-campanha.md) (C4).
- Classificação territorial por limiares de produto — [insight-classificacao-territorial.md](insight-classificacao-territorial.md) (A5); `computeVoteTrend` é série própria do candidato, não classificação de território.
- Salvador por bairro (E5) — registrado, sem plano de implementação neste ciclo.
- Previsão estatística de votos — fora de escopo do ciclo (roadmap, revisão 2026-07-17).
## Referências
- `docs/roadmap.md` — Trilha E / Ciclo 3, janelas 2–3
- `docs/sheets/Mapa_projecao_votos_Solla_2026.xlsx` e `docs/sheets/Mapa projeção de votos Solla 2026.xlsx` — planilhas-fonte (estrutura, abas RESUMO/PRIORITÁRIAS/SALVADOR)
- `src/collections/ElectoralNucleus.ts` — campos, hooks e access do núcleo (E1/E3)
- `src/lib/electionInsights.ts` + `src/utilities/nucleusElectoralBaseline.ts` — padrão de insight derivado e agregação do baseline (E2)
- `scripts/seed-tse-results.mjs` + `src/lib/electionResultsBuild.ts` + `src/lib/electionResultsCsv.ts` — seed TSE parametrizado por ano (E2)
- `src/components/campaign/NucleusElectoralBaseline.tsx`, `NucleusListOverview.tsx`, `NucleusIntelligenceDialog.tsx` — superfícies de UI a estender
- `src/lib/bahiaMunicipalityCodes.ts` (`canonicalizeMunicipalityName`) — reconciliação de nomes no import (E4)
- [escala-dry-pos-a4.md](escala-dry-pos-a4.md) — A7 F1, custo de I/O do loader do baseline
- [escala-dry-pos-e2.md](escala-dry-pos-e2.md) — E7, débitos pós-`/simplify` do E2
- AGENTS.md — Campaign auth, pessoa = `Contact` + junção, `overrideAccess: false`, migrations com `push: false`, naming