Live demo

14 series in. 4 alerts out.

Four vendor APIs, 60 days of your own outbound calls, 14 series. On day 42 the payments vendor deployed. They did not announce it, the status page stayed green, and every affected request returned 200. Find the change.

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 14 series and count what lands in the channel.

Naive rule
38
alerts sent · alert if |Δ| > 20% vs 7-day mean
Of those, false
14
fired before anything was wrong
This engine
4
alerts sent, all correct
Noise reduction
89%
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 alerts14 series tested · 4 fired · 0 suppressed by BH at FDR=0.05
MetricTrendBaselineNowChangeConfidenceMethod
p95 latency
vendor=payments
385ms777ms+102%99.9%CUSUM
Null rate, settlement_id
vendor=payments
0.62%14.7%+2285%99.9%CUSUM
4xx rate, code 402.7
vendor=payments
1.39%5.25%+278%99.9%CUSUM
Mean response size
vendor=payments
8414.175302.17−37.0%99.9%CUSUM

Show your work

p95 latency · vendor=payments

Baseline
385ms
After changepoint
777ms
Change
+102%
Confidence
99.9%
CUSUM (Page 1954)
The series60 daysdashed line = baseline mean · red = detected changepoint
320ms582ms844mschangepointJun 29Aug 27
The CUSUM accumulatorsmall deviations stack slowly; a real shift stacks fast
h = 0.251 (decision boundary)0.780

Baseline mean μ = 0.0020 and σ = 0.0627, both computed from the first two thirds of the day-over-day percentage changes. The slack k = 0.0313 is half a sigma, and the decision boundary h = 0.2506 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 |Δ| > 20% vs 7-day mean fired 5 times across this one metric. The statistical pipeline sent one message, on the day the regime actually changed.

What lands in Slack

One message, with the work already done

C
Contract DriftAPP#eng-alerts
🔴 p95 latency jumped +102%vendor=payments
Baseline
385ms
Now
777ms
Confidence
99.9%
Started
Aug 10
Vendor
payments
Endpoint
POST /v2/charges
Detected
Aug 10, from your own call logs
Calls
4.1M
Likely cause · Four independent signals on vendor=payments break at the same index and nowhere else. Latency doubled, one 4xx subcode went from 1.4% to 5.1%, settlement_id started arriving null on about 15% of responses, and mean payload size fell 39%, which is consistent with a nested object no longer being expanded by default. The 5xx rate never moved, so the vendor status page shows a green month.
  1. 1.Pull ten flagged responses and diff them field by field against ten from Aug 5.
  2. 2.Make settlement_id a required field in your parser so the null path stops silently writing empty rows.
  3. 3.Send the vendor the changepoint date, the subcode, and the payload diff. That is a support ticket they cannot close as "no known issues".
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.