Commit Graph

13 Commits

Author SHA1 Message Date
Fabio 802c49a89d Board: add a partisan-lean lens
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>
2026-07-06 08:59:51 -04:00
Fabio 77e7b4282e Board: make the county map zoomable and pannable
Wrap the county paths in a <g id="mapg"> and drive a translate/scale
transform from the mouse:

- scroll to zoom toward the cursor (1x-16x)
- drag to pan, clamped so the map can't leave the frame
- double-click to reset to the full view

County borders use vector-effect:non-scaling-stroke so they stay crisp
at every zoom level instead of thickening. The existing hover readout and
click-to-open county sheet keep working through event delegation; the
click handler is guarded (dragMoved / e.detail) so a pan gesture or the
reset double-click never pops a county sheet.

Pure view interaction — no data touched. Safe against `make board`, which
only rewrites the D/CDETAIL/TOTALS data lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:25:12 -04:00
Fabio 886938f1a9 Board: show each county's districts + representatives in the drill-down
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>
2026-07-04 22:40:00 -04:00
Fabio 3e62a1b44a Board: add page <title> (Republic OS — The Board)
The page had no <title>, so browser tabs / the Artifact fell back to the
filename. build_viz.py's injection only rewrites the const lines, so the
title is preserved across regenerations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:07:33 -04:00
Fabio 12850196f9 Board: regenerate board.html data in-place; drive header from TOTALS
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>
2026-07-04 20:55:43 -04:00
Fabio e37a897111 Board: fix stale officeholder total in header (11,285 → 13,329)
The "OFFICEHOLDERS" stat was hardcoded to the old v2 Person count; v3 has
13,329. Counties/candidates/districts were unaffected. (Follow-up worth
doing: emit these header totals from build_viz.py so they can't go stale.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:40:37 -04:00
Fabio 662ccecb26 Board: surface the newly-nested municipals in county drill-downs
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>
2026-07-04 20:36:42 -04:00
Fabio 7a2cc64362 Sync: officeholders v3 — fresh export from live Atlas DB (13,329 tenures)
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>
2026-07-04 18:56:37 -04:00
Fabio 092e54ae21 Board: drill down into each county
Click any county to open a detail panel: its demographics, then the
actual officeholders in the mirror — grouped by county government and by
municipality, each with name and role — and a link to the county's files
on GitHub.

Richest for Florida, mirrored to the municipal level: e.g. St. Lucie
opens to 33 officeholders (18 county — commission, sheriff, tax collector,
school board — plus Fort Pierce, Port St. Lucie, and St. Lucie Village).
Counties without officeholders yet show their demographic node and a note.

Detail data (viz/county_detail.json, 181 KB) extracted from the entity
tree: 67 FL counties, 1,058 county + 2,124 municipal officials. Modal
closes on X, backdrop, or Esc. Verified in-browser: click opens the
panel, names render First-Last, municipality sections and GitHub links
resolve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:28:21 -04:00
Fabio 41ec4bf173 Board: eliminate the fog of war
Dropped the Coverage lens and its dark-unmapped framing. The Board is now
a pure demographic instrument: three lenses (median income, population,
poverty rate), every one coloring all 3,109 counties — no darkness, no
'not yet mapped', no fog-of-war copy. Rail shows Districts (363) instead
of 'counties lit'; the ledger describes the demographic lenses.

Also removed the load-in fade, which was flashing the map black on first
paint — it now renders fully colored immediately.

Verified in-browser: income (green) and poverty (amber) choropleths both
render full-country; lens switching and hover readout intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:20:49 -04:00
Fabio 4e0c559227 Board: fix the county colors
The county fills were technically correct but visually near-black: linear
min-max scaling crammed a right-skewed distribution into the dark end, so
the map read as empty. Fixes:

  - Rank (percentile) coloring — spread the full palette evenly across
    counties, the standard choropleth technique, so every lens shows rich
    variation instead of a few bright outliers on black.
  - Brighter per-lens ramps (income green, population teal, poverty amber).
  - Coverage amber lifted so Florida's 67 lit counties are unmistakable
    against the fog-of-war country.
  - Default lens is now Median income, so the map opens full of color;
    Coverage (the fog of war) is one click away.

Verified in-browser: full green income choropleth, Florida clearly lit in
coverage, hover readout prints each county's demographics + officeholders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:11:44 -04:00
Fabio e126d3e374 The Board, v2: nationwide county map with lenses
The map grew with the data. The old Board showed coverage at the state
level with a Florida drill-down; now that every US county is a demographic
node, the Board renders all 3,109 contiguous counties directly, with
switchable lenses:

  Coverage       — the true fog of war: 67 FL counties lit (officeholders
                   mirrored), the other 3,000+ dark on the frontier.
  Population     — ACS 2023 choropleth, 33 to 9.8M.
  Median income  — k to k.
  Poverty rate   — 1.4% to 48%.

Hover any county for its full readout (demographics + officeholder count).
Self-contained: inlined county geometry (duplicate-point-collapsed),
state-border overlay, hand-rolled projection, event-delegated hover for
3,109 paths, vanilla JS — no external requests. Verified: projection
places counties correctly, 3,099/3,109 join to ACS data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:52:21 -04:00
Fabio 421c5b3553 The Board: fog-of-war coverage map (first visualization)
The one radical view whose data is fully real today (brief §4.5).
Renders every officeholder in the mirror as coverage: teal is the
federal + state tier (the roof, every state), amber is county +
municipal depth (the foundation, so far only Florida). Click Florida
to descend into its 67 counties, shaded by local officeholder count.

Self-contained: inlined US state + FL county geometry, hand-rolled
lng/lat projection, vanilla JS — no external requests, no build deps.
Instrument aesthetic: dark ground, mono readout, breathing amber to
invite the drill-down. viz/coverage.json is generated from the tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:00:52 -04:00