113 lines
4.2 KiB
Markdown
113 lines
4.2 KiB
Markdown
# Legislative Dashboard
|
||
|
||
This is the anonymous + logged-in dashboard for `cali_scraper`, implementing
|
||
spec section 5 (design documents: `design/API_CONTRACT.md`,
|
||
`design/DESIGN_DASHBOARD.md`). It is served by a stdlib `http.server`,
|
||
backed by the SQLite relational DB and the tag-cloud JSON, and produces the
|
||
frozen API-contract response shapes.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
scraper/dashboard/
|
||
__init__.py # package docstring + version
|
||
ranking.py # pure I/O-free float-to-top scoring (tested in isolation)
|
||
context.py # RelevanceContext from user prefs
|
||
data.py # read-only SQLite access, tag enrichment, committee lookup
|
||
prefs.py # JSON prefs store (watch/topic toggles, dev auth)
|
||
resolve.py # ZIP -> district two-tier resolution
|
||
api.py # API_CONTRACT handlers (no sockets) + 6 empty states
|
||
server.py # http.server ThreadingHTTPServer + static file routing
|
||
dashboard_cli.py # guarded CLI entry via cli.py subcommand registration
|
||
static/
|
||
index.html # frontend shell
|
||
style.css # visual system
|
||
app.js # fetch client, anon/personal rendering, ZIP resolve, watch/opt-in
|
||
```
|
||
|
||
### How it fits the CLI
|
||
|
||
`scraper/cli.py` wires in the dashboard with the same guarded pattern as
|
||
`analyze_cli` and `relate_cli`:
|
||
|
||
```python
|
||
try:
|
||
from .dashboard import dashboard_cli as _dcli
|
||
_dcli.register(sub)
|
||
except Exception:
|
||
pass
|
||
```
|
||
|
||
A broken or missing dashboard module never breaks `parse`, `correlate`, or
|
||
`analyze`.
|
||
|
||
### Run it
|
||
|
||
```bash
|
||
python3 -m scraper dashboard --port 8733
|
||
# browse http://127.0.0.1:8733/
|
||
```
|
||
|
||
## API
|
||
|
||
| Method | Path | Auth | Purpose |
|
||
|--------|------------------------|-------|--------------------------------------|
|
||
| GET | `/api/v1/bills` | any | Bill list, personal / anonymous |
|
||
| GET | `/api/v1/tags` | any | Tag cloud |
|
||
| GET | `/api/v1/legislators` | any | Legislators |
|
||
| GET | `/api/v1/districts` | any | Districts by chamber/zip |
|
||
| GET | `/api/v1/resolve` | any | ZIP -> district resolution |
|
||
| POST | `/api/v1/prefs` | auth | Update user prefs |
|
||
| POST | `/api/v1/auth/register`| none | Dev auth placeholder |
|
||
| POST | `/api/v1/prefs/watch` | auth | Add/remove watched legislator |
|
||
| POST | `/api/v1/prefs/topics` | auth | Add/remove opted-in topic |
|
||
|
||
### Float-to-top ranking
|
||
|
||
Personal view applies a pure scoring function (`ranking.py`) over the bill
|
||
list:
|
||
|
||
- **Watched-legislator match (author)** = +3
|
||
- **Watched-legislator's committee match** = +2
|
||
- **Opted-in tag match** = +2 × (number of matched tags)
|
||
- **Home-chamber match** = +1 (optional)
|
||
- Bills with score > 0 are moved to the front with a "Relevant to you"
|
||
marker and a "Floated: ..." reason line
|
||
- Zero-scored bills remain in their date-grouped order below
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
python3 -m pytest tests/test_dashboard_ranking.py tests/test_dashboard_api.py
|
||
```
|
||
|
||
32 tests (12 ranking + 20 API), all passing. Pure ranking logic is tested in
|
||
isolation; API handlers are tested with no sockets via the
|
||
`handler(req) -> (status, body)` pattern; a live `ThreadingHTTPServer` smoke test
|
||
confirms static + API endpoints serve.
|
||
|
||
## DASHBOARD.md
|
||
|
||
This document. The full UX specification is `design/DESIGN_DASHBOARD.md`
|
||
and the frozen API contract is `design/API_CONTRACT.md`.
|
||
|
||
## Renders
|
||
|
||
- `out/dashboard_render_anon.png` — anonymous dashboard
|
||
- `out/dashboard_render_personal.png` — personalized dashboard (ZIP 90210,
|
||
watch Schiavo, opt-in budget topic)
|
||
|
||
Both are data-faithful snapshots from the live API, rendered via macOS
|
||
`qlmanage` (no headless browser available in this environment; the browser
|
||
remote-debugging permission popup is a user-level security dialog that
|
||
cannot be auto-approved).
|
||
|
||
## Known limitations / next steps
|
||
|
||
- `out/tag_cloud.json` must exist for tags endpoint (118 tags as of
|
||
2026-09-06).
|
||
- `0 committee_members` roster — committee lookup degrades gracefully when
|
||
no roster data is supplied.
|
||
- Dev auth is a JSON-file local account, not production auth.
|
||
- ZIP -> district uses a documented demo dataset until roster ETL lands.
|