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>
This commit is contained in:
Fabio
2026-07-04 20:36:42 -04:00
parent 199d8b37d3
commit 662ccecb26
4 changed files with 33 additions and 23 deletions
+7 -6
View File
@@ -11,11 +11,11 @@ source. This regenerates its two companions deterministically:
— per-county drill-down roster.
County/municipal officials are mapped to a county the same way build.py places
them in the tree (county_slug / city helpers), so the viz and the tree agree by
construction. Officials whose county can't be resolved to an ACS FIPS (e.g. the
non-FL municipal rows still blocked on the place->county crosswalk) are counted
in the tree but do not appear in the county-keyed viz — an honest gap, not a
silent drop; the tally is printed at the end.
them in the tree (shared county_slug + place_resolver), so the viz and the tree
agree by construction. Officials whose county still can't be resolved to an ACS
FIPS (ambiguous names, county-level rows mislabeled municipal, CT planning
regions, label-less rows) are counted in the tree but do not appear in the
county-keyed viz — an honest gap, not a silent drop; the tally is printed.
Deterministic: sorted iteration, fixed key order. Two runs are byte-identical.
"""
@@ -47,6 +47,7 @@ def city_name(rec):
def main():
officeholders = build.load("officeholders-v3.jsonl")
acs_county = build.load("acs_county.jsonl")
place_to_cslug = build.place_resolver(acs_county, build.load("place_county_crosswalk.jsonl"))
# slug -> fips, and fips -> (name, state, demographics), deduped by fips
slug_to_fips = {}
@@ -72,7 +73,7 @@ def main():
for rec in officeholders:
if rec["level"] not in ("county", "municipal"):
continue
key = ((rec.get("state_abbr") or "").lower(), build.county_slug(rec))
key = ((rec.get("state_abbr") or "").lower(), build.county_slug(rec, place_to_cslug))
oh_count[key] += 1
name = rec.get("full_name") or rec.get("title") or "Unknown"
role = rec.get("title") or ""