Skip to main content
AI9 min readUpdated

AI Document Extraction for Finance: Filings, Statements, and Invoices at Production Accuracy

A pipeline for turning PDFs — 10-Ks, bank statements, invoices, term sheets — into validated structured data, with the confidence scoring and reconciliation checks that make the output trustworthy enough to post to a ledger.

Summary

Extraction accuracy in finance is not a model problem — it is a verification problem. The pipeline that works is: faithful document-to-text conversion, page-level routing, schema-constrained extraction with mandatory source spans, then deterministic validation that re-checks every number against arithmetic the document itself must satisfy. Anything that fails validation routes to a human queue rather than into your database. That last rule is what separates a demo from a system finance teams will actually use.

PythonTypeScriptClaude APIPostgresNext.js

Conversion comes before extraction

Most extraction failures happen before the model is called. A financial PDF is a layout, not a document: multi-column text, footnotes that modify the table above them, values in parenthesis meaning negative, units declared once in a header three pages earlier. If your text conversion flattens a table into a stream of numbers, no amount of prompting recovers the meaning.

  • Preserve table structure explicitly — convert tables to markdown or HTML with cells intact, never to whitespace-aligned text.
  • Keep page numbers and bounding boxes for every extracted block; you need them for citation and for the human review UI.
  • Detect scanned pages and route them through OCR separately, with a quality score you can act on.
  • Carry column headers and unit declarations ('in thousands, except per-share amounts') into every row you extract from that table.
  • For vision-capable models, sending the page image alongside the text is often more accurate than either alone — particularly for statements with heavy visual structure.

Schema-constrained extraction with mandatory provenance

Never ask for free-form output and parse it. Define the target schema as a tool and force the model to fill it. Crucially, every extracted value carries the page and the verbatim source text it came from — provenance is not a nice-to-have, it is what makes review possible and what lets you measure accuracy without re-reading the whole document.

const lineItem = {
  name: "extract_line_items",
  input_schema: {
    type: "object",
    properties: {
      items: {
        type: "array",
        items: {
          type: "object",
          properties: {
            label: { type: "string" },
            value: { type: "number" },
            unit: { type: "string", enum: ["USD", "USD_THOUSANDS", "USD_MILLIONS"] },
            period: { type: "string", description: "ISO date or YYYY-QN" },
            page: { type: "integer" },
            sourceText: {
              type: "string",
              description: "Verbatim text containing this value, copied exactly.",
            },
            confidence: { type: "number", minimum: 0, maximum: 1 },
          },
          required: ["label", "value", "unit", "period", "page", "sourceText"],
        },
      },
    },
    required: ["items"],
  },
} as const;

Then verify the provenance mechanically: if sourceText does not appear in the page it claims, discard the row. This single check catches a large share of fabricated values at near-zero cost, and it does not depend on the model being honest about its own confidence.

Validation is where accuracy actually comes from

Financial documents are full of redundancy, and redundancy is free verification. Use it. These deterministic checks catch errors that no confidence score will.

  • Assets equal liabilities plus equity, within a rounding tolerance derived from the stated units.
  • Subtotals equal the sum of their components; totals equal the sum of subtotals.
  • Period-over-period continuity: closing balance of one statement equals opening balance of the next.
  • Cross-statement agreement: net income on the income statement matches the top of the cash flow statement.
  • Magnitude sanity: a value 1,000x its prior period is almost always a units error, not a business event.
  • For invoices: line items sum to subtotal, tax rate applied to subtotal reproduces stated tax, and the sum reproduces the total.

Every failed check becomes a targeted re-extraction: send the model the specific page and the specific inconsistency, and ask it to resolve that one thing. Re-running the whole document rarely helps; a narrowed second pass usually does.

Confidence, routing, and the human queue

Model-reported confidence is weakly calibrated and should never be your only gate. Build a composite score from signals you control, and route on it.

SignalWeight in practice
Provenance string verified on the cited pageHard gate — fail means reject
Arithmetic validation passedHard gate for financial statements
Agreement across two independent extraction passesHigh
OCR quality score for the source pageMedium
Model self-reported confidenceLow — tiebreaker only

Set the auto-accept threshold from the cost of an error, not from a target automation rate. If a wrong number posts to a general ledger, the correct threshold is high and the review queue is a feature. Track the human corrections — they are your eval set, they tell you exactly which document types are failing, and after a few hundred of them they justify targeted fixes far better than any general prompt tuning.

Frequently asked questions

How accurate is LLM extraction on financial documents?
Raw single-pass extraction on clean digital PDFs is typically in the mid-to-high nineties per field. With provenance verification, arithmetic reconciliation, and targeted re-extraction on failures, a well-built pipeline reaches accuracy high enough to auto-post the validated majority while routing a small remainder to review — which is the shape finance teams actually want.
Is this cheaper than an existing document AI vendor?
For standard forms with mature vendor templates, often no. Custom pipelines win when documents are non-standard, when the schema is specific to your business, when data cannot leave your infrastructure, or when you need the extraction logic to evolve weekly. Compare on total cost including the review labour, not on per-page price.
Can this handle scanned or handwritten documents?
Scanned documents, yes — with an OCR stage and a page-quality score that routes poor scans to review. Handwriting is materially harder and accuracy varies widely with legibility; treat any handwritten field as review-required by default rather than assuming parity with printed text.

Building something like this?

I'm Harsh Mittal — I build production systems across Web3, AI, and financial infrastructure: smart contracts and DeFi protocols, RAG pipelines and LLM agents, market data infrastructure, and the interfaces on top of them. If this is the kind of problem you're working on, I can help you ship it.