Attest — Lease Intelligence with a Verifiable Trust Layer
A commercial lease abstraction tool where every extracted field is grounded, verified, and cited back to its source page before it's shown as fact — built to close the gap between AI adoption and AI trust in commercial real estate.
The problem & requirements
- Extract ~18 lease-economics fields across six groups: parties & premises, term, rent & escalation, options & notice, expenses, risk clauses
- Derive critical dates (notice windows, expiration) and risk flags from verified extraction data — never from a raw model guess
- Every field click-to-source: one click from any value to its exact page and cited passage in the source PDF
- Groundedness: no extracted value shown as fact without a citation verified against the source page
- Refuse, don't guess: a derived date on an unverified input field blocks with a stated reason rather than computing anyway
- Reproducibility: every extraction versioned by
run_idandprompt_version, so any two eval runs are diffable field by field
Scale & constraints
Built and scored against real, publicly filed commercial leases — not synthetic data.
Pipeline design
Data model
| Entity | Key fields |
|---|---|
| documents | id, slug, type, status, source_path |
| pages | document_id, page_number, text, item_index |
| extractions | field, value, evidence_text, page, confidence, run_id, prompt_version |
| derived_dates | document_id, kind, date, status (ok / blocked), reason |
| risk_flags | document_id, flag, present, source_extraction_id |
| gold_labels / eval_runs | field, expected_value, run_id, field_score, date_score |
run_id + prompt_version on every extraction is what makes a score regression traceable to the exact prompt change that caused it — without it, "accuracy dropped" has no owner.
Architecture
A document either seeds from fixtures or is uploaded; both paths run the same six-stage pipeline. The eval harness reads through the same persistence layer as the app — never a parallel scoring path.
Fig. 5a — six-stage pipeline; the trust layer (green) is where grounding and verification gate every field before it reaches the UI.
Key decisions & trade-offs
blocked with a stated reason — not computed. Date errors compound silently across multiple fields; refusing is safer than guessing.Lessons learned
- Grounding catches fabrication cheaply. String-matching evidence text against the source page is deterministic and free — it rejects a real class of hallucination before a value is ever persisted.
- The honest-gap split proved the thesis. Parties & premises scored 94% grounded; risk clauses scored 0% — not random error, but the system correctly refusing to assert an absent clause as a confirmed fact.
- Two-pass extraction paid for itself. Routing pages to field groups before extracting sharpened accuracy and cut cost versus one long-context prompt over the full document.
- Ship self-consistency scoring, not defer it. One of three planned trust signals was cut once grounding and the verifier pass caught most failures — a real gap that should close before this handles higher-stakes fields.
- Give extraction a way to say "confirmed absent." The current schema can't distinguish "not found in routed pages" from "clause confirmed absent" — the direct mechanism behind the 0% risk-clause number.
- Sequence backend before UI more strictly. Building review-workspace screens ahead of the wired backend was a deliberate, logged deviation — it worked, but it's a sequencing risk I'd tighten next time.