O /simplify da escala relativa do mapa achou um bug semantico real em computeAggregateTerritorialClass — somava o headroom dos slugs e comparava contra a mediana de UMA unidade, classificando um grupo grande como expansao quando ele e marginal. A mediana agora escala por slugs.length, com teste. As bolhas sairam do centro do bounding box para o centroide real e ganharam pane proprio (z-index 450), porque o bringToFront do hover as escondia atras do poligono. Legenda e pintura passaram a compartilhar o mesmo predicado, a copy dos quantis parou de prometer faixas de igual largura, e o subtitulo do painel parou de dizer "em 2026" com 2022 selecionado (defeito do B11). Fonte unica onde havia duas: NO_DATA_FILL, o raio das bolhas e formatPlacementOrdinal (ex-formatMunicipalityVoteRank, agora client-safe em electionFormat.ts). Os quatro debitos maiores que o cleanup viraram o fill-in B13+ (docs/plans/escala-dry-pos-b13.md); a flakiness dos e2e, reproduzida em main limpo, foi para o ledger com a causa afinada.
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import {
|
|
federalBaselineMunicipalitySlugs,
|
|
getMunicipalityFederalBaseline,
|
|
getStatewideFederalTotals,
|
|
} from '@/lib/bahiaElectionAggregates'
|
|
|
|
/** Year the mesa uses as the current priority lens (A11 list sort + default readout). */
|
|
export const DEFAULT_VOTE_RANK_YEAR = 2022
|
|
|
|
export type MunicipalityVoteRankEntry = {
|
|
rank: number
|
|
votes: number
|
|
/** Fraction of the candidate's statewide (artifact) total for the year — 0..1. */
|
|
share: number
|
|
totalUnits: number
|
|
}
|
|
|
|
const rankByYearCache = new Map<number, ReadonlyMap<string, MunicipalityVoteRankEntry>>()
|
|
|
|
/**
|
|
* Dense rank + share of own statewide vote for every catalog municipality slug
|
|
* in the committed federal baseline artifact. Ties share a rank; the next
|
|
* distinct total gets the next consecutive rank (no gaps).
|
|
*
|
|
* Cached at module scope — the artifact is immutable for the process lifetime.
|
|
*/
|
|
export const computeVoteRankByYear = (
|
|
year: number,
|
|
): ReadonlyMap<string, MunicipalityVoteRankEntry> => {
|
|
const cached = rankByYearCache.get(year)
|
|
if (cached) return cached
|
|
|
|
const yearKey = String(year)
|
|
const slugs = federalBaselineMunicipalitySlugs()
|
|
const rows = slugs.map((slug) => ({
|
|
slug,
|
|
votes: getMunicipalityFederalBaseline(slug).votesByYear[yearKey] ?? 0,
|
|
}))
|
|
|
|
const candidateTotal = getStatewideFederalTotals(year).ownVotes
|
|
|
|
rows.sort((a, b) => {
|
|
if (b.votes !== a.votes) return b.votes - a.votes
|
|
return a.slug.localeCompare(b.slug)
|
|
})
|
|
|
|
const totalUnits = rows.length
|
|
const bySlug = new Map<string, MunicipalityVoteRankEntry>()
|
|
let denseRank = 0
|
|
let previousVotes: number | undefined
|
|
|
|
for (const row of rows) {
|
|
if (row.votes !== previousVotes) {
|
|
denseRank += 1
|
|
previousVotes = row.votes
|
|
}
|
|
bySlug.set(row.slug, {
|
|
rank: denseRank,
|
|
votes: row.votes,
|
|
share: candidateTotal > 0 ? row.votes / candidateTotal : 0,
|
|
totalUnits,
|
|
})
|
|
}
|
|
|
|
rankByYearCache.set(year, bySlug)
|
|
return bySlug
|
|
}
|
|
|
|
export const getMunicipalityVoteRank = (
|
|
slug: string,
|
|
year: number = DEFAULT_VOTE_RANK_YEAR,
|
|
): MunicipalityVoteRankEntry | null => computeVoteRankByYear(year).get(slug) ?? null
|
|
|
|
/** Compare for list sort=votos. Zero-vote rows always sort last (any dir). */
|
|
export const compareMunicipalityVotesForSort = (
|
|
aVotes: number,
|
|
bVotes: number,
|
|
dir: 'asc' | 'desc',
|
|
): number => {
|
|
const aZero = aVotes === 0
|
|
const bZero = bVotes === 0
|
|
if (aZero !== bZero) return aZero ? 1 : -1
|
|
const cmp = aVotes - bVotes
|
|
return dir === 'asc' ? cmp : -cmp
|
|
}
|