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:
+37
-13
@@ -102,20 +102,43 @@ def main():
|
|||||||
"oh": oh_count.get(key, 0),
|
"oh": oh_count.get(key, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- county_detail.json (only counties with resolved officials) ----
|
# ---- district representation per county (fips -> {cd, ss, sh}) ----
|
||||||
county_detail = {}
|
rep_by_node = defaultdict(list) # district node id -> current rep name(s)
|
||||||
keys_with_people = set(county_roster) | set(muni_roster)
|
for rec in officeholders:
|
||||||
for key in keys_with_people:
|
if not rec.get("is_current"):
|
||||||
cf = slug_to_fips.get(key)
|
|
||||||
if cf is None:
|
|
||||||
continue
|
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 = []
|
munis = []
|
||||||
for city in sorted(muni_roster.get(key, {})):
|
if key:
|
||||||
people = [{"n": n, "r": r} for n, r in sorted(muni_roster[key][city])]
|
for city in sorted(muni_roster.get(key, {})):
|
||||||
munis.append({"m": city, "p": people})
|
munis.append({"m": city, "p": [{"n": n, "r": r}
|
||||||
county_detail[cf] = {"slug": key[1], "county": county, "munis": munis}
|
for n, r in sorted(muni_roster[key][city])]})
|
||||||
county_detail = {k: county_detail[k] for k in sorted(county_detail)}
|
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 = {
|
totals = {
|
||||||
"people": len(officeholders),
|
"people": len(officeholders),
|
||||||
@@ -138,7 +161,8 @@ def main():
|
|||||||
total_oh = sum(oh_count.values())
|
total_oh = sum(oh_count.values())
|
||||||
print(f"county_data.json: {len(county_data)} counties, "
|
print(f"county_data.json: {len(county_data)} counties, "
|
||||||
f"{sum(1 for v in county_data.values() if v['oh'])} with officeholders")
|
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} "
|
print(f"officials mapped: {total_oh - unresolved}/{total_oh} "
|
||||||
f"(county-unresolvable, tree-only: {unresolved})")
|
f"(county-unresolvable, tree-only: {unresolved})")
|
||||||
print(f"board.html injected: totals={totals}")
|
print(f"board.html injected: totals={totals}")
|
||||||
|
|||||||
+12
-3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user