The problem
Reconciliation is the job of proving two records of the same money agree. When they don’t, somebody has to find out why, and the cost of a tool that pairs the wrong two lines is not a lower score. It is a person spending a day chasing a break that was never there.
ReconMatch aligns bank-statement lines to internal ledger entries, gives every match a confidence and names the rule that produced it, and classifies everything left over as a typed break with a suggested resolution. The matcher is deterministic: same inputs, same output, no inference cost, no model in the loop. That is a design decision rather than a limitation. The numbers reproduce exactly, and every decision can be explained to an auditor who does not care how clever the system is.
How it matches
One deterministic pass. Every tier proposes candidates, and they compete in a single greedy assignment ordered by confidence, so a strong exact match always beats a weaker split that wants to poach its records. No line and no entry is ever used twice.
- ExactSame signed amount, same date, plus a reference match or description similarity ≥ 0.550.95–1.00
- WindowedSame amount, date within ±3 days, description similarity ≥ 0.550.6–0.95
- SplitTwo or three statement lines summing exactly to one ledger entry inside the window: partial and split payments~0.55
- BatchTwo or three ledger entries summing exactly to one statement line: payroll runs, bulk supplier payments~0.55
Amounts are Decimal everywhere and never float, because a reconciliation
engine that introduces its own rounding error has defeated its own purpose.
Descriptions are compared with a character-ratio and token-containment blend, since
banks mangle payee names with channel prefixes, uppercasing and truncation.
On my own data
- 95.9%auto-matched at the ≥0.99 precision bar
- 1.00observed pair precision at that rate
- 0.98pair F1, recall 0.96
- 0.83break-classification F1
Fifty held-out sets, seeds 100–149, never tuned on. The headline is the one a reconciliation team would actually buy: 96% of true matches clear automatically with zero false matches, leaving only genuine exceptions for a human. Break recall is 0.97, so few real exceptions get missed, while break precision sits lower at 0.75, because the engine deliberately over-flags amount-mismatch suspects rather than hide a possible error.
Then the caveat that makes the rest of this page worth reading: those are numbers on my own generator. My synthetic data and my matcher were built by the same person with the same assumptions about what a payment looks like. Evaluating one on the other partly measures how consistent I am with myself.
Internal held-out baseline, 19 July 2026.
On somebody else’s
BenchRec cash v1.0 is a licensed third-party reconciliation dataset with a published reference result. I reserved a single held-out scoring run, fixed every method decision on the training split first, and executed once. Scoring is strict exact-set: a record counts as correct only if its complete predicted allocation set equals the target set. I validated the scorer by reproducing the published reference’s own figures under the same rule, so this is like for like.
Exact-set match rate, BenchRec cash v1.0
- ReconMatch
- MatcherByChatGPT, the published reference
Precision on emitted predictions is 93.33% overall, with a Wilson 95% lower bound of 93.09%. The reference is more precise where it answers, 95.20%, but it answers far less often: 20,800 predictions against ReconMatch’s 30,228 out of the same 32,048 records. Declining to answer is a legitimate strategy, and comparing raw precision without coverage would hide that trade entirely.
Third-party frozen run, 21 July 2026.
The result I didn’t want
Before that run I fixed an auto-match bar in advance: a Wilson 95% lower bound of 99.8% precision at 50% coverage or better. Anything clearing it could be auto-matched. Anything short of it could not.
It did not clear it.
Coverage that clears the 99.8% precision bar
- 50%, the coverage the bar required
So the BenchRec results ship as SUGGESTED_FOR_REVIEW, and
no auto-match is claimed on that data. The 95.9% auto-match rate
above is a statement about my generator’s data and nothing else.
That work also lives in experiments/benchrec/, not in the shipped
matcher, so the live demo does not score 88.65%. It runs the deterministic tiers
described above.
The honest reading is that group membership on BenchRec is genuinely underdetermined from the observable fields: there is not enough signal in the data to know which records belong together with the certainty auto-matching requires. Beating a published baseline by 26 points and still declining to claim auto-match is a strange thing to publish. It is also the finding that would actually matter to anyone deploying this against real money.
What’s left over
Breaks are not an undifferentiated exceptions pile. Every residual item is classified, in order:
- Amount mismatch. A near miss or digit transposition against an in-window line, with both sides linked.
- Duplicate. Identical to an already-matched line.
- Missing in ledger. A statement line with no entry, usually a bank fee.
- Missing in statement. An entry the bank never saw.
Each one carries a plain-language suggestion naming the amounts involved, so the reviewer starts from a hypothesis instead of a spreadsheet.
Try it
A bundled sample pair, or your own two CSVs in
id,date,description,amount[,reference] form with signed amounts. Every
match comes back with its tier, its confidence and its reasoning.
ReconMatch is the second half of a pair. DocVal turns a
statement PDF into validated transactions, and an adapter maps them straight into
statement lines, signed as credit − debit. A PDF goes in one end
and a classified set of breaks comes out the other. Run provenance and input hashes
are in data/benchrec/artifacts/; the BenchRec dataset is CC BY 4.0 and
not redistributed.