Projects

Polibrief

A grounded, cited answer to "what has this candidate actually said about X?" — built on Postgres/pgvector retrieval over scraped campaign, press, and public-records sources.

Featured

Languages and discipline

Applied AI TypeScript SQL

Why it exists

Figuring out where a state legislative candidate actually stands on an issue means piecing it together from a Ballotpedia page, a campaign site, a handful of press hits, and an OpenSecrets filing — none of which agree on vocabulary, and none of which are cited against each other. Polibrief is an attempt to collapse that into one query: ask about a candidate and an issue, get an answer that quotes back to the actual source documents it came from, rather than a model's best guess.

The starting scope is deliberately narrow — Virginia State Senate, 2025 cycle — because a retrieval pipeline that works cleanly for one race is a much better foundation than one built abstractly for every race at once.

How it's built

  • Ingest (/api/ingest) pulls a candidate's source documents — Ballotpedia, campaign pages, press releases, OpenSecrets records — strips them to text, and chunks them for embedding.
  • Embed (lib/ai/embed.ts) turns each chunk into a vector, stored in Postgres via Supabase with the pgvector extension. A match_chunks RPC does the cosine-similarity lookup, scoped to one candidate.
  • Summarize (/api/summarize) retrieves the top matching chunks for a candidate/issue pair and asks an LLM (via OpenRouter) to answer from that evidence, keeping the source chunk IDs attached to the result so the summary can be checked against what it was actually given.
  • An /admin page manages candidates and kicks off ingestion; /demo is where the query flow will eventually be public-facing.

Stack: Next.js 16 (App Router) and React 19 on the frontend, Supabase/Postgres with pgvector for storage and retrieval, Zod for schema validation, and OpenRouter (defaulting to gpt-4o-mini) for the summarization call — which falls back to a mocked streamed response locally when no API key is set, so the ingest/embed/retrieve path is testable without spending on every dev iteration.

Where it stands

Early. The schema, ingest route, embedding pipeline, and summarize route exist and run; the UI is still scaffolding. No candidates are loaded yet. Next up is actually ingesting a first candidate end to end and seeing whether the retrieved evidence is good enough to summarize honestly — that's the real test of the approach, more than any of the code written so far.

← All projects