Fourth lens on the Board, coloring each county by the D/R balance of the
state + federal representatives who cover it — the offices that are
actually partisan (99% D/R coverage vs ~0% for nonpartisan local seats).
Pipeline (build_viz.py):
- Carry each district rep's party alongside their name through the
county<->district edge mapping.
- Per county, tally reps by party over districts covering >=5% of its
area (slivers excluded), and store lean = (R-D)/(R+D) in [-1,+1] plus
repD/repR counts. Head-count, not area-weighted, so the number matches
the readout and isn't skewed toward large rural districts. 3130/3131
counties resolve.
Board (board.html):
- "Partisan lean" lens with a diverging blue<->grey<->red ramp (centered
at 0, skipped in the percentile-rank machinery the other lenses use).
- Diverging legend (More Democratic / More Republican).
- Readout gains a Representation row (e.g. "8 D · 3 R D+45").
- Drill-down district reps get a D/R party badge.
Verified in preview: no console errors; Manhattan -1.0 (21D/0R), LA -0.46,
Palm Beach even (3D/3R), Loving TX +1.0; readout, color, and badges agree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces the new graph layer on the Board. build_viz.py resolves each
county's district edges to their current representatives (via
build.person_district_node) and injects a per-county `districts` block into
CDETAIL; board.html renders "In Congress / State Senate / State House"
sections in the drill-down, each rep with their district and % of county
covered. County detail now spans 3,221 counties (was 1,049) since every
county with district edges gets a drill-down, not only those with local
officials. Deterministic (board.html byte-identical on rerun).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Board is a self-contained Artifact, so its county data is INLINED
(const D / const CDETAIL) — the standalone county_*.json companions are not
read by it. build_viz.py was only writing those companions, so the rendered
Board kept showing the pre-crosswalk state (67 counties). Now build_viz.py
injects the fresh data straight into board.html's three marked const lines,
and the header stats read from an injected TOTALS object instead of being
hardcoded (so they can't go stale — the original 11,285 drift).
- scripts/build_viz.py — inject_board(): rewrite const D / CDETAIL / TOTALS
in board.html; load candidates + acs_cd for the totals
- viz/board.html — header driven by TOTALS; inlined data refreshed
(county drill-downs 67 → 1049 counties; e.g. Fulton GA now shows all 7
metro mayors)
Deterministic: board.html is byte-identical on rerun. Verified live —
header 13,329/2,494/3,131/363, Fulton/Bastrop drill-downs render nested
municipals.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_viz.py was resolving officials to counties without the place→county
crosswalk, so only FL's 67 counties had rosters. Factor the resolver out of
build.py into a shared build.place_resolver() and use it in both, so the
Board and the tree agree by construction.
- scripts/build.py — extract place_resolver(); main() now calls it (tree
output byte-identical, refactor is behavior-preserving)
- scripts/build_viz.py — resolve via the shared resolver
- viz/county_data.json, viz/county_detail.json — regenerated
County drill-downs: 67 → 1049 counties with rosters; officials mapped
5184/5226 (42 county-unresolvable, tree-only). Deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-exported directly from Postgres (10.0.0.116), not Atlas's introspection,
which under-reported the roster (its v3 export had 11,473, missing the
restored Florida League of Cities municipal set). Live DB current tenures:
state 7,561 · municipal 4,009 · county 1,217 · federal 542.
- build.py now reads officeholders-v3.jsonl (was v2); dataset vintage 2026-07-04
- fixed one corrupt start_date at source-export (21021-12-03 -> 2021-12-03,
Lorraine Borowski, Mayor of Decorah IA)
- scripts/build_viz.py: committed, deterministic generator for the Board's
county_data.json / county_detail.json (previously built ad-hoc)
- generate_changelog.py: ignore files whose only change is the vintage stamp,
so the diff reflects government change, not re-export bookkeeping
Person files 11,285 -> 13,329. Non-FL municipal officials (1,903) are in the
tree but not yet county-mappable (place->county crosswalk still NULL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>