learn-anomaly-detection-with-phoebe / Builder session 10 of 10
Learn Anomaly Detection with Phoebe · Builder track · Session 10 of 10

Productionizing & alerting: shipping a detector people trust

A detector that scores well in a notebook is worth nothing until it runs on live data without crying wolf. This capstone turns the b3 rolling detector into an online streaming detector, wraps it in an alerting layer that deduplicates, prioritises, and routes, closes the feedback loop so on-call dispositions tune the threshold, watches for concept drift by monitoring the detector's own precision, and keeps a human in the loop for high-stakes calls. This is where the whole track lands: a system the analyst from b1 would actually rely on.

🔴 Builder track Practitioners: analysts · data scientists · engineers Python 3.10+ · a notebook Final session · capstone
0-3 · Recap 3-18 · Streaming + alerting 18-42 · Build-along: stream + drift 42-45 · Wrap
Part 0

From notebook to on-call pager

Nine sessions built and measured detectors on a fixed series sitting still in memory. Production is different in every way that matters: points arrive one at a time, latency counts, the baseline drifts, and a false alarm is not a number on a chart but a human woken at 3am. Tonight we ship. We make the detector streaming so it scores each new point online against a rolling baseline; we build the alerting layer that turns raw flags into one trustworthy page per incident; we wire the feedback loop so the people who answer those pages teach the threshold what a real anomaly looks like; and we monitor the detector itself for drift, because "normal" never stops moving. The single enemy the whole time is alert fatigue - the moment the team mutes you, your recall is effectively zero no matter what the offline score said.

Live - presented in session Self-study - read after class ★ Run it now (Python) Sources covered (cited)
★ What you walk out with today A streaming detector that scores points as they arrive against a rolling baseline, with deduplication so one incident is one alert; an alerting layer with severity and routing; a feedback loop that turns on-call dispositions into a rolling precision the threshold can learn from; a drift monitor that raises a retrain flag before alert fatigue sets in; and the guardrail habit of keeping a human between a detector and any high-stakes action.
Part 1 · the serving pipeline

Score online, alert once, close the loop 6 min live

A production detector is a loop, not a script. Each new point is scored against a rolling or seasonal baseline that travels with the data; if it crosses the band, the flag enters an alerting layer that deduplicates a burst into one incident, tags severity, and routes it to the right on-call. The responder's verdict - real or false alarm - flows back as a label that tunes the threshold and feeds the next retrain. And before any automated high-stakes action, a guardrail gate hands off to a human.

The detector is a closed loop, not a one-shot script New pointarrives live Score vsrolling baseline Thresholdband cross? Dedup + route+ severity On-callresponder Guardrail gatehuman-in-loop high-stakes action held for a human Tune threshold / retrain disposition: real / false alarm real / false label feeds back Dedup makes one incident one alert; the feedback label is what keeps the threshold honest over time.
🔍 Click to zoom - the streaming detector as a closed loop with a guardrail gate
LiveThe four jobs of the alerting layer3 min▶

Raw flags are not alerts. Between the detector and the pager sits a layer that does four things, and skipping any one of them is how a good detector still gets muted:

  • Deduplicate - a three-day bot surge is one incident, not three pages. A cooldown or suppression window collapses a burst into a single alert with a running count.
  • Prioritise - severity from how far past the band, and from what the metric feeds. A checkout-flow drop is a page; a blog-traffic wobble is a ticket.
  • Route - the right on-call for the right system, with escalation if unacknowledged. An alert nobody owns is an alert nobody actions.
  • Suppress - during known maintenance or a declared incident, mute to avoid a storm of redundant pages.
Real world

Alert fatigue kills more detectors than bad math. A detector that pages ten times a night for nine false alarms gets a mute rule within a week, and then it catches nothing - your real recall is zero regardless of the offline F1. Deduplication and a threshold tuned toward precision are not polish; they are what keeps the system alive.

Demo 1 of 2

A streaming detector with deduplication ★ 12 min · everyone builds

Replay the Lumen series as a live stream, one point at a time. Maintain a rolling baseline, alert when a point leaves the band, and suppress repeat alerts inside a cooldown so one incident is one page.

Rebuild the Lumen series and replay it point by point, as it would arrive in production.

Keep a fixed-size rolling window; score each new point before adding it to the baseline.

Emit an alert with a severity when a point crosses the band - but only if the cooldown has elapsed.

Confirm the collective run fires one deduplicated alert, not three.

★ Run it now (Python) - online streaming detector + dedupimport pandas as pd, numpy as np from collections import deque # --- rebuild the Lumen series (standalone) and replay it as a stream --- idx = pd.date_range("2026-01-01", periods=90, freq="D") season = np.array([30,42,48,40,22,-95,-110]) i = np.arange(90) y = 900 + 1.2*i + season[i % 7] + 10*np.sin(i*1.7) + 7*np.cos(i*0.6) y = y.astype(float) y[20] += 430; y[45] -= 380; y[[70,71,72]] += [175,190,170] s = pd.Series(np.round(y), index=idx, name="sessions") class StreamingDetector: def __init__(self, window=14, k=3.5, cooldown=3): self.buf = deque(maxlen=window) # rolling baseline self.k = k # band width in std devs self.cooldown = cooldown # suppression window (dedup) self.since_alert = cooldown + 1 # allow the first alert def update(self, ts, value): self.since_alert += 1 alert = None if len(self.buf) >= self.buf.maxlen: mu = np.mean(self.buf); sd = np.std(self.buf) or 1.0 z = (value - mu) / sd if abs(z) > self.k and self.since_alert > self.cooldown: sev = "high" if abs(z) > 2 * self.k else "warn" alert = {"ts": ts, "value": value, "z": round(z, 2), "severity": sev} self.since_alert = 0 # open the suppression window self.buf.append(value) # baseline learns AFTER scoring return alert det = StreamingDetector() alerts = [a for ts, v in s.items() if (a := det.update(ts, v)) is not None] for a in alerts: print(a["ts"].date(), a["severity"].ljust(4), "z =", a["z"]) print(f"\n{len(alerts)} deduplicated alerts (the 3-day run fired once)")
Score before you learn The baseline updates after the point is scored, never before - otherwise the anomaly contaminates the very baseline it is measured against and hides itself. This one-line ordering is the difference between an online detector that works and one that quietly goes blind during exactly the events you built it to catch.
Part 2 · feedback and drift

Let the pager teach the threshold 5 min live

A shipped detector is not done - "normal" drifts as the product grows, seasons turn, and traffic mix shifts, so a threshold that was precise in spring floods the pager by autumn. The fix is a feedback loop: every alert comes back marked real or false by the responder, and those precious labels become a rolling precision you watch like a vital sign. When it slides below a floor, you have caught alert fatigue before the team mutes you - time to re-tune the threshold or retrain.

Rolling precision, alert by alert - watched like a vital sign precision 1.0 0 alerts over time → floor = 0.5 retrain triggered Precision below floor Too many alerts came back "false". Normal has drifted; the threshold is now too loose for today's data. Act before the mute Re-tune or retrain now, not after Monitoring the detector's own precision is how you catch drift before the team stops trusting it.
🔍 Click to zoom - rolling precision sliding below the floor triggers a retrain
Self-studyConcept drift, retrain cadence, and guardrails2 min read▶

Three habits keep a live detector trustworthy long after launch:

  • Monitor the detector, not just the metric. Track rolling precision (and recall, as investigations resolve) on the dispositions coming back. A quietly falling precision is concept drift showing up before anyone complains.
  • Retrain on a cadence and on a trigger. Refit the baseline on a schedule (rolling window, seasonal refit) and also on demand when the drift monitor fires. Version the threshold so you can roll back a bad tune.
  • Guardrail the high-stakes path. A detector can open a ticket or throttle a bot on its own; it should not lock a customer's account or halt a payment line without a human. Keep a person in the loop wherever a false alarm is expensive or irreversible.
Demo 2 of 2

Alert feedback + a drift early-warning ★ 8 min · build your own

Accumulate on-call dispositions into a rolling precision, and raise a retrain flag the moment it drops below a floor. This is the alert-fatigue smoke detector - it fires before the team reaches for the mute button.

Feed in the stream of dispositions (real or false alarm) as they come back from the pager.

Keep a rolling window of the last N verdicts and compute precision over it.

Raise a "retrain" flag when the rolling precision falls below the floor.

★ Run it now (Python) - rolling precision + retrain flagimport numpy as np from collections import deque # on-call dispositions arriving over time: True = real anomaly, False = false alarm # (in production these come back from the pager as each alert is triaged) dispositions = [True, True, False, True, False, False, True, False, False, False, False, True, False, False, False, False, False, True, False, False] class DriftMonitor: def __init__(self, window=10, floor=0.5): self.recent = deque(maxlen=window) # last N verdicts self.floor = floor def record(self, was_real): self.recent.append(1 if was_real else 0) precision = float(np.mean(self.recent)) if self.recent else 1.0 full = len(self.recent) == self.recent.maxlen degrading = full and precision < self.floor return precision, degrading mon = DriftMonitor(window=10, floor=0.5) for n, real in enumerate(dispositions, 1): precision, degrading = mon.record(real) flag = " <-- RETRAIN: rolling precision below floor" if degrading else "" print(f"alert {n:2d} rolling precision = {precision:.2f}{flag}") # precision drifts down as false alarms pile up; the flag fires before the team mutes you
Real world · closing the loop back to b1

This is the loop the analyst from session 1 lives inside. They set the baseline and label the incidents; the detector scores the stream; the alerting layer pages once; the responder's verdict flows back as a fresh label; the drift monitor decides when to retrain - and it lands right back at the analyst. Ten sessions ago an anomaly was "a point that does not fit the pattern." Now it is a governed, measured, self-correcting system that a real on-call team can trust at 3am. That is the whole job.

Homework

Try it yourself - this week ◐ 30-45 min total

Source material

Sources covered (cited)

Taught from established production practice and the streaming-detection literature. This session covers:

✓Industry practice - streaming detection, alert deduplication, on-call feedback, drift monitoring, human-in-the-loop guardrailsParts 1-2 · the serving pipeline and its operational habits
◐Numenta Anomaly Benchmark (NAB, Lavin & Ahmad 2015)Part 2 · streaming evaluation and early-detection scoring
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · In an online streaming detector, when should the rolling baseline be updated?

Score first, then append. If the anomaly enters the baseline before it is measured, it inflates the band and hides itself during exactly the events you built the detector to catch.

2 · Why is deduplication essential in the alerting layer?

One incident, one alert. Without a cooldown or suppression window, a three-day surge pages three times, and the fastest way to get muted is to cry wolf.

3 · What does a falling rolling precision on returned dispositions tell you?

Rising false alarms mean the world drifted out from under the threshold. Catching it in the rolling precision lets you act before alert fatigue sets in and the detector gets muted.

Builder track wrap - the whole journey · pin this

b1 · Foundationsthe labeled Lumen series; point / contextual / collective; base rate; z-score baseline.
b2 · Statisticalz-score, 3-sigma, IQR / Tukey, robust MAD - the honest bar to beat.
b3 · Rolling & seasonalmoving mean/std, EWMA, STL-residual - the live detector; context is everything.
b4 · Distance & densitykNN distance and Local Outlier Factor - local, not global, outliers.
b5 · Isolation Forestanomalies are few and different, so easy to isolate - short path length.
b6 · One-class & covarianceOne-Class SVM and Elliptic Envelope - learn normal, flag departures.
b7 · Time-serieschange-point, CUSUM, forecast-residual anomalies - shifts and drifts.
b8 · Deepautoencoder reconstruction error, LSTM-AE - surveyed and cited, not a silver bullet.
b9 · Evaluationprecision, recall, F1, PR-AUC, threshold choice, point-adjustment - the honest scoreboard.
b10 · Productionstreaming, dedup, routing, feedback loop, drift monitoring, human-in-the-loop guardrails.
The one enemyalert fatigue. Tune toward precision, dedup, and monitor drift, or you get muted.
The one ruleaccuracy lies on rare events. Measure with precision and recall, and keep labels precious.