# Project: 119th Congress Voting Dashboard _Written post-hoc by the PM agent (2026-05-24) to codify shipped reality through Phase 7 + Rounds A–D. The implementation plan that drove the initial build lives at `research/PLAN.md`. Only the PM agent may edit this file._ --- ## 1. Project Overview A static-file, framework-free interactive dashboard that surfaces member-level roll-call voting behavior for every member of the 119th US Congress. Eight standalone legacy HTML files (Massie, Khanna, AOC, Omar, MTG, Jordan, Donalds, Graham) were replaced with a unified single-page application covering all ~552 seated members, served from a single build artifact at `results/119/`. The project is a personal research and analysis tool, built and operated by a solo hobbyist analyst. It is designed for the analyst's own use and for sharing with third-party hosts who can embed the artifact via iframe or inline `
`. --- ## 2. Target Users | User | Description | |------|-------------| | Primary | The analyst themselves — personal research, exploration, and editorial work on congressional voting patterns. | | Secondary | Third-party hosts who embed the artifact via iframe or inline `
` into their own pages. Embedding is fully supported by design. | --- ## 3. Core Value Proposition Given any member of the 119th Congress, instantly render a reproducible, citation-quality breakdown of how their roll-call votes aligned with or diverged from each party's majority position — without a page reload, without a server, and without any external network dependency after the initial page load. --- ## 4. Scope of Work ### In Scope (as built through Phase 7 + Rounds A–D) | Feature | Description | Priority | |---------|-------------|----------| | Single-member SPA dashboard | `app.html` — searchable typeahead + sidebar filters (chamber, party, state); on selection, fetches per-member JSON and mutates Chart.js datasets in place without teardown | Must Have | | 5 core charts | Vote distribution, alignment doughnut, blocking bars, alignment-over-time line, with/against stacked bar — created once at page init; fixed-height frames (340px desktop / 280px mobile) with `.chart-canvas-wrap` to prevent Chart.js infinite-growth bug | Must Have | | 8 KPI tiles | Vote counts, alignment rates, plus "Voted With GOP" and "Voted With Dem" tiles (up from 6 at Phase 6) | Must Have | | Sortable/filterable vote table | Per-vote rows; all upstream strings rendered via `textContent` (no `innerHTML`) | Must Have | | URL deep-linking | `pushState` on member selection; `replaceState` on filter typing; reload restores state | Must Have | | localStorage persistence | Last-selected member persisted as `polisci:v119:lastMember`; validated against manifest on read | Could Have (shipped) | | Comparison view | `compare.html` — multi-select pills (up to 6 members); 5 overlay charts; shareable `?ids=` URL | Must Have | | All-member coverage | `enrich_roster.py` pulls the complete 119th roster from Congress.gov API; members with zero roll-call votes receive a `served_partial` banner | Must Have | | Framework-free embedding | Three modes: standalone, iframe (`sandbox="allow-scripts allow-same-origin"`), inline `
` | Must Have | | Zero CDN dependencies | Chart.js 4.4.0 + SortableJS 1.15.2 vendored locally in `template/vendor/`; no runtime external requests | Must Have | | Parameterized pipeline | All build scripts accept `--congress N`; generalizes to future Congresses without code changes | Must Have | | Reproducibility metadata | Every per-member JSON includes a `_meta` block: schema version, pipeline version, classifier hash, data snapshot date, source XML counts | Must Have | | Test suite | `pytest tests/test_analyze.py` with frozen XML fixtures covering partisan, bipartisan, absent-member, and failed-blocking cases | Must Have | | Rankings page | `ranking.html` + `ranking.js` — 14 sortable metrics across House/Senate; party + chamber filters; row-click opens member dashboard in new tab; shareable URL state; driven by per-member KPI dict (`k` field) in the manifest (one fetch only) | Must Have | | Structural member banners | `app.js` `renderNote()` detects and renders contextual banners by priority: delegate > unseated (member-elect) > died > replaced_by > replaces > served_partial > none; predecessor/successor names rendered as in-app links via `state.membersById` | Must Have | | Delegate detection | 6 territorial House delegates (AS/DC/GU/MP/PR/VI) detected by USPS code; `parse.py` corrects "XX" state in vote XMLs to real territory code; `is_delegate` flag in per-member JSON + `dl: true` in manifest; yellow explanatory banner in `app.js` | Must Have | | Replacement linking | 8 House replacement pairs auto-linked in the 119th (e.g. Gaetz→Patronis, Waltz→Fine, Grijalva R.→Grijalva A.); `replaces` / `replaced_by` bioguide refs in per-member JSON | Must Have | ### Out of Scope (Future / Deferred — see `NOTES.md` for full rationale) - **Editorial label rewording** — "Helped Republicans / Blocked Dem-Backed" language kept per user preference; neutral alternatives deferred to v1.1 before any third-party publication (NOTES item 1, compliance Finding 1, High risk) - **Comparison chart trimming** — all 5 overlay charts shipped; reducing the set deferred pending user-research feedback (NOTES item 2) - **Visible caveats panel** — collapsible "How to read this" panel describing methodology limitations deferred to v1.1 (NOTES item 3, compliance Finding 2) - **Full localStorage persistence** — only `lastMember` is persisted; filter state persistence deferred (NOTES item 4) - **120th Congress dry-run** — pipeline is parameterized but cannot be validated end-to-end until 120th data exists (NOTES item 5) - **True own-party-defection series** — `compare.html` uses `Helped Neither` as a proxy; a precise monthly series requires a future `analyze.py` enhancement (NOTES item 6) --- ## 5. Feature Flow **Single-member view (`app.html`):** ``` 1. Page loads → manifest inlined in