The problem
The city decides rezonings, permits, variances and street closures in public, but the records sit in three systems that few people read. Someone who sees a rezoning sign on their street has no easy way to learn what is planned nearby. I wanted to tell anyone, at a consumer price, what the city is about to do near their address.
What I built
A data pipeline for Nashville that pulls Metro Council items from Legistar, permits from the city’s ArcGIS service and planning agenda PDFs. Each item gets a plain-English summary, an impact score and, where the text allows, a county parcel. The corpus holds 32,100 items today, 27,906 of them relevant, and grows each night. The pipeline has 210 passing tests.
The web app is not built. A scaffold, a typeahead API and design mockups exist. Nothing is live.
How it works
Ingest writes every item with an idempotent upsert and keeps the raw document it came from, so a rerun is safe. A splitter cuts each planning agenda PDF into items at the “On Consent” marker and exits nonzero if a template change yields none.
Council and planning items go through my model gateway for field extraction and a one-sentence summary. Permits never touch a model; a SQL template handles them. Any change to prompts, extraction or taxonomy must pass a 95-item golden set: field accuracy of at least 0.9, no missed relevant items and zero errors. The last recorded run scored recall 1.0 and field accuracy 1.0.
Each item type has a default impact, such as 5 for a moratorium, 4 for a rezoning and 1 for a permit, which the model may move by at most one point. Gray-zone types such as easements are always flagged for review. Then the resolver ties each item to a parcel or an area. Cron runs the sync at 04:17 each night under a file lock, so it skips while a backfill runs.
What went wrong
A prompt-injection audit found that when the model marked an item irrelevant but its title carried a planning case number, the item vanished without a trace. The fix, merged to main, sends those items to a catch-all land-use type flagged for review; the nightly job still runs from a branch without it. Two AI coding sessions once worked the same tree at once and interleaved commits; I reviewed every commit and now claim work before dispatching it. My first database lost its binaries when a temp directory was wiped, and I moved the data into a PostGIS container.
What it proved
The rule I care about most is to fail closed on location. An item below the confidence bar never fires an address alert, because a wrong-address alert would end trust in the product. The source data has no hearing times at all, so the product will not invent any.

