Parvez Ahmed

Case study · DocVal

How I measure accuracy in document-extraction AI pipelines

Parvez Ahmed · Numbers current as of the held-out re-run of 18 July 2026, after the per-page extraction fix.

DocVal's extraction took a day or two. Knowing when it was wrong took the rest. This is how I measure it, and what measuring taught me.

Three layers, in order of trust

  1. Schema, cheapest. Every extraction lands in one Pydantic model: typed dates, decimal amounts, one of debit or credit per row.
  2. Deterministic validation, free on every document. Balances must reconcile, the running balance must chain, dates must stay in the period. No model in the loop, so no false authority.
  3. The eval harness, costs money, run on purpose: ground truth on a held-out set.

Decisions that mattered

Matches are earned, not fuzzy. A predicted row matches gold only on exact date and exact signed amount; description similarity only breaks ties. In finance a wrong amount is the error.

Split by source. The held-out F1 is 0.63. The split is the real story:

0.63 transaction F1, held out — the number on its own

0.92 synthetic statements
0.42 real scanned Indian bank statements — dense 6-page, ~160 transactions was 0.12 before the paged-extraction fix

One aggregate number would have hidden both that the pipeline works and where it breaks.

Beware circular evals. My synthetic generator and my native parser know the same layouts, so scoring one on the other partly measures agreement between two things I wrote. Only real third-party documents get full trust.

Twin leakage. The public dataset ships each statement twice, as a PDF and as a scan with identical ground truth. A random split leaks one twin into held-out. Twins now always share a split.

Cost is a metric. Every eval row records tokens and dollars: $0.008 per document, retries billed.

The regression gate

CI runs a free, offline subset on every push against a committed baseline, with a 1-point tolerance:

uv run python -m eval.run_eval --manifest tests/fixtures/ci_manifest.jsonl --no-vision
uv run python -m eval.compare results.json eval/baseline_ci.json

Nobody runs evals by hand. A red ✗ on a PR survives a deadline.

What the harness caught

The numbers

Held-out set · 100 documents · never used for tuning
MetricValue
Transaction F10.63synthetic 0.92 / real scans 0.42
Header field accuracy0.87
Validation pass rate0.25
Error rate0.01
Mean cost per document$0.008

What I learned

  1. Deterministic checks beat an LLM judge. Arithmetic doesn't hallucinate.
  2. Split accuracy by source; averages hide exactly what you need to know.
  3. Separate transient failures from persistent ones before fixing anything.
  4. Your worst honest number is your roadmap.

DocVal is open source: github.com/Sero01/docval. Live demo: docval-yy4s.onrender.com. I build document-extraction and reconciliation systems for financial operations.