← Back to series overview

From Weeks to Hours: Engineering an AI Pipeline for Site Investigation Reports

The whole story on one page: the business problem, the one boundary the architecture defends, hosted agents on Azure AI Foundry, the guarded data layer, and the trust engineering that makes an automated liability document signable.

Prefer it in parts?

This is the complete article. The same material is also published as a five-part series, one theme per part, if you'd rather read it over a week:

Table of Contents

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.

Parcel query
→
Resolver
Regrid parcels
→
4 agents
in parallel
→
Consolidation
deterministic
→
Exec summary
+ QA pass
→
Rendered DOCX
draft for engineer
Zoning & code retrieval
Geospatial & environmental
Section research
Utilities & contacts

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_multiplicity guard 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.

The business translation: the parts of the report a firm could be sued over behave like software, not like a chatbot. The model's creativity is confined to prose that is mechanically checked against the data it summarizes.

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:

  1. 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.
  2. 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.

SourceWhat it providesAccess pattern
Regrid parcel APIParcel records, geometry, ownership, zoning codesGuarded resolver: field allowlist, input sanitization, count-gate before large pulls, token from Key Vault
Municipal ordinance corpusZoning rules, dimensional standards, parking, signage, feesPDFs → Document Intelligence layout → section chunks with jurisdiction taxonomy → Azure AI Search hybrid retrieval, jurisdiction-scoped
FEMA NFHLFlood zones and panelsDirect feature service query per parcel geometry
Planetary ComputerElevation (3DEP/Copernicus DEM), land cover, soils rastersServer-side TiTiler statistics, no local raster processing
USDA Soil Data AccessSoil map units and interpretationsLive SQL query service
FWS National Wetlands InventoryWetland featuresFeature query with land-cover screening fallback
Azure MapsGeocoding, address backfill, disambiguation thumbnailsEntra-authenticated REST
Web research (fallback)Utility providers and contacts onlyBing-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.

The cost of this discipline was real: roughly as much code enforces the report as produces it. That ratio is the honest price of putting an LLM anywhere near a liability document, and budgeting for it up front is cheaper than retrofitting trust later.

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_engineer by 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.

Read it as a series: Part 1 → Back to All Articles