I built one, tested it against synthetic data with planted answers, and it passed. Then I fed it realistic retail patterns and it reported that a product had lost 5.9× more units than it had ever sold.
Every failure below is measured, not hypothetical. The numbers are reproducible.
You want to know what running out of stock cost you. You have an orders export. The obvious method:
Every step of that is wrong in a way that costs money in one direction: it overstates. And a merchant who believes an overstated loss buys stock they do not need.
A SKU that sells 20 units every Saturday and nothing else. Over a year: 52 Saturdays, 1,040 units.
The flat rate is 1,040 ÷ 365 = 2.85/day. Between Saturdays there are 6-day zero runs. At 2.85/day, six silent days is a 17-unit shortfall — which clears any reasonable significance bar. So all 52 gaps get flagged.
Reported loss: 6,122 units. Actual sales: 1,040.
The tool says you lost six times what you ever sold, on a product with no availability problem at all. And in a ranked table sorted by dollar value, this phantom sits at the top, above every real finding.
A refinement that sounds principled: once you have identified outage days, exclude them from the denominator, because the product wasn't available then.
pass 1: λ = 1040 / 365 = 2.85 → flags 52 gaps (312 days)
pass 2: λ = 1040 / (365-312) = 19.62
The rate is now 19.62/day for a product that sells 20 units a week. Every gap still clears the bar, so nothing gets un-flagged, and the loss is priced at the inflated rate. This is a positive feedback loop, and it converges on nonsense.
This is the one almost everyone misses.
Testing one gap at 95% confidence means a 5% chance of a false positive. But you are not testing one gap. Over 19 SKUs × 180 days you are running thousands of tests. Expected false positives scale accordingly.
Measured, on synthetic data with 5 planted stockouts:
| Planted | Flagged | Reported loss | Truth | |
|---|---|---|---|---|
| Per-gap 95% | 5 | 12 | $16,523 | $4,427 |
Nearly 4× overstatement. Worse, of the three largest findings by dollar value, two were products with no stockout at all — so the ranking a merchant would work down from is led by phantoms.
The fix is a family-wise threshold. Expected false runs across M SKU-days is roughly M · e^(−λN), so requiring
λN ≥ ln(M / α)
holds the expected number of false alarms in the whole report to α. For 30 SKUs over a year at α = 0.05 that is a bar of 12.3 expected missed units — much higher than per-gap testing suggests, and it has to be set once for the report, not per gap.
If your entire shop sold nothing on a day, that is a closure, an outage, or a tracking gap. It is not evidence about any individual SKU.
Counting those days per-SKU means one closure generates phantom loss across your whole catalogue simultaneously.
Worked through for a Mon–Fri shipper selling 6/day: 261 selling days a year, so a flat rate of 4.29/day. A weekend's two silent days score 8.6 — under the bar, fine. But a Thursday-to-Sunday holiday closure scores 17.2 and gets flagged. Two such closures put 8 days out, which after the pass-2 rate inflation prices at about 35 units of loss per SKU. Across a 30-SKU catalogue that is over 1,000 units of loss that never happened, appearing everywhere at once and outranking real findings.
I am giving that in units rather than dollars on purpose: the dollar figure depends on a unit price I would have to assume, and assuming it is how the overstatement starts.
Four changes, in order of how much they matter:
ln(M/α).Same adversarial fixture, after:
| Flagged | False positives | Reported | Truth | |
|---|---|---|---|---|
| Before | 12 | 9 | $16,523 | $4,427 |
| After | 3 | 0 | $3,681 | $4,427 |
A tighter run — one genuine 12-day stockout hidden among a weekend-only SKU, a discontinued SKU, a seasonal SKU and two holiday closures — returns exactly one finding: $1,735 against a true $1,800.
Being honest about this is the point, not a disclaimer.
The corrected total is an undercount by construction. That is the correct direction to be wrong in, because the merchant's response to an overstated loss is to spend money.
Every figure above is either computed directly from the stated algorithm or read off a run against synthetic data with planted answers:
Where a figure would have needed an assumption I could not defend, it is given in units rather than money.
The failures show up with a generator that produces realistic retail shapes rather than uniform Poisson demand. Mine passed every planted-answer test and was still badly wrong, because my synthetic data sold evenly every day. Real catalogues do not. The patterns that break these calculators:
1.234,56 parsed as 1.23456 — a 1000× error)05/11/2026 read as 11 May instead of 5 November, which scrambles the daily series and manufactures gaps)If you maintain one of these calculators, the weekend test takes ten minutes and is the one most likely to be failing right now.
I maintain a free stockout cost calculator that implements the corrected method. It runs entirely in the browser, requires no account, and reports which SKUs it refuses to judge. hyperbarrow.com/stockout-cost-calculator