Files
product-fpol/tools/generate_real_catalog.py
khodowany 78b2c383f1
ci / frontend (push) Successful in 9m13s
ci / backend (push) Failing after 6m40s
Game Features
2026-10-04 23:20:06 -05:00

90 lines
5.8 KiB
Python
Raw Permalink 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.
"""Reproduce reviewed public candidate facts and the national contest inventory.
NC: official state-only November 3 list, report dated September 21, 2026,
pages 1–3. Ballot names and parties only; no contact/address information.
Re-running does not fetch or imply a more recent review.
"""
import csv
import json
import pathlib
import re
ROOT = pathlib.Path(__file__).resolve().parents[1]
SOURCE = {
"name": "North Carolina State Board of Elections — November 2026 candidate list, pages 1–3",
"url": "https://s3.amazonaws.com/dl.ncsbe.gov/Elections/2026/Candidate%20Filing/2026_general_candidate_list_by_contest_federal_and_state.pdf",
"retrieved_at": "2026-10-04T00:00:00Z",
}
# Reviewed transcription. Party order follows the official list.
CONTESTS = {
"senate": "GRE:Michael Dublin|DEM:Roy Cooper|LIB:Shannon W. Bray|REP:Michael Whatley",
"house-01": "DEM:Don Davis|REP:Laurie Buckhout|LIB:Tom Bailey",
"house-02": "REP:Eugene F. Douglass|DEM:Deborah K. Ross|LIB:Matthew Laszacs",
"house-03": "LIB:Daniel Cavender|DEM:Raymond Smith|REP:Greg Murphy",
"house-04": "DEM:Valerie P. Foushee|LIB:Guy Meilleur|REP:Mahesh (Max) Ganorkar",
"house-05": "REP:Virginia Foxx|LIB:Robert B. Luffman|DEM:Chuck Hubbard",
"house-06": "REP:Addison McDowell|DEM:Cyril Jefferson",
"house-07": "LIB:Maad Abu-Ghazalah|REP:David Rouzer|DEM:Kimberly Hardy",
"house-08": "GRE:Bo Whitehead|DEM:Colby Watson|REP:Mark Harris",
"house-09": "DEM:Richard N. Ojeda II|REP:Richard Hudson",
"house-10": "LIB:Steven Feldman|DEM:Ashley Bell|REP:Pat Harrigan",
"house-11": "REP:Jennifer Capps Balkcom|DEM:Jamie Ager|LIB:Travis Groo",
"house-12": "REP:Jack Codiga|DEM:Alma S. Adams",
"house-13": "DEM:Paul Barringer|LIB:Steven Swinton|REP:Brad Knott",
"house-14": "DEM:Lakesha Womack|REP:Tim Moore",
}
SEATS = dict(zip("AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA WA WV WI WY".split(),
[7, 1, 9, 4, 52, 8, 5, 1, 28, 14, 2, 2, 17, 9, 4, 4, 6, 6, 2, 8, 9, 13, 8, 4, 8, 2, 3, 4, 2, 12, 3, 26, 14, 1, 15, 5, 6, 17, 2, 7, 1, 9, 38, 4, 1, 11, 10, 2, 8, 1]))
def write(path, value):
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n")
def main():
catalog = {"schema_version": 1, "kind": "catalog", "id": "nc-2026-10-04", "mode": "real",
"label": "North Carolina official list · partial US catalog", "as_of": SOURCE["retrieved_at"], "source": SOURCE,
"complete": False, "races": [], "candidates": [],
"notes": "Reviewed from official PDF pages 1–3, report dated 2026-09-21. Retrieval has day precision; midnight is a date placeholder. Federal contests only; NC has no scheduled 2026 governor race. Refresh against official changes before freezing."}
for contest, candidates in CONTESTS.items():
race = "2026-nc-" + contest + "-general"
catalog["races"].append({"id": race, "state": "NC", "office": contest.split("-")[0], "round": "general", "name": "North Carolina " + contest.replace("-", " ").title()})
for row in candidates.split("|"):
party, name = row.split(":", 1)
candidate_id = race + "-" + re.sub("[^a-z0-9]+", "-", name.lower()).strip("-")
catalog["candidates"].append({"id": candidate_id, "race_id": race, "name": name, "party": party,
"affiliation": {"DEM": "democrat", "REP": "republican"}.get(party, "other"), "source": SOURCE, "qualified": True, "ballot_lines": [party], "provider_ids": {}})
write(ROOT / "data/real/nc-2026-10-04.json", catalog)
combined = json.loads((ROOT / "data/real/mi-2026-10-04.json").read_text())
combined.update(id="us-2026-10-04-partial", label="Michigan + North Carolina · partial US catalog",
notes="Combined reviewed official lists. Each candidacy retains its original source and ID. Retrieval timestamps have day precision. 48 states still need imports; no live result feed is verified.",
sources=[combined["source"], SOURCE])
combined["races"] += catalog["races"]
combined["candidates"] += catalog["candidates"]
write(ROOT / "data/real/us-2026-10-04-partial.json", combined)
rows = list(csv.DictReader((ROOT / "data/coverage_2026.csv").open()))
inventory = []
for row in rows:
if row["state"] == "NC" and row["office"] in ("house", "senate"):
row["catalog"] = "official_partial"
if row["scheduled_2026"] != "yes":
continue
state, office = row["state"], row["office"]
for district in range(1, SEATS[state] + 1) if office == "house" else [None]:
seat = f"-{district:02}" if district else ""
special = office == "senate" and state in ("FL", "OH")
inventory.append({"id": f"2026-{state.lower()}-{office}{seat}-{'special' if special else 'general'}",
"state": state, "office": office, "district": district, "election_date": "2026-11-03", "contest_type": "special" if special else "general", "scoring_round": "november_first"})
with (ROOT / "data/coverage_2026.csv").open("w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=rows[0].keys(), lineterminator="\n")
writer.writeheader()
writer.writerows(rows)
assert len(inventory) == 506, len(inventory)
write(ROOT / "data/contests_2026.json", {"reviewed_at": "2026-10-04", "scope": "50 states; 435 House, 35 Senate (including FL/OH special), 36 governor; November contests only",
"sources": ["https://www.census.gov/data/tables/2020/dec/2020-apportionment-data.html", "https://www.senate.gov/senators/Class_II.htm", "https://about.usps.com/what/government-services/election-mail/pdf/2026-election-mail-kit.pdf"], "contests": inventory})
lines = ['#pragma once', '#include <array>', '#include <string_view>', '', 'namespace fpol {', 'inline constexpr auto national_races = std::array{']
lines += [' std::string_view{"' + race["id"] + '"},' for race in inventory]
lines += ['};', '}']
(ROOT / "src/national_races.hpp").write_text("\n".join(lines) + "\n")
if __name__ == "__main__":
main()