teqo-backup/src/lib/bahiaElectionAggregates.ts
Francisco Ignácio Fontoura Solla 29f45e093a B13: escala relativa no mapa dos Municipios (quantis/LQ/posicao) + artefato TSE v3
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.
2026-07-26 04:22:56 -03:00

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
}