@@ -19,6 +19,15 @@ read-only history.
1919
2020### Quick start: re-generate an existing year's CSVs
2121
22+ The framework runs on Python 3 via [ uv] ( https://docs.astral.sh/uv/ ) .
23+ If you don't have ` uv ` yet, install it with one of:
24+
25+ ``` bash
26+ curl -LsSf https://astral.sh/uv/install.sh | sh # Mac or Linux
27+ brew install uv # Mac with Homebrew
28+ pip install uv # any platform with Python + pip
29+ ```
30+
2231All ` uv ` commands below run in a terminal, from the project root
2332(the directory that contains this ` README.md ` ).
2433
@@ -44,7 +53,7 @@ the build's pre-flight scan, not at argument-parse time.
4453### Adding a new election year
4554
4655NH SoS publishes new election workbooks roughly six weeks after the
47- election. To add a year (say 2026), three commands total :
56+ election. To add a year (say 2026):
4857
49581 . ** Scaffold the year:**
5059
@@ -65,47 +74,32 @@ election. To add a year (say 2026), three commands total:
6574 [ scripts/fetch-raw.md] ( scripts/fetch-raw.md ) for the per-office
6675 download details.
6776
68- ** Drop the downloaded files into ` raw/2026/general/ ` .** You don't
69- have to rename them — the framework recognizes both canonical
70- short-form names and the longer names the SoS publishes
71- (` 2026-ge-house-belknap_1.xls ` and ` house-belknap.xls ` both work).
72- The canonical short forms for reference:
73-
74- | Office | Canonical filename(s) |
75- | --- | --- |
76- | President | ` president.xls[x] ` |
77- | Governor | ` governor.xls[x] ` |
78- | US Senate | ` us-senate.xls[x] ` |
79- | Congressional | ` congressional-1.xls[x] ` , ` congressional-2.xls[x] ` |
80- | Executive Council | ` executive-council.xls[x] ` |
81- | State Senate | ` state-senate.xls[x] ` |
82- | State Representative | ` house-belknap.xls[x] ` , ` house-carroll.xls[x] ` , … (one per NH county) |
83-
84- The build command's pre-flight scan tells you which files matched
85- which office (and which weren't recognized), so any naming mistake
86- is surfaced before parsing starts. Offices that aren't on the
87- year's ballot (e.g. no Presidential in midterms) just show up as
88- ` ⚠️ skipped ` in the summary — not an error.
77+ ** Drop the downloaded files into ` raw/2026/general/ ` .** No need to
78+ rename them — the framework recognizes SoS-shaped filenames as well
79+ as canonical short forms.
8980
90- 3 . ** Build everything in one shot :**
81+ 3 . ** Build the CSVs :**
9182
9283 ``` bash
9384 uv run python -m oe_nh.cli --year 2026
9485 ```
9586
96- You'll get a pre-flight scan ("found these files, will build these
97- offices, ignoring these unknowns"), per-office build lines, and a
98- trailing summary with ✅/⚠️/❌ status and total row counts. CSVs
99- land under ` 2026/ ` . Anything missing or surprising is reported
100- once, in the summary — no need to scroll through logs.
87+ You'll get a pre-flight scan, per-office build lines, and a trailing
88+ summary with ✅/⚠️/❌ status and row counts. CSVs land under
89+ ` 2026/ ` . Anything missing or surprising is reported once, in the
90+ summary — see "Details for Nerds" below for what ` ⚠️ ` and ` ❓ ` mean.
10191
102- 4 . ** Commit** the new raw files (` raw/2026/general/*.xls* ` ) AND the
103- generated CSVs (` 2026/*.csv ` ) AND ` raw/2026/.dates.json ` . All three
92+ 4 . ** Commit** the new raw files (` raw/2026/general/*.xls* ` ), the
93+ generated CSVs (` 2026/*.csv ` ), and ` raw/2026/.dates.json ` . All three
10494 belong in version control: raw files so the build is reproducible,
10595 CSVs because they're the published product OpenElections consumes,
10696 and the dates file because it's what tells the framework this year
10797 is registered.
10898
99+ ---------
100+
101+ ## Details for Nerds
102+
109103### Output conventions
110104
111105- ** Schema** is ` county,precinct,office,district,party,candidate,votes ` — one row per (precinct, candidate) pair.
@@ -134,3 +128,27 @@ Parser + Config dataclass (`CongressionalParser`,
134128` parse_workbook(path, config) ` factory dispatches on config type. Add a
135129new shape by writing a new Parser + Config and adding a branch to the
136130factory plus an entry to ` discovery._DISPATCH ` .
131+
132+ ### File Naming Details
133+
134+ ** Drop the downloaded files into ` raw/2026/general/ ` .** You don't
135+ have to rename them — the framework recognizes both canonical
136+ short-form names and the longer names the SoS publishes
137+ (` 2026-ge-house-belknap_1.xls ` and ` house-belknap.xls ` both work).
138+ The canonical short forms for reference:
139+
140+ | Office | Canonical filename(s) |
141+ | --- | --- |
142+ | President | ` president.xls[x] ` |
143+ | Governor | ` governor.xls[x] ` |
144+ | US Senate | ` us-senate.xls[x] ` |
145+ | Congressional | ` congressional-1.xls[x] ` , ` congressional-2.xls[x] ` |
146+ | Executive Council | ` executive-council.xls[x] ` |
147+ | State Senate | ` state-senate.xls[x] ` |
148+ | State Representative | ` house-belknap.xls[x] ` , ` house-carroll.xls[x] ` , … (one per NH county) |
149+
150+ The build command's pre-flight scan tells you which files matched
151+ which office (and which weren't recognized), so any naming mistake
152+ is surfaced before parsing starts. Offices that aren't on the
153+ year's ballot (e.g. no Presidential in midterms) just show up as
154+ ` ⚠️ skipped ` in the summary — not an error.
0 commit comments