O coropleto do Inicio era quase monocromatico porque 0-100% dos validos comprime uma disputa cujo topo estadual e 4,71%. Tres escalas relativas ao proprio candidato, todas em classes discretas: quantis (novo default), padrao proprio (LQ) e posicao no municipio, ao lado de "% dos validos" (B11) e "Total (votos)". Artefato v3: federalRankByIbgeCode com a colocacao entre todos os candidatos com votos > 0 na geografia (2014/2018/2022), chaveado por ibgeCode porque e o que o mapa desenha. Fica no artefato commitado, nao em query de request, que varreria ~100 mil linhas por ano para um dado imutavel. 534 -> 623 KB (orcamento 700 KB). LQ virou 5 classes nas ancoras do E10, nao diverging: o plano pedia as duas coisas e se contradizia. Vermelho x azul continua significando "Solla x adversario" e segue exclusivo do compare. computeAggregateTerritorialClass classifica um grupo de slugs como um territorio so, somando insumos e rodando o mesmo classificador (LQ e razao; a media de razoes nao e a razao das somas) - o E12 herda o helper. TERRITORIAL_CLASS_ANCHORS mudou para src/lib/territorialClassAnchors.ts para nao arrastar o artefato TSE ao bundle do cliente. Quantis sobre o escopo de acesso do usuario, degradando para 3 classes abaixo de 10 municipios com a legenda declarando a contagem efetiva. Rank indisponivel em 2026 (dado TSE); LQ em 2026 reusa o padrao estadual de 2022. Bolhas opcionais por validos projetados (area proporcional ao valor, cor da classe E10, interactive: false). 417 marcadores medidos antes de otimizar - nao derrubaram o frame, entao o corte para top-K nao foi feito. Sem migration, sem collection, sem server action; bundle client de /campanha byte a byte igual.
135 lines
5.6 KiB
TypeScript
135 lines
5.6 KiB
TypeScript
import artifactJson from '@/lib/electionAggregates/bahia-federal-baseline.json'
|
|
|
|
/**
|
|
* Committed per-municipality election baseline (immutable TSE 2014/2018/2022):
|
|
* Solla nominal votes + federal T1 valid votes, pre-cut by the same
|
|
* `municipalityCatalog` geography the runtime uses (Salvador split by zone,
|
|
* every other municipality whole — Camaçari included).
|
|
*
|
|
* v2 (E8 "conta da cadeira"): adds the curated campo-parties federal nominal
|
|
* votes, federal T1 turnout/blank/null tally, and 2022 majoritarian
|
|
* (presidente/governador #13) votes+tally used for "teto do campo",
|
|
* "share intracampo" and roll-off. 2022-only for the majoritarian slice by
|
|
* design (E8's formulas anchor the field ceiling and roll-off to the single
|
|
* most recent majoritarian race) — the DB has majoritária seeded for
|
|
* 2014/2018 too (`pnpm db:seed:tse`, since 2026-07-24), but it isn't cut into
|
|
* this artifact; a future consumer that needs it extends `majoritarian2022`
|
|
* into a `majoritarianByYear` map.
|
|
*
|
|
* v3 (B13 "posição no município"): adds `federalRankByIbgeCode` — where the
|
|
* candidate placed among every federal-deputy candidate voted inside that
|
|
* municipality. Keyed by IBGE code, not by catalog slug, because it is the
|
|
* map's key and because the competitive question only has an answer for a
|
|
* whole city: Salvador's 19 zone municipalities share one placement.
|
|
*
|
|
* Generated by `pnpm build:election-aggregates` from the locally seeded TSE
|
|
* data — never computed at build or request time (see the script header for
|
|
* the rationale). A snapshot test pins artifact keys ↔ catalog slugs.
|
|
*/
|
|
|
|
/** Turnout/valid/blank/null tally cell (`municipalityElectoralBaseline.ts`'s `OfficeTallyCell` is this same type). */
|
|
export type FederalBaselineTallyCell = {
|
|
comparecimento: number
|
|
votosValidos: number
|
|
votosBranco: number
|
|
votosNulo: number
|
|
}
|
|
|
|
/** One 2022 majoritarian office (presidente/governador #13): votes + tally. */
|
|
type MajoritarianOfficeCell = FederalBaselineTallyCell & {
|
|
votes: number
|
|
}
|
|
|
|
export type MunicipalityFederalBaseline = {
|
|
/** TSE year (as string) → Solla nominal votes inside the municipality geography. */
|
|
votesByYear: Record<string, number>
|
|
/** TSE year (as string) → federal T1 valid votes inside the municipality geography. */
|
|
validVotesByYear: Record<string, number>
|
|
/** TSE year (as string) → curated campo-parties (`campoParties.ts`) federal T1 nominal votes. */
|
|
campoFederalVotesByYear: Record<string, number>
|
|
/** TSE year (as string) → federal T1 turnout/valid/blank/null tally. */
|
|
federalTallyByYear: Record<string, FederalBaselineTallyCell>
|
|
/** 2022-only majoritarian tickets (absent for older artifacts / unknown slugs). */
|
|
majoritarian2022?: {
|
|
president: MajoritarianOfficeCell
|
|
governor: MajoritarianOfficeCell
|
|
}
|
|
}
|
|
|
|
/** Where the candidate placed among the federal-deputy candidates voted in one municipality. */
|
|
export type FederalCompetitiveRank = {
|
|
/** 1 = most voted. Ties share a placement (competition rank). */
|
|
rank: number
|
|
/** Candidates with at least one vote there — the honest denominator for "12º de …". */
|
|
candidates: number
|
|
}
|
|
|
|
type FederalBaselineArtifact = {
|
|
provenance: string
|
|
candidateNumber: number
|
|
candidateName: string
|
|
years: number[]
|
|
municipalities: Record<string, MunicipalityFederalBaseline>
|
|
/** IBGE codarea → year (as string) → placement. Absent where he took no votes. */
|
|
federalRankByIbgeCode: Record<string, Record<string, FederalCompetitiveRank>>
|
|
}
|
|
|
|
const artifact = artifactJson as FederalBaselineArtifact
|
|
|
|
export const FEDERAL_BASELINE_CANDIDATE_NUMBER = artifact.candidateNumber
|
|
|
|
export const federalBaselineMunicipalitySlugs = (): string[] => Object.keys(artifact.municipalities)
|
|
|
|
const EMPTY_BASELINE: MunicipalityFederalBaseline = {
|
|
votesByYear: {},
|
|
validVotesByYear: {},
|
|
campoFederalVotesByYear: {},
|
|
federalTallyByYear: {},
|
|
}
|
|
|
|
/**
|
|
* Baseline for one catalog municipality slug. Unknown slugs return an empty
|
|
* baseline (renders as 0 votes) — the artifact↔catalog snapshot test is the
|
|
* guard against silent drift.
|
|
*/
|
|
export const getMunicipalityFederalBaseline = (slug: string): MunicipalityFederalBaseline =>
|
|
artifact.municipalities[slug] ?? EMPTY_BASELINE
|
|
|
|
/**
|
|
* Competitive placement inside one IBGE municipality for one year, or `null`
|
|
* where the candidate took no votes there (never a misleading last place).
|
|
*/
|
|
export const getFederalCompetitiveRank = (
|
|
ibgeCode: string,
|
|
year: number,
|
|
): FederalCompetitiveRank | null => artifact.federalRankByIbgeCode[ibgeCode]?.[String(year)] ?? null
|
|
|
|
/** Statewide sums of the artifact for one year — the denominators of every relative reading. */
|
|
type StatewideFederalTotals = {
|
|
/** Candidate's own nominal votes across every catalog municipality. */
|
|
ownVotes: number
|
|
/** Federal T1 valid votes across every catalog municipality. */
|
|
validVotes: number
|
|
}
|
|
|
|
const statewideTotalsCache = new Map<number, StatewideFederalTotals>()
|
|
|
|
/**
|
|
* The artifact stores no statewide rollup, so every relative metric (A11's
|
|
* share of own vote, E10's LQ) needs the same sums over the 435 slugs. Cached
|
|
* at module scope — the artifact is immutable for the process lifetime.
|
|
*/
|
|
export const getStatewideFederalTotals = (year: number): StatewideFederalTotals => {
|
|
const cached = statewideTotalsCache.get(year)
|
|
if (cached) return cached
|
|
|
|
const yearKey = String(year)
|
|
const totals: StatewideFederalTotals = { ownVotes: 0, validVotes: 0 }
|
|
for (const baseline of Object.values(artifact.municipalities)) {
|
|
totals.ownVotes += baseline.votesByYear[yearKey] ?? 0
|
|
totals.validVotes += baseline.validVotesByYear[yearKey] ?? 0
|
|
}
|
|
|
|
statewideTotalsCache.set(year, totals)
|
|
return totals
|
|
}
|