The model reported 97.2% accuracy. Fourteen months after launch the operations team was still reviewing every single prediction by hand. Both facts were true, and the second one followed from how the first was measured.
The classifier sorted inbound claim documents into fourteen routing categories. The model card said 97.2% accuracy. The business case had promised a 60% reduction in manual triage effort.
Actual reduction after fourteen months: zero. Not "less than hoped": the operations team opened, read and confirmed every prediction, exactly as they had before the model existed. The model had added a step.
The CTO's theory was that operations was resistant to change and needed a mandate from above. I spent the first day with the operations team instead.
The figure was honest, competently produced, and answered a question nobody needed answering. It was measured on a balanced test set: roughly equal numbers of each of the fourteen classes, assembled by the data science team so the model wouldn't "just learn the priors."
That is a reasonable thing to do while developing a model. It is a catastrophic thing to report to a business, because production does not look like that at all:
| Class | Test set | Production | Per-class recall |
|---|---|---|---|
| Routine correspondence | 7.1% | 44.0% | 99.1% |
| Standard medical record | 7.1% | 28.0% | 98.4% |
| Proof of loss | 7.1% | 17.0% | 97.9% |
| … nine mid-volume classes … | 50.0% | 10.4% | 94–97% |
| Litigation notice | 7.1% | 0.4% | 71.2% |
| Large-loss first notice | 7.1% | 0.2% | 64.8% |
Three observations, in increasing order of how much they cost:
This was the part the CTO's "change resistance" theory got backwards. I asked the operations lead why she didn't trust the high-confidence predictions. Her answer took ten seconds:
"It says 0.94 on the ones it gets right and it says 0.94 on the ones it gets wrong. So what am I supposed to do with 0.94?"
She was describing a calibration failure, precisely, without the vocabulary. The model emitted confidence scores. Nobody had ever checked whether they meant anything. I plotted the reliability curve that afternoon:
| Stated confidence | Predictions | Actually correct | Gap |
|---|---|---|---|
| 0.90 – 0.95 | 31% | 88.2% | −4pp |
| 0.95 – 0.99 | 46% | 93.1% | −4pp |
| > 0.99 | 18% | 95.0% | −4pp |
Systematically overconfident, and — worse for her purposes — almost flat. Ninety-five percent of predictions sat above 0.90, and the difference in real accuracy between "0.91" and "0.995" was five points. The score carried almost no information about whether to trust an individual prediction, so the only rational policy was to check all of them. Operations was not resisting the model. Operations was responding correctly to a signal that was noise.
The balanced test set stayed for development. Every business-facing number moved to production-weighted, per-class precision and recall, with the two critical classes reported separately and never averaged into anything.
Temperature scaling on a held-out set, fitted per class group rather than globally, because the rare classes were miscalibrated in a different direction from the common ones. The point is not the technique. It is that after fitting, 0.9 means 0.9, and the score spreads across the range instead of piling up above 0.90. Only then is a threshold meaningful.
The team had been tuning for macro-F1, which treats a misrouted piece of routine correspondence as exactly as bad as a missed litigation notice. Operations knew the real ratio. Written down, it was roughly:
# cost of one misroute, in analyst-hours of downstream rework routine_correspondence 0.2 standard_medical 0.5 proof_of_loss 1.5 litigation_notice 40.0 # missed regulatory clock large_loss_fnol 60.0 # reserving delay, escalation
Thresholds were then chosen per class to minimise expected cost rather than to maximise a symmetric metric. In practice this means the common classes auto-route at a fairly relaxed threshold, and the two expensive classes route to a human unless the model is very sure, a policy any claims manager would have written by hand, and one no F1 optimisation would ever produce.
Calibration fixed the signal but did not fix the two classes that mattered. Litigation notices and large-loss first notices are rare, textually similar to routine documents, and the most expensive things in the estate to get wrong, so I stopped asking one model to carry them alone.
Anything the primary classifier places in a high-cost class, or that it places elsewhere with meaningful probability mass on a high-cost class, now goes to a second model from a different family for an independent read:
# high-cost classes only — 0.6% of volume, most of the risk both agree, high confidence → auto-route both agree, low confidence → review queue, ranked normally models disagree → review queue, top priority, both readings shown
The economics are not close. The council runs on well under one percent of volume, costs a few cents a day, and lifted large-loss recall further than any threshold change did, because the two models fail on different documents. Where a single model is confidently wrong, it is wrong alone; a disagreement is the cheapest possible early warning.
I deliberately did not extend this to the eleven ordinary classes. A council there would triple cost to protect against errors worth 0.2 analyst-hours. Multi-model review is a tool you point at expensive mistakes, not a default you apply everywhere.
Instead of "review everything", the queue now shows what the model predicted, its calibrated confidence, the runner-up class, whether the second model agreed, and the reason it was queued. It is ordered by expected cost of error, so the most consequential item is always at the top. Reviewing a prediction became a five-second confirm-or-correct rather than a full re-read.
And every correction is written back as a labelled example. This is the part the original build discarded entirely: fourteen months of an operations team correcting a model, and not one of those corrections reached the training set. The humans were doing the model's hardest work for free and the model never learned anything from it. Closing that loop means the review queue shrinks over time instead of being a permanent tax, which is the difference between human-in-the-loop as a design and human-in-the-loop as a euphemism for unpaid labelling.
| Measure | Before | After |
|---|---|---|
| Documents auto-routed | 0% | 63% |
| Precision on auto-routed | — | 99.4% |
| Litigation notice recall | 71.2% | 96.5% |
| Large-loss FNOL recall | 64.8% | 94.1% |
| Manual triage effort | baseline | −58% |
| Headline accuracy | 97.2% | 96.8% |
Note the last row. The number that had been reported for fourteen months went down. Recall on both classes the business actually cares about went up by twenty-five and twenty-nine points, and 58% of the manual effort disappeared. No model was retrained. Every gain came from measuring the right thing and choosing a threshold that reflected what errors actually cost.
A single accuracy number, averaged across classes, on a distribution you constructed, is not a measurement of anything a business can use. Measure per class, on the traffic you actually get, weighted by what each mistake costs you.
"What is that per class, on production traffic, weighted by cost of error?" is the question. If nobody can answer it, the headline figure is decoration. The triage call is thirty minutes and free.
Book a triage call