teqo/src/lib/electionResultsBuild.ts
Francisco Ignácio Fontoura Solla b0f4ebc1ed Add E2 TSE 2014/2018 vote series and historical trend insights.
Extends the electoral baseline with multi-year seeding, computeVoteTrend, list/detail UI, and registers post-simplify follow-ups as E7.
2026-07-19 05:09:43 -03:00

117 lines
3.3 KiB
TypeScript

import type {
CandidateVoteRow,
ElectionCandidateRow,
ElectionOffice,
TseDetalheApuracaoRow,
} from '@/lib/electionResults'
import {
ELECTION_OFFICES,
ELECTION_YEAR_2022,
FEDERAL_DEPUTY_OFFICE,
UnknownMunicipalityError,
} from '@/lib/electionResults'
import {
applyStateVoteTotals,
dedupeCandidates,
parseCandidateVoteRow,
parseConsultaCandRow,
parseDetalheApuracaoRow,
type TseCsvRow,
} from '@/lib/electionResultsParse'
const ALL_OFFICES = new Set<ElectionOffice>(ELECTION_OFFICES)
export const FEDERAL_ONLY_OFFICES = new Set<ElectionOffice>([FEDERAL_DEPUTY_OFFICE])
const STATE_OFFICES = new Set<ElectionOffice>([
'governador',
'deputado_federal',
'deputado_estadual',
])
const PRESIDENT_OFFICE = new Set<ElectionOffice>(['presidente'])
export type BuiltElectionResults = {
votes: CandidateVoteRow[]
tallies: TseDetalheApuracaoRow[]
candidates: ElectionCandidateRow[]
unknownMunicipalities: string[]
}
const collectParsed = <T extends { year: number }>(
rows: readonly TseCsvRow[],
year: number,
unknownMunicipalities: Set<string>,
parseRow: (row: TseCsvRow) => T | null,
): T[] => {
const out: T[] = []
for (const row of rows) {
try {
const parsed = parseRow(row)
if (parsed && parsed.year === year) out.push(parsed)
} catch (error) {
if (error instanceof UnknownMunicipalityError) {
unknownMunicipalities.add(error.tseName)
continue
}
throw error
}
}
return out
}
/**
* Build typed election rows from already-parsed TSE CSV tables.
* Safe for fixtures and for the full seed (no I/O).
*/
export const buildElectionResultsFromCsvRows = (args: {
voteRows: readonly TseCsvRow[]
tallyRows: readonly TseCsvRow[]
candBaRows: readonly TseCsvRow[]
candBrRows: readonly TseCsvRow[]
year?: number
offices?: ReadonlySet<ElectionOffice>
}): BuiltElectionResults => {
const year = args.year ?? ELECTION_YEAR_2022
const offices = args.offices ?? ALL_OFFICES
const includePresident = offices.has('presidente')
const includeStateTicket =
offices.has('governador') ||
offices.has('deputado_federal') ||
offices.has('deputado_estadual')
const unknownMunicipalities = new Set<string>()
const votes = collectParsed(args.voteRows, year, unknownMunicipalities, (row) =>
parseCandidateVoteRow(row, { stateFilter: 'BA', offices }),
)
const tallies = collectParsed(args.tallyRows, year, unknownMunicipalities, (row) =>
parseDetalheApuracaoRow(row, { stateFilter: 'BA', offices }),
)
const candidatesRaw: ElectionCandidateRow[] = []
if (includeStateTicket) {
for (const row of args.candBaRows) {
const parsed = parseConsultaCandRow(row, {
stateFilter: 'BA',
offices: STATE_OFFICES,
})
if (parsed && parsed.year === year && offices.has(parsed.office)) {
candidatesRaw.push(parsed)
}
}
}
if (includePresident) {
for (const row of args.candBrRows) {
const parsed = parseConsultaCandRow(row, {
stateFilter: 'BR',
offices: PRESIDENT_OFFICE,
forceState: 'BA',
})
if (parsed && parsed.year === year) candidatesRaw.push(parsed)
}
}
return {
votes,
tallies,
candidates: applyStateVoteTotals(dedupeCandidates(candidatesRaw), votes),
unknownMunicipalities: [...unknownMunicipalities].sort(),
}
}