Skip to the findings

LedgerMap research · 18/10/2026 · for Khush

AI in ledger matching: what it adds, measured

Today our matcher finds ledgers with rules: payee keys, exact matches, a measured confidence table, Suggestions and Suspense. I ran 13 model set-ups on the same 1,611 statement rows whose right ledger the books show, and on 231 rows users exported in production. I scored every answer, timed whole statements and counted real token costs. This page gives the answer first, then the evidence.

Made-up entries. The matcher stays first for what it is sure of. AI reads the rest: rows with no clue, and rows where the matcher is unsure. Rows that neither can place still go to Suspense.

The short answer

  1. AI helps, by about a third. On the book set, the matcher alone leaves 750 decisions on a company's first import and 637 once earlier imports are exported. With Claude Sonnet 5.5 (thinking off) placed after the matcher, that falls to 485 (35% fewer) and 375 (41% fewer). It is not 10×: most of the rest are rows that name no one, so no reader can place them.
  2. Which model: Claude Sonnet 5.5 with thinking off. It is the best where AI matters, on rows the matcher sends to Suspense. With the same blind prompt it placed 44.1% of them right; Opus 5.5 got 42.7%, Gemini 3.8 Flash 40.3%, GPT-6 Astra 38.7%, GPT-6.1 Sol 37.7%, Haiku 5.5 36.3%. With matcher clues and history Sonnet reached 48.8%. Sonnet costs about $0.98 per 1,000 entries (measured tokens), Haiku $0.047. Thinking did not help any model.
  3. Where: after the matcher, on every row it did not save: Suspense rows and red or yellow suggestions. Give the AI the party's ledger list, the matcher's clues and the party's exported earlier choices. Earlier choices matter more than model size: they add about 8 points for every model and cut confident wrong answers by up to three quarters.
  4. When AI may save by itself: only when AI and the matcher name the same ledger and both are at least 90% sure. With Sonnet this saved 207 more rows with 0 wrong on first imports, and 147 more with 1 wrong with history (Wilson lower bound about 99.0%). AI alone must never save: on first imports, at its own "95%", Claude made 1–36 wrong saves and GPT and Gemini 126–225; with history, Opus still made 23.
  5. The cost to the user: more suggestions are wrong. Rows that sat in Suspense now carry a guess, so wrong pre-selected suggestions rise from 179 to 359. The green/yellow/red mark must come from measured accuracy of each AI situation, not from the model's own percentage: a Sonnet "90–94%" on a Suspense row was right 82% of the time.
  6. Speed fits easily. Sending a statement's rows 10 per call, all at once, Sonnet answered whole statements in 3.6 s (median) and 5.2 s at most, over 36 statements. Haiku took 2.6 s, Opus 4.9 s. The 10–12 s budget is not at risk.

What I tested on

Two sets with known right answers. Every model got the same rows in the same order, the same prompt, and the same 40 rows per call.

Test sets
SetWhere it came fromRowsHow we know the right ledgerBias
Books32 statements of 10 companies from the Miracle books on this PC (the matcheval corpus)1,611The company's own books posted the row: same date, amount and side, one posting only (docs/matching-eval.md). About 1% label noise.None toward any model or the matcher. It holds the companies' own booking habits.
ProductionThe 4 statements users exported on app.ledgermap.in (16–17/10), copied read-only231
+ 11 put in Suspense
The ledger the user exported. Contra, skipped and in-books rows left out.Favours the matcher: users accepted most of its suggestions, so its own answers are the truth here.

Real traces agree with my replay. In production the live matcher saved 113 rows (users kept all 113), put 89 right first suggestions and 7 wrong ones, and sent 22 rows to Suspense. My replay of today's code gives 119, 85, 7 and 20. So production gives AI only about 30 rows to improve; the book set carries most of the measurement.

Two histories: fresh is a company's first import (no earlier choices). Prior means each statement comes after the earlier ones were done and exported, as in steady use. On the book set the matcher saves 340 rows fresh and 608 with history, and sends 571 and 510 to Suspense. Its green suggestions were right 267 of 270 times. 1,011 labelled rows go to a ledger the company had not used before in these statements.

Which model

"Blind" here: the model sees the party's ledger list (name and group) and the entries (date, paid or received, amount, narration, reference), and returns up to 3 ledgers, each with its probability, or nothing. It does not see the matcher's answer or any earlier choice.

Right ledger, blind, every model on the same rows
Model and settingBooks top-1top-3No ledgerWrong at ≥ 90%Production top-1Wrong at ≥ 90%s per 40 rows$ per 1,000 rows
Direct API (time and tokens from the API itself)
Claude Haiku 5.5
thinking off
65.5%68.7%9.1%111 of 881
12.6% wrong
83.5%6 of 1286.7$0.047
Claude Haiku 5.5
thinking on (medium)
67.0%69.1%12.0%100 of 680
14.7% wrong
88.3%3 of 14013.0$0.069
Claude Sonnet 5.5
thinking off
69.0%74.4%7.9%112 of 843
13.3% wrong
89.2%0 of 12510.5$0.98
Claude Sonnet 5.5
thinking on (medium)
68.4%73.3%7.9%122 of 851
14.3% wrong
91.3%4 of 13410.8$0.94
Claude Opus 5.5
low effort (thinking cannot be turned off)
69.0%75.4%8.6%85 of 907
9.4% wrong
90.5%0 of 15615.0$1.89
Claude Opus 5.5
medium effort
68.5%76.8%7.9%71 of 869
8.2% wrong
89.2%0 of 16321.1$1.59
Through a command-line tool (time includes the tool; tokens include the tool's own prompt, removed for cost)
GPT-6 Luna
low reasoning
64.0%64.9%14.8%173 of 1083
16.0% wrong
89.6%4 of 16912.0*$0.029
GPT-6.1 Sol
low reasoning
67.1%72.6%12.4%253 of 1270
19.9% wrong
91.8%4 of 19422.5*$0.64
GPT-6 Astra
low reasoning
67.5%73.7%12.4%231 of 1248
18.5% wrong
92.2%6 of 18827.8*$3.23
Gemini 3.8 Flash
low thinking
68.2%75.1%11.0%222 of 1218
18.2% wrong
91.8%5 of 19919.5*$0.51
Gemini 3.8 Flash
medium thinking 586 of 1611 rows
69.8%75.9%12.6%52 of 383
13.6% wrong
not complete0 of 760.1*$1.57
GPT-OSS 120B (open weights)
medium
50.1%50.2%23.8%139 of 536
25.9% wrong
61.5%13 of 10355.1*$0.55

* Times through a command-line tool include its start-up (4.5–15 s). CLI costs use API list prices (October 2026) on the tokens the tool reported, less the tool's own prompt (about 19,200 input tokens per call for Codex, 13,800 for agy Gemini, 11,500 for agy GPT-OSS); GPT-OSS is priced at a typical host's $0.15/$0.60. Claude costs are measured, including prompt-cache writes. Gemini 3.8 Flash prices double on 01/01/2027. Opus 5.5 cannot turn thinking off; low effort is its lightest setting. "Gave no ledger" is the share of rows where the model said nothing points to a ledger.

Three things stand out. First, the big models are close: on the book set every Claude, GPT and Gemini model is within 6 points (64–70%). Second, they differ a lot in honesty: when GPT-6.1 Sol said 90% or more it was wrong 19.9% of the time, Gemini 3.8 Flash 18.2%, the Claude models 8–15% across all their settings. Third, thinking does not help this task: Haiku gained 1.5 points, Sonnet lost 0.6, Opus at medium effort lost 0.5, and each got slower. Gemini 3.8 Flash at medium thinking was worse still: 43 of its 69 calls returned no answer, 40 of them because thinking used up the whole reply. Its row is scored only on the rows it answered.

On the rows that matter, the 571 rows the matcher sends to Suspense on a first import, the models spread out more (blind prompt, right first ledger): Sonnet 5.5 44.1%, Opus 5.5 42.7%, Gemini 3.8 Flash 40.3%, GPT-6 Astra 38.7%, GPT-6.1 Sol 37.7%, Haiku 5.5 36.3%, GPT-6 Luna 30.6%, GPT-OSS 120B 21.5%. These rows name no known payee, so the model must read a cut or glued name, or a kind of payment, against the ledger list.

The open models are not close. GPT-OSS 120B (open weights, run through agy) got 50% and often gave no ledger. I did not get to run a local open model: the PC's GPU and most of its memory were in use during the test. GPT-OSS 120B above shows where open models stand on this task. A local run would also be far too slow on our 4-core server.

What the model is given matters more than which model

Books set, top-1 right and confident errors, by what the prompt holds
ModelBlindwrong at ≥ 90%+ matcher clueswrong at ≥ 90%+ clues + exported earlier choiceswrong at ≥ 90%
Claude Haiku 5.5, thinking off65.5%111 of 88164.9%65 of 77573.2%27 of 940
Claude Sonnet 5.5, thinking off69.0%112 of 84369.5%62 of 83377.7%44 of 935
Claude Opus 5.5, low effort69.0%85 of 90769.0%70 of 91077.5%44 of 1038

Matcher clues (each row's suggestion and how often that kind of clue was right) do not raise top-1, but they cut confident wrong answers by about 40%. Earlier choices (up to 250 of the company's exported rows with the ledger chosen) add about 8 points and cut confident errors further. They teach the company's booking habits, which no model can guess: one company posts bank SMS charges to the bank's creditor ledger, not to Bank Charges; another posts a named worker's NEFT to Labour Wages, not to the worker's ledger.

One row per call, or the whole statement

On the same 531 sampled rows, one row per call was about as accurate as 40 rows per call: Haiku 72.7% against 74.0%, Sonnet 79.1% against 78.2%. One row per call answers in about 1.2 s with the ledger list cached, but costs about 2.5 times more ($0.12 per 1,000 rows with Haiku). Calls of about 10 rows, sent in parallel, keep the speed and most of the saving.

Where AI should go in the pipeline

For each place I combined the matcher's real output with the model's answer for every labelled row, and counted what the user would face. Decisions are rows the user must decide: not saved right and not pre-selected right. Wrong suggestions are rows pre-selected with a wrong ledger (the user must notice and change them). Wrong saves are saved with a wrong ledger and no user action.

Books set, first import (no history). Model prompt: ledger list + matcher clues
Pipeline placeHaiku 5.5, offSonnet 5.5, offOpus 5.5, low
DecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong saves
Matcher only (today)750179075017907501790
AI on Suspense rows only550377049436805203720
AI on Suspense rows and unsure (red/yellow) suggestions535362048535904953470
AI only re-orders the matcher's own candidates746175074417307441730
AI first for every row the matcher did not save565392049136504993510
AI checks exact saves (an exact it disputes becomes a suggestion)750179075017907501790
Books set, with history (earlier statements done and exported). Model prompt: ledger list + matcher clues + exported earlier choices
Pipeline placeHaiku 5.5, offSonnet 5.5, offOpus 5.5, low
DecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong saves
Matcher only (today)637126163712616371261
AI on Suspense rows only440299138827414042811
AI on Suspense rows and unsure (red/yellow) suggestions426285137526113682451
AI only re-orders the matcher's own candidates628117162611516271161
AI first for every row the matcher did not save429288135924513622391
AI checks exact saves (an exact it disputes becomes a suggestion)637126163712616371261
Production set (first imports; truth favours the matcher)
Pipeline placeHaiku 5.5, offSonnet 5.5, offOpus 5.5, low
DecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong savesDecisionsWrong sugg.Wrong saves
Matcher only (today)277027702770
AI on Suspense rows only181101715015110
AI on Suspense rows and unsure (red/yellow) suggestions221501614014100
AI only re-orders the matcher's own candidates3111027702770
AI first for every row the matcher did not save26190181601280
AI checks exact saves (an exact it disputes becomes a suggestion)277027702770

What the numbers say:

All other models, blind prompt, first import, AI on Suspense and unsure rows:

Books set, first import, blind prompt (no matcher clues, no history)
ModelDecisions (matcher alone: 750)Wrong suggestionsExtra saves when AI and matcher agree, both ≥ 90%Wrong saves from that ruleWrong saves if AI alone at ≥ 95% saved
Claude Haiku 5.5, off519377+18707
Claude Sonnet 5.5, off474349+164016
Claude Opus 5.5, low473337+187036
GPT-6 Luna542313+2231140
GPT-6.1 Sol492296+2231225
GPT-6 Astra486291+2230193
Gemini 3.8 Flash, low483307+2200126
GPT-OSS 120B610362+95050

When may AI save a choice by itself?

Each rule below adds saves on top of today's exact saves. Counts are on the book set (1,611 rows).

Extra rows saved, and wrong saves among them
RuleFirst import (clues)With history (clues + earlier choices)
HaikuSonnetOpusHaikuSonnetOpus
AI and matcher name the same ledger, both ≥ 90%+187
1 wrong
+207
0 wrong
+220
0 wrong
+172
1 wrong
+147
1 wrong
+160
0 wrong
AI and matcher agree, AI ≥ 90% (matcher any)+324
45 wrong
+372
52 wrong
+434
62 wrong
+266
24 wrong
+246
34 wrong
+295
35 wrong
AI and matcher agree, AI ≥ 70%+471
127 wrong
+469
90 wrong
+472
101 wrong
+349
79 wrong
+337
52 wrong
+350
52 wrong
AI alone says ≥ 95%+147
4 wrong
+60
1 wrong
+277
13 wrong
+134
0 wrong
+110
0 wrong
+200
23 wrong

Safe rule: save only when the AI and the matcher's green suggestion agree. The matcher's green suggestions alone were right 267 of 270 times; the AI's agreement removes most of the few wrong ones. Pooled over the six runs, the rule saved 1,093 extra rows with 3 wrong. That passes the 99% bar from the 12/10 audit only just (Wilson lower bound 99.0–99.3% per run), so it needs the replay gate on every change. Unsafe rules: agreement at lower AI confidence, or the AI alone at "95%".

How sure is the AI, really?

Stated probability of the first ledger against measured accuracy (books set)
Run0–49%50–69%70–89%90–94%95–100%
Opus 5.5 low, no history41/90
45.6%
59/150
39.3%
190/326
58.3%
424/473
89.6%
398/434
91.7%
Sonnet 5.5 off, with history56/130
43.1%
77/157
49.0%
228/274
83.2%
269/313
85.9%
622/622
100.0%

Without history the stated percentage runs high: "90–100%" is right about 90% of the time, and below 90 the number means little. With earlier choices, "95–100%" is right almost always, because it then repeats a choice the user made. So the colour mark must come from measured situations, as the matcher's does today (ledger_confidence_table.go): for example, AI agrees with a green matcher suggestion (right about 100%), AI disagrees with an unsure matcher suggestion (about 90%), AI on a Suspense row at 70–94% (about 75–80%, yellow), AI on a Suspense row below 70% (about 40%, red).

Speed against a 10–12 s budget

Measured through the direct API, one statement at a time, as the server would do it. The matcher runs first (instant). The rows it did not save go to the model in calls of 10 rows, all at once; the statement is ready when the slowest call returns. Model prompt: ledger list + matcher clues.

Whole statements, 10 rows per call (36 statements, up to 166 rows for the AI each)
ModelPrompt cacheMedian90% withinSlowestOver 12 sUSD per 1,000 rows
Claude Haiku 5.5, offnothing cached (a party's first import of the day)2.8 s3.3 s4.0 s0 of 36$0.14
Claude Haiku 5.5, offledger list cached2.7 s3.2 s5.6 s0 of 36$0.043
Claude Sonnet 5.5, offnothing cached (a party's first import of the day)3.6 s4.1 s5.0 s0 of 36$2.71
Claude Sonnet 5.5, offledger list cached3.5 s4.1 s4.9 s0 of 36$0.84
Claude Opus 5.5, lowpartly cached4.9 s5.8 s7.7 s0 of 36$4.17

All three fit the budget with room to spare. No statement took more than 8 s; Sonnet's slowest was 5.2 s and its median 3.6 s. An empty prompt cache (a party's first import of the day) made no difference to time. With 10 rows per call, up to 17 calls ran at once for the largest statement, with no rate-limit retries. Real statements also carry rows the books did not label, so they need a few more calls at once; the time of a statement is the time of its slowest call, not the number of calls, so this adds little. Earlier choices in the prompt (up to 250 rows) make each call longer to read but are cached with the ledger list.

The review steps are fixed when review starts, so the AI's answers must be ready by then. The AI needs only the party's ledger list, not the bank ledger, so it can start the moment the parse ends, while the user confirms the bank. A warm-up call when the user opens the party puts the ledger list in the prompt cache.

Cost

Measured tokens, API list prices (October 2026), prompt-cache writes included. A typical row costs about 50 output tokens (ledger number, probability, payee and kind) and a share of the ledger list.

The mistakes, and which checks catch them

Wrong first ledgers by kind, Sonnet 5.5 off, books set
KindFirst import: all… stated ≥ 90%With history: all… stated ≥ 90%
booking habit: party ledger vs expense/income ledger2336111341
guess with no name in the narration931961
two ledgers for one party (debtor/creditor copy, branch, person vs firm)210211
other140130
named ledger missed (name is in the narration)1011

Proposed design

1. Parse and matchAs today: the matcher saves exact rows and finds clues. Nothing changes for rows it is sure of.
2. AI reads the restOn the server, at the end of the parse: rows not saved, 10 per call, all at once, Claude Sonnet 5.5 with thinking off. Prompt: ledger list (cached), matcher clues, up to 250 exported earlier choices. The answer is saved with the entry and not asked again.
3. CombineOur code checks each answer is a ledger in the list and not a bank or suspense ledger. Matcher green + AI agree: save (if you approve). Matcher unsure + AI differs: AI's ledger first. Suspense row + AI answer: suggestion.
4. MarkGreen, yellow or red from a measured table of AI situations, kept in the repository like ledger_confidence_table.go, regenerated by the replay.
5. ReviewSteps fixed at the start, as today. No pop-ups. An AI suggestion is a clue, so its row goes to Suggestions. Rows neither can place go to Suspense.
6. Learn and measureLearning stays from exported choices only. Track each AI suggestion in tracking_events (model, situation, mark, accepted or changed), and add an AI replay to bun run matcheval as the gate.

If the AI call fails or is late, the import goes on with the matcher alone, as today.

Decisions for you

I have not changed any product code. These need your choice:

  1. Turn AI on at all, and which model? I recommend Claude Sonnet 5.5, thinking off (35%–41% fewer decisions, about $1 per 1,000 rows). Haiku 5.5 is the cheap choice (29%–33% fewer, $0.05).
  2. Which rows? I recommend Suspense rows plus red and yellow suggestions. Suspense rows alone give most of the gain at a lower cost.
  3. Auto-save when AI and the matcher's green suggestion agree? This reverses the 16/10 decision of "no auto-accept for green". Measured: 147–220 more saves per 1,611 rows, 0–1 wrong per run (3 wrong in 1,093 pooled).
  4. Weak AI guesses on Suspense rows (the AI says below 70%; right about 40% of the time). Your rule says any clue goes to Suggestions. Should a weak AI guess count as a clue (Suggestions, red mark), or stay in Suspense with the guess pre-filled?
  5. Consent. This sends narrations, amounts, dates and ledger names (not PDFs, not company files) to Anthropic. Customers must agree, as they did for Codex parsing. Should it be on by default?

Privacy

Limits of this study

How I ran it

All work is in the main checkout's ignored folder .local/ai-matching-research/. prod_corpus.py turns the read-only production export into the matcheval format. A throw-away Go test (build tag aimatch, never committed) records what today's matcher does for every row, fresh and with history. run_api.py calls Claude directly; run_cli.py runs GPT through Codex and Gemini and GPT-OSS through agy; run_local.py runs open models with llama.cpp. score.py, pipeline.py, tiers.py and analyze.py score the answers; latency.py times whole statements. Every model got the same prompt: read the entries, choose up to 3 ledgers from the numbered list with probabilities, or none; do not pick a ledger because it is common.