Hack #1: Firecrawl · Firecrawl
26 Mar, 17:09
Wisconsin has 28 races this cycle. Eleven people running for governor. A Supreme Court seat that decides the court's balance for the next decade. Three constitutional amendments that read like they were written to confuse you on purpose. Marquette Law School polled Wisconsin voters in March 2026. 38% of registered voters had heard nothing about the Supreme Court race. Not "a little." Nothing. 53% were undecided. The election is April 7th. I live in Milwaukee. I have nine races on my ballot. I couldn't tell you who half the candidates are, and I'm the one who built the app. The information is out there — scattered across government sites, campaign finance databases, fact-checkers, and local news. But nobody has time to open 20 tabs and piece it together. So I built something that does it for you. Ballot Badger is voice-first. You talk to it the way you'd ask a friend who follows politics. "Tell me about Tom Tiffany." "Where do I vote?" "Go deeper on his donors." ElevenLabs ElevenAgents runs the conversation — speech-to-text captures the question, GPT-OSS-120B figures out what you're asking and picks the right tools, then text-to-speech narrates the findings while the UI builds on screen. The agent has 17 tools registered through the ElevenLabs CLI. Six render cards — candidate profiles, voting records, donor tables, fact-check badges, endorsements, ballot measures. Three navigate Wisconsin's election site to find your polling place, preview your ballot, or check your registration. One runs a second pass of deeper research. Three control the UI. One shows candidates side by side. Every tool is non-blocking. The agent calls it, gets an instant response, and keeps talking while the data loads in the background. When results arrive, cards build on screen mid-sentence. Voice and display run on independent pipelines that converge on the same data. If the voice drops, cards still render. If data is slow, voice fills the gap. Firecrawl is also connected as an MCP server for anything the 17 tools don't cover. Firecrawl does three distinct jobs here, each using a different API. Candidate research runs through the Firecrawl v2 Search API. When you ask about someone, the app fires 5 parallel searches with full markdown content extraction. Queries are tuned by candidate type — incumbents get Congress.gov voting record and OpenSecrets donor searches, challengers get state finance queries, ballot measures get impact analysis. Firecrawl returns full page content, not snippets, which gives Claude Sonnet 4 enough context to pull out dollar amounts, vote records, and fact-check ratings. A single query searches Congress.gov, OpenSecrets, PolitiFact, Wisconsin Examiner, WPR, Milwaukee Journal Sentinel, and other local outlets. Typical result: 40 to 44 sources. Deep dives combine the Search and Scrape APIs. "Go deeper on donors" triggers 3 targeted queries plus a direct scrape of the candidate's Transparency USA or OpenSecrets page. The scrape returns full markdown of the donor records — individual contributions, PAC money, fundraising totals — which Claude extracts into structured data. Search and scrape run in parallel. A deep dive adds 15 to 20 sources. The voter lookup uses the Firecrawl Interact API, and it's the part I keep coming back to. "Where do I vote?" loads myvote.wi.gov in a headless browser via Firecrawl's Scrape API, handling the Cloudflare challenge and returning a session ID. Then the Interact API runs Playwright code that types your address into the form, clicks Search, and waits for results. That government site runs on ASP.NET WebForms, which was the hardest part of the whole build. Standard fill() doesn't fire the keyboard events ASP.NET needs, so the form stays "empty" and Search does nothing. The fix: pressSequentially() types each character individually, firing every keydown/keypress/input event. Each field needs a blur event to trigger validation. The Search button is wrapped in an invisible span that breaks Playwright's click, so it needs force: true. After results load, the app pulls raw text with document.body.innerText and hands it to Claude to parse into JSON — polling place name, address, hours, ward. Same flow for ballot preview: fills the form, waits for the ballot, reads every race and candidate. None of it is a database lookup. No cached data. A real browser fills out a real government form and reads real results. Every time.
