`.
---
## 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