teqo-backup/docs/plans/ranking-votos-municipio.md

9.7 KiB
Raw Blame History

A11 — Posição em votos do município (rank + % da própria votação) e ordenação por votação

Status: entregue 2026-07-24 Atualizado em: 2026-07-24 Item do roadmap: docs/roadmap.md (Demais itens abertos, A11; E9 absorve como coluna/ordenação da fila) Impeccable: B — encaixes em superfícies existentes (detalhe do município + lista), sem rota nova Appetite: ~0,5–1 dia eng; sem migration, sem collection, sem server action nova Responsável: —

Revisões:

  • 2026-07-24 (entrega + polish pós-critique): helper municipalityVoteRank; baseline + coluna lista; default da lista = votos desc (omitido na URL) — substituído pelo default deficit desc do E9 ✓ no mesmo dia, ?sort=votos continua disponível; ?sort=name para alfabético; share lidera a célula; rank no mobile; % dos válidos demoted. B15 amplia keys restantes.
  • 2026-07-24 (auditoria pre-implementação): B15 ainda ausente no código — esta entrega introduz sort/dir mínimo só para name|votos (fallback do plano); B15 amplia as demais keys depois. Seção Dados → decisão → apresentação preenchida. Questões em aberto fechadas: lista só 2022; sem delta de rank 2018→2022 na v1.

Dados → decisão → apresentação

  • Vou apresentar dados? Sim — superfície neste item (detalhe + lista).
  • Decisões desbloqueadas:
    • Staff: “este município é que fatia/posição da nossa votação?”
    • Staff: “abrir a lista na ordem da mesa (maior concentração da própria votação primeiro)”
  • Forma escolhida: número + contexto no detalhe (12º de 435 · 3,1% da votação); tabela na lista (coluna Votos 2022 + %, ordenável). Rejeitado: leaderboard gamificado; % dos válidos locais como âncora; chart.
  • Profile: numérico absoluto (votos) + share relativo da própria votação estadual; granularidade = município operacional (435); ano lista = 2022.
  • Anti-goals: sem % estadual absoluto como KPI; sem % do eleitorado local; sem medalhas/delta de rank na v1.

Self-check dados: 5/5.

Design (Impeccable)

Âncoras: PRODUCT.md (princípio 6 — métricas relativas e locais) / DESIGN.md (register product) · MunicipalityBaselineCard e MunicipalityList existentes.

Na implementação: craft compacto → critique → polish.

  • Persona / contexto: a mesa prioriza por % do voto do próprio deputado (sessão 2026-07-23 — o coordenador manteve o critério mesmo desafiado com o % do eleitorado local). Hoje essa lente não existe no produto: nem rank, nem share, nem ordenação por votação.
  • Job principal: abrir um município e saber na hora "que fatia da nossa votação ele é e em que posição está"; abrir a lista e vê-la na ordem da mesa.
  • Estratégia de cor: Restrained — rank/share são texto/badge neutros, sem heat.
  • Anti-goals: ranking gamificado; % do eleitorado local como âncora (é a lente que a mesa rejeitou); KPI estadual absoluto.

Contexto

A leitura "posição em votos absolutos + % da própria votação" é a concentração da própria votação do compêndio (§8.1 — Carvalho 2003; "de onde vem meu voto?"), e é exatamente o critério rígido de prioridade da coordenação (CUSTOMER.md, surpresa da sessão: prioridade por % do voto do deputado, não % local). O artefato commitado bahiaElectionAggregates.ts já tem a série Solla + válidos federais 2014/2018/2022 por slug do catálogo (435 unidades; Salvador por zona, Camaçari inteiro) — rank e share são deriváveis em memória, sem query. Este item entrega a lente da mesa antes de E8/E9; a fila (E9) depois absorve a coluna/ordenação como uma das suas.

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: chave de ordenação nova ?sort=votos (ano default 2022) no contrato URL de B15 (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/lib/municipalityVoteRank.ts: cache de módulo sobre o artefato imutável (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.

Decisões travadas

  • Rank sobre as 435 unidades do catálogo (zonas de Salvador ranqueiam individualmente — é a malha operacional; a soma de Salvador aparece em E17/TI, não aqui). Rejeitado: rank sobre 417 municípios IBGE (mistura malhas e não bate com a lista).
  • 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, key votos no contrato B15 (?sort=votos&dir=…); labels pt-BR ("Posição em votos", "% da votação").

Questões em aberto

  • Ano na URL da ordenação (?sort=votos&ano=2018)? Recomendação: v1 só 2022 (ano da lente atual da mesa); seletor de ano entra se a mesa pedir série na lista (o detalhe já mostra a série).
  • Mostrar variação de rank 2018→2022 no detalhe? Recomendação: não na v1 — tendência já existe (politicalTrend + série do card); rank delta é açúcar que pode confundir com a classificação (E10).

Abordagem proposta

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/>(contrato sort mínimo)"]
    Artifact --> Rank --> Card
    Rank --> List

Componentes tocados: src/lib/municipalityVoteRank.ts (novo, puro + unit tests), municipalityUi.ts (keys name|votos no allowlist), MunicipalityList.tsx / MunicipalitySortableHead (header votos + linha com votos/%), MunicipalityBaselineCard.tsx (posição/share por ano), municipalityPageData.ts (apply da key votos sobre o filtrado). B15 amplia as demais keys de coluna depois.

Dependências

  • Nenhuma dura de schema (artefato em produção desde o hardening 2026-07-23). Suave: B15 (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

  • Fila de decisão completa (E9); classificação/âncoras relativas (E10 — LQ/captura ficam lá); mudanças no mapa (B13); rank por TI (E17).

Rabbit holes

  • Virar "tabela de ranking" gamificada. Rank é atributo do município, não leaderboard: sem página própria, sem medalhas, sem delta de posição na v1.
  • Recalcular share com denominadores alternativos (válidos locais, eleitorado, campo). Cada um é outra métrica com outro dono (mapa/E8/E10); aqui é só concentração da própria votação.

Adiado com gatilho

  • 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.

Já resolvido no simplify/critique (não reabrir)

  • Hover no header 2022 (sem ? / CampaignInfoHint) · Limpar preserva sort · chevrons ativos/dormant · label curto 2022 (não "Concentração 2022").

Explicitamente fora

  • Extrair nextMunicipalitySortDir / fundir com CampaignInfoHint (Popover ≠ hover) · markup único mobile/desktop · rank em SQL (artefato em memória basta).

Referências

  • docs/roadmap.md (A11) · ordenacao-colunas-lista-municipios.md (B15 — contrato sort) · fila-de-alocacao.md (E9 — absorve) · 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
  • AGENTS.md — naming (identificadores em inglês, URL keys em inglês com valores pt onde forem dado)