WTWilly Tai
Case studyAI Governance & AssuranceLive demo · synthetic data

The matching cascade that leaves the model only the leftovers.

Cash and bank reconciliation is the single biggest close bottleneck: 20 to 50 hours a month matching bank statement lines to ledger open items, most of it still done by VLOOKUP on amount. Bank Rec Referee is a governed alternative. A deterministic four-rule cascade clears the pile on documentary evidence, the model is only allowed to argue for the residual with quoted citations, and a named human referees every call it makes. The whole run is hash-chained and replayable.

92.0%
cleared by rules alone
2,760 of 3,000 lines, four documentary keys.
8.0%
residual, to the referee
240 lines the model may argue for, with quotes.
1 in 3
residual abstentions
80 lines with no evidence go to investigate, never to suspense.
222
wrong bookings refused
What amount-only matching would have booked silently.

Synthetic month · 3,000 bank lines vs 3,400 open items · seeded generator + deterministic engine → identical matches and hashes on every run.

01 · The bottleneck

Everyone matches on amount, and amount is the weakest key on the statement.

Industry surveys consistently put spreadsheet-based reconciliation above 90% of mid-market companies; the analysis brief behind this demo cites 94%. The mechanics are always the same. Export the bank statement CSV, export the GL cash account and the AR/AP open items, then VLOOKUP on amount and eyeball the rest. Xero's own community forum has carried years of top-voted requests for matching by invoice number instead of amount, which tells you exactly which key practitioners trust and exactly which key the tools give them.

Amount-only matching fails on the four most ordinary things a bank statement does. Split and bundled payments mean no single open item carries the right amount. Bank fees netted from receipts mean the amount is close but never equal. Duplicate amounts mean the first VLOOKUP hit is frequently the wrong customer's invoice. And short payments with deduction claims look like errors when they are actually messages. The residual pile gets chased by hand, small differences get plugged to suspense, and unreconciled items roll quietly into next month with no record of why any match was made.

02 · Why the obvious AI fix makes it worse

An unconstrained matcher does the same thing faster, and then invents the rest.

The tempting move is to hand both files to a language model and ask it to reconcile. That inherits every weakness of the VLOOKUP workflow and adds new ones:

So the design question is allocation. Deterministic code should own everything a documentary key can decide. The model earns a narrow role at the edge, where remittance text is mangled and human-shaped reading genuinely helps, and even there it only proposes. In this demo the model layer is a deterministic stub, stated on screen, so the whole run reproduces exactly; the governance around it is what a real LLM would be caged in.

03 · The trust boundary, applied

Code decides the 92%. The model argues the 8%. A named human decides the argument.

The cascade runs four rules in order of evidentiary strength: an exact invoice number quoted in the remittance text, a unique payment reference, payer plus amount plus a three-day date window with exactly one candidate, and finally a solver that finds two or three open items summing to the line within a five-cent tolerance. Every auto-match writes its rule ID, matched keys and both record IDs to the ledger. What survives all four rules goes to the referee queue, where the model may propose a pairing only by quoting the exact substring of the source record that supports it. A code check verifies each quote is literally present before the proposal is shown, and below a confidence threshold the model must abstain. Accepting a proposal is a named human's decision, one click, logged.

DETERMINISTIC · computes and decides MODEL + HUMAN · proposes, then referees Bank lines3,000 · camt.053 shape Open items3,400 · AR + AP 4-rule cascadeinvoice no. → unique ref →payer+amt+date → split solverevery match cited to its key 2,760 matched92.0% · rules alone 240 residual8.0% · to the referee Model, propose onlyquotes source text · abstains Quote checkliteral presence, in code Named humanaccept / reject · logged Append-only, hash-chained ledgerevery auto-match (rule + keys + both record IDs) · every proposal, abstention and human decision · tamper-evident · replayable
fig.1 · the trust boundary: four deterministic rules decide the bulk, the model argues the residual with verified quotes, a named human decides
04 · The decision function

The split solver is arithmetic, so it is written as arithmetic.

Bundled remittances are the classic residual-pile item: one bank line settles two or three invoices and no single open item matches. That is a subset-sum question, and a subset-sum question should never be answered by a language model. Here is the fourth rule from the shipped engine, capped at combinations of three to stay client-side fast, with the consume step that makes every match exclusive:

// R4: subset of 2-3 of the payer's open items sums to the line
bank.forEach(function (line) {
  if (matched[line.i]) return;                       // rules run in order; strongest key wins
  var pool = (byParty[line.p + "|" + sideOf(line)] || [])
    .filter(function (it) { return open[it.i]; });   // only items still open
  var target = Math.abs(line.a), n = pool.length, i, j, k;
  for (i = 0; i < n; i++)
    for (j = i + 1; j < n; j++) {
      if (Math.abs(pool[i].a + pool[j].a - target) <= TOL)      // TOL = 5 cents
        return take(line, "R4", [pool[i], pool[j]], ["sum of 2"]);
      for (k = j + 1; k < n; k++)
        if (Math.abs(pool[i].a + pool[j].a + pool[k].a - target) <= TOL)
          return take(line, "R4", [pool[i], pool[j], pool[k]], ["sum of 3"]);
    }
});
// take() removes the items from the open pool and writes rule ID,
// matched keys and both record IDs to the hash-chained run ledger.
Amounts are integer cents, so "within tolerance" is exact arithmetic. The model appears nowhere in any of the four rules; it is only reached by the 240 lines that survive all of them.
05 · A worked month

Watch the pile get eaten, then referee what is left.

One click runs the cascade over the synthetic June statement. The waterfall clears 61.0% on invoice numbers quoted in remittance text, 18.0% on unique payment references, 9.0% on payer plus amount plus date, and 4.0% through the split solver. The 240-line residual then splits into four honest categories:

Residual classLinesWhat the model doesEvidence it must quoteWho decides
Truncated invoice digits85Proposes the one open item whose document number ends in the quoted digits, amounts agreeing to the centthe digit run, e.g. "241573"S. Lim, AR accountant
Bank charges netted from the receipt40Proposes the invoice plus a difference booked to bank charges expense"INV-0412xx" + "LESS BANK CHARGES 12.50"S. Lim, AR accountant
Short-pay with a deduction code35Proposes applying the cash and routing the shortfall to the deductions claims queue under its codethe code, e.g. "DED-QTY"S. Lim, AR accountant
No usable evidence80Abstains to the investigate queuenone exists, so none is claimedstays open, visibly

The short-pay class matters more than its 35 lines suggest. A customer who pays S$14,300 against a S$15,000 invoice with "DED-QTY" in the remittance is filing a quantity claim. The old workflow plugs the S$700 to suspense and loses the message. Here the disposition is explicit: the cash is applied, the shortfall goes to the claims queue under its deduction code, and nothing is written off without a named decision.

The memorable moment is the toggle. "Let the model match everything" re-runs the same book on amount alone, which is both the VLOOKUP workflow and what an unconstrained matcher converges to. It clears fewer lines (2,493 against the cascade's 2,760) and books 222 of them against the wrong open item, mostly duplicate amounts landing on the wrong customer's invoice. The diff view lists each one next to the documentary key that proves it wrong, and the governed run refuses every one of those bookings out loud.
06 · The numbers, and their limits

True by construction, and useful anyway.

The computed results, from the engine, every run: 92.0% of lines cleared by deterministic rules alone (1,830 + 540 + 270 + 120 of 3,000). The model touched only the 8.0% residual, proposed on 160 lines with verified quotes, and abstained on a third of it (80 of 240). Zero model-proposed matches are booked without a named human accepting them, which is enforced by the interface rather than promised by a policy. Every decision replays to identical hashes.

The limits, stated flatly. This is synthetic data, so real-world bank feed mess (encoding, MT940 dialect variants, PSP settlement batching) is only sampled, not covered. The split solver is capped at combinations of three to stay fast in the browser. Nothing posts to a real GL, so "reconciled" is a demo state. Match rates on real data depend heavily on remittance text quality, which varies by bank and by country; 92% is what this constructed month yields, a demonstration of the mechanism rather than a benchmark. And because I author both the data and the rules, the defensible claim is the architecture: documentary keys first, a propose-only model under a literal-quote check, a human gate, and a ledger that can replay the month.

07 · See it run

Run the cascade, then overrule the model.

Click once and watch the 3,000-line pile get eaten rule by rule. Open any residual line to see the model's quoted evidence highlighted in both records, and accept or reject it as the named referee. Flip the amount-only toggle to see the 222 wrong bookings the rules refused. Then tamper with the ledger and watch the chain locate the exact entry.

Open the live demo →Runs entirely in your browser · seeded synthetic data · identical results every run

Why the model has to argue like a junior.

The design rule underneath this demo comes from years of reviewing other people's reconciliations: a junior who says "these match, trust me" gets sent back to the desk, and a junior who says "these match because the remittance quotes invoice 241573 and the amounts agree to the cent" gets a tick. I hold the model to the junior's standard. It may only argue from quoted evidence, the quote is verified in code before anyone sees it, silence is an acceptable answer, and the sign-off is a named person's. That standard is centuries older than language models, which is exactly why it survives them. If your close still runs on amount-matching, the cascade is the easy win; the governance around the leftovers is where the audit trail gets decided.