wv-campaign-finance
Visualizing campaign finance data in West Virginia local, state, & federal elections
Overview
Let’s say you wanted to know how much money was spent on a particular race or candidate, or how much a specific group has spent influencing elections in West Virginia. You might want to compare spending between districts and candidates, see how it has changed over time, or spot trends and outliers in the data.
West Virginia campaign finance data is publicly available through the WV Secretary of State’s Campaign Finance Reporting System (CFRS) and the Federal Election Commission (FEC), but the official interfaces can be cumbersome to navigate and don’t make it particularly easy to compare activity in state and federal races.
This project provides an independent interface to browse campaign finance activity for West Virginia elections, starting with the 2026 cycle. It covers every committee registered with the WV Secretary of State and the committees in the state’s U.S. Senate and House races that file with the FEC: contributions, and independent expenditures for and against candidates. It also includes the county, municipal and party executive committee candidates from the Secretary of State’s candidate register, who don’t file with CFRS but are still named in outside spending reports.
Independent expenditures (IEs) are spending by individuals, PACs, corporations, labor organizations, and other groups to support or oppose candidates without coordinating with their campaigns. The modern legal landscape for independent spending was significantly shaped by Citizens United v. FEC, which struck down federal restrictions on independent expenditures by corporations and labor unions. The decision has had a profound impact on campaign finance, leading to increased spending by outside groups in elections.
Reading the filings
State-level IE filings present an unusual data problem. Unlike ordinary campaign finance reports submitted as structured data, West Virginia’s independent expenditure reports are filed on paper and published as PDFs. This leaves reporters, researchers, and anyone else trying to follow the money largely stuck reading each of the hundreds of filings by hand to compile the data.
The quality of the reports themselves can also vary considerably. State law says they’re to be filed “on a form prescribed by the Secretary of State” (W. Va. Code §3-8-2 et seq.), and while many filers use it, some instead write “see attached” across the form and include a PDF printout of their own spreadsheet. And the filings that do use the form aren’t always much easier: some are typed, some are photos taken with a cell phone, and some are messily handwritten with notes, crossed-out text, and arrows drawn on them.
To turn all of that into usable data, each filing gets sent to Mistral AI’s mistral-ocr-latest model (Mistral OCR 4.1) with a schema based on the official form, and comes back as structured rows. It was picked for its price and performance in document parsing, especially on scans, handwriting, and tables.
Candidate and payee names in the filings are free text, which leads to problems when filers misspell “Tom Takubo” as “Tom Tabuko” and “Acquire Digital” as “Aquire Digital”, or can’t agree on whether they’re paying “Meta”, “Meta Platforms, Inc.”, or “FB ADS”. So candidate names get matched against the list of registered candidates, and payees against the ones already seen in other filings, so those misspellings and other variations still align. Then unclear cases get sent to Jev by TypeSafe, a probabilistic decision model useful for resolving ambiguous selections from a known set.
About the site
The site has an interactive district map of outside spending across House of Delegates, State Senate, U.S. House and judicial districts, spending timelines, a breakdown of which outside groups spend on which candidates, and pages for every committee, candidate, office and party. Every report links to the PDF it came from, every chart can be shown as a table, and new filings are also published as an RSS feed.
A sync service checks CFRS, the candidate register, and the FEC for new or updated filings and candidates throughout the day. Each new IE report goes to Mistral OCR as filed with a JSON schema modeled on the variations of the state’s IE form. Candidate names are then matched by Jaro-Winkler similarity, weighted toward the surname, with Jev deciding the rest.
Built with:
- Astro on Cloudflare Workers runs the public site
- Cloudflare D1 (SQLite) holds the data, with a KV cache in front
- Cloudflare R2 keeps a copy of every source PDF and extraction result
- Cloudflare Workflows run each batch of IE report extractions
- Mistral AI’s
mistral-ocr-latest, through its batch API, reads the filings - Jev by TypeSafe, through Cloudflare AI Gateway, settles unclear name matches for candidates and payees
- Docker runs the scheduled sync with CFRS, the candidate register, and the FEC in a container, using supercronic
Questions or comments? Send an email to ~abs/wv-camp-fin@lists.sr.ht, or contact me directly.