Notes

Most stockout-cost calculators overstate losses. Here is the arithmetic.

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.

What these tools try to do

You want to know what running out of stock cost you. You have an orders export. The obvious method:

  1. For each SKU, lay sales out day by day
  2. Compute a daily rate: units ÷ days
  3. Find runs of consecutive zero-sale days
  4. Flag a run as a stockout if it is improbably long
  5. Lost units = rate × run length

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.

Failure 1 — a weekend-only product is not out of stock on Tuesday

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.

Failure 2 — recomputing the rate makes it worse

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.

Failure 3 — no correction for how many chances you gave it

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:

PlantedFlaggedReported lossTruth
Per-gap 95%512$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.

Failure 4 — a public holiday is not a stockout

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.

What survives

Four changes, in order of how much they matter:

  1. Per-weekday rates. Each weekday gets its own rate, shrunk toward the SKU mean so a thin weekday cannot produce a wild estimate. A Saturday product is measured against Saturdays.
  2. Discard store-wide zero days first, before anything else is computed.
  3. One threshold for the whole report, from ln(M/α).
  4. Hard cap: lost units can never exceed units sold. If the estimate wants to break that ceiling, the model does not fit — drop the SKU from the total and say so.

Same adversarial fixture, after:

FlaggedFalse positivesReportedTruth
Before129$16,523$4,427
After30$3,681$4,427
Failure 3 — same fixture, before and after Reported loss on one synthetic file with 5 planted stockouts. The truth is $4,427. Per-gap 95% testing flags 12 stockouts and reports $16,523, nearly four times the truth. The corrected method flags 3 and reports $3,681, slightly under the truth. Failure 3 — same fixture, before and after Truth 5 planted $4,427 Per-gap 95% 12 flagged $16,523 Corrected 3 flagged $3,681 truth
Both bars are the same input file. The only change is the threshold.

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.

What still cannot be known

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.

Where these numbers come from

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.

Reproducing this

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:

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