All projects

Mailroom-ML

ML training, evaluation & calibration

At a glance

95.98% M9c document-type accuracy on 323 test documents (310 correct)

0.0189 M9c window calibration error (ECE), not document-level calibration

39.01% M9c confidence-only acceptance upper bound (126 of 323); not production fast-path coverage

4 of 4 M9c release gates met; this does not certify every subclass

The current published release is M9c (m9c-20261014-113614), reviewed on 6 November 2026 against the source README at 01c2272⁠ and its held-out test report⁠. Conditional subclass accuracy is 229 of 310 scorable documents (73.87%). The 39.01% confidence-only figure omits support, catch-all, head-exclusion and subclass-projection guards; the full gate can accept fewer documents. Contract and correspondence remain excluded from fast-path routing by checkpoint policy.

The M9b results below and the technical report preserve the earlier experiment history. They are not the current release’s metrics, and none of these evaluation runs was rerun for this site refresh.

Published artifacts

M9c held-out measurement Result Scope
Document-type accuracy 95.98% (310 / 323) Documents
Conditional subclass accuracy 73.87% (229 / 310) Only documents with a correct type
Calibration error (ECE) 0.0189 541 windows, not 323 document probabilities
Confidence-only acceptance 39.01% (126 / 323) Upper bound before production guards
OOD flag rate 5.57% (18 / 323) Diagnostic on this test pool

The four specified #112 release gates pass. The separate report-only subclass target of 0.75 is not met (0.7387); passing the release gates is not a blanket subclass-quality claim. A reported M9c test-window sweep selects 0.94, but test-pool sweeps are diagnostics, not independently validated deployment thresholds.

The problem

A mailroom model reads incoming legal and insurance documents and decides what each one is. A large language model (LLM) can do that well, but it costs money on every document. Many documents are easy. A cheap classifier could handle those and pass only the hard or unfamiliar ones to the LLM.

That plan only works if the classifier knows when it is unsure. A model that is 95% accurate overall but wrong with high confidence on some documents would quietly send errors downstream. So the project was less about raw accuracy than about three questions. Are its confidence scores honest? Can I pick a cutoff with a known error rate? Can I trust the training data to measure any of this?

Approach

I trained one ModernBERT encoder with a document-type head and one subclass head per type. Long documents are cut into overlapping 8,192-token windows, and the windows vote on the answer. Each head gets its own temperature scaling. A routing gate combines calibrated confidence, how much the windows agree, and the margin over the runner-up. A document takes the fast path only if the gate passes. Everything else goes to the LLM.

What I built

Earlier results: M9b

The numbers below come from the earlier M9b run. It trained for four epochs and was scored on a test split of 323 documents (541 windows). The test split was not used for training, for choosing the checkpoint epoch or for fitting calibration. It was used for more than one evaluation, though: I compared runs on it, ran a confidence sweep on it and ran the decode sweep described below on it. The 0.97 cutoff itself is a preset from the project’s config, and the sweep landed on the same value.

Measure Result
Document-type accuracy 0.9505 (307 of 323)
Subclass accuracy, when the type is right 0.6287
Window calibration error (ECE) 0.0152, down from 0.0259 in the earlier run
Documents passing the routing gate 74.3%
Out-of-distribution flag rate 4.0%

The M9b sweep looks for the lowest confidence cutoff whose accepted windows have an error bound under 2% and a band calibration error under 0.05. Lower cutoffs (0.87 to 0.96) met the error budget but failed the calibration check, so it comes out at 0.97, which accepts about 80% of windows (431 of 541). Scoring on the L4 GPU took 0.575 seconds per document.

Heads up. The sweep that supports the 0.97 cutoff ran over these same test windows, and so did the decode sweep. That makes the fast-path and coverage numbers optimistic: they describe an operating point, not a validated setting. The tool that writes deployment thresholds refuses to run on a pool that contains test documents, so real thresholds are fit on the validation split.

The M9b subclass heads were the weak part. Insurance claims reached F1 of 1.000, but that class is highly templated (114 test documents), so I would not read it as general skill. Merger-agreement F1 was 0.000 because the model put all 17 test merger agreements into one rare subclass. Contract subclass F1 was 0.162 against a 0.20 floor, leaving one acceptance gate failing. A decode-time adjustment did not fix it; the best setting reached 0.170. M9c later passed the release gates, but that does not make every subclass reliable.

The figures are from an earlier 3-epoch run called Arm B, whose document-type accuracy is also 0.9505. “Arm A” and “Arm B” are the project’s names for two comparison runs: Arm A was a one-epoch smoke run and Arm B a three-epoch run with a heavier document-type loss weight. “M9b” is the four-epoch run in the historical table, not the later M9c release.

These saved Arm B charts illustrate the earlier error analysis, not the current M9c checkpoint. Their original chart directories are not present in the current source tree, so the manifest records historical provenance rather than a currently reproducible upstream chart path.

Confusion matrix of document type for the 323 test-split documents. Insurance claims and merger agreements are classified 100% correctly. Contracts are 88% correct with most errors going to corporate records. Corporate records are 89% correct. Correspondence is 95% correct.

Which type each test-split document was routed to, from the earlier 3-epoch Arm B run.

Bar chart for each document type showing subclass accuracy next to a collapse signal: the share of predictions landing on one subclass. A 0.75 accuracy gate line is drawn. Contract subclass accuracy is 0.04 and corporate record 0.45, both far below the gate, while insurance claim is 0.99.

Subclass accuracy against how much each head collapses onto a single answer, from the earlier 3-epoch Arm B run.

The full numbers, the calibration plots and the iteration history are in the technical report.

What I learned

Mailroom Reloaded uses this model as its first-stage bert_primary step.

Stack

Python PyTorch Transformers ModernBERT Hugging Face datasets scipy ONNX FastAPI Modal pytest uv