E10+ registra os debitos do /simplify do E10 como fill-in de engenharia
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).
This commit is contained in:
parent
0ef4ddac1d
commit
520613f862
4 changed files with 101 additions and 3 deletions
|
|
@ -147,7 +147,7 @@ Componentes:
|
|||
|
||||
- **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`, default `central`).
|
||||
- **Modelo de disclosure do grid de diagnóstico (`GoalAccountMetric`) ainda não "assentado" para mais métricas.** O `/impeccable critique` pó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 esta `dl`/grid (nesse ponto, decidir layout/idioma de disclosure antes de replicar `GoalAccountMetric` de novo). Largura tablet/foldable intermediária também não testada — mesmo gatilho.
|
||||
- **Modelo de disclosure do grid de diagnóstico (`GoalAccountMetric`) ainda não "assentado" para mais métricas.** O `/impeccable critique` pó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 esta `dl`/grid (nesse ponto, decidir layout/idioma de disclosure antes de replicar `GoalAccountMetric` de 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** da `dl` (`TerritorialClassRow`), não como 5ª métrica dentro dela — o gatilho continua **não** disparado, mas o `/simplify` do E10 registrou que essa linha já reimplementa o estilo do trigger de `GoalAccountMetric` (o comportamento de toque/dismiss é compartilhado via `CampaignHoverTooltip`, 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](escala-dry-pos-e10.md).
|
||||
|
||||
## Referências
|
||||
|
||||
|
|
|
|||
97
docs/plans/escala-dry-pos-e10.md
Normal file
97
docs/plans/escala-dry-pos-e10.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Escala e DRY pós-E10 (classificação territorial relativa)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-25
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Fill-ins abertos, **E10+**, fill-in de engenharia)
|
||||
Impeccable: A — N/A (sem superfície UI nova; as quatro fases preservam o comportamento visível de E10)
|
||||
Appetite: ~0,75–1 dia eng; quatro fases independentes, nenhuma com migration, collection ou `Consent`
|
||||
Responsável: —
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Não (N/A) — o lote é custo de leitura/render de métricas já entregues (A11, E8, E9, E10); nenhuma métrica, série ou escala nova.
|
||||
- **Anti-goals de dado:** N/A.
|
||||
|
||||
## Contexto
|
||||
|
||||
**E10** ([classificacao-territorial-relativa.md](classificacao-territorial-relativa.md), entregue 2026-07-25) classificou os 435 municípios em Reduto/Expansão/Manutenção/Marginal/Sem base sobre o artefato TSE commitado, com coluna+sort+filtro na lista, badge no card do E8 e na capa do dossiê, mais o seam genérico `cellTooltip` na `CampaignTable`. O `/simplify` da entrega (três revisores em paralelo — quality / performance / reuse, 2026-07-25) aplicou o cleanup barato na hora e deixou quatro achados **maiores que cleanup**, registrados aqui.
|
||||
|
||||
O achado mais importante não é novo: o A11 já havia registrado, em "Adiado com gatilho", a _hidratação única do sort derivado_ com o gatilho **"3º path derivado no loader"**. E10 é o **quarto** (`votos` A11, `deficit` e `frescor` E9, `classe` E10) — **o gatilho disparou**, e é por isso que este lote existe em vez de mais uma linha adiada.
|
||||
|
||||
**Já resolvido no simplify (não reabrir):** `campoVotes` (somado em todo rollup estadual, sem leitor) e `classification.fieldHeadroom` (duplicava o fator `field`) removidos; `formatTerritorialClassWhy` virou de fato a fonte única do `slice(0, 2)` **e** do fallback "sem série do TSE", eliminando o sentinela de string vazia (`why || TERRITORIAL_CLASS_NO_DATA`) nas duas superfícies de detalhe; a classe territorial virou um componente só com prop `layout: 'table' | 'card'`, como `VotePositionReadout`/`SignalAgeReadout` vizinhos; `parseExhaustiveEnumParam` em `campaignListUrl.ts` absorveu `trend` e `class` (o guard `includes` era morto — `allParamValues` já deduplica por `Set`); o bloco central passou a caminhar o `computeVoteRankByYear` memoizado do A11 em vez de reordenar os 435 slugs; o filtro de classe entrou no memo de `facetRows` (era aplicado 3× a arrays que costumam ser o mesmo objeto); `Intl.NumberFormat` hoisted; e dois comentários que **afirmavam o contrário do código** foram corrigidos (o de `CampaignCellTooltip` alegava que um elemento criado no servidor não sobreviveria ao clone de ref — `MunicipalityBaselineCard` o desmente desde o E8; e a regra do `sem_base`, que o `AGENTS.md` dava como "sempre travessão" enquanto card e dossiê entregam pílula).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Uma leitura da tabela `municipality` por request na lista de municípios, em vez de duas com o mesmo `where`.
|
||||
- O que é puro sobre o artefato commitado é memoizado **por processo**, não recomputado por request.
|
||||
- Um `TooltipProvider` por árvore `/campanha`, para o agrupamento de delay do Radix voltar a funcionar.
|
||||
- Os quatro filtros multi do mobile saem da mesma definição de dado que os single-select vizinhos já usam.
|
||||
- Guardrails: sem migration, sem collection, sem `Consent`, sem server action; contrato de URL da lista (`?sort=`/`?class=`/`?trend=`/…) e comportamento visível de E10/E9/A11 inalterados; `overrideAccess: false` preservado em toda leitura com `user`.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **F1 resolve o gatilho do A11 em vez de criar helper genérico de hidratação.** O A11 pediu "hidratação única do sort derivado"; o caminho barato é fazer `loadMunicipalityScope` aceitar docs já carregados quando `isPagedByPayload === false`, não desenhar uma camada de repositório. Fonte: `/simplify` E10 (2026-07-25) + gatilho do [ranking-votos-municipio.md](ranking-votos-municipio.md). **Rejeitado:** materializar a classe/deficit em coluna do Postgres (volta a ser schema e desfaz a pureza que é o ponto do E10); cache cross-request da lista (dado vivo de 2026 — proibido pela escada de cache do `engineering-standards.mdc`).
|
||||
- **F2 separa o que é puro do que depende do banco.** `computeAllMunicipalityPotentials()` é puro sobre o artefato; só `deriveSuggestedGoalsByScenario` depende de `campaignGoals`. O memo de processo cobre o primeiro e o `cache()` de request continua cobrindo o segundo. **Rejeitado:** memoizar `computeStatewideSuggestedGoals` inteiro por processo (erraria na hora em que o CG editasse a meta estadual no admin).
|
||||
- **F3 não muda o idioma de disclosure, só onde o Provider mora.** Hoist do `TooltipProvider` para o layout de `(app)`; `CampaignHoverTooltip` mantém estado controlado, o `onPointerUp` de toque e o dismiss por `pointerdown` conquistados no critique do E8 — nada disso vive no Provider. **Rejeitado:** trocar Tooltip por Popover nas células (muda o gesto em toda a lista; é decisão de produto do B22/B23, não deste lote).
|
||||
- **i18n e naming** seguem o AGENTS.md: identificadores em inglês (`loadMunicipalityScopeFromDocs`, `municipalityPotentialBySlug`, `mobileMultiFilterDefinitions`), strings visíveis em pt-BR.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **F3 deve virar um `TooltipProvider` global ou um por superfície de lista?** **Opções:** (a) um no layout de `(app)`; (b) um por página de lista; (c) manter como está. **Recomendação:** (a) — é o padrão do Radix, e o `delayDuration={300}` atual já é uniforme em todos os call sites, então não há política por superfície a preservar.
|
||||
- **F4 vale a pena com quatro cópias, ou espera a quinta?** **Opções:** (a) fazer agora; (b) adiar até B21/B33 trazerem outra lista com multi no mobile. **Recomendação:** (a) — os single-select do mesmo arquivo já saem de `municipalityFilterDefinitions` num `.map` (linha 220), então a assimetria é interna ao arquivo e barata de remover; não é abstração nova, é usar a que já existe.
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F1["F1 leitura única<br/>municipalityPageData"] --> Lista["/campanha/municipios"]
|
||||
F2["F2 memo de processo<br/>municipalityPotential"] --> Lista
|
||||
F2 --> Card["Card conta da cadeira"]
|
||||
F3["F3 TooltipProvider<br/>no layout (app)"] --> Chrome["Tooltips /campanha"]
|
||||
F4["F4 multi do mobile<br/>via definitions"] --> Filtros["MunicipalityFilters"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`loadMunicipalityScope`** (`src/utilities/campaignMunicipalityScope.ts:77`): hoje sempre consulta. Ganha uma variante que recebe os docs já lidos (`municipalityListSelect` é **superset estrito** do select do escopo — `city`, `zoneNumber`, `lastUpdateAt`, `politicalTrend` a mais), mantendo o `cache()` por request e executando só a metade de `aggregatePledgesByMunicipality`. Consumido por `municipalityPageData.ts:342` quando `isPagedByPayload === false` (`:317`).
|
||||
- **`computeAllMunicipalityPotentials`** (`src/utilities/municipalityPotential.ts:152`): passa a preencher um `Map<slug, MunicipalityPotential>` de módulo, no mesmo padrão de `statewideTotalsCache` (`bahiaElectionAggregates.ts:90`), `rankByYearCache` (`municipalityVoteRank.ts:18`) e `catalogContext` (`municipalityTerritorialClass.ts`). `computeStatewideSuggestedGoals` (`:296`) continua dentro do `cache()` de request porque lê `campaignGoals`.
|
||||
- **`CampaignHoverTooltip`** (`src/components/campaign/shared/CampaignHoverTooltip.tsx:75`): remove o `<TooltipProvider delayDuration={300}>` interno; o provider passa a ser renderizado uma vez em `src/app/(campaign)/campanha/(app)/layout.tsx`. Hoje a lista monta ~30 (5 headers + 25 células de Classe via `CampaignCellTooltip`), e `skipDelayDuration` do Radix só agrupa dentro de um mesmo Provider.
|
||||
- **`MunicipalityFilters`** (`src/components/campaign/municipality/MunicipalityFilters.tsx:203,261,279,297`): os quatro blocos `CampaignMobileMultiFilterField` (região, tendência, classe, assessor) viram um `.map` sobre as definitions multi, espelhando o `.map` dos single-select em `:220`. `municipalityFilterDefinitions` (`municipalityListFilters.ts:113`) já carrega `param`/`label`/`options`/`staffOnly`; o que falta é `selected` + `emptyLabel` por param.
|
||||
- **Migration**: sem migration, sem collection, sem server action.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura de outro item aberto. F1 fecha o "Adiado com gatilho" de [ranking-votos-municipio.md](ranking-votos-municipio.md) e trata junto o full-scan de `expectedVotes`/`coverage` que o B15 documenta. F4 é suave com **B21**/**B33** (se a extração do head compartilhado chegar antes, herda a peça).
|
||||
- Reusa: `campaignMunicipalityScope.ts`, `municipalityPageData.ts`, `municipalityPotential.ts`, `municipalityListFilters.ts`, `CampaignMobileMultiFilterField`, `CampaignHoverTooltip`, padrão de memo de `bahiaElectionAggregates.ts`.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- **Seletor de colunas / filtros salvos** — **B17** / **B18**.
|
||||
- **Tooltip por célula como feature de produto** (nomes de assessores, justificativa de tendência) — **B23**; este lote só move o Provider.
|
||||
- **Coluna de rank, cenário na URL, símbolo proporcional** — A11 / "Cenário junto aos filtros" / **B13**.
|
||||
- **`TerritoryListColumns` com formatador de percentual próprio** — descartado no triage: outra superfície, difere só no zero à direita, e unificar exige decisão de produto sobre a casa decimal.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **"Já que estou no loader, unifico todos os sorts derivados numa camada."** Se alguém "só completar": nasce um repositório de municípios e o `where` do Payload vira DSL própria. **Mitigação neste item:** F1 toca **um** branch (`isPagedByPayload === false`) e não muda assinatura pública de `loadMunicipalityScope`.
|
||||
- **"Já que estou no Provider, revejo o disclosure do card."** Se alguém "só completar": cai no debate Tooltip×Popover que o critique do E8 deixou explicitamente adiado. **Mitigação:** F3 é hoist puro; qualquer mudança de gesto fica com o gatilho já registrado em [conta-da-cadeira.md](conta-da-cadeira.md).
|
||||
- **"Memoizo tudo que é puro por processo."** Se alguém "só completar": memoiza algo que lê `campaignGoals`/pledges e a mesa vê número velho. **Mitigação:** F2 memoiza só o que não toca o banco; o teste do lote pina que editar `campaignGoals` muda a meta sugerida na hora.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **`TERRITORIAL_CLASSES` sem consumidor e as três derivações `Object.keys(...) as ...`.** Mover a tupla + tipos para módulo de contrato client-safe removeria o cast, mas `trend` usa o **mesmo** idioma no mesmo arquivo — corrigir só o novo deixa o arquivo menos consistente. Revisitar quando: 3º enum multi entrar na lista (o **B33** traz Partido em `/campanha/dobradinhas`), tratando trend+class+partido de uma vez.
|
||||
- **`TerritorialClassRow` reimplementa o trigger do `GoalAccountMetric`.** Só o estilo do botão é duplicado — o comportamento de toque/dismiss vive no `CampaignHoverTooltip` compartilhado, então o risco de regressão assimétrica é menor do que parece. Revisitar sob o gatilho **já registrado** em [conta-da-cadeira.md](conta-da-cadeira.md): quando E9–E14 adicionarem uma 5ª métrica a essa `dl`/grid, decidir layout/idioma antes de replicar o componente de novo.
|
||||
- **Três caches de módulo no classificador viram um só.** Legibilidade, não custo — o sort redundante já morreu no simplify. Revisitar quando **E15** recalibrar as âncoras (é quando alguém vai reler as três lifetimes de uma vez).
|
||||
|
||||
## Referências
|
||||
|
||||
- `docs/roadmap.md` (Fill-ins abertos — **E10+**)
|
||||
- [classificacao-territorial-relativa.md](classificacao-territorial-relativa.md) — o pai do lote (E10 ✓)
|
||||
- [ranking-votos-municipio.md](ranking-votos-municipio.md) — "Adiado com gatilho" que F1 fecha (3º path derivado)
|
||||
- [conta-da-cadeira.md](conta-da-cadeira.md) — gatilho do disclosure do `GoalAccountMetric` (E8)
|
||||
- `src/utilities/municipalityPageData.ts` (`:317` `isPagedByPayload`, `:319` `listQuery`, `:342` escopo) — F1
|
||||
- `src/utilities/campaignMunicipalityScope.ts` (`:77`) — F1
|
||||
- `src/utilities/municipalityPotential.ts` (`:152`, `:296`) — F2
|
||||
- `src/components/campaign/shared/CampaignHoverTooltip.tsx` (`:75`) + `src/app/(campaign)/campanha/(app)/layout.tsx` — F3
|
||||
- `src/components/campaign/municipality/MunicipalityFilters.tsx` (`:203`–`:313`) + `src/utilities/municipalityListFilters.ts` (`:113`) — F4
|
||||
- AGENTS.md — `overrideAccess: false` com `user`, escada de cache (React `cache()` vs memo de processo vs artefato), gate por entrega
|
||||
|
|
@ -90,7 +90,7 @@ Componentes tocados: `src/lib/municipalityVoteRank.ts` (novo, puro + unit tests)
|
|||
|
||||
- **Seletor de ano na ordenação da lista.** Gatilho: pedido da mesa por leitura de série na lista.
|
||||
- **Coluna de rank na fila do E9.** Entra junto com E9 (a fila referencia este helper).
|
||||
- **Hidratação única do sort derivado `votos` (evitar find filtrado + `loadMunicipalityScope` redundante).** Gatilho: lista lenta no recorte amplo ou 3º path derivado no loader; B15 já documenta full-scan de `expectedVotes`/`coverage` — tratar junto.
|
||||
- ~~**Hidratação única do sort derivado `votos` (evitar find filtrado + `loadMunicipalityScope` redundante).** Gatilho: lista lenta no recorte amplo ou 3º path derivado no loader; B15 já documenta full-scan de `expectedVotes`/`coverage` — tratar junto.~~ — **gatilho disparou em 2026-07-25**: com `classe` (E10) o loader chegou ao 4º path derivado (`votos`, `deficit`, `frescor`, `classe`). Registrado como **E10+ F1** · [plano](escala-dry-pos-e10.md).
|
||||
|
||||
## Já resolvido no simplify/critique (não reabrir)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue