As a sitting councilor, a group of questions would routinely come up during meetings and discussions with constituents: "When did this item pass?" "Who voted for it?" "What were the discussions around it, if any?"
I made this tool to process and document council packets going back to 2015 (the earliest ones still available) so I could get some historical context into past council thought and decision-making processes. The technical bits follow.
Semantic and keyword search, plus cited RAG Q&A, over Prairie Village, Kansas
city council packets and minutes (2015–present). PDFs are chunked and embedded
into a local ChromaDB index with
Voyage AI embeddings; pvc ask synthesizes
answers with Claude, citing the source packet, page, and meeting date.
Documents are fetched from the city's public CivicClerk portal API. Scanned/image-only PDFs are OCR'd with Tesseract.
Requires Python 3.12+, uv, and optionally
Tesseract (brew install tesseract) for OCR of scanned documents.
cp .env.example .env # add your Voyage AI and Anthropic API keys
uv syncuv run pvc download # fetch new packets/minutes from CivicClerk
uv run pvc index # chunk + embed new documents (incremental)
uv run pvc search "stormwater drainage"
uv run pvc search "ordinance 2428" --keyword
uv run pvc search "housing density" --type packet --after 2023-01-01
uv run pvc ask "What has the council decided about the community center?"
uv run pvc statspvc extract-votes runs every minutes document through Claude with a JSON
schema, producing votes.json: each formal motion, its mover/seconder,
result, tally, and — where the minutes explicitly record them (roll calls,
named dissents) — who voted yes/no/abstain. Individual votes are never
inferred from attendance. Extraction is incremental; pass --batch to use
the Message Batches API (50% cheaper) for large re-extractions.
uv run pvc extract-votes # extract new minutes (incremental)
uv run pvc extract-votes --batch --force # full re-extraction via Batches API
uv run pvc votes --result failed --after 2024-01-01
uv run pvc votes --member Reddell --side no
uv run pvc votes --search "community center" --summary
uv run pvc votes --markdown council-votes.md # regenerate the full reportThe full voting record is checked in as council-votes.md — every recorded vote from April 2015 onward, grouped by meeting.
An MCP server (pvcouncil.mcp_server) exposes search, ask, votes,
list_documents, and stats as tools for MCP clients.
pv_council_docs/ (source PDFs) and chroma_db/ (the vector index) are not
tracked in git. Rebuild them from scratch with pvc download && pvc index
(embedding the full corpus costs roughly $0.20 in Voyage API usage).