teqo-backup/scripts/build-election-aggregates.mjs
Francisco Ignácio Fontoura Solla a32ba3b4f0 Cache the campaign hot path: committed TSE artifact + request-scoped dedup
- committed artifact src/lib/electionAggregates/bahia-federal-baseline.json
  (Solla nominal + federal T1 valid votes per catalog municipality × TSE year,
  Salvador split by zone) generated by pnpm build:election-aggregates from the
  locally seeded TSE data; typed loader src/lib/bahiaElectionAggregates.ts;
  snapshot spec pins artifact ↔ catalog ↔ baseline ticket
- map bundle: historical years now read the artifact (was 6 statewide
  city×zone scans per dashboard visit); compare mode keeps only the OTHER
  candidate on the DB path (Solla side from the artifact)
- loadMunicipalityScope (React cache()): one municipality find + pledge
  aggregate per request shared by dashboard KPIs, map bundle, and the
  municípios list overview (was 2-3 duplicate loads)
- unstable_cache + election-tse tag on the immutable elections-tab loaders
  (baseline, candidate comparison, candidate options); tag added to the
  /api/revalidate allowlist — runbook: re-seed then bust the tag
- municipalityMapContract.ts: client-safe map vocabulary/bundle types so the
  client panel no longer imports the server data module (build had pulled
  @payload-config into the client graph); municipalityMapData is server-only
  and re-exports the contract; PLAZA_MAP_* renamed to MUNICIPALITY_MAP_*
- petition generateStaticParams enumerates bare ids (getPetitionIds)
2026-07-23 23:06:28 -03:00

111 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Builds the committed per-municipality election baseline artifact consumed by
* the campaign map (src/lib/electionAggregates/bahia-federal-baseline.json).
*
* Why an artifact instead of runtime queries: the 2014/2018/2022 TSE results
* are immutable, but the map used to re-scan statewide city×zone slices on
* every dashboard visit. Precomputing the per-municipality sums (Solla nominal
* votes + federal T1 valid votes, cut by the same `municipalityCatalog`
* geography used at runtime — Salvador split into zone municipalities, every
* other municipality whole) removes all historical-year DB work.
*
* Why not compute during `pnpm build`: Vercel builds must not depend on the
* production database CONTENT (TSE seeds are local-only by policy), and the
* artifact should only change when the electoral scope deliberately changes.
*
* Prerequisite: `pnpm db:seed:tse` (2022) + `--year=2018` + `--year=2014` on
* the LOCAL database.
*
* Usage:
* pnpm build:election-aggregates
*
* Safety: read-only against the local database (assertLocalDatabase guard);
* writes only src/lib/electionAggregates/.
*/
import { mkdir, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { config as loadEnv } from 'dotenv'
import { getPayload } from 'payload'
import { assertLocalDatabase } from './assert-local-database.mjs'
loadEnv({ path: '.env.local' })
loadEnv({ path: '.env' })
const payloadConfig = (await import('../src/payload.config.ts')).default
const { BASELINE_TICKET_2022, HISTORICAL_SERIES_YEARS } = await import(
'../src/lib/electionResults.ts'
)
const { municipalityCatalog } = await import('../src/lib/municipalityCatalog.ts')
const { municipalityElectionGeography } = await import(
'../src/utilities/municipalityElectionGeography.ts'
)
const {
loadCandidateVotesByCityZone,
loadValidVotesByCityZone,
sumVotesForGeography,
} = await import('../src/utilities/municipalityElectoralBaseline.ts')
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
const OUTPUT_DIR = join(ROOT, 'src', 'lib', 'electionAggregates')
const OUTPUT_FILE = join(OUTPUT_DIR, 'bahia-federal-baseline.json')
assertLocalDatabase(
'build:election-aggregates',
'This script only reads the LOCAL seeded TSE data (pnpm db:seed:tse).',
)
const payload = await getPayload({ config: payloadConfig })
/** CLI reader — satisfies the election-data read assertion (admin realm). */
const cliReader = { collection: 'users' }
const candidateNumber = BASELINE_TICKET_2022.candidate.candidateNumber
const municipalities = {}
for (const entry of municipalityCatalog) {
municipalities[entry.slug] = { votesByYear: {}, validVotesByYear: {} }
}
for (const year of HISTORICAL_SERIES_YEARS) {
const [candidateVotes, validVotes] = await Promise.all([
loadCandidateVotesByCityZone(payload, cliReader, { year, candidateNumber }),
loadValidVotesByCityZone(payload, cliReader, { year }),
])
if (candidateVotes.size === 0) {
console.error(
`[build:election-aggregates] No nominal votes found for ${year}. ` +
`Seed the local database first: pnpm db:seed:tse -- --year=${year}`,
)
process.exit(1)
}
let yearTotal = 0
for (const entry of municipalityCatalog) {
const geography = municipalityElectionGeography(entry)
const votes = sumVotesForGeography(candidateVotes, geography)
const valid = sumVotesForGeography(validVotes, geography)
municipalities[entry.slug].votesByYear[String(year)] = votes
municipalities[entry.slug].validVotesByYear[String(year)] = valid
yearTotal += votes
}
console.log(`[build:election-aggregates] ${year}: ${yearTotal} votes for #${candidateNumber}.`)
}
const artifact = {
provenance:
'Derived from TSE open data seeded by pnpm db:seed:tse; regenerate with pnpm build:election-aggregates.',
candidateNumber,
candidateName: BASELINE_TICKET_2022.candidate.name,
years: [...HISTORICAL_SERIES_YEARS],
municipalities,
}
await mkdir(OUTPUT_DIR, { recursive: true })
await writeFile(OUTPUT_FILE, `${JSON.stringify(artifact, null, 2)}\n`)
console.log(
`[build:election-aggregates] Wrote ${OUTPUT_FILE} (${municipalityCatalog.length} municipalities × ${HISTORICAL_SERIES_YEARS.length} years).`,
)
process.exit(0)