Inspiration

A resident finds out something's being built near their house, and the actual details are buried in a 200-page PDF posted three days before a 7 p.m. meeting. Developers and repeat applicants already know the process, the people, the history. Residents usually don't. Cities publish plenty of public records, they just don't make them usable. Sentinel exists to close that gap.

What it does

Enter an address, and Sentinel connects it to nearby zoning cases, parcels, and land-use decisions. For any case it explains the proposal, shows who's behind it and their track record, surfaces documented support or opposition, and links every claim to the document page or meeting-video timestamp it came from. It can even draft a public comment.

How we built it

Ingestion scripts pull from CivicClerk, Granicus/Legistar, PrimeGov, NovusAgenda, Esri ArcGIS parcel/zoning services, and public YouTube meeting video into SQLite. An LLM extraction pass turns that text into structured entities (Case, Person, Organization, Parcel, ZoningDistrict, Meeting, Document), which get loaded into a Neo4j graph, plus an open layer for anything that doesn't fit one. A precomputed snapshot cache sits on top, so the live app never waits on a slow query or a model call. Node server, Vite frontend, sub-second responses.

GPT-5.6 powers the offline extraction and resolution passes: it turns public-record text into provenance-linked graph facts, reconciles inconsistent entity names, and drafts grounded explanations only from verified evidence. Codex accelerated the build across the ingestion pipeline, graph UI, validation, and deployment; we made the key calls on the data model, verification rules, and evidence-first product experience.

Fishers, Indiana is our reference city:

  • 184 meetings
  • 167 documents (20M+ characters)
  • ~39,000 parcels
  • 100 YouTube transcripts
  • 168,000+ graph relationships

Other cities run through the same pipeline and only ever show what they've actually verified and ingested.

Challenges we ran into

Government data isn't built to be machine-readable. Some minutes are scanned PDFs that need OCR. CivicClerk's API exposes case outcomes but not individual vote direction, so we left that gap visible instead of faking it. The same person or org shows up under slightly different names across time, and consolidating those without the llm hallucinating and filling in gaps with educated guesses was the most difficult. We'd rather show nothing than mislead someone when it comes to the law.

Accomplishments that we're proud of

The reference build holds up at scale, with tens of thousands of parcels, 75,000+ grounding edges back to source documents, and response times in the tens of milliseconds because the heavy lifting happens offline. The pipeline already generalizes: onboarding recognizes thousands of San Jose meetings under the same verification standards we held Fishers to.

What we learned

The hard part was never the model, it was ingestion: normalizing inconsistent government formats, resolving duplicate entities, deciding what to do when a source just doesn't have the answer. Trust the query, not the narration. A count should come from \(\text{COUNT}\) over a table or a Cypher aggregation, never a sentence the model wrote on its own. Shrinking the model's job down to "phrase these facts and cite them" made the product more trustworthy, not less capable.

What's next for Sentinel

More cities onboarded so any resident can look up their own address. OCR for the remaining scanned documents, better entity resolution across years, full map geometry instead of the current 35 cases. Eventually, proactive alerts so residents don't have to remember to check back before a case near them gets decided.

Built With

Share this project:

Updates