teqo-backup/tests/unit/bahiaElectionAggregates.unit.spec.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

162 lines
6.8 KiB
TypeScript

import { statSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
FEDERAL_BASELINE_CANDIDATE_NUMBER,
federalBaselineMunicipalitySlugs,
getFederalCompetitiveRank,
getMunicipalityFederalBaseline,
} from '@/lib/bahiaElectionAggregates'
import {
BASELINE_TICKET_2022,
ELECTION_YEAR_2022,
HISTORICAL_SERIES_YEARS,
} from '@/lib/electionResults'
import { municipalityCatalog } from '@/lib/municipalityCatalog'
const ARTIFACT_PATH = join(process.cwd(), 'src/lib/electionAggregates/bahia-federal-baseline.json')
/**
* Pins the committed election-aggregate artifact to the municipality catalog.
* If either changes shape (new remodel, new TSE scope), regenerate with
* `pnpm build:election-aggregates` — a drift here means the map would render
* zeros silently.
*/
describe('election aggregates artifact', () => {
it('covers exactly the municipality catalog slugs', () => {
const artifactSlugs = new Set(federalBaselineMunicipalitySlugs())
const catalogSlugs = new Set(municipalityCatalog.map((entry) => entry.slug))
expect(artifactSlugs.size).toBe(catalogSlugs.size)
for (const slug of catalogSlugs) {
expect(artifactSlugs.has(slug)).toBe(true)
}
})
it('is generated for the baseline ticket candidate', () => {
expect(FEDERAL_BASELINE_CANDIDATE_NUMBER).toBe(BASELINE_TICKET_2022.candidate.candidateNumber)
})
it('has every historical year populated with plausible statewide totals', () => {
for (const year of HISTORICAL_SERIES_YEARS) {
let candidateTotal = 0
let validTotal = 0
for (const slug of federalBaselineMunicipalitySlugs()) {
const baseline = getMunicipalityFederalBaseline(slug)
const votes = baseline.votesByYear[String(year)]
const valid = baseline.validVotesByYear[String(year)]
expect(votes).toBeTypeOf('number')
expect(valid).toBeTypeOf('number')
expect(votes).toBeGreaterThanOrEqual(0)
expect(valid).toBeGreaterThanOrEqual(votes > 0 ? 1 : 0)
candidateTotal += votes ?? 0
validTotal += valid ?? 0
}
// A statewide federal-deputy campaign always lands in the 5-7 digit range.
expect(candidateTotal).toBeGreaterThan(10_000)
expect(validTotal).toBeGreaterThan(candidateTotal)
}
})
it('returns an empty baseline for unknown slugs', () => {
const baseline = getMunicipalityFederalBaseline('nao-existe')
expect(baseline.votesByYear).toEqual({})
expect(baseline.validVotesByYear).toEqual({})
expect(baseline.campoFederalVotesByYear).toEqual({})
expect(baseline.federalTallyByYear).toEqual({})
expect(baseline.majoritarian2022).toBeUndefined()
})
it('has campo federal votes + federal tally for every historical year (v2 fields)', () => {
for (const year of HISTORICAL_SERIES_YEARS) {
let campoTotal = 0
let tallyComparecimentoTotal = 0
for (const slug of federalBaselineMunicipalitySlugs()) {
const baseline = getMunicipalityFederalBaseline(slug)
const campo = baseline.campoFederalVotesByYear[String(year)]
const tally = baseline.federalTallyByYear[String(year)]
expect(campo).toBeTypeOf('number')
expect(campo).toBeGreaterThanOrEqual(0)
expect(tally).toBeDefined()
expect(tally.comparecimento).toBeGreaterThanOrEqual(0)
expect(tally.votosValidos).toBeGreaterThanOrEqual(0)
campoTotal += campo ?? 0
tallyComparecimentoTotal += tally?.comparecimento ?? 0
}
// The campo (PT + core-left partners) always outpolls Solla alone, and
// turnout always dwarfs any single candidate's nominal votes.
expect(campoTotal).toBeGreaterThan(10_000)
expect(tallyComparecimentoTotal).toBeGreaterThan(campoTotal)
}
})
it('has 2022 majoritarian (presidente/governador #13) votes+tally for every municipality', () => {
let presidentTotal = 0
for (const slug of federalBaselineMunicipalitySlugs()) {
const baseline = getMunicipalityFederalBaseline(slug)
const majoritarian = baseline.majoritarian2022
expect(majoritarian).toBeDefined()
if (!majoritarian) continue
expect(majoritarian.president.votes).toBeGreaterThanOrEqual(0)
expect(majoritarian.governor.votes).toBeGreaterThanOrEqual(0)
expect(majoritarian.president.comparecimento).toBeGreaterThanOrEqual(
majoritarian.president.votes,
)
presidentTotal += majoritarian.president.votes
}
expect(presidentTotal).toBeGreaterThan(10_000)
// Sanity: the 2022 majoritarian slice must not silently regress to a
// different election year (only 2022 is seeded/expected here).
expect(HISTORICAL_SERIES_YEARS).toContain(ELECTION_YEAR_2022)
})
it('has a plausible competitive placement per IBGE municipality (v3 field)', () => {
const ibgeCodes = new Set(municipalityCatalog.map((entry) => entry.ibgeCode))
let placed = 0
for (const ibgeCode of ibgeCodes) {
for (const year of HISTORICAL_SERIES_YEARS) {
const placement = getFederalCompetitiveRank(ibgeCode, year)
// Absent is legitimate: he took no votes in that municipality that
// year (351/417 municipalities placed in 2014, 416/417 in 2022).
if (!placement) continue
placed += 1
expect(placement.rank).toBeGreaterThanOrEqual(1)
expect(placement.candidates).toBeGreaterThanOrEqual(placement.rank)
}
}
// A statewide campaign places in most municipalities in most years.
expect(placed).toBeGreaterThan(ibgeCodes.size * 2)
})
it('gives Salvador one whole-city placement, not one per zone municipality', () => {
const salvadorZones = municipalityCatalog.filter((entry) => entry.kind === 'zona')
const salvadorCodes = new Set(salvadorZones.map((entry) => entry.ibgeCode))
// The 19 zone municipalities share Salvador's IBGE code, which is why the
// placement is keyed by code: "which place did he come in" only has an
// answer for the whole city.
expect(salvadorZones.length).toBeGreaterThan(1)
expect(salvadorCodes.size).toBe(1)
const [salvadorCode] = salvadorCodes
expect(getFederalCompetitiveRank(salvadorCode, ELECTION_YEAR_2022)).not.toBeNull()
})
it('has no placement for an unknown IBGE code', () => {
expect(getFederalCompetitiveRank('0000000', ELECTION_YEAR_2022)).toBeNull()
})
it('stays under the committed artifact byte budget', () => {
const { size } = statSync(ARTIFACT_PATH)
// v1 (votes + valid votes only) was ~96 KB; v2 adds campo votes, federal
// tally and the 2022 majoritarian slice, measured at ~522 KB; v3 adds the
// per-IBGE competitive placement, measured at ~613 KB — budget with
// headroom, not a precise pin, so future fields don't require touching
// this test.
expect(size).toBeLessThan(700 * 1024)
})
})