"I can raise a thousand alarms an hour. Whether anyone still reads the thousand-and-first is a question my ROC curve refuses to answer."
An Overzealous AI Agent
The Big Picture
Every detector in this chapter ends the same way: a number crosses a line and someone, or something, is told to react. The previous sections taught you to build good scores and to evaluate them rigorously. This one is about the last, most consequential decision, where the threshold actually goes, and it forces a truth the math tends to hide. A threshold is not a statistical object; it is an economic one. Move it down and you catch more faults but you also spend more of a scarce, non-renewable resource: the attention of the human or downstream system that must act on each alert. Spend too much and you get alert fatigue, the well-documented collapse where operators start ignoring, muting, or auto-closing alarms because the base rate of useful ones has fallen below the effort of checking. At that point a detector with a beautiful precision-recall curve delivers zero real recall, because nobody looks. This section reframes threshold selection as a budget problem: you have an alert budget set by human bandwidth, each alert has a cost and an expected value, and the job is to place the line so that the alerts you emit are the ones worth someone's time. Get this wrong and the most accurate model in the fleet is operationally dead on arrival.
This section assumes you can already produce a calibrated anomaly score and reason about its error trade-offs, which is the work of Section 12.3 and Section 12.6. It also leans on the cost-of-errors framing from the probability primer in Chapter 4. Where earlier sections asked "is this alarm correct," here we ask the sharper operational question: "is this alarm worth raising, given everything else competing for attention right now."
Why low precision is fatal even when recall looks great
The uncomfortable arithmetic behind alert fatigue is the base-rate effect. Faults are rare. Suppose a machine truly fails on \(1\) sample in \(10{,}000\) and your detector is excellent: \(99\%\) recall and a false-positive rate of just \(1\%\). Run it on a million samples and you catch essentially every one of the \(100\) real faults, but you also fire on \(1\%\) of the \(999{,}900\) healthy samples, which is roughly \(9{,}999\) false alarms. The precision is about \(1\%\): ninety-nine out of every hundred alerts are noise. No human triages at that ratio for long. The formal object here is the precision at a fixed base rate,
$$\text{precision} \;=\; \frac{\pi\,\text{TPR}}{\pi\,\text{TPR} + (1-\pi)\,\text{FPR}},$$where \(\pi\) is the fraction of anomalous samples. When \(\pi\) is tiny, the \((1-\pi)\,\text{FPR}\) term dominates unless FPR is driven to something comparable to \(\pi\) itself. That is the whole game: to keep precision usable at a base rate of \(10^{-4}\), your false-positive rate has to live near \(10^{-4}\) too, which is far stricter than the "three sigma" reflex from Section 12.3 delivers.
Key Insight
Recall is a property of the detector; sustained recall is a property of the human loop. Once precision falls below the level at which operators keep responding, effective recall collapses to zero no matter how good the model is, because unread alarms detect nothing. So the operational objective is never "maximize recall." It is "maximize recall subject to precision staying above the fatigue threshold, and subject to the alert rate staying inside the budget the responders can actually absorb." Optimizing the model without optimizing the loop is optimizing the wrong system.
Threshold economics: a cost model for the line
To place the threshold deliberately, attach money (or time, or risk) to each cell of the confusion matrix. Let a missed fault cost \(C_{\text{FN}}\), a false alarm cost \(C_{\text{FP}}\) (the responder minutes plus the erosion of trust), and let true positives carry a benefit \(V_{\text{TP}}\) from acting early. For a threshold \(\tau\) that yields \(\text{TPR}(\tau)\) and \(\text{FPR}(\tau)\), the expected cost per sample is
$$\mathbb{E}[\text{cost}](\tau) \;=\; \pi\bigl(1-\text{TPR}(\tau)\bigr)C_{\text{FN}} \;+\; (1-\pi)\,\text{FPR}(\tau)\,C_{\text{FP}} \;-\; \pi\,\text{TPR}(\tau)\,V_{\text{TP}}.$$Minimizing this over \(\tau\) gives the economically optimal operating point, and it is almost never where a symmetric statistical rule lands. The ratio \(C_{\text{FN}}/C_{\text{FP}}\) is the single most important number you will elicit from a domain expert, and it swings across domains by orders of magnitude. A missed bearing failure that scraps a turbine dwarfs the cost of a nuisance ticket, so you push \(\tau\) down. A false cardiac arrhythmia alert that sends someone to the emergency department at 3 a.m. is expensive and frightening, so you push \(\tau\) up. The math is identical; only the costs move the line. This is also why a single global threshold across a heterogeneous fleet is usually wrong: the base rate \(\pi\) and the costs both vary per asset, so the optimal \(\tau\) does too.
Practical Example: the pump room that learned to ignore itself
A water utility instrumented eighty pumps with vibration and current sensors and shipped a residual detector tuned to a crisp \(3\sigma\) rule, validated at \(95\%\) recall on a held-out fault set. In the first month the on-call team received about \(140\) alerts per day across the fleet. Roughly nine in ten were benign: a pump ramping under normal demand, a sensor reseating after maintenance, a transient during a valve change. Within three weeks the team had built a filter rule in their ticketing tool that auto-closed anything from the "vibration anomaly" source overnight. When a real seal failure finally arrived, its alert was auto-closed with the rest, and the pump ran to destruction. The post-mortem found the detector had been technically correct every step of the way; the failure was economic. The fix was not a better model but a re-placed line: per-pump thresholds solved from each unit's own healthy-state distribution, an explicit budget of at most fifteen fleet alerts per shift, and a cost model that valued a missed seal failure at two orders of magnitude above a nuisance ticket. Recall on real faults barely moved; daily alert volume dropped by \(88\%\), and the team started reading alarms again. The same story recurs across Chapter 37 condition-monitoring deployments.
Budgeting attention: alert rate as a first-class constraint
Once you accept that attention is finite, the natural control variable is not the score threshold directly but the alert rate it produces. Pick the volume a responder can absorb, say \(k\) alerts per shift, and choose \(\tau\) so the empirical firing rate on recent healthy data matches that budget. This is the alarm-flood lesson codified in the process-industry standards (ISA-18.2, EEMUA-191), which put concrete numbers on tolerable load: roughly one alarm per operator every ten minutes as a manageable steady state, and floods above ten alarms in ten minutes as the point where operators stop coping. Rate budgeting has three practical virtues over raw thresholding. It is robust to score drift, because you re-solve \(\tau\) from the current distribution rather than trusting a frozen number. It composes across a fleet, because you can allocate the global budget to assets by expected value. And it degrades gracefully, because when a genuine broad event arrives the budget forces prioritization of the most surprising alerts rather than a formless flood.
Two mechanisms turn a raw score stream into a budgeted, low-fatigue alert stream. Hysteresis (dual thresholds: fire at a high level, clear only after dropping below a lower one) kills the chatter of a score dithering across a single line, which otherwise generates dozens of open-close pairs per real event. Debouncing or persistence requires the score to stay elevated for \(m\) consecutive samples before alerting, trading a little detection latency for a large cut in single-sample false positives. The persistence idea connects directly to the change-point accumulators of Section 12.4: a CUSUM that must build up evidence is a debounced detector by construction, which is exactly why change-point methods tend to alert less noisily than pointwise thresholds.
import numpy as np
def budgeted_threshold(scores_healthy, k_per_shift, samples_per_shift):
"""Pick the score threshold that fires at most k times per shift on healthy data."""
rate = k_per_shift / samples_per_shift # target per-sample alert rate
tau = np.quantile(scores_healthy, 1.0 - rate) # invert the empirical tail
return tau
def alerts_with_hysteresis(scores, tau_hi, tau_lo, persistence=3):
"""Emit one alert per sustained excursion, not one per sample above the line."""
alerts, armed, run = [], True, 0
for t, s in enumerate(scores):
run = run + 1 if s > tau_hi else 0
if armed and run >= persistence: # sustained, and not already firing
alerts.append(t); armed = False
if not armed and s < tau_lo: # cleared: re-arm for the next event
armed = True
return alerts
rng = np.random.default_rng(0)
healthy = rng.normal(0, 1, 200_000)
tau_hi = budgeted_threshold(healthy, k_per_shift=15, samples_per_shift=28_800)
stream = np.r_[rng.normal(0, 1, 5_000), rng.normal(4.5, 1, 60), rng.normal(0, 1, 5_000)]
print(f"tau_hi={tau_hi:.2f}, alerts={len(alerts_with_hysteresis(stream, tau_hi, tau_hi-1.0))}")
budgeted_threshold inverts the healthy-score tail so the detector fires at most fifteen times per eight-hour shift (28,800 one-second samples), and alerts_with_hysteresis collapses each sustained excursion into a single alert instead of one per sample. Together they convert a raw score into an attention-respecting alert stream.The snippet above shows the two moves in isolation, but production alerting stacks add deduplication, grouping of correlated assets, escalation tiers, and suppression during known maintenance windows. You should not hand-roll all of that.
Right Tool: let the alerting layer do the plumbing
The rate-budgeting math is a few lines, but the operational envelope around it (grouping, deduplication, inhibition windows, escalation, on-call routing) is hundreds of lines of stateful, well-tested infrastructure. Alert managers such as Prometheus Alertmanager or Grafana OnCall express hysteresis with for: durations, deduplicate and group correlated alerts with a few lines of routing config, and mute maintenance windows with a single silence rule, replacing roughly 300 to 500 lines of bespoke state machine with declarative YAML. Keep your model's job to emitting a calibrated score and a suggested threshold; let the alerting layer own the fatigue-control policy, where it can be tuned by operators without redeploying the model.
Measuring fatigue and closing the loop
Threshold economics only works if you instrument the loop you are trying to protect. The metrics that matter are operational, not statistical: the alert-to-action ratio (fraction of alerts that lead to any intervention), the acknowledgement latency and its drift over time (rising latency is the early signature of fatigue), the auto-close and mute rates (an operator silencing a source is a loud signal that precision has failed), and the true faults that arrived without a preceding open alert. Feed these back into the cost model: the empirical false-alarm cost \(C_{\text{FP}}\) is not a constant but rises as responders tire, so a system that ignores fatigue systematically under-prices its own false positives. Practically, review the alert budget on the same cadence you retrain, treat any asset whose mute rate crosses a threshold as mis-calibrated, and remember that lowering the alert rate is often a larger real-recall win than any model improvement, because it buys back the attention that turns alerts into actions. This closes the loop with the fleet-operations concerns of Chapter 69, where alert quality is a monitored, drifting quantity like any other.
Exercise
You run a wearable arrhythmia detector on \(2\) million overnight heartbeats per user-month; the true prevalence of the target event is \(\pi = 5\times10^{-5}\). Product tells you a user will disable notifications once fewer than one alert in five is real (precision below \(0.2\)). (a) Using the precision formula, what is the maximum false-positive rate you can tolerate at recall \(0.9\)? (b) Your current detector runs at FPR \(=10^{-3}\); by what factor must you cut it? (c) Suppose halving the alert rate costs you \(5\) points of recall. Argue with a cost model whether that trade is worth it when a missed event costs \(50\times\) a false alarm, and state which term in the expected-cost equation decides it.
Self-Check
1. Why can a detector with \(99\%\) recall and \(1\%\) false-positive rate still be operationally useless, and which quantity makes it so? 2. Give two reasons a single global threshold across a heterogeneous fleet produces worse alert economics than per-asset thresholds. 3. What does hysteresis fix that a persistence (debounce) requirement does not, and vice versa?
Lab 12
build a telemetry anomaly detector; analyze precision-recall under different alert budgets and a leakage-safe protocol.
Bibliography
Alarm management and fatigue
The reference standard for alarm rationalization, giving concrete tolerable-load numbers (alarms per operator per ten minutes) and the flood definition that underpins alert budgeting.
Clinical documentation of alert fatigue as a named safety hazard, showing how low-precision monitor alarms lead staff to mute and ignore, with fatal consequences.
The complementary industrial guide to ISA-18.2, widely used to benchmark operator alarm load and to justify rate-based thresholding in control rooms.
Detection theory and cost-sensitive evaluation
Van Trees, H. L. (1968). Detection, Estimation, and Modulation Theory, Part I. Wiley.
The canonical treatment of the likelihood-ratio test and how the decision threshold follows from the ratio of error costs, the theory behind threshold economics.
Fawcett, T. (2006). An Introduction to ROC Analysis. Pattern Recognition Letters.
The standard exposition of ROC and iso-cost lines, showing graphically how the optimal operating point moves with class prior and cost ratio.
Quantifies why ROC hides the base-rate problem and precision-recall exposes it, the empirical case for judging rare-event detectors by precision.
Operations and anomaly-detection practice
Ren, H. et al. (2019). Time-Series Anomaly Detection Service at Microsoft. KDD.
A production account that treats alert volume and operator trust as first-class design constraints, not afterthoughts, for a large monitoring service.
Codifies the discipline of paging only on actionable, symptom-based alerts, the operational counterpart to the cost model in this section.
What's Next
In Chapter 13, we leave hand-built scores and thresholds behind and let the model learn its own representation of a sensor stream. Neural encoders will give us richer anomaly scores, but the economic lesson of this section carries forward unchanged: a learned detector still emits alerts into a finite pool of attention, and the fanciest representation in the world earns nothing until its threshold respects the budget.