Receipt Audit

RA-Bench: receipt extraction, fake detection, evidence and duplicates

We build Receipt Audit and we run this benchmark. Our engine is one of the targets, so read it as a vendor benchmark: headline numbers come from a held-out set nobody tuned on, every run is kept (incomplete ones included), and losses are listed first. The public cases, their labels, every target's outputs and the harness are available on request: email hello@godard.me for them, to reproduce a number or to dispute one.

Reproduce it with cd apps/receipt-check && nub run bench run --cases benchmarks/receipt-audit/heldout-cases.jsonl --split private --targets self,diy:claude-haiku-5.5,diy:gemini-3.8-flash,decision:gpt-6-luna --reps 3. Dispute a number: hello@godard.me.

Preliminary · n=151 cases · 3 repetitions · run 2026-10-18 · suite 0.2.0 · private splitRun 20261018T165538Z-0363fe, 2026-10-18T16:55:38.100Z to 2026-10-18T17:12:34.377Z. Dataset hash 9cfa2c4c2d6e596579470768c8b182ffc7d264123e0c0fc7ee707a6de9dd6d79, git 36dc1354ad55.

Where we lose

  • Price: $0.016 a check, 16× a plain Claude Haiku call ($0.0010), 4× plain Gemini 3.8 Flash ($0.0044) and about 90× the OpenAI decision model ($0.00017). What it buys is listed under the wins.
  • Speed and consistency against the decision model: it answers in 0.4 s at the median (ours 4.1 s) and gave the same decision in every repetition (ours 97%). It catches far fewer fakes (see the wins).

Where we win

  • Duplicates: 99% of resubmitted copies recognised (re-encoded, resized, cropped, rotated, screenshotted and re-photographed; n=30), with 4.5% false matches on look-alike receipts (n=11). Plain prompts and decision models have no memory across checks and can't do this.
  • Proof: 53% of fakes were caught with evidence a caller can re-check without trusting a model (amounts that don't add up, an AI or receipt-generator tag in the file, an edit record, a duplicate match); 2.5% of genuine receipts carried such a finding. A plain prompt or a decision model only gives an opinion.
  • Against the OpenAI decision model: 78% of fakes caught at a 5% false-flag rate vs 35%, and a ROC AUC of 0.88 vs 0.64.
  • Speed against plain prompts: a median of 4.1 s vs 6.6 s (Gemini) and 8.6 s (Claude).

Ties

  • Catching fakes against plain prompts, ahead but not proven: 78% caught at a 5% false-flag rate vs 65% (Gemini) and 53% (Claude); ROC AUC 0.88 vs 0.81. With 40 fakes the 95% intervals overlap.
  • Expected cost of decisions per 1,000 receipts at risk ≥ 0.7 (1% of receipts fake, $100 per missed fake, $17 per false flag; sources in the methodology): $483 vs $683 (Gemini), $753 (Claude), $800 (decision model) and $1,000 for approving everything. The intervals overlap with the plain prompts.
  • False flags at risk ≥ 0.5: 2.1% of genuine receipts vs 1.2% (Gemini), 2.9% (Claude) and 0% (decision model); not a proven difference.
  • Extraction: 97% of documents with every labelled field right, vs 94% for both plain prompts.
  • Naming the edited field on edited fakes: 33%, vs 30% (Gemini) and 37% (Claude).
  • Calibration error of the risk: 8%, vs 10–12% for the plain prompts and 18% for the decision model.

Targets

Every target ran the same cases with the same scoring and timeout; only the target changes.

What ran, pinned to a version
TargetKindVersionPrices as ofNot supported
Receipt Audit (Claude Haiku 5.5)selfoursengine.0.2.1+anthropic/claude-haiku-5.5+prompt.9a2a5877e979+git.36dc1354ad552026-10-18—
Claude Haiku 5.5, plain promptdiy:claude-haiku-5.5DIY baselineclaude-haiku-5-5@claude-code-cli+prompt.512dfb3893be+git.36dc1354ad552026-10-18Arithmetic and tax checks, Duplicates, Proof, not opinion
Gemini 3.8 Flash, plain promptdiy:gemini-3.8-flashDIY baselinegoogle/gemini-3.8-flash+prompt.512dfb3893be+git.36dc1354ad552026-10-18Arithmetic and tax checks, Duplicates, Proof, not opinion
OpenAI decision model (gpt-6-luna), asked if it's fakedecision:gpt-6-lunaDIY baselineopenai/gpt-6-luna-decisions+prompt.977e84771afc+git.36dc1354ad552026-10-12Field extraction, Arithmetic and tax checks, Duplicates, Proof, not opinion
Configuration of self
{
  "engine": "ReceiptChecks (apps/receipt-check/lib/server/ReceiptChecks.ts), in process",
  "engineVersion": "0.2.1",
  "production": true,
  "model": "anthropic/claude-haiku-5.5",
  "prompt": "apps/receipt-check/lib/receipts/model.ts (INSTRUCTIONS, USER_PROMPT and the ModelAnalysis output schema)",
  "promptSha256": "9a2a5877e9795c37df6d2c2cb89f56cae64b76ae10bbd672cc1ab6bb41426a71",
  "instructionsSha256": "da94bf411e9d3c47462a4d77b59a373a2b478a3e6afffb86889dc5e93ad452fb",
  "hashVersion": 2,
  "endpoint": "https://ai-gateway.vercel.sh/v1/chat/completions",
  "reasoningEffort": "low",
  "structuredOutput": true,
  "modelTimeout": "90 seconds",
  "modelRetries": "2, on errors effect/ai marks retryable",
  "filePreparation": "upright, at most 3072 px and 3.75 MB (lib/receipts/model.ts)",
  "duplicateNetwork": "the production Convex functions on an in-memory backend, empty at the start of each repetition",
  "submitter": "one account per case (the case id)",
  "gitSha": "36dc1354ad557fcc6490a5cd144f8960073941db",
  "pricePerCheckUsd": 0.01633333333333333,
  "cost": "cost_per_check_usd is what a caller pays Receipt Audit per check: the list price of the smallest credit pack (credits-3k: $49 for 3,000 checks, $0.0163 per check). model_cost_per_check_usd (and each result's costUsd) is the engine's own model call: tokens × the AI Gateway list price on the price date."
}
Configuration of diy:claude-haiku-5.5
{
  "transport": "claude-code-cli",
  "endpoint": "http://127.0.0.1:8789/v1/messages (claude-local: one headless `claude -p` per call, no tools, no MCP, no settings, empty cwd)",
  "billing": "subscription",
  "cli": "claude 2.1.296",
  "thinking": "Claude Code default (adaptive; can't be fully disabled via the CLI)",
  "ignored": [
    "max_tokens"
  ],
  "costUsd": "API-equivalent: the reported tokens × the AI Gateway list price, i.e. what the check costs when called through the API",
  "usage": "as the CLI reports it, including Claude Code's own cached system prompt (about 3,500 tokens, mostly cache reads); inputTokens counts uncached, cache-read and cache-write tokens",
  "model": "claude-haiku-5-5",
  "max_tokens": 4096,
  "message": "one user turn: the file (image block, or document block for a PDF), then the prompt",
  "rateLimits": "HTTP 429 (a subscription limit on claude-local) is retried with backoff like a transport error",
  "prompt": "apps/receipt-check/bench/prompt.ts (DIY_PLAIN_PROMPT)",
  "promptSha256": "512dfb3893be5aa7f89ff7be2259d8f9c94c948fe8b2d6e3dca9bc6f5889bc37",
  "gitSha": "36dc1354ad557fcc6490a5cd144f8960073941db",
  "cost": "cost_per_check_usd and model_cost_per_check_usd are both the API-equivalent token cost: tokens × the AI Gateway list price on the price date"
}
Configuration of diy:gemini-3.8-flash
{
  "endpoint": "https://ai-gateway.vercel.sh/v1/chat/completions (Vercel AI Gateway, OpenAI-compatible)",
  "model": "google/gemini-3.8-flash",
  "max_tokens": "not set (provider default)",
  "reasoning": "not set (provider default)",
  "temperature": "not set (provider default)",
  "message": "one user turn: the file (image_url part, or a file part for a PDF), then the prompt",
  "prompt": "apps/receipt-check/bench/prompt.ts (DIY_PLAIN_PROMPT)",
  "promptSha256": "512dfb3893be5aa7f89ff7be2259d8f9c94c948fe8b2d6e3dca9bc6f5889bc37",
  "gitSha": "36dc1354ad557fcc6490a5cd144f8960073941db",
  "cost": "cost_per_check_usd and model_cost_per_check_usd are both the API-equivalent token cost: tokens × the AI Gateway list price on the price date"
}
Configuration of decision:gpt-6-luna
{
  "endpoint": "https://ai-gateway.vercel.sh/v1/decisions (Vercel AI Gateway → OpenAI Decisions API, public beta)",
  "model": "openai/gpt-6-luna-decisions",
  "questions": "apps/receipt-check/bench/prompt.ts (DECISION_QUESTIONS): a predicate (is it fake) and two choices (how it was made, which part looks altered)",
  "questionsSha256": "e17d0fb5b77b6147ffdb12fa1e22ed2dc99e5edec56d69731f297ad2c86aca37",
  "input": "one user message: DECISION_INPUT_TEXT and the image as a base64 data URL; a PDF's first page rendered at 150 dpi (pdftoppm)",
  "risk": "the predicate's probability",
  "reasons": "the two choices, in plain words",
  "cost": "input tokens × $0.1 per 1M (no output charge), price of 2026-10-12",
  "gitSha": "36dc1354ad557fcc6490a5cd144f8960073941db"
}

Results

Each cell: the value, its 95% interval and n. Cost per check and p95 latency sit under each task's accuracy. "Not supported" means the target can't do that task by construction.

Field extraction

Field extraction: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Merchant righthigher is better98.2%95% interval 93.2–99.6%n=9597.9%95% interval 92.6–99.4%n=9598.2%95% interval 93.2–99.6%n=95not supported
Date righthigher is better98.8%95% interval 94.6–99.7%n=11297.0%95% interval 92.0–98.9%n=11299.1%95% interval 95.1–99.8%n=112not supported
Currency righthigher is better100.0%95% interval 95.8–100.0%n=88100.0%95% interval 95.8–100.0%n=88100.0%95% interval 95.8–100.0%n=88not supported
Subtotal righthigher is better93.8%95% interval 71.7–98.9%n=1689.6%95% interval 66.4–97.4%n=1685.4%95% interval 61.6–95.5%n=16not supported
Tax righthigher is better100.0%95% interval 83.9–100.0%n=20100.0%95% interval 83.9–100.0%n=20100.0%95% interval 83.9–100.0%n=20not supported
Total righthigher is better100.0%95% interval 96.9–100.0%n=12199.4%95% interval 95.9–99.9%n=12198.1%95% interval 93.8–99.4%n=121not supported
Receipt number righthigher is better100.0%95% interval 74.1–100.0%n=11100.0%95% interval 74.1–100.0%n=11100.0%95% interval 74.1–100.0%n=11not supported
All key fields righthigher is better96.7%95% interval 91.8–98.7%n=12193.7%95% interval 87.8–96.8%n=12193.9%95% interval 88.2–97.0%n=121not supported
Line items (F1)higher is better97.3%95% interval 93.6–100.0%n=2093.4%95% interval 86.9–98.5%n=2091.1%95% interval 83.3–97.7%n=20not supported
Valid structured outputhigher is better100.0%95% interval 96.9–100.0%n=121100.0%95% interval 96.9–100.0%n=121100.0%95% interval 96.9–100.0%n=121not supported
Price per checklower is better$0.0163n=453$0.0010n=453$0.0044n=453not supported
Latency p95lower is better8,907 msn=45313,551 msn=45315,763 msn=453not supported

Broken down by printed amounts, genuine receipt it was made from, capture, fake class, country, currency, date era, document type, language, provenance metadata, source in the Markdown and JSON versions.

Arithmetic and tax checks

Arithmetic and tax checks: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Totals that don't add up, caughthigher is better38.9%95% interval 20.3–61.4%n=18not supportednot supportednot supported
Totals flagged that really don't add uphigher is better87.5%95% interval 52.9–97.8%n=8not supportednot supportednot supported
False alarms on genuine receiptslower is better1.3%95% interval 0.2–6.9%n=78not supportednot supportednot supported
Receipts the checks could run onhigher is better59.8%95% interval 50.9–68.1%n=121not supportednot supportednot supported
Price per checklower is better$0.0163n=453not supportednot supportednot supported
Latency p95lower is better8,907 msn=453not supportednot supportednot supported

Broken down by printed amounts, genuine receipt it was made from, capture, fake class, country, currency, date era, document type, language, provenance metadata, source in the Markdown and JSON versions.

Edited or generated receipts

Edited or generated receipts: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
ROC AUChigher is better0.88395% interval 0.804–0.949n=1210.80795% interval 0.711–0.898n=1210.80895% interval 0.703–0.895n=1210.63795% interval 0.519–0.748n=121
Fakes caught at a 1% false-flag ratehigher is better52.5%95% interval 37.5–82.5%n=4032.5%95% interval 20.0–65.0%n=4035.0%95% interval 22.5–75.0%n=4032.5%95% interval 17.5–47.5%n=40
Fakes caught at a 5% false-flag ratehigher is better77.5%95% interval 55.0–90.0%n=4052.5%95% interval 37.5–72.5%n=4065.0%95% interval 47.5–80.0%n=4035.0%95% interval 22.5–50.0%n=40
Fakes caught at a 10% false-flag ratehigher is better77.5%95% interval 65.0–90.0%n=4062.5%95% interval 45.0–77.6%n=4065.0%95% interval 50.0–77.6%n=4037.5%95% interval 22.5–52.5%n=40
Fakes caught at risk ≥ 0.5higher is better58.3%95% interval 43.0–72.2%n=4045.0%95% interval 30.7–60.2%n=4053.3%95% interval 38.3–67.8%n=4022.5%95% interval 12.3–37.5%n=40
Genuine receipts flagged at risk ≥ 0.5lower is better2.1%95% interval 0.5–7.9%n=812.9%95% interval 0.9–9.2%n=811.2%95% interval 0.2–6.7%n=810.0%95% interval 0.0–4.5%n=81
Fakes caught at risk ≥ 0.7higher is better51.7%95% interval 36.7–66.3%n=4031.7%95% interval 19.4–47.1%n=4052.5%95% interval 37.5–67.1%n=4020.0%95% interval 10.5–34.8%n=40
Genuine receipts flagged at risk ≥ 0.7lower is better0.0%95% interval 0.0–4.5%n=810.4%95% interval 0.0–5.3%n=811.2%95% interval 0.2–6.7%n=810.0%95% interval 0.0–4.5%n=81
Price per checklower is better$0.0163n=453$0.0010n=453$0.0044n=453$0.0002n=453
Latency p95lower is better8,907 msn=45313,551 msn=45315,763 msn=453680 msn=453

Broken down by genuine receipt it was made from, capture, country, currency, date era, document type, language, source, provenance metadata, fake class, printed amounts in the Markdown and JSON versions.

Duplicates

Duplicates: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Copies recognisedhigher is better98.9%95% interval 86.8–99.9%n=30not supportednot supportednot supported
Exact copies recognisedhigher is better6.7%95% interval 1.8–21.3%n=30not supportednot supportednot supported
Look-alikes wrongly matchedlower is better4.5%95% interval 0.8–21.8%n=22not supportednot supportednot supported
Other genuine receipts wrongly matchedlower is better0.0%95% interval 0.0–6.1%n=59not supportednot supportednot supported
Price per checklower is better$0.0163n=453not supportednot supportednot supported
Latency p95lower is better8,907 msn=453not supportednot supportednot supported

Broken down by printed amounts, capture, fake class, country, currency, date era, document type, language, provenance metadata, source in the Markdown and JSON versions.

Decisions you can act on

Decisions you can act on: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Same decision in every repetitionhigher is better96.7%95% interval 91.8–98.7%n=12192.6%95% interval 86.5–96.0%n=12195.9%95% interval 90.7–98.2%n=121100.0%95% interval 96.9–100.0%n=121
Same total in every repetitionhigher is better100.0%95% interval 96.9–100.0%n=12198.3%95% interval 94.2–99.5%n=12199.2%95% interval 95.5–99.9%n=121100.0%95% interval 96.9–100.0%n=121
Calibration errorlower is better8.2%95% interval 5.9–12.9%n=12110.2%95% interval 7.7–14.1%n=12111.8%95% interval 7.8–17.0%n=12118.3%95% interval 14.7–21.6%n=121
Expected cost per 1,000 receipts, flagging at risk ≥ 0.5lower is better$762.9695% interval $341.67–$1,320.25n=121$1,034.8195% interval $621.71–$1,586.50n=121$674.4495% interval $358.33–$1,148.33n=121$775.0095% interval $625.00–$900.00n=121
Expected cost per 1,000 receipts, flagging at risk ≥ 0.7lower is better$483.3395% interval $333.13–$633.33n=121$752.5995% interval $575.00–$949.44n=121$682.7895% interval $358.33–$1,206.86n=121$800.0095% interval $675.00–$925.00n=121
Cost per 1,000 receipts of approving everythinglower is better$1,000.00n=121$1,000.00n=121$1,000.00n=121$1,000.00n=121
Price per checklower is better$0.0163n=453$0.0010n=453$0.0044n=453$0.0002n=453
Latency p95lower is better8,907 msn=45313,551 msn=45315,763 msn=453680 msn=453

Broken down by cost assumption in the Markdown and JSON versions.

What's wrong, and where

What's wrong, and where: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Edited fakes flagged with the edited field namedhigher is better33.3%95% interval 16.9–55.1%n=2036.7%95% interval 19.4–58.3%n=2030.0%95% interval 14.5–51.9%n=205.0%95% interval 0.9–23.6%n=20
Price per checklower is better$0.0163n=453$0.0010n=453$0.0044n=453$0.0002n=453
Latency p95lower is better8,907 msn=45313,551 msn=45315,763 msn=453680 msn=453

Proof, not opinion

Proof, not opinion: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Fakes caught with verifiable proofhigher is better53.3%95% interval 38.3–67.8%n=40not supportednot supportednot supported
Genuine receipts with false prooflower is better2.5%95% interval 0.7–8.6%n=81not supportednot supportednot supported
Price per checklower is better$0.0163n=453not supportednot supportednot supported
Latency p95lower is better8,907 msn=453not supportednot supportednot supported

Broken down by fake class in the Markdown and JSON versions.

Cost, speed and failures

Cost, speed and failures: value, 95% interval and n per target
MetricReceipt Audit (Claude Haiku 5.5)oursClaude Haiku 5.5, plain promptDIY baselineGemini 3.8 Flash, plain promptDIY baselineOpenAI decision model (gpt-6-luna), asked if it's fakeDIY baseline
Latency p50lower is better4,057 msn=4538,565 msn=4536,598 msn=453384 msn=453
Latency p95lower is better8,907 msn=45313,551 msn=45315,763 msn=453680 msn=453
Latency p99lower is better10,351 msn=45316,298 msn=45322,150 msn=453876 msn=453
Price per checklower is better$0.0163n=453$0.0010n=453$0.0044n=453$0.0002n=453
Model cost per checklower is better$0.0011n=453$0.0010n=453$0.0044n=453$0.0002n=453
Errorslower is better0.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=453
Refusalslower is better0.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=453
Timeoutslower is better0.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=4530.0%95% interval 0.0–0.8%n=453

Broken down by printed amounts, arithmetic error, genuine receipt it was made from, capture, fake class, country, currency, date era, document type, edited field, language, generation prompt, provenance metadata, source, transform in the Markdown and JSON versions.

Examples

No examples in this run.

Datasets and licences

Limitations

  • Preliminary: small samples, so intervals are wide. Read the intervals, not the point estimates.
  • Public cases come from datasets that are probably in the models' training data.
  • The DIY baselines use one plain prompt; a stronger prompt would do better. A strong-prompt baseline comes later.
  • DIY targets have no memory across checks and no deterministic checks: those tasks are marked not supported, not scored as zero.
  • Our engine runs in process, so its latency has no network hop to receiptaudit.com; DIY latency includes the round trip to the model.
  • Costs are model tokens at list price on the price date; no infrastructure cost is included.
  • When the Claude baseline's config says transport claude-code-cli, it ran through Claude Code's CLI on a subscription: thinking is adaptive and can't be fully turned off, its cost is what the same tokens cost through the API, and its latency includes the CLI's start-up.
  • The duplicate network starts empty for each repetition: it only knows the run's own earlier cases, not production traffic.
  • We made the fakes ourselves, with current image models and edits; fakes in the wild may differ.
  • Most genuine receipts are scans from 2016–2018 (SROIE, CORD), with a few recent ones; fakes are dated to match, so a model can't tell them apart by date alone. More recent genuine receipts are being added.
  • This run is not blind for false proof. After run 20261012T223232Z-63be0d we looked at held-out outputs to find why 16% of genuine receipts carried a finding labelled proof, and fixed it (engine 0.2.1: a warning is no longer proof; cash rounding for MYR and others). The 2.5% here is measured on those same cases; the next held-out rotation re-checks it.
  • The OpenAI decision model (Decisions API, gpt-6-luna) is in public beta.

Methodology

Written and committed before the run, published as written.

RA-Bench 0.2.0. These definitions, thresholds and exclusions were written before the run and change only in a new suite version. Who and what. Receipt Audit runs this benchmark on its own product. Every target gets the same cases, the same files, the same timeout (180 s per attempt by default) and the same scoring; only the target changes. A target is our engine ("self", in process, with the duplicate network on an empty in-memory backend that lives for one repetition) or an agent's own model asked with one plain prompt ("diy"). DIY targets get the same file preparation as our engine (upright, at most 3072 px and 3.75 MB) so that API size limits don't count against them. Transport failures (network errors, HTTP 408, 429 and 5xx) are retried up to 3 times with exponential backoff; nothing else is retried, and a wrong answer is a result. Models are pinned: no fallback to another model. Units and intervals. A case run several times counts once, with the share of its runs that succeeded. Rates carry a 95% Wilson interval over cases (operations rates: over calls). ROC AUC, detection at a fixed false-flag rate and line-item F1 carry a 95% percentile bootstrap interval (2000 resamples, genuine receipts and fakes resampled separately, seeded per row so a re-scored run gives the same interval). Latency and cost have no interval. Every row reports n. Rows are given for all cases and for every stratum (a "dimension=value" tag on the case, or a label-derived group) with at least 10 cases. Which cases count. Extraction, checks and authenticity use base cases only: cases that aren't a transform of another case (no lineage). Duplicate transforms count only for the duplicates task. Operations use every call. Extraction. Scored only on fields the label covers; a field labelled null means the document doesn't show it, and only a null answer is right. Merchant name: lowercase, accents stripped, "&" read as "and", punctuation dropped, then a match at edit-distance similarity ≥ 0.9. Date: the exact ISO date (YYYY-MM-DD). Subtotal, tax total and total: within 0.01. Currency: the exact ISO 4217 code. Receipt number: equal after keeping only letters and digits, lowercased. "All fields right": every labelled scalar field of the document right in that run (line items excluded). Line-item F1: labelled and extracted items paired one to one when descriptions are at least 0.8 alike (same normalisation) and totals match within 0.01, micro-averaged over all documents. Valid output: the call succeeded and returned readable fields. A failed call or an unreadable answer gets every field wrong. Checks. totals_add_up is scored against the label "the printed amounts add up". A positive is a document whose amounts don't add up; the check flags it when it reports failed. Recall over documents that don't add up; precision over documents it flagged; false alarms over genuine documents that do add up; coverage is the share of documents where the check ran (a check that couldn't run isn't output). Targets without deterministic checks report this task as not supported. Authenticity (genuine vs fake). Higher risk means more likely fake. ROC AUC uses each target's risk as the score; a result without a risk scores 0.5 (no opinion), ties count half. Detection at a 1%, 5% and 10% false-flag rate: the share of fakes flagged by the lowest threshold that flags at most that share of genuine receipts (ties at the threshold are all flagged). At the documented thresholds risk ≥ 0.5 and risk ≥ 0.7: the share of fakes flagged (detection) and of genuine receipts flagged (false flags); a result without a risk is never flagged. Strata from the labels: each fake class ("class=ai_full", …) and fakes whose amounts add up or don't ("arithmetic=consistent", "arithmetic=inconsistent"), each compared with every genuine receipt; and provenance metadata ("provenance=intact", "stripped", "none"), genuine receipts and fakes alike. Duplicates. Every case is submitted by a different account, so a repeat is "someone else submitted this receipt before". Recall: the share of duplicate transforms (re-encoded, cropped, re-shot copies of an earlier case) the target recognised as seen before; exact recall counts only matches on the identical image hash. False matches: the share of distinct receipts reported as seen before, for hard negatives (different receipts that look alike: same merchant, template or day; "false_match_rate") and for the other genuine receipts ("false_match_rate_genuine"). Originals always run before their transforms. Targets without memory across checks (DIY prompts) report the task as not supported, not as zero. Decisions. A decision is the yes/no a caller acts on: flagged when risk ≥ 0.5. Consistency: over base cases run at least twice, the share whose decision is the same in every repetition; total consistency, the share whose extracted total is the same (within 0.01, or absent) in every repetition. Calibration error: expected calibration error of risk against "is fake" over every run with a risk, 10 equal-width bins, weighted by run count (lower is better; it depends on this set's share of fakes). Expected cost per 1,000 receipts at risk ≥ 0.5 and ≥ 0.7: 1000 × ((1 − p) × false-flag rate × C_review + p × (1 − detection) × C_miss). Defaults, each from a cited, graded source (research/41-benchmark-cost-model.md): p = 1% of submitted receipts are fake (SAP Concur's share of receipts flagged as AI-generated; no study measures the true share, so this is the best available assumption, not a measurement); C_miss = $100, the face value of a missed fake (AppZen's mean AI-generated fake, 12 months to May 2026); C_review = $17, one false flag's review (18 minutes, GBTA, at a loaded auditor wage of $57.47/h, BLS). At these defaults one point of false-flag rate costs as much as about 17 points of detection. The sweep varies one assumption at a time over its published spread ("assumption=…": fakes 0.1% and 5%; a missed fake $32 and $182; a review $8 and $73). Approving everything costs 1000 × p × C_miss ($1000 at the defaults), reported as its own row: a detector above it loses money under those assumptions. The formula counts only mistakes (it treats reviewing a fake that was rightly flagged as free) and leaves out unmeasured costs: an employee's time and trust after a false flag, and repeat fraud or tax penalties after a miss. This set has far more fakes than real submissions, so the cost re-weights the measured rates by p rather than using the set's own mix. Evidence. A yes/no is easy to get from any model; a reviewer also needs to know what is wrong. Edited field named: over fakes whose label names the edited field (AI-inpainted and hand-edited fakes), the share of runs that flag the receipt and whose first 3 reasons point at that field. A reason points at a field through an explicit "@field" tag, a deterministic check about it (an arithmetic check points at the total and subtotal), or, for free-text reasons, a pre-registered keyword (total, amount due; subtotal; tax, VAT, GST; date; merchant, store, vendor, logo, name; item, line, quantity, price). Verifiable evidence. A finding is proof of tampering when a caller can check it without trusting a model and it shows the document was generated or changed: an AI-generator, AI source-type or receipt/invoice-generator tag read from the file, PDF modification records, a thumbnail that doesn't match the image, a photo taken before the printed purchase date, printed amounts that don't add up (with the numbers), or a duplicate-network match. Other file facts (an editor or office tool, missing camera data) and plausibility or policy checks (tax rate, the receipt's age, currency) are real but don't show tampering, and don't count. A model's visual judgement is an opinion, however confident. Fakes caught with proof: the share of fakes flagged with at least one proof among their reasons. Genuine receipts with proof: the guard against false proof (lower is better). Only targets that can return verifiable findings report this task; a plain prompt returns opinions by construction and reports it as not supported. Operations. Latency: wall time of the final attempt of each successful call, measured by the harness (p50, p95, p99). Cost per check is what the caller pays for one check: for Receipt Audit, the list price of its smallest credit pack per check; for a DIY baseline, its model tokens × the AI Gateway list price on the run's price date. Model cost per check is the model tokens × that list price for every target, our engine's own model call included (infrastructure not included). Both are averaged over successful calls. Error, refusal and timeout rates over all calls. Findings. Each of our targets is compared with each baseline on the headline rows of every task (stratum "all"): a win or a loss only when the 95% intervals don't overlap, otherwise a tie; for latency and cost, which have no interval, only when one value is at least 1.2 times the other. A comparison resting on fewer than 10 cases (or calls) on either side is always a tie. A task a baseline can't do by construction is reported as such, not as a score of zero.

Reproduce or dispute

cd apps/receipt-check && nub run bench run --cases benchmarks/receipt-audit/heldout-cases.jsonl --split private --targets self,diy:claude-haiku-5.5,diy:gemini-3.8-flash,decision:gpt-6-luna --reps 3

History