The business problem
Before anyone buys, finances, or builds on a commercial property, a civil engineer produces a Site Investigation Report (SIR): a due-diligence document covering the parcel's ownership, zoning, flood risk, wetlands, soils, topography, utilities, and the local ordinances that govern what can be built there.
Producing one today is slow, expensive, and expert-bound. An engineer spends days to weeks pulling data from a dozen disconnected sources, county parcel systems, FEMA flood maps, USDA soil surveys, wetlands inventories, municipal code libraries, then transcribing it into a Word template by hand. Every hop is a chance to mis-key a number. Quality depends on which engineer you got and how much time they had. And the work is pure cost: clients pay for the engineer's judgment, not for the copy-paste that consumes most of the hours.
The obvious answer in 2026 is "point an LLM at it." The obvious answer is wrong, and the reason it is wrong shaped the entire architecture of this project. A due-diligence report is a liability document. A hallucinated flood zone or a wrong parcel acreage isn't an embarrassing chatbot answer, it's a professional-negligence problem. This article describes a system that automates the SIR end-to-end while keeping every factual value on a deterministic, auditable path.
What the system does
Give the pipeline a parcel query, a street address, a coordinate, an APN, a block/lot, an owner name, or a drawn area, and it returns a fully cited Word draft of the SIR for an engineer's review. Not a chat answer: the same DOCX template the firm already uses, with every value carrying its source and every gap carrying its reason.
Regrid parcels
in parallel
deterministic
+ QA pass
draft for engineer
Reading left to right: the Resolver turns the query into real parcel records via the Regrid national parcel API. If matches are ambiguous, it pauses and asks a human one question rather than guessing. Four agents then enrich the site in parallel: zoning and dimensional standards from an indexed municipal-ordinance corpus, flood/soil/terrain/wetlands from federal geospatial services, report topics like parking and signage from the same corpus, and utility contacts with a web-research fallback. A deterministic consolidation stage merges everything, computes conflicts, and freezes the object. Only then does a language model write the executive summary, which a QA pass checks against the data. Finally the frozen object fills the firm's DOCX template.
The whole run was validated live end to end on Azure: a real Durham, NC parcel went from address to rendered report through the hosted pipeline.
The core principle: the model narrates, code decides
The architecture rests on one boundary, drawn early and defended everywhere: the LLM orchestrates and narrates; every factual value flows through deterministic, unit-tested Python. The model never invents parcels, never picks a jurisdiction, never fills a field. Resolution, provenance assembly, jurisdiction scoping, and conflict detection are plain code behind the tools.
Four rules make that boundary real:
- Every value carries provenance. Each attribute records its source, capture timestamp, and a locator (API endpoint, document chunk) pointing back to the authoritative record: FEMA's flood layer, the county's parcel record, the ordinance section itself. An engineer can click from any number in the report to where it came from.
- Every gap carries a reason. If a service fails or has no coverage, the field renders as an em dash with a
reason_null. The gap is visible, never papered over. A partial failure never silently drops a parcel. - Multiplicity is never collapsed. A "site" is often several parcels spanning several jurisdictions. Agents attach data per parcel; a shared
assert_multiplicityguard at every pipeline boundary hard-fails if a parcel or jurisdiction ever goes missing. - Conflicts become divergences, not averages. When parcels disagree on a material attribute, two zoning designations, different flood zones, consolidation emits an explicit divergence entry flagged
requires_engineer_review, with a deterministic source-authority recommendation attached as an annotation. The system never resolves the conflict itself.
Only two stages involve a model at all: constrained extraction from cited ordinance text, and the executive summary. Both are post-checked by plain code. The summary agent reads the frozen, consolidated site, it has no tools and no data access, and a deterministic grounding check verifies that every sentence maps to an existing cited attribute and that every divergence is surfaced in the narrative. "No new facts" is not a prompt instruction; it is an enforced gate.
Architecture: hosted agents on Azure AI Foundry
The pipeline runs as hosted agents on Azure AI Foundry, orchestrated with the Microsoft Agent Framework (MAF). Each agent has its own Entra identity and least-privileged RBAC: the zoning agent can read the search index but cannot touch Key Vault; the summary agent has no tool access at all. Authentication is DefaultAzureCredential everywhere; no API keys live in code.
A single shared Site object, one canonical set of Pydantic models for site, parcels, attributes, provenance, and divergence, is the state. Every hop passes the whole object forward; agents attach to it, never replace or prune it. That "attach-only" contract is what makes the boundary guards enforceable: at every handoff, validators assert the parcel set is intact, every new attribute carries provenance, and nothing was overwritten.
Humans enter the loop at exactly two points, both deliberate:
- Disambiguation. When the Resolver finds multiple plausible parcels, it auto-selects only if exactly one candidate clears a confidence threshold, and notes the auto-selection in provenance. Otherwise it pauses and asks the engineer one question, showing owner, address, and a map thumbnail per candidate. The spec's phrasing became a team motto: a wrong silent pick violates the charter more than a clarifying question.
- Terminal review. The output's review state is
draft_for_engineer, never a final authority. The engineer's judgment stays the product; the pipeline removes the transcription.
Observability is not bolted on, it is the same record as the report. Every model call, tool invocation, and agent handoff lands in one OpenTelemetry trace in Azure Monitor, with the site ID, parcel IDs, and each value's source endpoint stamped on the spans. The provenance an engineer sees in the report and the production trace an operator sees in App Insights are one and the same data. A custom Foundry evaluator replays the pipeline's invariant assertions over sampled production traffic and raises alerts on any breach, linked back to the originating trace.
Infrastructure is a single Bicep template: Foundry account and project, AI Search, Key Vault, Azure Maps, Container Registry, App Insights. The agent is a Docker image invoked over Foundry's Invocations protocol, one POST with a strategy and a query.
The data layer: authoritative sources, wrapped as guarded tools
A due-diligence report is only as defensible as its sources, so the pipeline goes to the systems of record directly and cites them, never a derived copy.
| 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 |
Two patterns are worth stealing. First, every external call is wrapped in a guarded tool, not handed to the model raw: the Regrid resolver sanitizes inputs, allowlists fields, and gates on a count query before any large pull, so a confused agent physically cannot request the wrong thing at scale. Second, sources are ranked. County record beats commercial aggregate beats web page; the divergence recommendation and the utilities fallback both encode that hierarchy, and web-sourced values are explicitly labeled as the weakest tier rather than blending in.
The ordinance corpus deserves a note: municipal codes arrive as sprawling PDFs. An ingestion pipeline converts them through Azure Document Intelligence into structured markdown, chunks them by section with cross-references and a jurisdiction taxonomy, and indexes them for hybrid (keyword + vector) search. Retrieval is always scoped to the parcel's actual jurisdiction path, so the Trenton agent cannot accidentally cite Cleveland's setback rules.
Trust engineering: the guardrails are the product
In most software, tests and guardrails protect the product. Here they largely are the product: what a firm is buying is the confidence that an automated report can be signed off on. Three layers deliver it.
Test discipline. The suite is 300+ tests and runs entirely offline by default: fake HTTP transports and canned model chats, so every guard, merge rule, and failure path is exercised without Azure. Live smoke tests against the real public services (Soil Data Access, wetlands, Planetary Computer) are a deliberate opt-in flag. Multiplicity guards are unit-tested against the hard cases, a real four-parcel assemblage and a synthetic two-jurisdiction divergence.
Runtime gates. The invariant checkpoints run in production on every request, and hard-fail rather than degrade: if consolidation cannot prove that no parcel was dropped and every value is cited or explained, the pipeline halts, emits the trace, and surfaces the failure to the engineer. A report that cannot pass its own audit never ships. The model-facing stages get the same treatment: extraction output is grounding-checked against the chunks it cites, the QA score is computed in code from the issue list rather than asked of the model, and high-severity revisions must re-pass the grounding gate.
Production evaluation. A custom Foundry evaluator re-runs the invariant assertions over sampled live traffic, so drift or a regression shows up as an Azure Monitor alert linked to the exact trace, not as a bad report discovered by a client.
What changes for the business
The pipeline turns report production from days of expert data-gathering into a machine run plus a focused review. The economics follow from where the hours go:
- Turnaround. The gathering-and-transcription phase, the bulk of elapsed time on a manual SIR, becomes minutes of pipeline execution. The engineer's time shifts to what clients actually pay for: reviewing divergences, exercising judgment, signing off.
- Consistency. Every report draws from the same sources, applies the same source-authority ranking, and renders through the same template. Quality stops depending on which engineer had bandwidth that week.
- Auditability. Each value links to its authoritative record and each run leaves a complete trace. When a client, lender, or insurer asks "where did this flood zone come from?", the answer is a citation, not an archaeology project.
- Leverage, not replacement. The terminal state is
draft_for_engineerby design. Senior engineers review more sites instead of transcribing fewer; the disambiguation pause keeps them in control of exactly the decisions that need a human. - Scalability. Marginal cost per report is API calls and model tokens, not engineer-days, which changes what portfolio-scale due diligence (screening dozens of candidate sites, not just confirming one) can look like.
None of this is hypothetical plumbing: the stack was deployed to Azure and validated end to end, a real Durham, NC parcel resolved through disambiguation, enrichment, consolidation, and QA to a rendered, cited DOCX, before being deliberately torn down, with the entire system reproducible from one Bicep template and a runbook. Infrastructure-as-code means the capability is an asset, not a snowflake environment.
Lessons for teams building agentic document pipelines
Write the invariants before the agents. The project's most valuable artifact is not an agent, it is the shared Pydantic model plus the boundary guard that every hop must pass. Once "no parcel ever disappears, every value is cited or explained" existed as executable code, agent design became easy: any agent that violates the contract fails loudly at the boundary, so you can iterate on prompts and orchestration without fearing silent corruption.
Decide what the model is for, narrowly. "LLM for language, code for facts" sounds obvious and is constantly tempting to violate. Letting the model "just pick" the jurisdiction or "just merge" the conflict would have saved code in the moment and destroyed the audit story. Each carve-out for determinism (resolution, merging, divergence, scoring) is a place the system cannot hallucinate, and their sum is the product's credibility.
Post-check the model; don't trust the prompt. Every instruction to the model, no new facts, surface divergences, cite chunks, exists twice: once in the prompt, once as a deterministic check on the output. The prompt gets compliance most of the time; the check makes "most of the time" irrelevant.
Design the escalation, not just the happy path. The single best-received behavior is the pipeline refusing to guess: pausing on ambiguous parcels and asking one well-formed question with a map. Knowing when to stop is a feature you must design deliberately; it will not emerge from a capable model on its own.
Make the tests independent of the cloud. Because the suite runs on fake transports and canned chats, the full 300+ tests validated every behavior while the Azure environment was completely torn down. That independence is what made tearing down the environment, and someday redeploying it, a non-event.
Provenance doubles as observability. Stamping each value's source onto the OpenTelemetry span meant the compliance story and the debugging story became a single investment rather than two.
What's next
The report path is done; the roadmap now points at the data around it.
- A reference-data lakehouse. At portfolio scale, clipping the big federal datasets (wetlands, soils, building footprints) to the service area on a quarterly schedule and serving them as versioned Delta tables beats fetching per report. The provider protocols are already the seam, so agent code doesn't change, and serving tables would carry dual provenance (upstream source and snapshot vintage) so citations still reach the federal record.
- Portfolio analytics and ad-hoc Q&A. Landing consolidated site objects in the lakehouse opens conversational analysis, "which pipeline sites touch a flood zone?", and dashboards over divergence and evaluator trends.
- One guardrail, kept deliberately. Analytics layers are LLM-mediated and belong beside the engineer, not inside the report. Report values keep flowing through the deterministic providers with per-value provenance, whatever gets built around them.
The broader takeaway travels beyond real estate. Any regulated document, environmental assessments, insurance inspections, compliance filings, has the same shape: facts that must be authoritative and auditable, wrapped in narrative that must be readable. The pattern here, deterministic facts, guarded tools, attach-only state, machine-checked narration, and a human holding the pen at the end, is how you get the speed of automation without gambling the signature on the last page.