Skip to content
All insights AI finance-operations automation

Bank reconciliation automation: deterministic matching first, LLM for the tail

Most reconciliation should never touch a model. Here is how we split exact matching from the fuzzy exceptions an LLM handles, with an audit trail a controller trusts.

4 min read #reconciliation#finance-ops#automation
Financial services professionals working through an AI initiative

Most bank reconciliation should never touch a model. A bank line and a ledger entry that agree on amount, date and reference are the same event, and rules pair them in milliseconds. Reserve the LLM for the tail: the few percent of lines where amounts are aggregated, references are missing, and a payer name in free text is your only clue.

Bank reconciliation is a matching problem, and the bulk of it is genuinely easy. Automation earns its place on the remainder, so the instinct to point a model at the whole file is the expensive mistake. You take a task a hash join solves in milliseconds, wrap it in a probabilistic system that costs money per call, drifts, and cannot explain itself to a controller, and you inherit a review burden on matches that were never in doubt. The model should see a line only after the deterministic layer has given up on it.

The deterministic core does the volume

Start by treating both sides as feeds that need cleaning before they meet. Bank statements arrive as MT940, BAI2 or CAMT.053; the ledger comes from your ERP. Normalise amounts and signs, parse value dates against posting dates, and strip the reference field down to the identifiers that actually carry meaning, invoice numbers, end-to-end IDs, the payer’s structured remittance. This is unglamorous plumbing and it decides everything downstream. A reference parser that misreads a leading zero manufactures breaks that no matching logic can recover.

Then match in tiers, strongest key first:

  • Exact match on a unique reference plus amount. This clears the majority of clean corporate payments and needs no scoring.
  • Amount and value date within a tolerance window, blocked by counterparty account, for lines where the reference is absent but the pairing is unambiguous.
  • Many-to-one and one-to-many: a single bank credit that settles several invoices, or a batch payment split across ledger entries. Solve these as a subset-sum over a bounded candidate set, not by asking a model to add numbers.

Everything a tier pairs is committed automatically and recorded with the rule that made the call. What survives all tiers is the exception queue. Keep that queue small and honest; if a rule is pairing things it should not, you are hiding errors, not reconciling. A false match that nets two unrelated events is worse than a break, because a break gets looked at and a bad match does not.

The LLM works the tail, as a proposer

The exceptions are the interesting part, and they are interesting precisely because the signal is language. A wire arrives with “payment re: the March delivery, less the credit we discussed” and no invoice number. A customer pays three invoices in one lump and rounds down. A counterparty’s name on the statement is a trading name that never appears in your ledger. Rules cannot read intent out of a memo field. A model can, if you constrain what you ask of it.

Give the model a narrow job. For one unmatched bank line, present a shortlist of candidate ledger entries that a cheap retrieval step has already filtered by amount range, date window and fuzzy counterparty, and ask it to rank them and explain the match against specific fields. Do not let it invent a counterparty, adjust an amount, or reach outside the candidate set. Its output is a proposal with a confidence score and the evidence it used, nothing more. A high score above your threshold can auto-commit; the middle band routes to a human with the model’s reasoning already attached; a low score stays a break.

Two disciplines keep this from rotting:

  • A labelled eval set of real historical exceptions with known-correct pairings, run on every prompt or model change, scored on precision at your chosen threshold. You are managing a false-positive budget, so watch the matches it commits, not just the ones it misses.
  • Point-in-time correctness. When you replay the eval, feed the model only what was knowable on the reconciliation date. An open invoice that was later cancelled must look open, or your test leaks the future and flatters the score.

Counterparty resolution sits underneath both layers. Whether a rule blocks on account number or the model reasons over a name, “ACME LTD”, “Acme Limited” and “ACME (UK)” have to collapse to one entity first, which is an entity-resolution job worth doing once and reusing everywhere.

The audit trail is the deliverable

A controller does not sign off on a match rate. They sign off on being able to answer, for any pairing, why the system believed it. So build the trail as the primary output rather than something you bolt on once matching works. Every committed match carries the two source records, the tier or model that proposed it, the fields compared, the score if there was one, and the human who confirmed it when a human did.

That record is what makes the automation defensible at quarter-end and under audit. It also makes the model safe to extend, because when a match is wrong you can see exactly which field misled which layer and fix the cause rather than nudging a prompt. Lineage from bank line to ledger entry to the decision that joined them is the thing you are actually building. The matching is just how you populate it.

Keep the split clean and the system stays cheap and explainable: rules clear the volume with no per-call cost and a rule you can point to, the model handles only what genuinely needs reading, and nothing gets committed without a record a person can stand behind. Reverse that split, and you pay a model to do arithmetic while a controller loses the one thing they came for.

FAQ

Should an LLM match transactions?

No, not the bulk of them. Amount, date and reference matching is deterministic and should run as rules against an index. Reserve the model for the exception queue, where the evidence is a name string or a payment note that rules cannot parse.

How do you keep an LLM's matches auditable?

Treat the model as a proposer, not a decider. It suggests a match with a confidence score and the fields it relied on; the pairing is only committed by a rule threshold or a human, and both the proposal and the decision are written to the trail with the source records attached.

What break rate should we expect after automation?

It depends entirely on your data quality and reference discipline, so treat any single figure with suspicion. Measure your own straight-through rate on a labelled sample before and after, and track the exception queue over a full quarter-end, not a quiet week.

Working on something similar?

Tell us about your data and the workflow around it, and we will give you a straight read.

Book a 30-min intro call