mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
Register B15 column-header sort on the municipalities list.
This commit is contained in:
parent
39cbce8d79
commit
df0acc6500
4 changed files with 144 additions and 14 deletions
|
|
@ -7,10 +7,10 @@ alwaysApply: false
|
|||
|
||||
## Status
|
||||
|
||||
- **B15 registrado (2026-07-24):** ordenar `/campanha/municipios` pelo header da coluna — contrato URL `?sort=`/`?dir=`, clique no header (desktop) + select mobile; A11 consome a key `votos`. Plano: [`ordenacao-colunas-lista-municipios.md`](../../../docs/plans/ordenacao-colunas-lista-municipios.md).
|
||||
- **Fill-in registrado (2026-07-24):** seletor **Cenário** de `/campanha/municipios` → fileira de `MunicipalityFilters` (provider envolve filtros+resultados; overview sem seletor). Plano: [`cenario-junto-filtros-municipios.md`](../../../docs/plans/cenario-junto-filtros-municipios.md).
|
||||
- **E4R entregue em código (2026-07-24):** `pnpm db:seed:projecao` (`scripts/import-projecao.mjs` + `src/lib/projectionSheetParse.ts`) importa MAPA GERAL canônico → `expectedVotes` (Bom→otimista, Regular→média, Mínimo→pessimista) + `priority` com **overwrite-always** (re-seed quando chegar planilha nova); Salvador pulado; zero PII; dry-run + runbook. No mesmo dia o grupo duplicado `voteGoals` foi removido do app (ver Decisões). Seed local: arquivo `docs/sheets/Mapa projeção de votos Solla 2026.xlsx`, **189** municípios com estimativas, **50** `priority=alta`, re-run delta=0. Produção: após smoke, `ALLOW_REMOTE_DB=true … pnpm db:seed:projecao -- --dry-run` → apply. Plano: [`import-planilha-projecao.md`](../../../docs/plans/import-planilha-projecao.md).
|
||||
|
||||
- **Sync roadmap × sessão de campo (2026-07-24):** os achados da sessão real do CG (2026-07-23, `docs/CUSTOMER.md`) foram incorporados ao roadmap e aos planos de inteligência — âncora da mesa = **% da própria votação** (A11 entrega a lente; E9/E10/B13 expõem ao lado de LQ/captura); piso 150 mil resolve a meta inicial de E8; O6 vira **E16 dossiê do município** + gap **G11 emendas por município**; O7 confirma C12 não cortável; E13 rebaixado na fila de corte ("perna" é a restrição dominante); gatilho de D3 reformulado (deltas do ZAP não absorvidos pelo fluxo sede-digita/C12); FD2 absorvido por R6; O8 "ilhas isoladas" em A validar. **Decisão de produto: E4 reaberto como E4R** — seed único via script das planilhas de `docs/sheets/` (arquivo com espaços = canônico de estratégia; underscore = referência Salvador; v1 só `voteGoals`/`priority`, sem PII; Salvador pulado com relatório). Novos itens: **E4R** (janela 1, não cortável), **A11**, **E17 tabela TI no Início** (pedido do candidato; primeira fatia de E12), **B14 município mais próximo** (geolocalização → atalho no Início staff; prompt 1×/sessão; client-only). Planos novos: `import-planilha-projecao.md`, `ranking-votos-municipio.md`, `tabela-ti-inicio.md`, `dossie-municipio.md`, `municipio-mais-proximo.md`.
|
||||
- **Sync roadmap × sessão de campo (2026-07-24):** os achados da sessão real do CG (2026-07-23, `docs/CUSTOMER.md`) foram incorporados ao roadmap e aos planos de inteligência — âncora da mesa = **% da própria votação** (A11 entrega a lente; E9/E10/B13 expõem ao lado de LQ/captura); piso 150 mil resolve a meta inicial de E8; O6 vira **E16 dossiê do município** + gap **G11 emendas por município**; O7 confirma C12 não cortável; E13 rebaixado na fila de corte ("perna" é a restrição dominante); gatilho de D3 reformulado (deltas do ZAP não absorvidos pelo fluxo sede-digita/C12); FD2 absorvido por R6; O8 "ilhas isoladas" em A validar. **Decisão de produto: E4 reaberto como E4R** — seed único via script das planilhas de `docs/sheets/` (arquivo com espaços = canônico de estratégia; underscore = referência Salvador; v1 só `expectedVotes`/`priority`, sem PII; Salvador pulado com relatório). Novos itens: **E4R** (janela 1, não cortável), **A11**, **E17 tabela TI no Início** (pedido do candidato; primeira fatia de E12), **B14 município mais próximo** (geolocalização → atalho no Início staff; prompt 1×/sessão; client-only), **B15 ordenar lista por coluna**. Planos novos: `import-planilha-projecao.md`, `ranking-votos-municipio.md`, `tabela-ti-inicio.md`, `dossie-municipio.md`, `municipio-mais-proximo.md`, `ordenacao-colunas-lista-municipios.md`, `cenario-junto-filtros-municipios.md`.
|
||||
- **DEPLOY EM PRODUÇÃO REALIZADO (2026-07-23).** A cadeia destrutiva da remodelagem (`20260721_020109_remodel_municipalities` → `20260723_200000_remodel_municipalities` + `20260723_202000_reconcile_municipality_remodel`) foi aplicada em produção pelos builds Vercel de 2026-07-23 (deploys production READY dos commits `a7b8f1b`/`c7a1b74`/`59868a9`). **Este item supersede toda menção "deploy pendente" nas entradas abaixo.** Pendências operacionais: smoke pós-deploy + onboarding do time (Onda 0 §2/§4 em [`docs/roadmap.md`](../../../docs/roadmap.md)); hold de PII real segue no lote jurídico.
|
||||
- **Hardening de engenharia entregue e em produção (2026-07-23, Fases 0–6):** (0) access lockdown — `users`/`signature`/`subscription`/`consent` admin-only, escrita de CMS (`post`/`tag`/`media`/`petition`) admin-only, gates de rota do leader em `demandas*`/`planos*`, `createLeaderSupporter` com escopo correto, `candidate` tratado como staff irrestrito em todas as superfícies; (1) tooling gate — knip no CI (`.github/workflows/ci.yml`, mirror GitHub), `pnpm lint --max-warnings=0`, ban `as never` (helpers `hookFilledCreateData`/`stub<T>`), tsconfig consolidado + `noUnusedLocals`, ~12 módulos mortos deletados; (2) caching — artefato TSE commitado (`pnpm build:election-aggregates` → `src/lib/electionAggregates/`, loader `bahiaElectionAggregates.ts`), `unstable_cache` com tag `election-tse` nos loaders de eleições, `loadMunicipalityScope` (React `cache()`) dedupe dashboard+mapa+lista; (3) pending honesto — bug de revalidação do toggle de tarefa, `CampaignListPendingBoundary` esmaecendo resultados, transição no compare do mapa, mapa do dashboard + abas do detalhe em Suspense; (4) state scoping — hover do mapa isolado em `MunicipalityMapSelection`, provider de cenário estreitado, `LeaderContactForm` extraído como ilha cliente; (5) estrutura — `campaignAccess` dividido em `src/utilities/access/*` (re-export preservado), `supporterImport.ts` separado, `runStaffEntityMutation` + `runCampaignRedirectFormAction` dedupando twins e 4 ladders de formActions, rename `PLAZA_*`→`MUNICIPALITY_*` concluído; (6) docs + regra `.cursor/rules/engineering-standards.mdc`. Tracker: [`docs/IMPROVE-CODE-QUALITY-PLAN.md`](../../../docs/IMPROVE-CODE-QUALITY-PLAN.md) · ledger: [`docs/TECH-DEBT.md`](../../../docs/TECH-DEBT.md) · mapa de testes: [`docs/TESTING.md`](../../../docs/TESTING.md).
|
||||
- **Remodelagem Municípios entregue em código (2026-07-23; deploy pendente — migrações `20260723_200000_remodel_municipalities` + `20260723_202000_reconcile_municipality_remodel` são destrutivas para a vertical campanha).** Decisões pós-reunião 2026-07-23: rename Praça→Município (`municipality`, `/campanha/municipios`); **435** entradas (Camaçari única; Salvador 19 zonas); role `candidate` (visão irrestrita); demandas staff-only; leader lockdown (ferramenta de contatos de apoiadores); dobradinhas (`stateDeputy`); mapa no Início (dashboard). Plano-mestre: [`remodelagem-municipios.md`](../../../docs/plans/remodelagem-municipios.md) (substitui `remodelagem-pracas.md`).
|
||||
|
|
|
|||
128
docs/plans/ordenacao-colunas-lista-municipios.md
Normal file
128
docs/plans/ordenacao-colunas-lista-municipios.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# B15 — Ordenar lista de municípios pelo header da coluna
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-24
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Demais itens abertos, B15; superfície de coordenação)
|
||||
Impeccable: B — encaixe em `MunicipalityList` / `MunicipalityFilters` em `/campanha/municipios`; sem rota nova
|
||||
Appetite: ~0,5–1 dia eng; URL `sort`/`dir` + headers clicáveis + apply no loader; sem migration, sem collection, sem Consent
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (princípios 2 — clareza sob pressão — e 8 — Feel the action) / `DESIGN.md` (register `product`; Field Desk) · tema `data-theme='campaign'` · shells `CampaignPageShell`, `CampaignListPendingBoundary`, shadcn `Table`.
|
||||
|
||||
Na implementação (`implement-roadmap-item`): craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** Alex (CG / Assessor / Candidato) na lista de até 435 municípios, montando um recorte e precisando **ver quem fica no topo** por uma coluna (votos estimados, frescor, cobertura) sem sair da tabela.
|
||||
- **Job principal:** um clique no header reordena a lista (e a paginação) por aquela coluna; segundo clique inverte a direção.
|
||||
- **Estratégia de cor:** Restrained — indicador de sort sóbrio (`aria-sort` + ícone Chevron), sem heatmap na header.
|
||||
- **Edit where you see:** não — só leitura/navegação da ordem; células editáveis (B9) permanecem; sort não vira modo planilha.
|
||||
- **Anti-goals:** data-grid / spreadsheet mode; lib de tabela nova; sort só na página corrente (mentiroso); select "Ordenar por" como affordance principal no desktop (header é o padrão).
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim, superfície neste item — a tabela já existente; este item só controla a **ordem** (não inventa métrica).
|
||||
- **Decisões desbloqueadas:**
|
||||
- Staff: "quais municípios do meu recorte têm menos votos estimados / estão sem atualização / sem assessor — atacar primeiro?"
|
||||
- Staff: "ordenar por nome/TI para achar rápido no recorte filtrado?"
|
||||
- **Forma escolhida:** **tabela / lista ranqueada** (já é a forma) + affordance de sort no header — **por quê:** comparar muitas entidades por uma coluna é exatamente o degrau tabela. **Rejeitado:** chart de ranking; segundo painel "top N"; sort client-only da página atual (ordem global falsa).
|
||||
- **Profile:** categórico + numérico + temporal na mesma tabela; granularidade = município operacional; tamanho típico = página de 25 sobre filtro ≤435; absoluto vs relativo inalterado (A11/`votos` entra depois).
|
||||
- **Anti-goals de dado:** sem % estadual; sem reordenar o mapa por este `sort` (mapa tem outro job; filtro de lista já o restringe via B7).
|
||||
|
||||
Self-check dados: 5/5.
|
||||
|
||||
## Contexto
|
||||
|
||||
Em `/campanha/municipios`, `MunicipalityList` (`src/components/campaign/MunicipalityList.tsx`) renderiza headers estáticos (`Praça`, `Território de identidade`, `Tipo`, e no staff `Assessores`, `Tendência`, `Votos estimados`, `Última atualização`, `Cobertura`). O loader (`loadMunicipalityListPageBundle` em `municipalityPageData.ts`) pagina com `sort: 'name'` fixo. Filtros já vivem na URL via `MunicipalityListState` / `campaignListUrl.ts` (`q`, `region`, `kind`, `coverage`, `priority`, `trend`, `page`) — **não há** `sort`/`dir`.
|
||||
|
||||
**A11** ([ranking-votos-municipio.md](ranking-votos-municipio.md)) propõe `?sort=votos` via select nos filtros (lente de concentração da própria votação). Sem um contrato de ordenação por coluna, A11 e depois **E9** reinventam o mesmo estado. Este item entrega o contrato + a UX de header; A11 só acrescenta a chave `votos` (e a coluna/leitura de rank).
|
||||
|
||||
Pedido de produto (2026-07-24): reordenar a tabela ao clicar no header da coluna.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Headers clicáveis na tabela desktop de `/campanha/municipios` para as colunas sortáveis; `aria-sort` + indicação visual da coluna/direção ativas.
|
||||
- Estado canônico na URL: `?sort=<key>&dir=asc|desc` (omitir defaults), integrado a `parseMunicipalityListParams` / `buildMunicipalityListHref` / `resolveMunicipalityListUrl` (params desconhecidos continuam a redirecionar).
|
||||
- Ordenação **global no conjunto filtrado** (não só a página 25) + paginação coerente; reset de `page` para 1 ao mudar `sort`/`dir`.
|
||||
- Pending honesto via `CampaignListPendingBoundary` / transição já usada pelos filtros (Feel the action).
|
||||
- Mobile (cards): mesma ordem via URL; affordance compacta "Ordenar" no disclosure de `MunicipalityFilters` (sem fingir headers em cards).
|
||||
- Guardrails: **sem migration**, sem collection, sem Consent, sem server action; leader segue sem a página (redirect); access inalterado (`overrideAccess: false`).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Contrato URL `sort` + `dir` (não só `sort=-campo`).** Keys estáveis em inglês; `dir` explícito `asc|desc`. Defaults omitidos na URL: `sort=name` + `dir=asc` (comportamento atual). **Rejeitado:** Payload-style `-name` só na query (opaco p/ toggle e p/ A11/`votos`); sort só em state client sem URL (quebra share/back); cookie/localStorage de preferência (surpresa entre atores/dispositivos).
|
||||
- **Affordance principal = clique no header (desktop); select compacto no mobile.** Mesmo builder de href. **Rejeitado:** só select "Ordenar por" no desktop (A11 rascunhou isso — insuficiente p/ multi-coluna e piora a descoberta); client-side DataTable sem RSC (mente a paginação e compete com B9).
|
||||
- **Chaves v1 (coluna → key):** `name` (Praça), `region` (TI), `kind` (Tipo), `trend` (Tendência), `expectedVotes` (Votos estimados — cenário **central**), `lastUpdateAt` (Última atualização), `coverage` (Cobertura = tem assessor). **Assessores não é sortável na v1** (célula é editor B9; ordenar por nome/contagem é ambíguo). **Rejeitado:** sort por cada assessor individual; sort por `priority` sem coluna dedicada (já há filtro `?priority=alta`).
|
||||
- **Apply: Payload `sort` quando o campo é nativo; in-memory sobre o conjunto filtrado quando derivado.** `name` / `region` / `kind` / `lastUpdateAt` / `politicalTrend.status` via `payload.find({ sort })`. `expectedVotes` (group → central) e `coverage` (existência/contagem de advisors): carregar o filtrado (`pagination: false` ou reusar docs do `loadMunicipalityScope` no path staff), ordenar, fatiar a página — ≤435 linhas, aceitável. **Rejeitado:** sort só dos 25 da página; PostGIS/SQL custom neste item; materializar colunas derivadas.
|
||||
- **Toggle:** 1º clique numa coluna aplica direção default (texto/`name`/`region`/`kind`/`trend`/`coverage` → `asc`; `expectedVotes`/`lastUpdateAt` → `desc`); 2º clique na mesma inverte `dir`. **Rejeitado:** ciclo de 3 estados voltando ao default (mais difícil de explicar); sempre `asc` no 1º clique em números (esconde o pior/mais recente).
|
||||
- **i18n e naming:** `MunicipalityListSortKey`, `municipalityListSortDefaults`, `MunicipalitySortableHead`, `buildMunicipalitySortHref`, `applyMunicipalityListSort`; labels pt-BR inalterados; `aria-label` do botão do header em pt-BR (ex. "Ordenar por votos estimados").
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Nulls / sem valor (sem tendência, sem `lastUpdateAt`, sem `expectedVotes`)?** **Opções:** A) sempre no fim | B) sempre no início | C) tratar como 0 / data mínima. **Recomendação:** **A** (nulls last em qualquer `dir`) — evita "Sem atualização" conquistar o topo em `desc`. _(assumido — validar no craft)_
|
||||
- **Renomear header "Praça" → "Município" neste item?** **Opções:** A) sim de passagem | B) deixar para R6. **Recomendação:** **B** — copy da remodelagem é lote visual R6; não misturar com sort.
|
||||
- **Espelhar sort por header em outras listas (apoiadores, lideranças)?** **Opções:** A) neste item | B) fill-ins sob demanda | C) never. **Recomendação:** **B** — 1 call site; extrair `SortableTableHead` só se o 2º consumidor aparecer (depth check).
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
URL["?sort=&dir=\nmunicipalityUi.ts"]
|
||||
Filters["MunicipalityFilters\n(select mobile)"]
|
||||
Head["MunicipalitySortableHead\n(desktop)"]
|
||||
Loader["loadMunicipalityListPageBundle"]
|
||||
Apply["Payload sort | in-memory\n+ slice page"]
|
||||
List["MunicipalityList"]
|
||||
Pending["CampaignListPendingBoundary"]
|
||||
|
||||
URL --> Filters
|
||||
URL --> Head
|
||||
Filters --> Pending
|
||||
Head --> Pending
|
||||
Pending --> Loader --> Apply --> List
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`municipalityUi.ts`**: estender `MunicipalityListState` com `sort?: MunicipalityListSortKey` e `dir?: 'asc' | 'desc'`; allowlist em `municipalityListParamNames`; parse/build/canonical omitindo defaults; helper `buildMunicipalitySortHref(state, nextKey)` (toggle dir / default dir / `page: 1`).
|
||||
- **`MunicipalitySortableHead`** (em `src/components/campaign/`, client leve ou Link+transition): botão/`Link` no `TableHead` com `aria-sort`, ícone, `useTransition` + pending da lista (reusar padrão dos filtros — **não** inventar segundo pending).
|
||||
- **`MunicipalityList.tsx`**: receber `state` (ou `sort`/`dir` + builder); trocar headers estáticos pelos sortáveis; cards mobile inalterados na estrutura.
|
||||
- **`MunicipalityFilters.tsx`**: control compacto "Ordenar" visível em `md:hidden` (ou sempre no disclosure mobile) espelhando as mesmas keys; desktop pode omitir o select.
|
||||
- **`municipalityPageData.ts`**: map key→Payload sort string; branch in-memory para `expectedVotes`/`coverage`; unit tests do comparator puro (extrair `compareMunicipalityListRows` em `src/lib/` ou util puro se ficar testável sem Payload).
|
||||
- **Sem migration, sem collection, sem server action.**
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura de outro item aberto. Reusa `campaignListUrl.ts`, bundle A9+ (`loadMunicipalityScope` / lista), B9 (células editáveis coexistindo), pending do hardening.
|
||||
- **Suaves (consumidores):** **A11** adiciona key `votos` + coluna/leitura sobre este contrato (atualizar plano A11); **E9** depois adiciona `deficit` / risco-frescor na fila (pode ser outra superfície, mas o padrão de URL/header nasce aqui).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Lente rank/% da própria votação e key `votos` → [A11](ranking-votos-municipio.md).
|
||||
- Fila de alocação / ordenação por déficit → [E9](fila-de-alocacao.md).
|
||||
- Critique/polish amplo e rename Praça→Município na UI → R6.
|
||||
- Sort em apoiadores / lideranças / organizações (fill-in futuro).
|
||||
- Reordenar features do mapa por `?sort=` (mapa não é esta tabela).
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Spreadsheet / data-grid mode.** Header sort ≠ editar grade; não puxar TanStack Table / reorder de colunas / resize. **Mitigação:** só `TableHead` botão + URL; B9 continua Popover por célula.
|
||||
- **Abstrair `useSortableList` / factory multi-lista cedo.** Um call site. **Mitigação:** helper de href + comparator locais; extrair no 3º consumidor.
|
||||
- **Sort client-only da página atual “porque é mais rápido”.** Mente o ranking global. **Mitigação:** decisão travada de apply no loader.
|
||||
- **Optimistic reorder sem refresh.** Lista é fonte da verdade RSC. **Mitigação:** transition + dim de resultados; anti-goal "optimistic list without refresh" (`campanha-action-feedback`).
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Coluna Assessores sortável (por contagem ou nome do 1º).** Revisitar quando: pedido explícito da mesa ou A11/E9 exigirem desempate por rede.
|
||||
- **Select "Ordenar por" no desktop** além do header. Revisitar quando: discovery mostrar que o header não é descoberto (improvável) ou A11 quiser atalho sem coluna ainda visível.
|
||||
- **Shared `SortableTableHead` em `components/ui`.** Revisitar quando: 2ª lista de `/campanha` pedir o mesmo padrão.
|
||||
|
||||
## Referências
|
||||
|
||||
- `docs/roadmap.md` (B15) · [ranking-votos-municipio.md](ranking-votos-municipio.md) (A11 — consome contrato) · [fila-de-alocacao.md](fila-de-alocacao.md) (E9) · [filtros-auto-pracas.md](filtros-auto-pracas.md) (URL + pending) · [edicao-rapida-lista-pracas.md](edicao-rapida-lista-pracas.md) (B9 — coexistência)
|
||||
- `src/components/campaign/MunicipalityList.tsx`, `MunicipalityFilters.tsx`, `CampaignListPending.tsx`
|
||||
- `src/utilities/municipalityUi.ts`, `municipalityPageData.ts`, `campaignListUrl.ts`, `municipalityViewModels.ts`
|
||||
- AGENTS.md — naming; Campaign auth / staff vs leader; engineering-standards (pending, zero warnings)
|
||||
- `PRODUCT.md` / `DESIGN.md` — Field Desk; Feel the action; anti SaaS-grid
|
||||
|
|
@ -25,7 +25,7 @@ A leitura "posição em votos absolutos + % da própria votação" é a **concen
|
|||
## Objetivos
|
||||
|
||||
- **Detalhe do município:** no `MunicipalityBaselineCard` (ou card irmão compacto), por ano da série (default 2022): posição em votos absolutos (rank denso entre as 435 unidades do catálogo, ex.: "12º de 435") e **% da própria votação estadual** (votos no município ÷ Σ votos do candidato no ano, ex.: "3,1% da votação").
|
||||
- **Lista de municípios:** ordenação nova `?sort=votos` (ano default 2022) em `municipalityUi.ts`/`campaignListUrl.ts`, preservando filtros/paginação; coluna/linha secundária com votos 2022 + % da própria votação.
|
||||
- **Lista de municípios:** chave de ordenação nova `?sort=votos` (ano default 2022) no contrato URL de **B15** ([ordenacao-colunas-lista-municipios.md](ordenacao-colunas-lista-municipios.md) — `sort`/`dir` + header clicável); preservar filtros/paginação; coluna/linha secundária com votos 2022 + % da própria votação.
|
||||
- **Helper puro** `src/utilities/municipalityVoteRank.ts`: computa uma vez por request (rank map + shares por ano sobre `federalBaselineMunicipalitySlugs()`), testável em unit; consumido pelo detalhe, pela lista e depois por E9/E17.
|
||||
- Access: superfícies staff (coordinator/advisor/candidate); leader segue em lockdown (sem páginas de município) — nada muda.
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ A leitura "posição em votos absolutos + % da própria votação" é a **concen
|
|||
- **Denominador do share = Σ votos do candidato no ano no artefato** (recorte BA). É a concentração da própria votação — não % dos válidos locais (dominância, outra métrica, já existe no mapa) nem % do eleitorado. **Rejeitado:** % do eleitorado local como âncora (a mesa rejeitou explicitamente).
|
||||
- **Rank denso** (empates dividem posição) e formatação pt-BR compacta. **Rejeitado:** rank ordinal com desempate arbitrário.
|
||||
- **Sem persistência** — derivado do artefato em memória (435 chaves; O(n log n) por ano, cacheável por request). **Rejeitado:** coluna materializada/migration (dado imutável já commitado).
|
||||
- **i18n e naming:** `municipalityVoteRank.ts`, `computeVoteRankByYear`, `voteShareOfCandidate`, `?sort=votos`; labels pt-BR ("Posição em votos", "% da votação").
|
||||
- **i18n e naming:** `municipalityVoteRank.ts`, `computeVoteRankByYear`, `voteShareOfCandidate`, key `votos` no contrato B15 (`?sort=votos&dir=…`); labels pt-BR ("Posição em votos", "% da votação").
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
|
|
@ -49,16 +49,16 @@ flowchart LR
|
|||
Artifact["bahiaElectionAggregates.ts<br/>(votesByYear por slug)"]
|
||||
Rank["municipalityVoteRank.ts<br/>(rank denso + share por ano)"]
|
||||
Card["MunicipalityBaselineCard<br/>(posição + % da votação)"]
|
||||
List["MunicipalityList + sort=votos<br/>(municipalityUi.ts)"]
|
||||
List["MunicipalityList + sort=votos<br/>(contrato B15)"]
|
||||
Artifact --> Rank --> Card
|
||||
Rank --> List
|
||||
```
|
||||
|
||||
Componentes tocados: `src/utilities/municipalityVoteRank.ts` (novo, puro + unit tests), `municipalityUi.ts` (`MunicipalityListState.sort`), `MunicipalityFilters.tsx` (select "Ordenar por" — padrão auto-aplicado dos filtros), `MunicipalityList.tsx` (linha com votos/% quando ordenado por votos), `MunicipalityBaselineCard.tsx` (posição/share por ano), `municipalityPageData.ts` (ordenar o conjunto já carregado — o bundle A9+ carrega o conjunto filtrado inteiro; sem paginação SQL nova).
|
||||
Componentes tocados: `src/utilities/municipalityVoteRank.ts` (novo, puro + unit tests), `municipalityUi.ts` (nova key `votos` no allowlist B15), `MunicipalityList.tsx` (header `votos` + linha com votos/%), `MunicipalityBaselineCard.tsx` (posição/share por ano), `municipalityPageData.ts` (apply da key `votos` sobre o filtrado — mesmo path in-memory do B15). **Não** reinventar select "Ordenar por" no desktop se B15 já entregou header + select mobile.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura (artefato em produção desde o hardening 2026-07-23). Relação: **E9** absorve `sort`/coluna como parte da fila (o select de ordenação nasce aqui e E9 adiciona `deficit`/`risco-frescor`); **E17** reusa o helper para o rollup por TI; **E4R** deixa `priority` real para o cruzamento visual.
|
||||
- Nenhuma dura de schema (artefato em produção desde o hardening 2026-07-23). **Suave:** **B15** ([ordenacao-colunas-lista-municipios.md](ordenacao-colunas-lista-municipios.md)) — se B15 ainda não estiver entregue, A11 pode introduzir `sort`/`dir` mínimos só para `votos`+`name`, mas o padrão canônico é reusar B15. Relação: **E9** absorve `sort`/coluna como parte da fila (E9 adiciona `deficit`/`risco-frescor`); **E17** reusa o helper para o rollup por TI; **E4R** deixa `priority` real para o cruzamento visual.
|
||||
|
||||
## Não escopo
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Componentes tocados: `src/utilities/municipalityVoteRank.ts` (novo, puro + unit
|
|||
|
||||
## Referências
|
||||
|
||||
- `docs/roadmap.md` (A11) · [fila-de-alocacao.md](fila-de-alocacao.md) (E9 — absorve) · [tabela-ti-inicio.md](tabela-ti-inicio.md) (E17 — reusa helper)
|
||||
- `docs/roadmap.md` (A11) · [ordenacao-colunas-lista-municipios.md](ordenacao-colunas-lista-municipios.md) (B15 — contrato sort) · [fila-de-alocacao.md](fila-de-alocacao.md) (E9 — absorve) · [tabela-ti-inicio.md](tabela-ti-inicio.md) (E17 — reusa helper)
|
||||
- `docs/CUSTOMER.md` — surpresa da sessão 2026-07-23 (prioridade por % do voto do deputado)
|
||||
- `docs/research/literatura-campanha-deputado-federal-ba.md` §8.1 (concentração da própria votação)
|
||||
- `src/lib/bahiaElectionAggregates.ts`, `src/utilities/municipalityUi.ts`, `src/utilities/campaignListUrl.ts`, `src/components/campaign/MunicipalityBaselineCard.tsx`, `src/components/campaign/MunicipalityList.tsx`, `src/components/campaign/MunicipalityFilters.tsx`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# Roadmap — Teqo
|
||||
|
||||
Atualizado em: 2026-07-24 (fill-in: seletor Cenário junto aos filtros em `/campanha/municipios` — [plano](plans/cenario-junto-filtros-municipios.md); E4R ✓; B14; janela 1: smoke, A11/E17, R6, E8)
|
||||
|
||||
Atualizado em: 2026-07-24 (B15 ordenação por coluna na lista; fill-in Cenário junto aos filtros — [plano](plans/cenario-junto-filtros-municipios.md); E4R ✓; B14; janela 1: smoke, A11/E17, B15, R6, E8)
|
||||
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
|
||||
|
||||
## Âncoras do calendário eleitoral 2026 (Res. TSE 23.760/2026)
|
||||
|
|
@ -70,7 +69,7 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
|
|||
### Demais itens abertos
|
||||
|
||||
- ~~**E4R** import único da planilha de projeção~~ — **entregue 2026-07-24** (`pnpm db:seed:projecao`; overwrite-always; [plano](plans/import-planilha-projecao.md))
|
||||
- **A11** posição em votos do município (rank absoluto + % da própria votação) + ordenação da lista por votação · a lente de prioridade atual da mesa (sessão 2026-07-23) · ~0,5–1d · Janela 1–2 · E9 absorve como coluna/ordenação da fila · [plano](plans/ranking-votos-municipio.md)
|
||||
- **A11** posição em votos do município (rank absoluto + % da própria votação) + ordenação da lista por votação · a lente de prioridade atual da mesa (sessão 2026-07-23) · ~0,5–1d · Janela 1–2 · reusa contrato `sort`/`dir` de **B15**; E9 absorve como coluna/ordenação da fila · [plano](plans/ranking-votos-municipio.md)
|
||||
- **E17** tabela comparativa dos Territórios de Identidade no Início (pedido do candidato) · somas/razões apenas, Metropolitano decomposto (salvaguardas MAUP) · ~1d · Janela 1–2 · primeira fatia de E12 · [plano](plans/tabela-ti-inicio.md)
|
||||
- **A6** dobradinha 2026 automática quando o TSE publicar candidaturas · gatilho externo: pós-15/08 · camada de insight sobre o registro operacional `stateDeputy` (M4) · alimenta **E13** · [plano](plans/insight-dobradinha-2026.md)
|
||||
- **B5 F2–F3** cache CLI compartilhado + factory mun/TI (scripts continuam) · [plano](plans/escala-dry-pos-b2.md)
|
||||
|
|
@ -83,6 +82,7 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
|
|||
- **D5** inbox da própria sessão → rascunhos (`municipalityUpdate`/demanda) com humano no loop · depende de D3 · [plano](plans/whatsapp-sugestao-atualizacoes.md)
|
||||
- **R6** critique/polish visual da vertical remodelada (ciclo /impeccable completo por superfície; smoke visual coordenador feito em 2026-07-21) · absorve os débitos de produto/UX remanescentes de FD2 ([field-desk-ux-pos-critique.md](plans/field-desk-ux-pos-critique.md)): glossário inline (O3 — hipótese ainda sem evidência), triagem em lote, empty states de coordenador · gatilho: antes de 16/08
|
||||
- **B14** município mais próximo (geolocalização → atalho no Início staff) · pede permissão **1× por sessão** se ainda não concedida; matching client-side sobre centroides IBGE; Salvador multi-zona → lista filtrada até B8 F2 · ~1d · Janela 1–2 · sem deps duras · cortável · [plano](plans/municipio-mais-proximo.md)
|
||||
- **B15** ordenar lista de municípios pelo header da coluna · `?sort=`/`?dir=` na URL + clique no header (desktop) / select compacto (mobile); ordenação global no filtrado · ~0,5–1d · Janela 1 · sem deps duras; **A11** consome o contrato (key `votos`) · cortável · [plano](plans/ordenacao-colunas-lista-municipios.md)
|
||||
|
||||
### Fill-ins abertos
|
||||
|
||||
|
|
@ -115,6 +115,7 @@ flowchart TD
|
|||
D4 -.thread.-> D5
|
||||
B8F2["B8 F2 Polígonos<br/>zonas de Salvador"]
|
||||
B14n["B14 Município mais próximo"]
|
||||
B15n["B15 Ordenar lista<br/>por coluna"]
|
||||
E4R["E4R ✓ Import planilha<br/>(seed estratégia)"]
|
||||
A11n["A11 Posição em votos"]
|
||||
E17n["E17 Tabela TI no Início"]
|
||||
|
|
@ -134,6 +135,7 @@ flowchart TD
|
|||
end
|
||||
|
||||
E4R -.metas seedadas.-> E8i
|
||||
B15n -.contrato sort/dir.-> A11n
|
||||
A11n -.coluna/ordenação.-> E9i
|
||||
E17n -.primeira fatia.-> E12i
|
||||
E8i --> E9i --> E11i
|
||||
|
|
@ -156,11 +158,11 @@ flowchart TD
|
|||
B8F2 -.ZE Salvador.-> B14n
|
||||
```
|
||||
|
||||
Paralelizáveis agora: **A11/E17** (semana de onboarding; E4R ✓ seedou o quadro), **E8** (A10 ✓ e remodelagem em produção), **C12** (paralelo a E8), **E16** (compõe o existente), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), fill-ins (O0+, RS+). **D3** só após smoke + folga e só se o fluxo sede-digita (C12) não absorver os deltas do ZAP (não compete com E8/E9).
|
||||
Paralelizáveis agora: **B15** (sort por coluna na lista; barato, desbloqueia UX de A11), **A11/E17** (semana de onboarding; E4R ✓ seedou o quadro; A11 soft-dep B15), **E8** (A10 ✓ e remodelagem em produção), **C12** (paralelo a E8), **E16** (compõe o existente), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), fill-ins (Cenário junto aos filtros, O0+, RS+). **D3** só após smoke + folga e só se o fluxo sede-digita (C12) não absorver os deltas do ZAP (não compete com E8/E9).
|
||||
|
||||
### Sequência por janela (só pendentes)
|
||||
|
||||
**Janela 1 — agora → 05/08 (convenções):** smoke pós-deploy em produção + onboarding do time (Onda 0 §2/§4); **E4R ✓** em código (aplicar `pnpm db:seed:projecao` em produção após smoke); **A11/E17** na semana de onboarding; gate de adoção: sinal Little Hire — ≥1 update espontâneo até 30/07, cobrança da tabela dispensada (planilhas já em `docs/sheets/`) — acompanhamento em [IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md); **R6** critique/polish; **E8** pode começar em paralelo; **B14** (atalho geo) se sobrar folga de campo no onboarding; Onda 0 jurídica em paralelo (externa).
|
||||
**Janela 1 — agora → 05/08 (convenções):** smoke pós-deploy em produção + onboarding do time (Onda 0 §2/§4); **E4R ✓** em código (aplicar `pnpm db:seed:projecao` em produção após smoke); **B15** (sort por coluna) + **A11/E17** na semana de onboarding; gate de adoção: sinal Little Hire — ≥1 update espontâneo até 30/07, cobrança da tabela dispensada (planilhas já em `docs/sheets/`) — acompanhamento em [IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md); **R6** critique/polish; **E8** pode começar em paralelo; **B14** (atalho geo) se sobrar folga de campo no onboarding; Onda 0 jurídica em paralelo (externa).
|
||||
|
||||
**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; **E8** conta da cadeira → **E9** fila de alocação, com **C12** registro-fundação em paralelo (migrations cedo, longe do congelamento); **E16** dossiê do município (pedido O6 — compõe o existente, melhora com E8); D2 se sobrar folga.
|
||||
|
||||
|
|
@ -172,7 +174,7 @@ Paralelizáveis agora: **A11/E17** (semana de onboarding; E4R ✓ seedou o quadr
|
|||
|
||||
**Não cortáveis:** Onda 0 (jurídico/Consent); ~~**E4R** seed da planilha~~ (entregue 2026-07-24 — ainda aplicar em produção após smoke); C2 dados reais; assimetria declarado×estimado (relação de campo); **E8**+**E9**+**C12** (a conta da cadeira, a fila e o registro ex-ante são o mínimo de "inteligência, não planilha" — e C12 é irrecuperável se não registrar durante a campanha).
|
||||
|
||||
**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **A11**/**E17** (baratos e sem deps — cortar só em último caso); **E16** dossiê (último dos extras — pedido explícito de campo); débitos/fill-ins.
|
||||
**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **B15** sort por coluna (lista continua em nome; A11 ainda pode introduzir `sort=votos` mínimo); **A11**/**E17** (baratos — cortar só em último caso); **E16** dossiê (último dos extras — pedido explícito de campo); débitos/fill-ins.
|
||||
|
||||
## Bloqueadores atuais
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue