Hapax · Case study · Ledger anomaly detection

The anomaly nobody caught

The three screening layers from the case study above, scored against a held-out answer key. The catch matrix shows what each layer found and missed. Expand any row to read the evidence each analysis cited.

Fabricated data. Saarnitukku Oy is fictional. 87 planted irregularities and 18 planted benign exceptions in 50,000 rows. Every figure is scored against the answer key.
50,000ledger rows, FY2025
264flags: 64 rule + 200 model
87planted anomalies (0.17%)
39 ×3assessed groups × independent runs
2false positives in 117 verdicts
1story caught by nobody

A year of flags

Two automated layers screened the ledger. Seven deterministic rule tests (the audit classics: duplicates, round sums, threshold games, weekend postings, mis-posted accounts) raised 64 flags. An Isolation Forest over eight engineered features raised exactly 200. The flags were grouped into 39 assessed items, and three independent analyst runs had to call each one worry or stand down, with recomputable evidence either way.

FLAGS PER POSTING MONTH · 2025 JanFebMarAprMayJunJulAugSepOctNovDec 25 26 9
rule-test flags (64) detector flags (200)

Detector flags cluster where the ledger is busiest, at the year-end seasonal peak. They turn out to be statistical texture.

Three layers, marked separately

The rules

48 / 64

flags on planted anomalies, for a first-pass precision of 75%, matching the prediction written before the data was screened, with 100% recall on every rule-shaped class. The other 16 flags are the designed benign bank: contractual round rent, an agreed stocktake weekend, one coincidental equal-amount pair.

A second pass (recurring-identical suppression, materiality floors) would silence most of the 16. That pass is described in the write-up but was not implemented.

The detector

0 / 200

flags on anything planted. The Isolation Forest missed all three designed catches (the drift vendor’s best invoice ranked 818th of 50,000) and reaching the designed six catches would have required flagging 29% of the whole ledger. The result was committed to in advance and published without adjustment.

Diagnosis in the notebook: a sustained drift inflates the very baseline it is measured against, and single-invoice vendors present no history to be unusual against.

The analysts

37 · 35 · 38

of 39 assessments correct across three independent runs. All 26 detector units were correctly stood down by all three, and the 200-flag triage burden was carried cleanly. Two false positives in total, both the same cautious hedge on one designed benign pair.

364 evidence figures machine-checked: zero disagreements; two −€1,000 headline slips recorded.

The catch matrix, designed against as-run

Eight anomaly classes were planted, each with a prediction, written before screening, of which layer would catch it. Seven behaved. The eighth did not.

ClassWhat was plantedTxnsRulesDetectorAnalysts
DDuplicate invoices – vendor re-sends after an unanswered week, both get posted12designed12/120/123/33/33/3RU-02 · RU-03 · RU-04
RRound sums and amounts hugging the €10,000 approval tier10designed10/100/101/22/22/2RU-05 · RU-06
WOne user posting on weekends and holidays8designed8/80/80/11/11/1RU-13
SPurchases split to stay under the approval tier10designed10/100/103/30/33/3RU-07 · RU-08 · RU-09
AAn advertising agency’s invoices mis-coded to IT services8designed8/80/81/11/11/1RU-01
VOne supplier as three vendor records – three spellings, one VAT id, three IBANs150/150/15designed1/11/11/1free hunt
GThe exemplum: freight silently bundled into goods invoices, then price rises hidden inside the bundle – breadcrumbed in the memos, mirrored in the freight carrier’s decline120/12designed0/120/10/10/1best rank 818
CThe ceiling: twelve unremarkable consulting invoices, catchable only with data this ledger does not carry120/120/121/11/11/1
BBenign exceptions flagged by rules16designed16/160/163/32/32/3RU-10 · RU-11 · RU-12
BDesigned catches the detector missed20/2designed0/2ranks 9,069 · 6,641–

Gold marker shows the layer each class was designed for

The case was designed with one class nothing catches. As run, it has two. A ~€1,800-a-month drift on one vendor is invisible in account-level totals. Only a per-vendor movers sweep with a drill-down would have surfaced it, and no run drilled there.

All 39 assessed groups, all three runs

Each group, its expected verdict from the answer key, and what each run actually said. Wine-marked rows are where a run got it wrong.

Try this – open a wine-bordered row below, RU-05 or RU-11 are good ones. Each expands to show what all three runs actually said and the evidence each run read, so you can see a plausible-sounding call go wrong for yourself.

worry expected/called stand down wrong call

Rule-side units (13)

RU-01Mainostoimisto Kaarniala Oymapping · 8 flags · €36,934exp WWWW
The planted mis-posting (class A): every invoice coded to IT services instead of marketing, by one clerk, in September and October. All three runs called it, all three named the mechanism.
RU-02Pulttitarvike Tmiduplicate · 4 flags · €2,915exp WWWW
Two planted duplicate pairs (class D) – same amount, days apart, different invoice numbers, same order reference. 3/3 worried, correctly.
RU-03Poralogistiikka Tmiduplicate · 4 flags · €10,291exp WWWW
Two more D pairs. 3/3 worried.
RU-04Kumipalvelu Tmiduplicate · 4 flags · €8,122exp WWWW
The last two D pairs. 3/3 worried.
RU-05Säiliöpalvelu Oy Abround_sum · 6 flags · €41,000exp WSWW
Six invoices at exact €1,000 multiples (class R). Run 01 read them as contract-style billing and stood down – wrong against the key. Runs 02 and 03 called the round-sum habit.
RU-06Ratashuolto Oynear_threshold · 4 flags · €38,843exp WWWW
Four invoices at €9,580–9,899.99, the only vendor ledger-wide living in the band just under the €10,000 tier. 3/3 worried; run 03 rated it act-level.
RU-07Kantamateriaali Tmisplit · 4 flags · €24,567exp WWSW
Two split-purchase events (class S): pairs days apart, each part under €10,000, sums well over. Run 02 accepted the "osatoimitus" (partial delivery) memos at face value and stood down – the one reading the design planted the memos to test.
RU-08Verkkokomponentti Oy Absplit · 4 flags · €22,767exp WWSW
Two more S events; same split of opinion, same miss by run 02.
RU-09Kairakomponentti Oy Absplit · 2 flags · €13,331exp WWSW
The fifth S event. Runs 01 and 03 worried; run 02 stood down.
RU-10Kiinteistö Oy Vantaan Teollisuustaloround_sum · 12 flags · €180,000exp SSSS
The landlord’s rent: €15,000.00, monthly, by contract (benign B1). All three runs stood down with the recurring-contract evidence – the designed round-sum false positive, triaged correctly three times.
RU-11Ruuvitukku Halonen Oyduplicate · 2 flags · €14,840exp SSWW
The designed coincidence (benign B3): €7,420.00 twice, four days apart, genuinely distinct deliveries evidenced by two different PO references in the memos. Run 01 read the POs and stood down. Runs 02 and 03 saw the same POs and hedged to worry anyway – the case’s only two false positives, both cautious hedges on genuine uncertainty.
RU-12U-031calendar · 2 flags · €3,101exp SSSS
Two Saturday postings whose memos state the agreed stocktake overtime (benign B2). Stood down 3/3, memo quoted as evidence each time.
RU-13U-117calendar · 8 flags · €7,505exp WSWW
One user, eight postings on Saturdays and holidays, no stated justification, peers at zero (class W). Run 01 read it as flexible-hours data entry and stood down; runs 02 and 03 flagged the control question.

Detector-side units (26) – every one benign, every one stood down by every run

DU-01…2424 vendor clusters, 160 flags, €0 wrong3–17 flags each · €1,267–€66,828exp S ×24S ×72/72
Twenty-four vendors whose statistically odd rows – rare-but-allowed accounts, own-history amount tails, seasonal timing – explain themselves on inspection. All three runs stood every one down, each verdict with recomputed within-vendor evidence. 72 of 72 verdicts were correct on the model’s noise, which is the false-positive discipline the case was built to measure.
SINGLESNotable singles (top 10 by score)10 flags · €46,650exp SSSS
The detector’s ten highest-scoring unclustered rows. Nothing planted among them; stood down 3/3.
RESIDUALResidual tail30 flags · €105,891exp SSSS
Everything left. One aggregate distributional verdict per run; stood down 3/3.

What reasoning caught and what it missed

Found 3/3: the Kärrenbach trio

One German seals supplier existing as three vendor records (Kärrenbach, Kaerrenbach, KARRENBACH), one VAT id, one Hamburg address, three different IBANs, €49,001 of spend split across them. No rule test was built to see it (the byte-exact name check scores zero by design), and it lives across records, not within any one. Every run’s free hunt found it independently, made it the top action item, and named the payment-integrity mechanism.

Found 0/3: the drift

From July, Teräskontio’s invoices carry "sis. rahtikulut" (freight bundled in, a +8% step) and then price rises hidden inside the bundle, December landing 19% over baseline, while the freight carrier’s own billing shows the mirrored decline. The evidence sits in plain memo text and one vendor-level comparison. The detector missed it. All three analysts, spending their bounded free hunts elsewhere, missed it too.

The ceiling class held in both directions: nothing flagged Neuvantila’s twelve unremarkable consulting invoices, and no run accused them without evidence. The production catches this ledger deliberately cannot support (three-way matching, fuzzy vendor-master hygiene, recurring-invoice suppression) are stated in the write-up.

What this would look like on your data

Your own purchase ledger already carries this shape of signal: duplicate submissions, round-sum habits, a vendor whose invoicing has quietly drifted. Deterministic rule tests and a statistical detector cover whatever the numbers alone can show, and reasoning agents read memos and vendor records for whatever needs explaining. On this ledger, the detector caught none of what was planted, and the reasoning layer carried the account.

Read the long-form case study · Code and dataset · Contact Hapax