Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Legislation Dashboard

Open the dashboard →

A static, client-side dashboard over bill data from every tracked jurisdiction, filterable by state/territory, session, chamber, topic tag, and free-text search. It is plain HTML/JS with no external dependencies, deployed as part of this docs site by the existing GitHub Pages workflow.

What it shows

  • Stat tiles — bill count, jurisdictions, sessions, and share of bills with topic tags
  • Bills by jurisdiction and bills by topic bar charts (click a bar to filter)
  • Activity by month — bills by the month of their most recent recorded action
  • Bills table — sortable, with topic chips and links to each bill’s official source

All charts, tiles, and the table re-render against the same filtered slice, so the numbers always agree. A “Data as of” badge under the title shows when the snapshot was built.

Jurisdictions whose upstream scraper repo cloned but published no bills yet are listed as pending under the jurisdiction chart (rather than silently omitted), and appear automatically once their *-legislation repo starts carrying data.

Where the data comes from

The page reads a single data.json produced by scripts/build_dashboard_data.py, which scans cloned govbot dataset repos for bills in either format — govbot’s OCD-files layout (**/bills/<ID>/metadata.json) or raw OpenStates scraper output (_data/<locale>/bill_<uuid>.json) — and joins topic tags from govbot tag output (tags/*.tag.json).

On every Pages deploy (and on a daily 8am UTC schedule), the workflow runs govbot clone all to fetch govbot’s processed dataset (the chn-openstates-files *-legislation repos, in govbot’s OCD-files layout), tags the bills with govbot’s embedding model, and rebuilds data.json from all of them, so the published dashboard covers every tracked jurisdiction. (The embedding tagger needs that layout — govbot logs/govbot tag read the per-bill logs/ structure, which the raw OpenStates scraper repos don’t have.) The topic taxonomy lives in scripts/govbot-dashboard.yml; scripts/dashboard_tags.json mirrors the same topic names as a keyword fallback for any bill the embedding tagger didn’t reach.

Tagging is incremental: after the first full pass, each run re-embeds only bills that are new or whose text changed, so the daily build stays fast. This is powered by two caches (a shared copy of the ~90MB embedding model, and a per-repo ledger + snapshot of the tag files) plus scripts/filter_new_bills.py, which drops unchanged bills before they reach the tagger. Editing scripts/govbot-dashboard.yml changes the cache key and triggers one full re-tag. Every stage degrades gracefully: a failed tagger falls back to keyword tags, and a failed data build falls back to the committed sample data rather than breaking the docs site.

The committed sample data is built from the offline mocks (actions/govbot/mocks/govbot_data — Wyoming and Guam), with demo topics derived from the keyword definitions in scripts/dashboard_tags.json (the same shape as the tags: section of govbot.yml, keyword-only mode):

python3 scripts/build_dashboard_data.py \
  --govbot-dir actions/govbot/mocks/govbot_data \
  --tags-config scripts/dashboard_tags.json \
  --output docs/src/dashboard/data.json

Regenerating locally with real data

govbot clone all                       # clone the dataset repos (~/govbot_data/repos)
cp scripts/govbot-dashboard.yml govbot.yml   # tag definitions (govbot tag reads ./govbot.yml)
govbot logs --join bill --limit none | govbot tag --overwrite   # score bills (embedding mode)
python3 scripts/build_dashboard_data.py --output docs/src/dashboard/data.json

govbot tag downloads the embedding model (all-MiniLM-L6-v2) to ./govbot_data on first use and writes tags/*.tag.json next to each session’s bills/. When those files exist for a session they take precedence over the keyword fallback; a bill gets a tag when its final_score meets the tag’s configured threshold. Commit the regenerated data.json and the Pages workflow publishes it with the rest of the docs.

The Pages workflow does the same across all repos but per-repo (so tags land inside each clone) and incrementally — see scripts/tag_dashboard_repo.sh and scripts/filter_new_bills.py.

Committee Hearings & Witness Slips (separate page)

A second page, Committee Hearings & Witness Slips →, sits alongside this one (a tab bar at the top links the two). It lists upcoming committee hearings where the public can weigh in — live for Illinois & Washington, plus a how-to-participate directory for every state. It is an entirely separate pipeline from the bill data: hearings are live artifacts each statehouse publishes on its own endpoint (not OpenStates), scraped by actions/scrape-hearings into docs/src/dashboard/hearings.json + an RSS feed hearings.xml (schema schemas/govbot.hearings.schema.json), and the state directory is a static docs/src/dashboard/participation.json (schema schemas/govbot.participation.schema.json). The Pages deploy rebuilds the hearings feed on the same twice-daily schedule. See that page’s own notes and actions/scrape-hearings/README.md for details.

The hearings page also uses govbot’s own bill data: at build time each hearing’s bills are cross-referenced against data.json (govbot tracks Illinois & Washington bills), and where a match is found the bill shows its govbot title and topic tags plus a “via govbot ↗” link that opens that bill in the Legislation Dashboard tab (index.html#q=<bill>).