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>
This commit is contained in:
Fabio
2026-07-04 22:40:00 -04:00
parent 1beeaab2db
commit 886938f1a9
3 changed files with 50 additions and 17 deletions
+36 -12
View File
@@ -102,20 +102,43 @@ def main():
"oh": oh_count.get(key, 0),
}
# ---- county_detail.json (only counties with resolved officials) ----
county_detail = {}
keys_with_people = set(county_roster) | set(muni_roster)
for key in keys_with_people:
cf = slug_to_fips.get(key)
if cf is None:
# ---- district representation per county (fips -> {cd, ss, sh}) ----
rep_by_node = defaultdict(list) # district node id -> current rep name(s)
for rec in officeholders:
if not rec.get("is_current"):
continue
county = [{"n": n, "r": r} for n, r in sorted(county_roster.get(key, []))]
nid = build.person_district_node(rec)
if nid:
rep_by_node[nid].append(rec.get("full_name") or "?")
KEYMAP = {"CD": "cd", "SS": "ss", "SH": "sh"}
districts_by_fips = defaultdict(lambda: {"cd": [], "ss": [], "sh": []})
for e in sorted(build.load("county_district_edges.jsonl"),
key=lambda x: (x["county_geoid"], build.DTYPE_RANK[x["type"]], -x["area_weight"])):
dt = e["type"]
ident = build.cd_ident(e["district_label"]) if dt == "CD" else build.leg_ident(e["district_label"])
reps = sorted(set(rep_by_node.get(build.edge_node_id(e), [])))
districts_by_fips[build.canonical_fips(e["county_geoid"])][KEYMAP[dt]].append(
{"d": build.district_title(dt, e["district_state"], ident),
"w": round(e["area_weight"], 3), "rep": ", ".join(reps) or None})
# ---- county_detail.json (any county with officials OR districts) ----
fips_to_key = {cf: key for key, cf in slug_to_fips.items()}
fips_with_people = {slug_to_fips[k] for k in (set(county_roster) | set(muni_roster))
if k in slug_to_fips}
county_detail = {}
for cf in sorted(fips_with_people | set(districts_by_fips)):
key = fips_to_key.get(cf)
county = [{"n": n, "r": r} for n, r in sorted(county_roster.get(key, []))] if key else []
munis = []
if key:
for city in sorted(muni_roster.get(key, {})):
people = [{"n": n, "r": r} for n, r in sorted(muni_roster[key][city])]
munis.append({"m": city, "p": people})
county_detail[cf] = {"slug": key[1], "county": county, "munis": munis}
county_detail = {k: county_detail[k] for k in sorted(county_detail)}
munis.append({"m": city, "p": [{"n": n, "r": r}
for n, r in sorted(muni_roster[key][city])]})
entry = {"slug": (county_meta.get(cf) or {}).get("slug") or (key[1] if key else ""),
"county": county, "munis": munis}
if cf in districts_by_fips:
entry["districts"] = districts_by_fips[cf]
county_detail[cf] = entry
totals = {
"people": len(officeholders),
@@ -138,7 +161,8 @@ def main():
total_oh = sum(oh_count.values())
print(f"county_data.json: {len(county_data)} counties, "
f"{sum(1 for v in county_data.values() if v['oh'])} with officeholders")
print(f"county_detail.json: {len(county_detail)} counties with rosters")
print(f"county_detail.json: {len(county_detail)} counties "
f"({len(districts_by_fips)} with district representation)")
print(f"officials mapped: {total_oh - unresolved}/{total_oh} "
f"(county-unresolvable, tree-only: {unresolved})")
print(f"board.html injected: totals={totals}")
+12 -3
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long