Go to the systems of record
Back to the customer for a second: when a lender asks "where did this flood zone come from?", the answer has to be a citation, not an archaeology project. So the pipeline goes to the systems of record directly and cites them, never a derived copy, never a summary someone else made.
That sounds obvious until you're tempted by a convenient aggregator that has everything in one API. The aggregator is fine as a source, as long as it is labeled as one and ranked below the county record it was derived from. Ranking is Pattern 2 below.
The sources and their access patterns
| Source | What it provides | Access pattern |
|---|---|---|
| Regrid parcel API | Parcel records, geometry, ownership, zoning codes | Guarded resolver: field allowlist, input sanitization, count-gate before large pulls, token from Key Vault |
| Municipal ordinance corpus | Zoning rules, dimensional standards, parking, signage, fees | PDFs → Document Intelligence layout → section chunks with jurisdiction taxonomy → Azure AI Search hybrid retrieval, jurisdiction-scoped |
| FEMA NFHL | Flood zones and panels | Direct feature service query per parcel geometry |
| Planetary Computer | Elevation (3DEP / Copernicus DEM), land cover, soils rasters | Server-side TiTiler statistics, no local raster processing |
| USDA Soil Data Access | Soil map units and interpretations | Live SQL query service |
| FWS National Wetlands Inventory | Wetland features | Feature query with land-cover screening fallback |
| Azure Maps | Geocoding, address backfill, disambiguation thumbnails | Entra-authenticated REST |
| Web research (fallback) | Utility providers and contacts only | Bing-grounded agent, tagged Source.WEB, the weakest provenance tier, corpus-first |
Pattern 1: guarded tools, not raw APIs
Every external call is wrapped in a guarded tool, not handed to the model raw. The Regrid resolver is the clearest example. It sanitizes inputs, allowlists the fields it will request, and gates on a count query before any large pull, so a confused agent physically cannot request the wrong thing at scale.
ALLOWED_FIELDS = {"parcelnumb", "owner", "address", "geometry", "zoning", "ll_gisacre"}
MAX_PARCELS = 50
def resolve_parcels(strategy: Strategy, query: str) -> list[ParcelRecord]:
q = sanitize(strategy, query) # strip operators, normalize, validate shape
n = regrid.count(q) # cheap count first
if n == 0:
return [] # caller records reason_null
if n > MAX_PARCELS:
raise TooManyCandidates(n) # never a 10,000-parcel pull by accident
return regrid.search(q, fields=ALLOWED_FIELDS, token=keyvault.get("regrid-token"))
The model sees a tool called "resolve parcels" that takes a strategy and a query. It does not see Regrid's query language, it does not see the token, and it cannot ask for fields outside the allowlist. Same idea for every other row in the table: the guard lives in code, the model lives behind it.
Pattern 2: sources are ranked
County record beats commercial aggregate beats web page. That hierarchy is encoded in two places: the divergence recommendation from Part 2 (when two sources disagree, the annotation says which one ranks higher) and the utilities fallback (corpus first, web only if the corpus has nothing).
| Tier | Example | How it shows up |
|---|---|---|
| 1. Authoritative record | County parcel record, FEMA NFHL, ordinance section | CITED default tier, drives divergence recommendations |
| 2. Commercial aggregate | Regrid-derived zoning code | CITED labeled as aggregate, outranked by tier 1 on conflict |
| 3. Web research | Utility provider contact from a search | SOURCE.WEB explicitly labeled weakest tier, never blended in |
Web-sourced values are explicitly labeled as the weakest tier rather than blending in. If a phone number for the water utility came from a web page, the report says so, and the engineer can decide whether to trust it.
The ordinance corpus
Municipal codes arrive as sprawling PDFs, hundreds of pages, inconsistent formatting, cross-references everywhere. The ingestion pipeline handles them in four steps:
per municipality
layout → markdown
cross-refs + jurisdiction taxonomy
hybrid keyword + vector
- Convert each PDF through Azure Document Intelligence's layout model into structured markdown that keeps headings and tables intact.
- Chunk by section, not by token count, and carry the section number, its cross-references and a jurisdiction taxonomy path on each chunk.
- Index for hybrid retrieval, keyword plus vector, so "minimum front setback" and "§ 6.4.2" both find the right chunk. Docs: hybrid search in Azure AI Search.
- Scope every query to the parcel's actual jurisdiction path, as a filter, not as a prompt hint.
The constrained extraction step from Part 2 then reads the retrieved chunk and turns it into a value, and the grounding check confirms the value is actually supported by the chunk it cites. Locator on the attribute is the chunk id, so the engineer can open the exact section.
What's next in Part 5
The finale is about trust engineering: the 300+ offline tests, the runtime gates that hard-fail rather than degrade, the Foundry evaluator replaying invariants over live traffic, what all of this changes for the business, six lessons for anyone building an agentic document pipeline, and where the roadmap goes next.