Initial commit

This commit is contained in:
kfj001 committed 2026-09-06 10:18:26 -07:00
commit 5f2db08b43
100 files changed
+16985

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
# 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.