Files
cali_scraper/scraper/dashboard/DASHBOARD.md
T
2026-09-06 10:18:26 -07:00

113 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.