← All work
RAG / Document Intelligence · Prototype · 2026

Deal-Room Exact Answers

Design for answering hard deal-room questions with figures traced to a printed cell, compiled SQL instead of LLM arithmetic, and a published error list.

Deal-Room Exact Answers
Year
2026
Status
Prototype
Category
RAG / Document Intelligence
Role
Architect & Lead

Key metrics

Design; build scheduled
Status
Under 1% error (K ≥ 299)
Claim floor
Under 0.1% (K ≥ 2,995)
Stretch
None yet
Results

Architecture

Deterministic parsers and small fine-tuned classifiers fill a typed, bitemporal fact store in Postgres, where every figure keeps its source cell, scale source and vintage. A grammar-constrained local planner turns a question into a closed query IR over a governed metric registry; a deterministic compiler turns the IR into parameterised SQL, which runs under row-level security and is recomputed in Python. The hybrid BM25 + vector stack only finds evidence and proposes candidates, and a numeric-token guard blocks any figure in the prose that did not come from a SQL row. A value is released as EXACT only when it passes anchor, independent re-read, scale, confidence, corroboration and conflict gates; otherwise the system flags it, shows it as missing or conflicting, or abstains.

Case study

Deal-Room Exact Answers

Status: Design and evaluation plan, September 2026. Build scheduled; no results yet. Every number on this page is either a third-party published figure (linked and dated) or a target we have committed to measure. None of it is a measured result of ours.

[[toc]]

The questions deal teams actually ask

These are three of the twelve hard questions this design is built to answer, word for word:

What was Target X's FY2025 audited consolidated revenue, and where is it printed?

For every target: FY2025 revenue, EBITDA margin, and whether a change-of-control clause exists.

Which facilities carry a CoC mandatory prepayment, and what total principal becomes repayable at closing?

None of them is a search problem. The first needs one number, from one cell, with the right period, scale, basis and vintage. The second needs a complete grid: every target, every cell, and an honest statement of which cells are missing. The third needs a chain of documents (target to facility to amendment to clause) plus a sum that is only valid if every link resolves.

A deal team cannot use an answer that is "probably right". It needs a figure it can defend in an investment committee, with the cell it came from, or a clear statement that the room does not support one.

Why "chunk, embed, retrieve" is out of date

Our earlier deal-room design, Deal-Room Document Intelligence, was a strong 2025 hybrid retrieval system: metadata pre-filters, hybrid search, reranking and a corrective loop, with an LLM reading the retrieved passages and writing the answer. It is good at finding documents. It is not enough for exact figures, and the 2026 literature says why.

  • Retrieval misses the right table most of the time. TCR-Bench (arXiv 2607.17742, July 2026) reports 75.5% accuracy when the model is given the correct table, against 33% with top-5 retrieval. Answerability-aware reranking lifts top-1 from 18.2% to 57.4% (authors' figures). Better, but still far from exact.
  • Financial hard negatives defeat embedders. FinRank (arXiv 2608.07400, August 2026) finds the best embedder reaches 44.8% Recall@10 over 22 companies' filings, sub-1B encoders gain at most 3.5 points over BM25, and a finance-tuned embedder trails BM25 by 9.7 points (AUTHOR-ONLY, small benchmark).
  • Agents do not complete tables. WideSearch (arXiv 2508.07999, August 2025) tested more than ten agent systems on "fill this whole table" tasks: most scored near 0% whole-table success, the best 5%, humans near 100%. Ko-WideSearch (arXiv 2606.27595, June 2026) shows where it breaks: agents recover the set of entities (Item-F1 92.8) but not the rows (Row-F1 53.7) (AUTHOR-ONLY). Filling cells is the hard part.
  • LLM-built knowledge graphs are too expensive at deal-room scale, and no longer necessary. HippoRAG 2 spends about 6.2M LLM tokens to index 2Wiki; LinearRAG (arXiv 2510.10114, revised November 2025) builds a relation-free graph with zero LLM tokens in 250 s against 1,147 s, and reports higher accuracy (63.7 vs 55.0 on 2Wiki, against a re-run baseline; AUTHOR-ONLY). Microsoft's LazyGraphRAG (blog, 2024-11-25) indexes at 0.1% of GraphRAG's cost (VENDOR). At 10M documents, LLM triples over every passage would be about 26B output tokens by our estimate: months of compute on local hardware.
  • Free-form text-to-SQL is not exact either. The best BIRD test score is 82.39% execution accuracy against 92.96% for humans (BIRD leaderboard, entry dated 2026-08-22, REPORTED).

So a buyer asking "is this up to date?" in 2026 is asking a fair question. The current approach, which the rest of this page describes, stops asking a model to produce the number. The model helps decide which question is being asked. Code, a database and a set of release gates decide which value comes back.

The design in one picture

Ingest turns every document into typed facts with cell-level provenance. Deterministic parsers do most of the work; small classifiers tag headers; no LLM runs over every page.

flowchart TD
  A["Deal-room upload
per-deal partition + ACL"] --> DD["Near-duplicate and version detection"] DD --> B{"Router by source type"} B -->|iXBRL / xlsx / HTML| C["Deterministic cell parsers"] B -->|born-digital PDF| E["Docling text cells + bbox"] B -->|scan| H["OCR path: never EXACT"] E --> TD["Table detection + structure (TATR)"] C --> GR[("Table grid, JSONB + bboxes")] TD --> GR H --> GR GR --> I["Header tagger (Laya) + unit, scale,
sign, period, currency parsers"] I --> QA["Ingest identity checks"] E --> RR["Independent geometric re-reader"] QA --> EX["EXACT gates"] RR --> EX EX --> K[("Fact store: bitemporal, tiered")] E --> N[("Passage layer: BM25 + halfvec")]

Query time never lets a model write SQL or produce a figure.

flowchart TD
  Q["Question + user ACL"] --> R0{"Intent router confident?"}
  R0 -->|yes| IR["Closed query IR"]
  R0 -->|no| P["Local planner, grammar-constrained, 3 runs"]
  P -->|runs agree| IR
  P -->|disagree| CL["CLARIFY or DECLINE"]
  IR --> TC["IR type-check: registry, units,
additivity, dates confirmed"] TC -->|fail| CL TC --> CMP["Deterministic compiler to SQL"] CMP --> SQL["Read-only SQL under RLS"] SQL --> PX["Post-exec checks + Python recompute"] PX -->|miss or conflict| RET["Hybrid retrieval: candidates only"] PX --> PT["Policy table: tier per cell"] RET --> PT PT --> A["LLM prose around SQL values"] A --> NG["Numeric-token guard"]

The hybrid passage stack (BM25, vectors, a late-interaction reranker) is still there. Its job has changed: it finds evidence and proposes candidates when a slot is empty or disputed. It never fills an exact cell.

Where every figure comes from

Facts are relational rows in Postgres 18, partitioned per deal, with row-level security on every table. The graph (documents, sections, tables, entities, people) is typed Postgres tables walked with recursive CTEs, built without LLM calls.

Table What it holds (key fields)
document source type (iXBRL, xlsx, HTML, native PDF, scan), doc class, doc date, version_of, superseded_by, parse status
tbl one JSONB grid per table: raw text, parsed value, bbox, header paths; stated scale and where the scale came from
fact entity, metric, dims (customer, segment, facility), period, fiscal basis, value (NUMERIC), as-printed string, unit kind, scale, currency, vintage, valid_from/valid_to, source cell, tier, gate results
fact_relation subtotal_of, derived_from, restates, addback_of
clause_fact / clause_attr clause type, presence, and attributes such as consequence (terminate, consent, prepay), notice days, with spans
deal_target_list the human-confirmed list of targets: the authoritative M in "N of M"
deal_parameter user-confirmed dates such as expected closing; never inferred by an LLM
query_audit the IR, its English rendering, compiled SQL hash, every check result, tiers, N of M, guard result

Cell-level provenance. Every value can be clicked through to a page crop with the cell's bounding box highlighted, its header path, its scale source ("in thousands, running header p.3"), its vintage, which corroborations passed, and the query_audit id.

Restatements and vintages. Facts are bitemporal and never deleted. The default is the latest audited value, restated if a restatement exists, with the original shown beside it as a flag. Drafts are kept but ignored once a final exists. If a fact is re-tagged, every past answer that cited it is listed for invalidation.

The exactness layer

The rule the whole design enforces, adopted from "Never the Number" (arXiv 2608.13926, AUTHOR-ONLY):

A component that can fabricate may influence which question is answered, never which value is returned.

Four mechanisms make that testable:

  1. Closed query plan. The planner (a local Qwen3-8B, decoded under a grammar) can only emit a small, typed IR over a governed metric registry. If the IR cannot express the question, the answer is DECLINE or CLARIFY, never an approximation.
  2. Deterministic SQL compiler. Each IR operation maps to a fixed, parameterised SQL template. The model never writes SQL text. The compiler is tested in CI against an independent pure-Python evaluator on thousands of generated fact stores; any divergence blocks the merge.
  3. Recompute. Every derived value (margins, net debt, leverage) is computed in SQL and recomputed in Python with exact decimals. They must match.
  4. Numeric-token guard. The LLM writes only the prose around the values. Every number and date in that prose must equal a value recorded in query_audit. One regeneration is allowed, then the answer is blocked and escalated.

EXACT release gates. A value is released as EXACT only if all six hold:

  • its printed string sits inside the cited cell's bounding box in the PDF text layer (so OCR-derived values can never be EXACT);
  • an independent geometric re-read (row label and column header from text positions alone) agrees with the table model;
  • the scale source is known;
  • tag confidence clears a calibrated threshold;
  • at least one corroboration exists: an identity check (segments sum to total), cross-document agreement, a roll-forward, or iXBRL/xlsx provenance;
  • no conflict is unresolved.

Anything less is released as VERIFIED-PARTIAL with reasons, shown as MISSING or CONFLICT with candidate links, or withheld. A derived value takes the lowest tier of its inputs.

N-of-M set answers. "For every target" means the targets on the human-confirmed list. The answer header says "N of M targets, M from [source], confirmed by [user] on [date]", and every cell that is not EXACT says why.

Abstain and clarify. A non-December year-end asked about "FY2025", "margin" with no basis, mixed currencies without an FX step, or an unconfirmed closing date all trigger CLARIFY. An empty slot abstains. A question about an undefined metric is declined, naming the nearest registered metrics.

Twelve hard questions, answered exactly

# Question How it is answered Provenance shown
1 FY2025 audited consolidated revenue for Target X, and where is it printed? Registry slot with vintage precedence; a conflict probe over the same key; ambiguous FY triggers CLARIFY Cell crop, header path, scale source, vintage, corroborations
2 For every target: FY2025 revenue, EBITDA margin, change-of-control clause Confirmed target list × three slots; reported margin first, else derived with same entity, period, basis, currency; CoC is tri-state Tier-badge grid under "N of M"; margin formula card; clause span or contracts scanned/unparsed with recall
3 Adjusted EBITDA, and does the addback bridge reconcile? Addback facts linked by subtotal_of; SQL checks reported + addbacks = adjusted at printed precision Each addback cell, the identity result, any gap
4 Was FY2024 revenue restated? Both vintages joined by a restates relation Both documents and cells, dates, restatement evidence
5 Gross debt, cash, net debt, net debt / LTM EBITDA per target Latest balance-sheet date per entity; LTM as four contiguous quarters; misaligned dates shown, not hidden Formula card, every input cell, both dates
6 Which CIM figures differ from the audited accounts? Self-join on the full slot key; tolerance is the printed precision Paired cells side by side
7 Which targets' EBITDA margin fell more than 200 bps? Both margins derived or reported; difference computed in SQL; unit parsing separates fraction from points Formula card per target and year
8 Leverage covenant compliance at the last four test dates? Compliance-certificate figures first; ratio recomputed must match; no certificate means "proxy", never a verdict Certificate cells, threshold span, EBITDA definition span
9 Facilities with a CoC mandatory prepayment, and principal repayable at closing? Closing date confirmed by a user; target → facility → amendments; sum released only if every facility resolves Each hop's evidence span, clause span, principal cells, FX source
10 Top-10 customer share of revenue; does the list sum to total? Customer facts with a customer dimension; identity check; unconfirmed entity merges never summed Customer table cells, identity result, merge candidates
11 Ownership chain above Target X; who holds more than 25%? Recursive CTE over document-sourced shareholder edges; percentages multiplied exactly Every edge's document, date and span
12 FY2025 was 53 weeks: growth on a 52-week basis? No reported 52-week figure means ABSTAIN; pro-rating would be an estimate The reason, plus documents and periods searched

Question 12 is deliberately unanswerable. A system that returns a number for it has failed.

An event flow

Question 2, the owner's own example, from question to grid:

sequenceDiagram
  participant U as Analyst
  participant R as Router / planner
  participant C as Compiler
  participant DB as Postgres (RLS)
  participant V as Verifier
  participant G as Prose + guard
  U->>R: For every target, FY2025 revenue, EBITDA margin, CoC clause
  R->>R: Match template, check FYE conventions
  R-->>U: Rendered IR in plain English
  U->>R: Confirm
  R->>C: IR over confirmed target list, 3 slots
  C->>DB: Parameterised SQL (read-only role)
  DB-->>V: One row per target and slot
  V->>V: Cardinality, conflicts, unit checks
  V->>V: Recompute margin in Python
  V->>V: Apply EXACT gates and policy table
  V-->>G: Values with tiers, N of M
  G->>G: Every number in prose equals a SQL value
  G-->>U: Grid with tier badges and provenance cards

Code walkthrough (design sketches)

These are design sketches, not shipped code. They show the shape of each step.

Design sketch: the IR for question 2. The planner can only produce this structure; it cannot name a table or a column.

{
  "targets": {"confirmed_list": "deal_42"},
  "slots": [
    {"metric": "revenue", "period": "FY2025", "basis": "reported"},
    {"metric": "ebitda_margin", "period": "FY2025",
     "prefer": "company_reported",
     "fallback": {"derive": "ratio(ebitda, revenue)",
                  "require_same": ["entity", "period", "basis", "currency", "vintage_class"]}},
    {"clause_type": "change_of_control", "state": "tri_state"}
  ],
  "output": "table"
}

Design sketch: the compiled shape of one slot. Vintage precedence picks the value; a separate count detects conflicts instead of silently choosing one.

WITH slot AS (
  SELECT DISTINCT ON (f.entity_id)
         f.entity_id, f.value, f.as_printed, f.fact_id, f.tier
  FROM   fact f
  JOIN   deal_target_list t USING (deal_id, entity_id)
  WHERE  f.deal_id = $1 AND f.metric_id = $2
    AND  f.period_end = $3 AND f.basis = $4 AND f.status = 'active'
  ORDER  BY f.entity_id, f.vintage_rank, f.source_doc_date DESC
),
probe AS (
  SELECT entity_id, COUNT(DISTINCT value) AS n_values
  FROM   fact
  WHERE  deal_id = $1 AND metric_id = $2 AND period_end = $3 AND basis = $4
    AND  status = 'active' AND vintage_rank = 1
  GROUP  BY entity_id
)
SELECT s.*, (p.n_values > 1) AS conflict
FROM   slot s JOIN probe p USING (entity_id);

Design sketch: the numeric-token guard. Any number in the prose that is not a released value blocks the answer.

def numeric_guard(prose: str, audit: QueryAudit) -> bool:
    allowed = {normalise(v) for v in audit.released_values()}
    allowed |= {normalise(d) for d in audit.released_dates()}
    for token in extract_numbers_and_dates(prose):
        if normalise(token) not in allowed:
            audit.record_guard_failure(token)
            return False
    return True

How accuracy will be proven

Decision: prove accuracy before building at full scale. The full build (about 22,000 lines, estimated) is postponed. The first deliverable is a seeded synthetic deal room and a test set that can falsify the design.

The synthetic deal room. It is built from public filings (SEC EDGAR, ESEF) and existing example deal documents, grouped into virtual deal rooms with defined target lists. Inline XBRL tags are stripped and documents are re-rendered to PDF, scan and HTML with our own renderer, which gives cell-level gold bounding boxes. Planted traps include restatements, drafts, near-duplicates, scale stated only in a running header, per-share rows inside "in thousands" tables, fraction vs points vs bps, 52/53-week years, year-end ambiguity and mixed currencies. Board-pack-style templated documents supply customer, covenant and working-capital facts; that gold is synthetic and labelled as synthetic.

DealRoom-Exact-v1. A frozen, hashed (sha256) and pre-registered test set of templated questions across eleven answer classes, stratified by source type and query type (lookup, set, derived, period, multi-hop, clause, reconciliation). About 25% of items should produce CLARIFY or ABSTAIN. Every system/gold disagreement is adjudicated by two people; gold corrections are published, never applied silently. Development happens on a separate split; the test split runs once.

Public benchmarks, for comparability.

Layer Benchmark Published reference
Exact figures LEDGER Single-KPI R 91.4 / P 93.5 (published)
End-to-end RAG T2-RAGBench Hybrid BM25 41.7 vs oracle 76.2 Number Match (published)
Set answers S-RAG Hotels VectorRAG 0.352 vs gold schema 0.845 (published)
Hard negatives FinRank, FinanceBench Best R@10 44.8% (published, AUTHOR-ONLY)
Clauses CUAD + EX-10 Change-of-control recall at our threshold
Compiler Differential tests vs Python reference 0 divergences, as a CI gate

A control arm, the plain hybrid stack answering with LLM extraction, runs on the same public data.

Metrics. Exact match on all nine fields (value at printed precision, period, unit kind, scale, currency, entity, basis, vintage, source cell); coverage (share of answerable slots released as EXACT); false releases; correct-abstention rate; Item-F1 and Row-F1 for sets; per-hop evidence for multi-hop, where a right answer by a wrong path counts as an error.

The exact claim wording. Every claim takes this form, per source type, with native PDF leading and nothing pooled:

"On DealRoom-Exact-v1 (sha256 ⟨hash⟩, pre-registered ⟨date⟩), pdf_native stratum: n = ⟨n⟩ answerable slots; K released as EXACT; all K matched the adjudicated gold on all 9 fields: 0 errors; one-sided 95% upper bound on the EXACT error rate = 1 − 0.05^(1/K) (Clopper-Pearson). Coverage K/n; the withheld slots are listed with reasons."

With zero errors the bound is roughly 3/K:

Claim Exact figures needed (0 errors) Status
Floor: EXACT error rate under 1% K ≥ 299 (about 300) Target, not yet measured
Stretch: under 0.1% K ≥ 2,995 (about 3,000) Target, not yet measured

These process metrics must be exactly zero for any such statement: numeric-guard escapes, ACL leaks, EXACT values from OCR or fallback paths, and compiler divergences.

What we will not claim. Accuracy on private deal-room documents beyond a small hand-labelled stand-in set; end-to-end precision across 10M documents; "10M processed" before it has been measured; any comparison with a vendor's accuracy. The weakest classes (covenants, clause attributes, related parties) start at VERIFIED-PARTIAL by policy until measured.

Scale, honestly

Measured (once built): everything in the accuracy section, on labelled data.

Shown as curves at 1M, 3M and 10M documents: retrieval recall, p50/p95 latency by query type, and gate behaviour (tier distribution, abstention rate). The larger points pad the seeded room with unlabelled public filings. The 10M point covers BM25 plus a sampled or quantized vector tier.

Extrapolated, and labelled as such: full-corpus processing at 10M documents. Our estimate is roughly 100M pages and months of serial compute on the target hardware (an M2 Max and a T4). No published method has been tested at 10M documents, and neither have we.

The pilot. The first milestone is a 10,000-document pilot that measures per-stage throughput, table-detection rates, the error overlap between the table model and the geometric re-reader, EXACT-tier coverage on the development split, partition overhead and latency.

Storage (estimated). Half-precision 1024-d vectors for 100–300M passages come to 200–600 GB before index overhead, which does not fit in RAM. The plan: HNSW indexes per deal partition, a binary-quantized first pass, cold deals on disk, and external NVMe as a hard requirement. Storing each table as one JSONB grid, with fact rows only for mapped cells, keeps the fact table far smaller than one row per cell.

How this compares with the market

What vendors say on their own pages (VENDOR, fetched 2026-09-24 and summarised; claims only, not tested by us):

Vendor Published claim Method or test set published?
Hebbia Matrix Company × metric grid with a basis column and a shown derivation No accuracy figure on the pages fetched
AlphaSense Sentence-level citations; 500M+ documents No method
Daloopa ">99% accuracy across millions of data points", every number source-linked No method, test set or error list
Datasite + Blueflame "3x higher retrieval accuracy"; every answer cited No baseline or method
Kira (Litera) "95%+ precision on target provisions" No recall figure

These are serious products, and several do things this design copies: Hebbia's grid and shown derivation, and Excel deliverables. We have not benchmarked any of them and will not publish comparative numbers we did not measure.

The difference we can offer is method, not a bigger number:

  1. A published error list. Per-field exact match per source type, coverage, false releases and every abstention, on a hashed, pre-registered set. None of the pages above publishes that.
  2. A testable "no figure from an LLM" mechanism: closed IR, compiler, recompute, numeric guard.
  3. Restatement and draft handling with bitemporal provenance.
  4. Set completeness against a human-confirmed target list.
  5. Absence claims with measured recall, not an unqualified "no".
  6. Fully local processing, so deal documents never leave the client's hardware.

Sources

All accessed or checked 2026-09-24. Labels: REPORTED (primary source, partly checked), AUTHOR-ONLY (authors' own claim, not reproduced), VENDOR (vendor's own material).

Tech stack

Postgrespgvector + BM25Bitemporal fact storeDeterministic SQL compilerLayaLettuceDetect / Berry

Other 2026 work