#!/usr/bin/env python3 """Generate self-contained HTML dashboard for Massie 119th Congress voting analysis.""" import json from collections import Counter, defaultdict votes = json.load(open("/home/user/polisci/votes.json")) # Aggregate align_counts = Counter(v["alignment"] for v in votes) massie_counts = Counter(v["massie"] for v in votes) blocked_counts = Counter(v["blocked"] for v in votes if v["blocked"]) # Monthly alignment trend month_align = defaultdict(lambda: Counter()) MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] def month_key(v): # date like "3-Jan-2025" try: d, m, y = v["date"].split("-") return f"{y}-{MONTHS.index(m)+1:02d}" except Exception: return None for v in votes: mk = month_key(v) if mk: month_align[mk][v["alignment"]] += 1 months_sorted = sorted(month_align.keys()) align_labels = ["Helped Republicans", "Helped Democrats", "Helped Both", "Helped Neither"] monthly_series = {lab: [month_align[m].get(lab, 0) for m in months_sorted] for lab in align_labels} # Blocked measures - list for table blocked_dem = [v for v in votes if v["blocked"] == "Democrat"] blocked_rep = [v for v in votes if v["blocked"] == "Republican"] # All vote rows (compact) for filterable table def row(v): return { "y": v["year"], "r": v["roll"], "d": v["date"], "ln": v["legis_num"], "q": v["question"], "ds": v["desc"][:90], "rs": v["result"], "m": v["massie"], "ry": v["R"]["yea"], "rn": v["R"]["nay"], "dy": v["D"]["yea"], "dn": v["D"]["nay"], "a": v["alignment"], "b": v["blocked"] or "", } rows = [row(v) for v in votes] # Summary stats total = len(votes) voting = sum(1 for v in votes if v["massie"] in ("Yea","Nay","Aye","No")) yeas = sum(1 for v in votes if v["massie"] in ("Yea","Aye")) nays = sum(1 for v in votes if v["massie"] in ("Nay","No")) nv = sum(1 for v in votes if v["massie"] == "Not Voting") present = sum(1 for v in votes if v["massie"] == "Present") # Lone-wolf: cases where Massie was on the losing side with very few co-defectors of his own party lone_wolf = 0 for v in votes: if v["massie"] not in ("Yea","Nay","Aye","No"): continue m_norm = "Yea" if v["massie"] in ("Yea","Aye") else "Nay" r_pos = "Yea" if v["R"]["yea"] > v["R"]["nay"] else ("Nay" if v["R"]["nay"] > v["R"]["yea"] else "Split") if r_pos != "Split" and m_norm != r_pos: # how many Republicans defected with him? defectors = v["R"]["nay"] if r_pos == "Yea" else v["R"]["yea"] if defectors <= 5: lone_wolf += 1 voted_against_gop = 0 voted_against_dem = 0 voted_with_gop = 0 voted_with_dem = 0 for v in votes: if v["massie"] not in ("Yea","Nay","Aye","No"): continue m_norm = "Yea" if v["massie"] in ("Yea","Aye") else "Nay" r_pos = "Yea" if v["R"]["yea"] > v["R"]["nay"] else ("Nay" if v["R"]["nay"] > v["R"]["yea"] else "Split") d_pos = "Yea" if v["D"]["yea"] > v["D"]["nay"] else ("Nay" if v["D"]["nay"] > v["D"]["yea"] else "Split") if r_pos != "Split": if m_norm == r_pos: voted_with_gop += 1 else: voted_against_gop += 1 if d_pos != "Split": if m_norm == d_pos: voted_with_dem += 1 else: voted_against_dem += 1 data = { "total": total, "voting": voting, "yeas": yeas, "nays": nays, "nv": nv, "present": present, "alignment": dict(align_counts), "massie": dict(massie_counts), "blocked": dict(blocked_counts), "months": months_sorted, "monthly": monthly_series, "rows": rows, "blocked_dem_count": len(blocked_dem), "blocked_rep_count": len(blocked_rep), "lone_wolf": lone_wolf, "voted_against_gop": voted_against_gop, "voted_against_dem": voted_against_dem, "voted_with_gop": voted_with_gop, "voted_with_dem": voted_with_dem, } # Write to template HTML = """ Thomas Massie - 119th Congress Voting Dashboard

Thomas Massie (R-KY) — 119th Congress Voting Analysis

Bioguide M001184 · House roll-call votes, Jan 3, 2025 – present · Source: clerk.house.gov
↕ Drag any card to rearrange.
Roll Calls / Participation
__VOTING__ / __TOTAL__ __VOTING_PCT__%
119th Congress, Jan 2025 – May 2026 · Yea/Aye: __YEAS__ · Nay/No: __NAYS__ · Not Voting: __NV__ · Present: __PRESENT__
Voted Against GOP Majority
__VA_GOP__ __VA_GOP_PCT__%
of __PARTISAN_R__ votes where R majority took a side
Voted Against Dem Majority
__VA_DEM__ __VA_DEM_PCT__%
of __PARTISAN_D__ votes where D majority took a side
Blocked Dem-Backed
__BLK_DEM__ __BLK_DEM_PCT__%
Massie Nay + D wanted Yea + measure failed
Blocked GOP-Backed
__BLK_REP__ __BLK_REP_PCT__%
Massie Nay + R wanted Yea + measure failed
Lone Wolf Defections
__LONE__ __LONE_PCT__%
Against R majority w/ ≤5 fellow GOP defectors

Alignment Classification

Massie's Vote Distribution

Voted With vs. Against — by Party Majority

Blocking Wins

Alignment Over Time (monthly)

All Votes (filterable)

Yr#Date BillQuestionDescription ResultMassie R YeaR Nay D YeaD Nay AlignmentBlocked
Click column headers to sort. Showing 0 rows.
""" def pct(n, d): return f"{(n/d*100):.1f}" if d else "0.0" partisan_r = data["voted_with_gop"] + data["voted_against_gop"] partisan_d = data["voted_with_dem"] + data["voted_against_dem"] html = (HTML .replace("__TOTAL__", str(data["total"])) .replace("__VOTING__", str(data["voting"])) .replace("__VOTING_PCT__", pct(data["voting"], data["total"])) .replace("__YEAS__", str(data["yeas"])) .replace("__NAYS__", str(data["nays"])) .replace("__NV__", str(data["nv"])) .replace("__PRESENT__", str(data["present"])) .replace("__VA_GOP__", str(data["voted_against_gop"])) .replace("__VA_GOP_PCT__", pct(data["voted_against_gop"], partisan_r)) .replace("__VA_DEM__", str(data["voted_against_dem"])) .replace("__VA_DEM_PCT__", pct(data["voted_against_dem"], partisan_d)) .replace("__PARTISAN_R__", str(partisan_r)) .replace("__PARTISAN_D__", str(partisan_d)) .replace("__BLK_DEM__", str(data["blocked_dem_count"])) .replace("__BLK_DEM_PCT__", pct(data["blocked_dem_count"], data["total"])) .replace("__BLK_REP__", str(data["blocked_rep_count"])) .replace("__BLK_REP_PCT__", pct(data["blocked_rep_count"], data["total"])) .replace("__LONE__", str(data["lone_wolf"])) .replace("__LONE_PCT__", pct(data["lone_wolf"], data["voting"])) .replace("__DATA__", json.dumps(data)) ) with open("/home/user/polisci/dashboard.html", "w") as f: f.write(html) print(f"Wrote dashboard.html: {len(html)} bytes, {data['lone_wolf']} lone-wolf votes")