The payment that vanished
· extraction, accuracy
Here is a customer statement, as the accounts system printed it:
14/04/2026 00153140 I 2071.30
14/04/2026 00153141 I 517.82
15/04/2026 ON A/C P -1294.56
19/05/2026 00153235 I 1176.00
23/06/2026 00153320 I 2589.12
────────
5059.68
Five lines. The script reading it produced four.
The missing one is the payment. The customer had paid £1,294.56 and the statement they received showed four charges adding to £6,354.24 above a total of £5,059.68. A £1,294.56 hole with nothing to explain it, and what was missing was their money.
Why
The rule that recognised a transaction line required an eight-digit reference:
^\s{1,4}(\d{2}/\d{2}/\d{4})\s+(\d{8})\s+([A-Z])\s+...
^^^^^^^^
00153140 is eight digits. ON A/C is not, so that line simply was not a
transaction as far as the code was concerned. No error, no warning. Credit notes
survived, because they still carry a numeric reference. It was specifically
payments on account that disappeared, the one row type where the system prints
words instead of a number.
Only one such row in that month’s file. Rare and silent is the expensive combination: it surfaces as a customer query months later, not as a failure anyone can act on.
The thing that actually matters
The bug is easy to fix. What is interesting is that nothing could have told you it was there.
The script ran without error. The output was well-formed. Every field that came back was correct. You would have to read sixty-three statements and add them up by hand to notice, and nobody does that.
Unless you ask the document to prove itself:
sum(transactions.outstandingValue) == totalOutstanding
Every statement carries its own proof. The lines should add up to the printed total. Declare that once, and the sixty-two good statements pass silently while the one bad one raises its hand, with the difference named.
That check takes fifteen seconds to write and would have caught this the first time it happened.
What we changed
The row rule now recognises a transaction by position. Is there a date here, is
there a value there? That replaces asking about the shape of the reference. A
rule based on where things sit cannot express this bug: ON A/C occupies exactly
the same columns as 00153140, because the file was laid out for a printer and
printers do not care what words mean.
That is the argument for extracting by position rather than by pattern, on documents that were designed for paper. Not that it is easier, but that the mistakes it can make are different, and smaller.
The arithmetic check went in too. It is the part that would have found this without anyone looking.