Live demo

16 series in. 4 alerts out.

A retail chain, 60 days of POS and cycle-count data, 16 series across four store-department pairs. On day 40 one pair started losing product. Sales volume and foot traffic at that store never moved, and the chain-wide shrink rate cannot move. The next physical count is in January. Find the pair and the date.

Every number on this page is computed in your browser right now, by the same CUSUM and Bayesian changepoint code that runs in production. Nothing here is a screenshot.

The comparison that matters

Against the rule most teams actually ship

The standard configuration is a percentage or sigma threshold against a trailing mean, evaluated per metric. Run it over the exact same 16 series and count what lands in the channel.

Naive rule
132
alerts sent · alert if |Δ| > 15% vs 7-day mean
Of those, false
69
fired before anything was wrong
This engine
4
alerts sent, all correct
Noise reduction
97%
fewer messages, same catch

The false-alarm count is not a judgement call. The incident was injected at a known index, so every naive alert before that index is wrong by construction.

What the engine found

Detection run

Detection run4 alerts16 series tested · 4 fired · 0 suppressed by BH at FDR=0.05
MetricTrendBaselineNowChangeConfidenceMethod
Unexplained unit variance
store=1147, dept=Health & Beauty
12.5136.68+193%99.9%CUSUM
Void transaction rate
store=1147, dept=Health & Beauty
1.20%3.28%+174%99.9%CUSUM
No-sale drawer opens
store=1147, dept=Health & Beauty
14.5037.55+159%99.9%CUSUM
Sell-through gap
store=1147, dept=Health & Beauty
2.43%6.85%+181%99.9%CUSUM

Show your work

Unexplained unit variance · store=1147, dept=Health & Beauty

Baseline
12.51
After changepoint
36.68
Change
+193%
Confidence
99.9%
CUSUM (Page 1954)
The series60 daysdashed line = baseline mean · red = detected changepoint
8.2924.4840.67changepointJun 29Aug 27
The CUSUM accumulatorsmall deviations stack slowly; a real shift stacks fast
h = 0.517 (decision boundary)1.190

Baseline mean μ = 0.0083 and σ = 0.1293, both computed from the first two thirds of the day-over-day percentage changes. The slack k = 0.0646 is half a sigma, and the decision boundary h = 0.5172 is four. The accumulator runs on percentage changes rather than raw levels so a healthy growing series cannot drift across the boundary on its own.

What the naive rule did on this same series

alert if |Δ| > 15% vs 7-day mean fired 10 times across this one metric, including 3 before anything was actually wrong. The statistical pipeline sent one message, on the day the regime actually changed.

What lands in Slack

One message, with the work already done

S
ShrinkAPP#eng-alerts
🔴 Unexplained unit variance jumped +193%store=1147, dept=Health & Beauty
Baseline
12.51
Now
36.68
Confidence
99.9%
Started
Aug 9
Store
1147
Department
Health & Beauty
Window
Aug 8 onward
Next count
Jan 2027
Likely cause · Four independent measures on one store-department pair moved together on Aug 8 and 9: unexplained unit variance at cycle count, void rate, no-sale drawer opens, and the gap between units that left the shelf and units that were sold. Sales volume and foot traffic at that store are unchanged, so this is not a demand story. Chain-wide shrink rate never moved, because one department in one store is a fraction of a percent of chain units. This flags a location and a date range for investigation. It identifies no individual and assigns no cause.
  1. 1.Pull exception reports for store 1147 health and beauty from Aug 8 onward.
  2. 2.Schedule an off-cycle count on the affected categories rather than waiting for January.
  3. 3.Review CCTV retention for the window before it ages out, following the chain investigation policy.
AcknowledgeFalse positiveCUSUM · q < 0.0001 · BH-corrected

The written cause is generated only after the statistics confirm the change. The model never decides whether something is an anomaly — it explains one that has already been established. Getting that order backwards is how these products hallucinate.