govbot
Every U.S. legislature, as data you can clone. govbot turns government updates into git repositories you can analyze, query, and build on β no scraper to maintain, no data platform to pay for.
- π₯ Clone the legislation of 56 jurisdictions in under a minute β every dataset is just a git repo.
- π Tag and summarize bills with private, local models β optimized to run for free on GitHub Actions. No API keys, no per-token bill.
- π Analyze it your way β stream it as JSON Lines through Unix pipes, or load it into DuckDB for SQL across every state at once.
By the numbers
| 56 | jurisdictions covered β all 50 states + Federal + DC + 4 territories |
| 14,474 | distinct federal (Congress) bills, and counting |
| < 1 min | to clone every dataset |
| $0 | cost to tag bills β models run locally on free CI |
What We Offer
The main Govbot dataset covers 56 jurisdictions β all 50 states, the U.S. House & Senate (Federal), DC, and the territories of Puerto Rico, Guam, the U.S. Virgin Islands, and the Northern Mariana Islands β as .json files organized using the Project Open Data catalog format.
The Govbot scrapers update regularly, appending new logs. New bills are then tagged and scored on-device by a private sentence-transformer model (ONNX) with a keyword fallback β small enough to run for free on GitHub Actions, so no bill text ever leaves your pipeline and thereβs no per-token cost. From there, the data can be analyzed with SQL via a DuckDB interface, browsed on our live legislation dashboard, or plugged into applications like:
- Transportation Legislation Bluesky bot β transportation bills nationwide, posted as they move.
- Data Center & AI Legislation Bluesky bot β AI and data-center bills across every jurisdiction.
- WindyCivi, our example website, and an early BlueSky bot built in collaboration with U.S. Representative Hoan Huynh.
Why govbot?
Why donβt we pay attention to our representatives between elections?
Legislative data is hard to parse, track, and organize. Activists, concerned citizens, and the curious may not have the time, resources, or expertise to build out duplicative tech stacks. Existing solutions may be limited by the willingness of organizations and companies to continue to run and host them - such as in the case of Googleβs Civic Information API, which was shut down earlier this year. What would a decentralized, open-source legislative data solution look like?
The Govbot teamβs goal is to bridge this gap - building the framework for the building and use of federated, open-source, non-profit legislative data. Built as a Chi Hack Night Breakout Group, the project includes an open-source, simplified, and expanded version of OpenStatesβ data on state and federal legislation, as well as example applications.
How Do I Use It?
1. Install
sh -c "$(curl -fsSL https://raw.githubusercontent.com/chihacknight/govbot/main/actions/govbot/scripts/install-nightly.sh)"
2. Run govbot
govbot
Thatβs it. If no govbot.yml exists, an interactive wizard walks you through setup:
- Sources - Choose all 56 jurisdictions or pick specific ones
- Tags - Start with an example tag, or get an AI prompt you can copy-paste to create your own
- Publishing - RSS feeds configured automatically
The wizard creates govbot.yml, .gitignore, and a GitHub Actions workflow.
3. Run the pipeline
Once set up, running govbot again executes the full pipeline:
- Clones/updates legislation repositories
- Tags bills based on your tag definitions
- Generates RSS feeds in the
docs/directory
Other Commands
govbot clone all # download all state legislation datasets
govbot clone il ca ny # download specific states
govbot logs # stream legislative activity as JSON Lines
govbot logs | govbot tag # process and tag data
govbot build # generate RSS feeds
govbot load # load bill metadata into DuckDB database
govbot delete all # remove all downloaded data
govbot update # update govbot to latest version
govbot --help # see all commands and options
Dataset Key:
- π: the localeβs data received updates since your last cloning
- β : the data youβve cloned is up-to-date with the most current version
- π: the data is currently being updated
- β: the data is not currently accessible
Querying in SQL using DuckDB
You can query the data using SQL, via DuckDB, which creates a simiulated database from the .json log files. See DUCKDB.md for more details.
Running Queries in the Command Line
-- Load JSON extension
INSTALL json;
LOAD json;
-- Query all bill metadata
SELECT *
FROM read_json_auto('~/govbot_data/repos/**/bills/*/metadata.json')
LIMIT 10;
Additional Commands, and Querying via the Web UI
Additional examples of commands, and setup for the web UI, can be found below:
# Load all data into a database (default: govbot.duckdb)
govbot load
# Or specify a custom database file
govbot load --database my-bills.duckdb
# With memory limit and thread settings
govbot load --memory-limit 32GB --threads 8
# Open in DuckDB UI (opens in your browser)
duckdb --ui govbot.duckdb
Helper Scripts
# Run example queries
./duckdb-query.sh examples/duckdb-example.sql
Contributing & Testing
Prerequisites
Folks looking to contirbute should have knowledge of Rust: just. just setup to start, and then just govbot ... to develop the cli.
The following should also be installed:
- Rust & Cargo: Install the Rust Toolchain
- Just: Install the task runner:
cargo install just
Development Workflow
Use just govbot ... as your cli βdevβ environment.
Other Useful Commands
just- See all available tasksjust test- Run all testsjust review- Review snapshot test changesjust mocks [LOCALES...]- Update mock data for testing
We build snapshots off examples. Add examples to make a test.
Advanced
GOVBOT_REPO_URL_TEMPLATE="https://gitsite.com/org/{locale}.git" govbot ...
Project History
The Govbot project began in 2022, with a vision to create a destination for simplified, summarized updates on legislative action, with the ability to follow or filter for certain legislative topics. The result was the initial Windy Civi app, and website, launched in beta in 2024.
While building the solution, the team began to consider the limitations of a centrally-managed data source and platform, versus one that could be decentralized, that was open-source, and that allowed for exploration and use of the data in ways beyond initial designs.
Our vision now has pivoted to building that data set, as well as building sample applications and solutions to ensure that government accountability can be accessible to all.
FAQs
Can I See The Repo?
Yes! Our main repo can be found here. The repo that is being used to run and store the data - the βtoolkitβ repo - can be found here.
How Is The Data Structured?
You an find the file format structure and .json schema in the readme.md located here.
How Do I Clone This Data?
Each locale is scaped using a GitHub Actions tempate that is defined and explained in detail here. You can follow this template to create a new repository of locale data.
To help manage multiple pipelines or locales, look at our pipeline manager documentation
How Can I Stay Updated, Or Get In Touch?
You can stay updated by following our work at Chi Hack Night, as well as on the related Slack (see below). You can also follow our commits and updates on Github and this Docs page,
You can message us on the Chi Hack Night Slack - we have our own channel.
Legislation 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>).
State Status Reference
One row per jurisdiction, covering scrape/extraction health, session timing, and bill-text format β the operational data needed to decide when a scraper should be turned on or off, and whatβs currently broken vs. just out of session.
This is a living document β most rows below are placeholders (TBD) to be filled in over
time, not a claim that data is missing or wrong. The columns that are already fully populated
(Machine Readable Bill Text) come from bill-format-audit.md (last updated 2026-07-02, archived
2026-07-24 β no longer in the repo, this table is now the canonical copy of that data).
Why this exists
Turning scrapers on/off by session is already automatable in principle: each stateβs
chn-openstates-scrape.yml config has a template field that flips between openstates-scrape
(active) and openstates-scrape-paused (paused). An automated version of this
(check-sessions.py, driven by the OpenStates APIβs own session dates) is currently disabled
β its dates were repeatedly wrong and caused false βfrozenβ alarms. Until thatβs trustworthy
again, session dates here should be manually verified, not copied from the API, and the
βShould beβ column is the actual signal to act on: does the verified session status match what
the config is currently set to?
Status code legend
Reuses the failure taxonomy already produced by scrape.sh / scrape-summary.json and
referenced in scraper-status.md, so a row here can be copy-pasted straight from an automated
run summary instead of re-classified into a separate scheme.
| Code | Meaning |
|---|---|
| β | Working, no known issue |
N1/N3 | Active block (connection refused / reset) |
N2 | Connectivity (timeout, connection aborted) |
N4 | DNS failure |
H1 | Active block (HTTP 403) |
H2 | Auth failure (401) |
H3 | Rate limited (429) |
H4 | Server down (503) |
S1/S2 | Out of session (soft failure β expected, not broken) |
S3 | Session config mismatch |
S4/S5 | Site structure changed (upstream scraper needs a fix) |
S6 | OCD validation failure |
P1 | Shrinking output β fresh scrape produced fewer files than committed; guard refused to overwrite |
UNKNOWN | Failed, cause not yet classified |
TBD | Not yet checked |
Reference table
| State | Current Session | Session Dates (verified) | Config | Should Be | Scraper | Text-Extraction | Machine-Readable Bill Text | Bill Count (current session) | Last Verified / Notes |
|---|---|---|---|---|---|---|---|---|---|
| AK | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| AL | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| AR | TBD | TBD | TBD | TBD | TBD | TBD | β (no bills yet as of audit) | TBD | β |
| AZ | TBD | TBD | TBD | TBD | β | π running | TBD | 2,190 | Fixed 2026-07-24 (--fastmode cache poisoning, not the old cookie bug) β first successful scrape ever, live via PR #5742. See tamara-notes/scraper-status/not-working.md for the full writeup |
| CA | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| CO | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| CT | TBD | TBD | TBD | TBD | P1 | TBD | β (pdf only) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). Self-hosted required (Azure IP block on FTP server) |
| DC | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| DE | TBD | TBD | TBD | TBD | β | TBD | β (pdf, text/html) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| FL | 2026 Regular (+ 2026F special) | TBD | Self-hosted | Active | π§ in progress | TBD | β (pdf only) | TBD | 2026-07-23/24: found two distinct bugs β flhouse.gov bot detection (PR #5724, issue #1386) and a separate missing-timeout hang on the same host. Both fixes pushed, awaiting maintainer review. Self-hosted required; tinyproxy path untested until amd64 image fix lands. |
| GA | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | β |
| GU | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| HI | TBD | TBD | TBD | TBD | β | TBD | β (no bills yet as of audit) | TBD | WAF block (Cloudflare) per bill-format-audit (Scraper β per 2026-07-21 audit) |
| IA | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| ID | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| IL | TBD | TBD | TBD | TBD | β | TBD | β (pdf, text/html) | TBD | Self-hosted required (Azure IPs served different content, broke title xpath) (Scraper β per 2026-07-21 audit) |
| IN | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| KS | TBD | TBD | TBD | TBD | β | TBD | β (pdf, text/html) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| KY | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| LA | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| MA | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | Self-hosted required (malegislature.gov blocks Azure); known runner-uptime gaps have caused missed nights |
| MD | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| ME | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| MI | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf, text/html) | TBD | Fails on every hosting path β legislature.mi.gov doesnβt serve its full TLS cert chain, not a proxy/hosting issue |
| MN | TBD | TBD | TBD | TBD | TBD | TBD | β (text/html) | TBD | β |
| MO | TBD | TBD | TBD | TBD | P1 | TBD | β (pdf only) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). |
| MP | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | Blank-title OCD validation crash (S6), fix identified, not yet filed upstream |
| MS | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| MT | 2025 (only active session upstream β worth double-checking this is still correct given MT meets biennially) | TBD | Not self-hosted (plain GitHub-hosted) | TBD | P1 (disputed) | π§ fixed 2026-07-24 | β (no version links) | ~4,495 (disputed β see notes) | 2026-07-23/24: shrink-guard blocking scrapes since 07-21; investigated at length, real duplication confirmed (~1-2%) but doesnβt explain the gap between the committed baseline (~6,900 unique estimated) and format/fresh-scrape output (4,495) β cause still open. Separately, fixed the org-wide broken extract-text restart mechanism (PAT_WORKFLOW_TRIGGER β GitHub App token, matching scrapeβformat pattern); applied to this repo only so far, worth rolling out to all states. |
| NC | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Self-hosted required β was NOT an IP block, was frozen ~7 months for a different reason, see scraper-health.md (Scraper β per 2026-07-21 audit) |
| ND | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| NE | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | β |
| NH | TBD | TBD | TBD | TBD | TBD | TBD | β (no version links) | TBD | Site blocks scraping 6am-9pm ET β schedule around this, not a real block |
| NJ | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| NM | TBD | TBD | TBD | TBD | TBD | TBD | β (no bills yet as of audit) | TBD | Intermittent FTP server issue (confirmed via direct curl testing), not a permanent dead end, not hosting-related |
| NV | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | Meets biennially, no regular session until 2027 β low bill count is expected, not broken |
| NY | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| OH | TBD | TBD | TBD | TBD | P1 | TBD | β (pdf, text/html) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). |
| OK | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| OR | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | β |
| PA | TBD | TBD | TBD | TBD | P1 | TBD | β (pdf, text/html, msword) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). |
| PR | TBD | TBD | TBD | TBD | P1 | TBD | β (msword) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). |
| RI | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| SC | TBD | TBD | TBD | TBD | β | TBD | β (text/html, docx) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| SD | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| TN | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| TX | TBD | TBD | TBD | TBD | β | TBD | β (text/html, pdf) | TBD | Blocks GitHub Actions IP ranges at the firewall β self-hosted only, see tx-backfill-runbook.md (Scraper β
per 2026-07-21 audit) |
| USA | TBD | TBD | TBD | TBD | P1 | TBD | β (text/xml, pdf) | TBD | Hit shrink-guard 2026-07-21 β duplicate bill objects under different UUIDs (1.4-2.9x inflation), single-session not multi-session. Cleared and re-dispatched same day per scraper-status.md; not independently re-verified since (see MT for a case where a similar βfixedβ claim didnβt fully hold up). |
| UT | TBD | TBD | TBD | TBD | β | TBD | β (text/xml, pdf) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| VA | TBD | TBD | TBD | TBD | β | TBD | β (no bills yet as of audit) | TBD | Workflow disabled since 2026-04-01, reason unclear β worth investigating (Scraper β per 2026-07-21 audit) |
| VI | TBD | TBD | TBD | TBD | TBD | TBD | β (pdf only) | TBD | Source server itself offline (billtracking.legvi.org:8082) β not a code problem, fails on every hosting path |
| VT | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| WA | TBD | TBD | TBD | TBD | TBD | TBD | β (no version links) | TBD | β |
| WI | TBD | TBD | TBD | TBD | β | TBD | β (pdf, text/html) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
| WV | TBD | TBD | TBD | TBD | β | TBD | β (text/html) | TBD | Self-hosted required (same Azure-block pattern as IL/CT/HI/MA/TN) (Scraper β per 2026-07-21 audit) |
| WY | TBD | TBD | TBD | TBD | β | TBD | β (pdf only) | TBD | Scraper β per 2026-07-21 full 56-state audit (scraper-status.md) β not re-verified since; other columns still unchecked |
Hosting Path History (audited 2026-07-24)
For every state without a confirmed-healthy scraper, pulled the last 10 scrape workflow runs and
determined the actual hosting path each one used (not what config claims β verified directly
from each runβs job log: presence of a Hosted Compute Agent/Azure Region block means
GitHub-hosted, USE_PROXY: true within that splits Tinyproxy from plain; its absence entirely
means it ran on the real MacBookPro runner). cancelled runs are excluded entirely β those mean
the runner/proxy was never available to pick up the job at all, not that a path was tried and
failed. Only non-fl/mt states are P1-noted from the same 07-21 audit as the main table above.
| State | Paths Tried | Clean Runs (per path) | Best Path So Far | Notes |
|---|---|---|---|---|
| AR | Tinyproxy, MacBookPro | Tinyproxy 6/6, MacBookPro 0/1 | Tinyproxy | MacBookPro has only one real (non-cancelled) data point, and it failed β not enough to judge that path yet |
| AZ | Tinyproxy, MacBookPro, GitHub-hosted-plain | 0/6, 0/1, 0/2 pre-fix; β clean on Tinyproxy post-fix | Tinyproxy | Not a hosting issue at all β was --fastmode cache poisoning, identical failure on every path was the tell. Fixed 2026-07-24, PR #5742 |
| CT | Tinyproxy, MacBookPro, GitHub-hosted-plain | Tinyproxy 4/5, MacBookPro 2/3, GitHub-hosted-plain 0/1 | Tinyproxy or MacBookPro | GitHub-hosted-plainβs one real data point was S1_OUT_OF_SESSION β a soft/expected failure, not evidence the path itself is broken |
| FL | Tinyproxy, MacBookPro | 0/6, 0/3 | None confirmed yet | See dedicated FL section above β two distinct bugs found and fixed 2026-07-23/24, awaiting merge |
| GA | GitHub-hosted-plain only | 2/10 (+4 no clear signal) | Only path tried | Never tried Tinyproxy or MacBookPro |
| MA | MacBookPro only (2 real runs) | 0/2 | Neither confirmed | No real Tinyproxy data at all; both real MacBookPro runs failed. Known runner-uptime gaps explain most of this stateβs cancelled runs |
| MI | Tinyproxy, MacBookPro | 0/6, 0/2 | None β fails everywhere | Root cause confirmed unrelated to hosting: legislature.mi.gov doesnβt serve its full TLS cert chain, fails identically on every path including genuine self-hosted |
| MN | GitHub-hosted-plain only | 5/10 (+3 no clear signal) | Only path tried | Never tried Tinyproxy or MacBookPro |
| MO | GitHub-hosted-plain only | 4/9 (+2 no clear signal) | Only path tried | Repeated P1 shrink-guard hits, not a hosting problem; MacBookProβs only entry was cancelled (discarded) |
| MP | GitHub-hosted-plain only | 0/10 | None β fails every time | Never tried Tinyproxy or MacBookPro. S6_VALIDATION/H3_RATE_LIMITED β known blank-title crash + rate limiting |
| MT | GitHub-hosted-plain, MacBookPro (1 real run) | GitHub-hosted-plain 3/9, MacBookPro 1/1 | MacBookPro (only one data point, but clean) | GitHub-hosted-plain repeatedly hits the disputed P1 shrink-guard β see tamara-notes/state-problems.md for full MT writeup |
| NE | Tinyproxy, MacBookPro | 0/5, 0/3 | None confirmed yet | Both paths failing β Tinyproxy hits shrink-guard/rate-limit, MacBookProβs 3 real runs all failed outright, worth investigating |
| NH | GitHub-hosted-plain, MacBookPro (1 real run) | 0/8, 0/1 | None β fails everywhere | H3_RATE_LIMITED on both paths β known site blocks scraping 6am-9pm ET, likely a scheduling/timing issue rather than hosting |
| NM | Tinyproxy, MacBookPro | 0/6, 0/2 | None confirmed yet | Known intermittent FTP server issue (confirmed via direct curl testing), not hosting-related |
| NV | Tinyproxy, MacBookPro (1 real run) | Tinyproxy 5/5, MacBookPro 0/1 | Tinyproxy | Strong Tinyproxy track record; MacBookProβs one real run had no clear success/fail signal |
| OH | Tinyproxy, MacBookPro | Tinyproxy 2/5, MacBookPro 1/2 | Mixed, no clear winner | Both paths hit shrink-guard/failures sometimes |
| OR | GitHub-hosted-plain only | 6/10 (+4 no clear signal) | Only path tried | Good track record on the only path tried |
| PA | Tinyproxy, MacBookPro | Tinyproxy 2/5 (3 unclear), MacBookPro 1/2 | Mixed, no clear winner | Known duplicate-cruft/shrink-guard history, see scraper-status.md |
| PR | GitHub-hosted-plain, MacBookPro (1 real run) | GitHub-hosted-plain 2/9, MacBookPro 1/1 | MacBookPro (only one data point, but clean) | GitHub-hosted-plain repeatedly hits P1 shrink-guard |
| USA | Tinyproxy, MacBookPro | Tinyproxy 3/5, MacBookPro 0/2 | Tinyproxy | MacBookProβs 2 real runs both failed outright |
| VI | Tinyproxy, MacBookPro (1 real run) | 0/6, 0/1 | None β fails everywhere | Source server itself offline (billtracking.legvi.org:8082) β confirmed not a hosting problem |
| WA | GitHub-hosted-plain only | 5/10 (+4 no clear signal) | Only path tried | Never tried Tinyproxy or MacBookPro |
Related docs
bill-format-audit.md,scraper-health.md,error-tracking.mdβ archived 2026-07-24 (no longer in the repo); this doc andtamara-notes/state-problems.mdare their replacementsactions/pipeline-manager/chn-openstates-scrape.ymlβ the actual per-state config this doc should stay consistent with (runner,template,scrape_cron)actions/pipeline-manager/check-sessions.pyβ the disabled session-pause automation this docβs session columns are meant to eventually feed
Let The People Take Back Their Government
Our 2024 efforts taught us a few things:
- People donβt want to download another app or use another portal.
- Getting bill data is really hard, and usually involves using private APIs.
- Creating AI summaries and topics should be controlled by organizations/individuals.
As such, our effort has become 2-fold
Contributors
- Austin McLaughlin
- Brandon
- Daniel Cappy
- Edwin Cuevas
- Jeff Leverenz
- Sartaj Chowdhury
- S.Murakami
- Tamara Dowis
2025 Deck β Democratic Infrastructure
marp: true theme: default paginate: true
Govbot
Federated, open-source legislative data for everyone
Overview
The Problem Our Solultion What We Offer Features Setup + Core Functions
The Problem
Why donβt we pay attention to our representatives between elections?
Legislative data is hard to parse, track, and organize. Activists, concerned citizens, and the curious may not have the time, resources, or expertise to build out duplicative tech stacks.
The Problem (cont.)
Existing solutions may be limited by the willingness of organizations and companies to continue to run and host them - such as in the case of Googleβs Civic Information API, which was shut down earlier this year.
What would a decentralized, open-source legislative data solution look like?
Our Solution
The Govbot teamβs goal is to bridge this gap - building the framework for federated, open-source, non-profit legislative data.
Built as a Chi Hack Night Breakout Group, this project offers frameworks and tools built on top of OpenStatesβ data on state and federal legislation.
What We Offer
The main Govbot dataset currently includes legislative updates from:
- the U.S. House & Senate
- Legislatures from all 50 states
- Legislatures from U.S. territories
Data is organized as .json files using the Project Open Data catalog format, scraped and appended regularly.
Features
- A decentralized, regularly updating, legislative data catalog
- AI-powered, topic-based tagging and summaries, customized using .yml
- SQL querying via DuckDB interface
- Example applications, like custom websites (see our demo WindyCivi site), and social media bots (see our BlueSky bot, made in collaboration with U.S. Representative Hoan Huynh)
Setup
You can download the setup script via one-line install, from our GitHub repository:
sh -c β$(curl -fsSL https://raw.githubusercontent.com/chihacknight/govbot/main/actions/govbot/scripts/install-nightly.sh)
Core Functions
Once installed, you can:
- Clone the entire dataset
- Clone specific items (state, session, or bill)
- Load metadata into a SQL-accessible DuckDB database
Project History
2022: socratic.center
The initial hypothesis: *What if citizens could easily track and understand the bills being voted on?*
civi.social
This experiment helped us understand how citizens wanted to engage with civic data in their existing communities.
myChicago + Jarvis
The goal of Jarvis, our AI-powered assistant, would have been to help users understand legislation through:
- Simplified bill summaries
- Contextual information
- Guided engagement tools
Windy Civi: Full Launch
The goal was to enable citizens to:
- Track bills by topic
- Receive personalized updates
- Connect directly with representatives
Rethinking Our Approach
While building these solutions, we began to ask a critical question:
What are the limitations of a centrally-managed platform?
- Can it scale to serve all communities?
- What happens if we stop maintaining it?
- How can others build on this work?
Our New Vision
Our vision has now pivoted to building the infrastructure itself:
- A decentralized legislative data data catalog
- Reusable frameworks for communities to build their own tools
- Sample applications demonstrating use cases
Our goal: Ensure that government accountability is accessible to all.
Live Demos
Basic Setup + Commands Querying via DuckDB Creating Social Media Bots
Basic Setup + Commands
Install via:
sh -c β$(curl -fsSL https://raw.githubusercontent.com/chihacknight/govbot/main/actions/govbot/scripts/install-nightly.sh)
Once installed, you can download and set up the data using the following commands
govbot # to see help
govbot clone # to show available datasets
govbot clone {{locale}} {{locale}} # download specific items
govbot delete {{locale}} # delete specific items
govbot delete all # delete everything
govbot load # load bill metadata into DuckDB
Querying with DuckDB
First, set up DuckDB, which creates a simulated database from the .json log files:
govbot load #Load all data into a database
govbot load βdatabase my-bills.duckdb #Specify a custom database file
govbot load βmemory-limit 32GB βthreads 8 #With memory limit and thread settings
duckdb βui govbot.duckdb #Open in DuckDB UI (opens in browser)
Once the DuckDB database is created, you can query as normal
β Load JSON extension
INSTALL json;
LOAD json;
β Query all bill metadata
SELECT *
FROM read_json_auto(β~/govbot_data/repos/**/bills/*/metadata.jsonβ)
LIMIT 10;
Creating Social Media Bots
Technical Details
Our Open Civic Data Proposal
Democratizing government data
- What does it mean to democratize government data?
- Today: legislation (with room to expand to courts, agencies, and more)
- To understand the solution, it helps to first understand the problem
The problem
Legislative data is commonly distributed through APIs or large database dumps. These approaches work well for transactional access, but they introduce real limitations when the goal is long-term analysis and accountability.
They make it harder to:
- Perform bulk or historical analysis
- Track changes over time
- Analyze data without running a database server
They also introduce fragility:
- APIs change or disappear
- Long-term access and verification become difficult
Why this matters
- Civic trust
- Research
- Accountability
- Anyone can verify, not just institutions
- A shared source of truth without interpretation baked in
What we built (and why Git)
- File-based structure
- Bills, events, logs
- Deterministic paths to find things
- Built on Git for history, distribution, cheap branching, and broad accessibility
- Aligned with Open States data and Open Civic Data (OCD) identifiers
- Formalized through an Open Civic Data proposal
This design treats the filesystem as the primary interface for civic data.
The OCD proposal (why this matters upstream)
- Makes the model reusable beyond Windy Civi
- Provides shared vocabulary and structure
- Enables other projects to adopt or adapt the approach
Technical challenges and triumphs
- Making transformations deterministic so Git diffs remain meaningful
- Interpreting and triaging state-by-state scraper errors
- Passing data cleanly between CI steps (artifacts, environment variables, Docker parity)
- Designing self-contained log entries that remain analyzable outside their folder context
- Building a βlast seenβ mechanism when upstream sources return full snapshots
- Identifying hard limits: PDF redlines and crossouts remain an open problem
A Dive Into Local AI Tagging
Use two models for two very different roles
- Smart LLM (ChatGPT / Claude / Cursor)
- Human-in-the-loop
- Used during development
- Produces tag configuration
- Small embedding model
- Fully automated
- Used in production
- Categorizes every update
The smart LLM helps write the rules The small model runs them
Step 1: Tag Authoring (Developer Workflow)
A developer sits down with:
- Sample legislative updates
- Court rulings
- Regulatory notices
Using ChatGPT / Claude / Cursor, they prompt:
βCreate a tag config for legislative bill introductions. Include examples, negative examples, and keywords.β
The output is reviewed, edited, and committed like code.
Important Clarification
The βsmartβ LLM is not part of production.
It is used the same way youβd use:
- A code editor
- A linter
- A schema generator
Think of ChatGPT / Claude / Cursor as a tag authoring tool.
What the Smart LLM Actually Does
The smart LLM is used interactively by a developer to:
- Define new tags
- Refine descriptions
- Generate examples and edge cases
- Identify negative examples
- Propose include / exclude keywords
It replaces manual taxonomy writing β not runtime logic.
Whatβs next for the project
- Building relationships with activists and journalists
- Creating + designing customizable tagging templates + a system to share them
- Incorporating Executive Orders, judicial opinions, and other relevant non-legislative documents
- Exploring use cases for the data, such as automated content pipelines
- Add donation data for analysis of legislative priorities and campaign promises
Special thanks to the following contributors:
Sartaj Chowdhury Tamara Dowis Edwin Chalas Cuevas Andrew Dauphinais Emme Kari Douglass Marissa Heffler Sartaj Chowdhury Zach Schoneman Brian Burns
Thank You!
- Chi Hack Night
- Open States
- Open Civic Data community
Building government accountability tools accessible to all
Appendix
Contributing & Testing FAQs
Contributing & Testing
Prerequisites
Knowledge of Rust and the just task runner required.
- Rust & Cargo: Install the Rust Toolchain
- Just: Install the task runner:
cargo install just
Development Workflow
Use just govbot ... as your CLI βdevβ environment.
Useful Commands:
just- See all available tasksjust test- Run all testsjust review- Review snapshot test changesjust mocks [LOCALES...]- Update mock data for testing
Dataset Status Key
- π The localeβs data received updates since your last cloning
- β Your data is up-to-date with the most current version
- π The data is currently being updated
- β The data is not currently accessible
FAQs: Repositories
Can I See The Repo?
- Main repo: windy-civi/windy-civi
- Toolkit repo: chihacknight/govbot
FAQs: Data Structure
How Is The Data Structured?
Find the file format structure and .json schema in the readme.md: DATA_STRUCTURES.md
FAQs: Cloning Data
How Do I Clone This Data?
Each locale is scraped using a GitHub Actions template explained here: README_TEMPLATE.md
To manage multiple pipelines or locales, see our pipeline manager documentation
Stay Connected
How Can I Stay Updated, Or Get In Touch?
- Follow our work at Chi Hack Night
- Check commits and updates on GitHub
- Visit our Docs page
- Join the Chi Hack Night Slack
2025 Bill Blockchain
Open Civic Data Blockchain Proposal
This proposal outlines a decentralized, peer-to-peer system for managing and publishing civic data using a blockchain-like append-only log. Built on the Open Civic Data schema and powered by Git, this architecture enables transparency, tamper-resistance, and flexibility in how public information is stored, shared, and consumed. By treating government data as a series of verifiable, timestamped events, we create an ecosystem where organizations and individuals can build custom civic feeds, automate updates, and uncover hidden dynamics in governanceβall without relying on centralized servers.
Why Use a Hashed Append-Only Log?
-
π Truly Peer-to-Peer
Everyone keeps their own copy of the dataβno central server needed, no extra cost. -
π The Constitution Is Basically a Blockchain
Government changes through amendments. Our log reflects this: permanent, append-only, and transparent. -
π» Highly Tailored Custom Feeds Built With Code + AI
Composable event logs will be easy to filter, tag, and summarize. Orgs can compose those feeds too in order to make highly tailored feeds for publishing. -
π€ Publish Everywhere with Bots
Organizations can automate updates to any number of platforms easily, from Blue Sky Bot Alert posters βthink Reddit replies or Bluesky postsβon top of each other. In addition, we can make tooling to have public RSS feeds that can then be imported by news organizations. -
βοΈ Blockchain without the Cringe or Cost
Blockchain hashes + public key signatures let users verify data themselves without expensive proof algorithms. For IDs, Decentralized Identifiers are the new standard, and interop with Bluesky. -
βοΈ Network Agnostic
Supports everything: peer-to-peer, pub-sub, polling, WebRTC, email, RSS, pushβnotifications, etc. They will all work naturally. -
π± Our App Becomes A Glorified P2P Feed Reader With Civic Tendencies
By being a P2P feed reader with special features around civic data, we simplify the app itself, and allow others to make their own client apps. -
π RSS Feeds Just Work
Feed-based design lets us easily pull in existing sources like Executive Orders or court decisions via RSS, and allows organizations to pull news website feeds. -
βͺ Bonus: Reveal Power Dynamics
Replay legislative logs to uncover hidden patternsβwho votes when, with whom, and under whose influence.
Why Open Civic Data as the Base Schema?
-
π€ Plug Into the Civic Tech Ecosystem
Uses familiar Open Civic Data formats, making it easy to integrate with existing tools and scrapers. -
π Reuse Existing Data
Works with platforms like OpenStates and Councilmatic, giving us access to many data sources.
Why Git for Data Storage?
-
π Folders + Files = Maximum Portability
The most universal data structureβeasy to read, edit, and share across tools and platforms. -
π Git Is Already Peer-to-Peer
Git is built on a distributed log.git pullworks seamlessly in our app and AI workflows. -
π GitHub = Easy Browsing
Markdown rendering and file previews make GitHub a friendly UI for exploring without needing to clone. We can also expose RSS feeds via GHPages. -
π§© Submodules Keep Repos Lean
Git submodules let us split large datasets across repos, so no single repo gets bloated.
Folder Structure + Filename Convention
/open-civic-data-blockchain/
βββ country:us/ # United States
β βββ state:il/ # Illinois state
β β βββ sessions/ # Legislative sessions
β β β βββ ocd-session/country:us/state:il/2023-2024/ # Full OCD session ID
β β β β βββ bills/ # Bills in this session
β β β β β βββ sb1234/ # Senate Bill 1234
β β β β β β βββ logs/ # Event logs folder
β β β β β β β βββ 20240115T123045Z_session_bill_created.json # Initial bill creation in session
β β β β β β β βββ 20240115T123045Z_metadata_created.json # Initial metadata creation
β β β β β β β βββ 20240117T143022Z_metadata_updated.json # Metadata update with field mask
β β β β β β β βββ 20240117T143156Z_sponsor_added.json # Sponsors added
β β β β β β β βββ 20240120T092133Z_version_added.json # Version document added
β β β β β β β βββ 20240130T152247Z_action_added.json # Action recorded
β β β β β β β βββ 20240215T103045Z_doc_added.json # Supporting document added
β β β β β β β βββ 20240315T140011Z_vote_initiated.json # Vote started
β β β β β β β βββ 20240315T143022Z_vote_updated.json # Vote partial results
β β β β β β β βββ 20240315T150537Z_vote_finalized.json # Vote complete
β β β β β β βββ files/ # Raw file storage
β β β β β β βββ bill_introduced.pdf # Original version document
β β β β β β βββ bill_amended.pdf # Amended version document
β β β β β β βββ fiscal_note.pdf # Supporting document
β β β β β βββ hb0789/ # House Bill 789
β β β β β β βββ logs/ # Event logs folder
β β β β β β β βββ 20240118T090023Z_session_bill_created.json # Initial bill creation in session
β β β β β β β βββ 20240118T090023Z_metadata_created.json # Initial metadata creation
β β β β β β β βββ ...
β β β β β β βββ files/ # Raw file storage
β β β β β β βββ ...
β β β β β βββ ...
β β β β βββ events/ # Events for this session
β β β β βββ 2024-04-15-senate-appropriations-hearing.json # Senate committee hearing
β β β β βββ 2024-02-22-house-floor-session.json # House floor session
β β β β βββ ...
β β β βββ ocd-session/country:us/state:il/2021-2022/ # Previous session
β β β β βββ ...
β β β βββ ...
β β βββ events/ # Events not tied to a specific session
β β βββ 2024-07-15-joint-commission-meeting.json # Joint commission meeting
β β βββ 2024-08-20-special-task-force.json # Special task force meeting
β β βββ ...
β βββ state:ca/ # California state
β β βββ ...
β βββ state:ny/ # New York state
β βββ ...
βββ country:ca/ # Canada
βββ ...
Git Architecture
We plan to auto-generate many git repos.
Session Git Repo
This repo should be a blockchain-like append only log, making syncing data as easy as git pull.
Question: what about the files like PDFS? They feel right to keep in here as a copy, but also, would balloon the size of these. Maybe yet another submodule for session files?
/
βββ README.md # Session-specific information
βββ bills/ # Bills in this session
β βββ sb1234/ # Senate Bill 1234
β β βββ logs/ # Event logs folder
β β β βββ 20240115T123045Z_session_bill_created.json
β β β βββ 20240115T123045Z_metadata_created.json
β β β βββ 20240117T143022Z_metadata_updated.json
β β β βββ ...
β β βββ files/ # Raw file storage
β β βββ bill_introduced.pdf
β β βββ bill_amended.pdf
β β βββ fiscal_note.pdf
β βββ hb0789/ # House Bill 789
β β βββ logs/
β β β βββ ...
β β βββ files/
β β βββ ...
β βββ ...
βββ events/ # Events for this session
βββ 2024-04-15-senate-appropriations-hearing.json
βββ 2024-02-22-house-floor-session.json
βββ ...
Locale Git Repo
Overall locale repo (also generated). Contain links to git submodules that have event logs for different sessions/events. Will also contain scripts to rebuild data into Open Civic Data formats.
ocd-blockchain-illinois/
βββ .gitmodules
βββ README.md
βββ scripts/
β βββ scrape.py # Shortcut to directly scrape for this locale
| βββ rebuild.py # To rebuild OCD data from blockchain logs
βββ sessions/
β βββ ocd-blockchain-illinois/ocd-session/country:us/state:il/2023-2024/
β βββ ocd-blockchain-illinois/ocd-session/country:us/state:il/2021-2022/
β βββ ocd-blockchain-illinois/ocd-session/country:us/state:il/2019-2020/
βββ events/
βββ 2022-2026/
βββ 2018-2022/
βββ 2014-2018/
Main Repo
The primary repo (also generated) that people can clone to get all civic data easily via the submodules.
open-civic-data-blockchain/
βββ .gitmodules
βββ README.md
βββ scripts/
β βββ update_all.sh
β βββ integrity_check.py
β βββ generate_cross_jurisdictional_report.py
βββ jurisdictions/
βββ country:us/
β βββ state:il/ # Illinois submodule
β βββ state:ca/ # California submodule
β βββ state:ny/ # New York submodule
β βββ district:dc/ # Washington DC submodule
β βββ county:us/state:va/fairfax/ # Fairfax County submodule
β βββ place:us/state:tx/austin/ # City of Austin submodule
βββ country:ca/
β βββ province:on/ # Ontario province submodule
β βββ province:bc/ # British Columbia submodule
βββ country:uk/
βββ england/ # England submodule
βββ scotland/ # Scotland submodule
TODO List
- Timestamps: Scrape-Oriented vs. Gov-Oriented
Are log timestamps the time we scraped the data, or the time of the actual government update?
What if a specific event doesnβt have a timestamp?
β€ Open Civic Data also discussed this - Unique IDs
OpenStates uses a lot of generated UUIDs. Ideally, our folder/file structure and naming conventions should follow official legislative data.- Jurisdiction ID: Follows OCD naming convention β
country:us/state:fl/government - Session ID: TODO
- Bill ID:
jurisdiction_id/sessions/:session_id/bill.identifierβ use official ID likeHB250 - Vote Event ID: TODO
- Person ID: TODO
- Event ID: TODO
- Jurisdiction ID: Follows OCD naming convention β
- Bill Folder + Filename Convention
bill.metadata:bill_id/log/metadata_update_{TODO}.jsonbill.actions:bill_id/log/action_{TODO}.jsonbill.votes:bill_id/log/vote_{TODO}.jsonbill.sponsors:bill_id/log/sponsor_update_{TODO}.jsonbill.versions:- File:
bill_id/files/version_{TODO}.pdf - Log:
bill_id/log/version_add_{TODO}.json(we can extract PDF content to JSON)
- File:
bill.documents:- File:
bill_id/files/documents_{TODO}.pdf - Log:
bill_id/log/document_add_{TODO}.json(we can extract PDF content to JSON)
- File:
- Event Folder Convention
Events tied to sessions should live inside the session folder.
Out-of-session events: can we define a reliable alternate time span for organization? - How to Handle Metadata Changes
Metadata (likebill) may change from scrape to scrape.
UsefieldMaskfor lightweight updates, or consider JSON Patch.
β€ https://jsonpatch.com// bill.metadata_events { "fieldMask": ["from_organization"], "bill": { "from_organization": "" } }
Environment Setup
For now, we arenβt doing any coding that touches the previous code. All code/decisions should be in this scraper_next folder as an isolated experiment. If you donβt have git access, message @sartaj.
Easy: Download Data and Explore With SQL Explorers
- OpenState Illinois Scraper Output Files
- State/Federal OpenStates Data Explorer
- password is ChiHackNight closing group phrase all lowercase
- Chicago OCD Data Explorer Explore Councilmatic PG Dump for Chicago OCD data
Advanced: Running Scrapers / Importing PG Dumps
- Open States
- via Scraper. We are using this for v1. By running the scrapers directly, data will be much more up to date as it scrapes data directly. It also allow us to run certain scrapers, like USA, multiple times a day.
- via SQL Dump, which updates every few days, and has bill full text, in addition to a lot of other content like maps data.
- Chicago SQL Dump. This updates every night and is managed by Datamade, who we have already been collaborating with on Chicago data. They also do stuff like AI summaries that we can pre-pull.
Prior Art
- Washington DC made Github their official law source of truth. It looks immutable.
- How append-only logs are used in p2p/blockchain applications.
- Beginners guide to event sourced databases and their benefits.
- Bluesky LGBTQ+ Legislation Alerts This incredible team has manually created a system that I think we can make tooling for that they would potentially want to use.
Communications
- Discussion via Slack
- Task Board via Slack
- (this file) Collaborative Brainstorming via Git: Feel free to edit.
Bill Bot Designer
Overview
The Bill Bot Designer is a tool for creating automated bots that monitor and publish legislative updates from the Open Civic Data Blockchain. These bots can be configured to watch specific bills, jurisdictions, or events and automatically post updates to various platforms like Bluesky, Twitter, RSS feeds, or custom webhooks.
Why Bots?
- π€ Automated Monitoring: Bots can continuously watch for legislative changes without human intervention
- π’ Multi-Platform Publishing: Single bot configuration can publish to multiple platforms simultaneously
- π― Targeted Alerts: Organizations can create highly specific feeds for their constituents
- β‘ Real-Time Updates: Instant notifications when important legislative events occur
- π Consistent Formatting: Standardized message formats across all platforms
Bot Architecture
Event-Driven Design
Bots operate on an event-driven architecture, listening to the append-only log of legislative events:
Legislative Event β Blockchain Log β Bot Filter β Message Generation β Platform Publishing
Bot Components
- Event Listener: Monitors the blockchain log for new events
- Filter Engine: Applies rules to determine if an event should trigger the bot
- Message Generator: Creates platform-specific messages from event data
- Publisher: Sends messages to configured platforms
- Rate Limiter: Ensures compliance with platform API limits
Configuration Examples
Basic Bill Monitor Bot
name: "Illinois Bill Monitor"
description: "Monitors all Illinois bills for key actions"
# Event filtering
filters:
- jurisdiction: "country:us/state:il"
- event_types: ["bill_introduced", "bill_passed", "bill_vetoed"]
- keywords: ["environment", "education", "healthcare"]
# Message template
message_template: |
π {bill.identifier}: {bill.title}
ποΈ {action.description}
π
{action.date}
π {bill.url}
# Publishing platforms
platforms:
- type: "bluesky"
account: "@legislative-alerts.bsky.social"
rate_limit: "10/hour"
- type: "rss"
feed_url: "https://example.com/il-bills.xml"
update_frequency: "immediate"
Specialized Committee Bot
name: "Senate Appropriations Monitor"
description: "Tracks all bills going through Senate Appropriations"
filters:
- jurisdiction: "country:us/state:il"
- committee: "Senate Appropriations"
- event_types: ["bill_referred", "bill_hearing_scheduled", "bill_vote"]
message_template: |
π° Senate Appropriations Update
π {bill.identifier}: {bill.title}
π Fiscal Impact: {bill.fiscal_note.summary}
π
Next Action: {next_action.description}
ποΈ Date: {next_action.date}
platforms:
- type: "webhook"
url: "https://api.example.com/appropriations-webhook"
headers:
Authorization: "Bearer {webhook_token}"
- type: "email"
recipients: ["budget@example.org", "finance@example.org"]
subject: "Senate Appropriations Alert: {bill.identifier}"
Constituent Alert Bot
name: "District 5 Constituent Alerts"
description: "Alerts constituents about bills affecting their district"
filters:
- jurisdiction: "country:us/state:il"
- sponsor_district: "5"
- event_types: ["bill_introduced", "bill_passed", "bill_signed"]
message_template: |
π District 5 Update
π {bill.identifier}: {bill.title}
π€ Sponsored by: {sponsor.name}
π Summary: {bill.summary}
π
Status: {bill.status}
π Learn more: {bill.url}
platforms:
- type: "sms"
phone_numbers: ["+15551234567", "+15559876543"]
provider: "twilio"
- type: "slack"
channel: "#district-5-alerts"
workspace: "example-org"
Platform Integrations
Bluesky
- Rate Limit: 10 posts per hour
- Character Limit: 300 characters
- Features: Rich text, links, images
- Authentication: App password required
Twitter/X
- Rate Limit: 300 tweets per 3 hours
- Character Limit: 280 characters
- Features: Text, images, polls
- Authentication: OAuth 2.0
RSS Feeds
- Format: RSS 2.0 or Atom
- Update Frequency: Configurable
- Features: Full text, categories, enclosures
- Hosting: GitHub Pages, custom server
Webhooks
- Method: POST
- Content-Type: application/json
- Authentication: Bearer token or API key
- Retry Logic: Exponential backoff
- Providers: SMTP, SendGrid, Mailgun
- Templates: HTML and plain text
- Attachments: PDF bills, documents
- Rate Limits: Varies by provider
SMS
- Providers: Twilio, AWS SNS
- Character Limit: 160 characters
- Features: Text only
- Cost: Per message
Advanced Features
Conditional Logic
filters:
- jurisdiction: "country:us/state:il"
- conditions:
- if: "bill.fiscal_impact > 1000000"
then: "priority = high"
- if: "bill.sponsor.party == 'Republican'"
then: "include_opposition_analysis = true"
Message Templates with Variables
message_template: |
{#if bill.fiscal_impact > 1000000}π° HIGH COST BILL {/if}
π {bill.identifier}: {bill.title}
π€ Sponsor: {bill.sponsors[0].name} ({bill.sponsors[0].party})
π Fiscal Impact: ${bill.fiscal_impact:,.0f}
π
{action.date | date_format: "%B %d, %Y"}
π {bill.url}
{#if bill.summary}
π {bill.summary | truncate: 200}
{/if}
Scheduled Publishing
publishing:
schedule:
- time: "09:00"
timezone: "America/Chicago"
days: ["monday", "tuesday", "wednesday", "thursday", "friday"]
- time: "17:00"
timezone: "America/Chicago"
days: ["monday", "tuesday", "wednesday", "thursday", "friday"]
batch_size: 5
delay_between_posts: "30s"
Analytics and Monitoring
analytics:
track_engagement: true
platforms:
- bluesky
- twitter
- webhook
metrics:
- posts_sent
- engagement_rate
- error_rate
- response_time
alerts:
- condition: "error_rate > 0.05"
action: "email_admin"
- condition: "no_posts_24h"
action: "slack_alert"
Best Practices
Content Guidelines
- Be Accurate: Always verify data before publishing
- Stay Neutral: Present information without bias
- Include Context: Provide background information when relevant
- Use Clear Language: Avoid jargon and technical terms
- Include Sources: Always link to official sources
Technical Guidelines
- Rate Limiting: Respect platform API limits
- Error Handling: Implement retry logic and fallbacks
- Monitoring: Track bot performance and errors
- Testing: Test configurations before going live
- Documentation: Document bot purposes and configurations
Legal Considerations
- Copyright: Respect copyright on bill text and documents
- Attribution: Always credit original sources
- Disclaimers: Include appropriate disclaimers
- Compliance: Follow platform terms of service
- Privacy: Donβt collect or store personal information
Getting Started
1. Choose Your Use Case
- General Monitoring: Track all bills in a jurisdiction
- Committee Focus: Monitor specific committees
- Issue-Based: Track bills by topic or keywords
- Constituent Service: Alert constituents about relevant bills
2. Design Your Filters
- Jurisdiction: Which government body to monitor
- Event Types: What actions to track
- Keywords: Specific topics or terms
- Sponsors: Bills from specific legislators
3. Create Your Message Template
- Platform Limits: Consider character limits
- Required Information: Bill ID, title, action, date
- Optional Details: Sponsor, summary, fiscal impact
- Call to Action: Links to learn more or take action
4. Configure Platforms
- Primary Platform: Choose your main publishing platform
- Secondary Platforms: Add additional platforms for reach
- Testing: Test with a small audience first
- Monitoring: Set up alerts and analytics
5. Deploy and Monitor
- Gradual Rollout: Start with limited scope
- Monitor Performance: Track engagement and errors
- Iterate: Refine based on feedback and data
- Scale: Expand to additional jurisdictions or topics
Examples in Action
Bluesky Legislative Alerts
The Bluesky LGBTQ+ Legislation Alerts bot demonstrates how effective automated legislative monitoring can be. It:
- Monitors bills across multiple states
- Filters for LGBTQ+ related legislation
- Posts concise, informative updates
- Builds a community around legislative transparency
Chicago Councilmatic
The Chicago Councilmatic system shows how bots can enhance existing civic data platforms:
- Integrates with existing Open Civic Data sources
- Provides real-time updates on city council activities
- Maintains historical records of all legislative actions
- Enables custom feeds for different stakeholders
Future Enhancements
AI-Powered Features
- Smart Summaries: AI-generated bill summaries
- Impact Analysis: Automated analysis of bill effects
- Sentiment Analysis: Track public opinion on bills
- Predictive Modeling: Forecast bill outcomes
Advanced Integrations
- Calendar Integration: Add events to personal calendars
- CRM Integration: Track constituent interactions
- Newsletter Integration: Compile weekly summaries
- API Access: Allow third-party integrations
Enhanced Analytics
- Engagement Tracking: Measure bot effectiveness
- A/B Testing: Test different message formats
- Audience Insights: Understand whoβs following bots
- Performance Optimization: Improve delivery rates
Windy Civi
A unified portal with notifications for Chicago residents, showing local, state, and federal bills with AI summaries and topics, allowing users to get notifications.
Contributors
- Andrew Dauphinais
- Emme
- Kari Douglass
- Marissa Heffler
- Sartaj Chowdhury
- Zach Schoneman
Additional Contributors
- haileyplusplus
- Fiona Tang
- Miroslava Osorio
- Nate Johnson
December 2024 Presentation
The following is our presentation from December 2024
Slides
Civi Social + MyChicago
Allow residents of Chicago to directly interact with their elected officials.
Contributors
Additional Contributors
- Charles Cole
- Sue Kwong
History
Socratic.Center
Easily find who represents you.
This era involved making www.socratic.center, a site to easily find your representative. From here, this project merged into Chi Hack Night as a breakout group.