90 lines
5.8 KiB
Python
90 lines
5.8 KiB
Python
"""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()
|