How the index is actually built.
A benchmark is worth exactly as much as its method. This page states the method in full, including the parts that limit what the index can claim. Everything here is a description of running code, not an intention.
The model reads, the code computes
A language model reads the statement and returns what is printed on it: the processor, the billing period, gross card volume, the transaction count, the statement's own total fee figure, and one row for every fee line, with the description and amount as printed. It is asked for a label guess on each fee line and nothing else.
No arithmetic is asked of it and none is accepted from it. The effective rate, the four category totals, the percentile, and the annual overpayment figure are all computed after extraction, in integer cents, by the same shared functions the product uses when a reviewer corrects a fee line and the document is checked again. A model that adds numbers up is a model that will one day add them up wrong, quietly, in a way no reader can catch.
This is also why the tool can be free. The expensive, uncertain part is reading a page; division is not expensive.
The reconciliation gate
Every processing statement prints its own total fees. The fee lines we extracted have to add up to that number within one cent. This is the same check vocabulary the rest of LedgerBox reconciles bank statements and invoices with, declared on the document type rather than written specially for this tool.
The check does three jobs at once. It measures whether we read the statement correctly, because a missed table or an unread page shows up as a difference. It filters fabricated submissions, because a made-up statement does not add up. And it is the admission gate: a submission enters the index only when it has no unresolved hard findings against it.
When the gate fails you still get your result. The numbers are still your numbers, the screen says plainly that the lines did not add up and the figure should be treated as approximate, and the submission is excluded from the index.
Anonymization by schema
The extraction schema for a merchant statement has no field for a business name, an address, a merchant id, or an account number. Not a field that is emptied later: no field at all. There is nowhere for those values to land, so they are never read, never stored, and never present to be leaked, and the anonymization does not depend on a cleanup pass running correctly every time.
The submission table is shaped the same way. It holds a processor, a period, volumes, fee totals, the fee lines, a volume tier, and the industry and state you chose from fixed lists. It has no column that could carry your identity. Your network address is stored only as a salted hash, for rate limiting and abuse forensics.
The industry and state lists are fixed choices rather than free text, because free text re-identifies people and does not aggregate.
What gets published
Aggregates, and only aggregates. A published cell is a processor, a volume tier, and at most one further axis: an industry or a state. It carries the count behind it, the 10th, 25th, 50th, 75th and 90th percentile rates, and the average share of fees that was processor markup and monthly charges.
A cell with fewer than 20 submissions behind it is never published. It is not published in the CSV, it is not published on a benchmark page, and it is not served by the API in any form, because the floor is enforced where the query runs rather than where the page renders. Below the floor a cell reads as collecting, which is the truth about it.
Two figures describe the index rather than a cell, and both clear the same floor before they appear. The first is the shape of the whole index: every consented rate counted into bins a tenth of a percent wide, which is what the curve on the tool page draws, one dot per statement. The second is each processor across every volume tier at once, as a count and a 10th, 50th and 90th percentile, which is what the strip under that curve reads. Neither is published until 20 statements sit behind it, and a bin inside the distribution names no processor, no tier, no industry, and no state.
There is no per-submission publication at any count. Not in the download, not through the API, not on request.
- Under $10k a month under_10k
- $10k to $50k a month 10k_50k
- $50k to $250k a month 50k_250k
- $250k to $1m a month 250k_1m
- Over $1m a month over_1m
Volume is normalized to an average month of 30.44 days before the tier is chosen, so a 28-day February and a 31-day March put the same business in the same bucket.
Outliers and quarantine
A rate below 0.5% or above 12% is held back from the index automatically. Once a cell is deep enough to have a shape, a rate outside its 1st to 99th percentile range is held back too. Held-back submissions are excluded from every aggregate until a human has looked at them.
A held-back submitter still sees their full result, unchanged. Their statement said what it said. The index is what declines to ingest an extreme figure before somebody has checked it, and that reluctance is what keeps a median honest.
Taxonomy versioning
Each fee line is sorted into one of four categories by an ordered rules table matched against the printed description. The rules were written against descriptions that appear verbatim on real statements, and the first matching rule wins. A description no rule covers falls back to the model's label guess and is recorded as having done so, which is how the list of rules to write next gets built.
-
interchange
Paid straight through to the bank that issued the card. Nobody in the chain gets to discount it, which makes it the floor under any rate anyone quotes you.
-
assessments
The card networks' own fees: Visa, Mastercard, Discover, and Amex dues, assessments, and per-transaction network charges. Also a floor.
-
processor_markup
What the processor keeps: the discount rate, per-item charges, and any surcharge on top of the two floors above. This is the part a negotiation moves.
-
other_fees
Monthly, statement, PCI, batch, chargeback, and equipment lines. Small individually, and on a low-volume statement they are frequently the largest share of the bill.
The table carries a version, currently 2026-08.1, and that version is stamped on every submission and every published cell. Adding, removing, or reordering a rule bumps it. That is what lets the dataset changelog say which dollars moved between categories and when, instead of a category quietly meaning something new.
What happens to your file
The statement you upload goes into storage that this tool alone uses. As soon as the result lands, the handler deletes the uploaded file and every artifact derived from it: the page images, the page text, the raw model response, and the normalized extraction. The deletion is recorded on the submission, so a run that did not clean up is identifiable rather than assumed.
Behind that, a storage lifecycle rule deletes anything older than one day in both of the tool's containers. That backstop exists for the run that crashes between reading a file and cleaning up after itself, which is the case a promise made only in application code would quietly miss.
One caveat, stated because it is true: the storage account keeps a deletion safety net, so a deleted blob remains recoverable by us, and only by us, for up to 14 days before it is gone beyond recovery. Nothing reads from that safety net in normal operation. It exists so that an operational mistake is not a data loss, and it applies to the deleted file the same way it applies to everything else in the account.
This is a statement about this tool and the file you hand it here. Documents you upload to the LedgerBox app are a different thing: they stay in your workspace until you delete them, which is the point of a workspace.
What survives, when you consent, is the fee data: descriptions, amounts, categories, totals, processor, volume tier, and the industry and state you chose. When you do not consent, only the non-identifying result and a hash of the file survive, so re-uploading the same statement returns your result instead of spending compute again.