Day 51 Hard Alerting Base Rate Signal Detection Observability

Alerting Under a Low Base Rate — How Report-Level False Positives Drown the TruthBayesian precision, independent oracles, and the metrics trap of per-decision vs per-report FP rates

Problem Scenario + Requirements

You are designing an automated alerting system for a fleet of tens of thousands of instances: a swarm of probes, assertions, anomaly detectors, plus an LLM "judge" that stamps each verdict as "incident or not." Scale is the crux — about 10 million checks per day, while the base rate of events that genuinely deserve to page a human is minuscule: say only about 5 real incidents a day, so the prior probability that any single check is "true" is roughly 5 × 10⁻⁷.

Intuition says "my detector is accurate, FP is only 0.1%" — but that is exactly the base-rate fallacy: 0.1% times ten million checks is 10,000 false positives a day, drowning those 5 true reports, so P(true│alert) ≈ 5/10005 ≈ 0.05%. Of the 2,000 alerts an engineer receives, 1 is real; within weeks everyone learns the same reflex — swipe every alert away. At that point high recall is meaningless: a true report no one reads equals a miss. This shares DNA with Day 50 ("Code Review as Signal Detection"), but here the enemy isn't noise itself — it's the base rate.

High-Level Architecture (base-rate funnel → independent backstop → triage → page)

graph LR
    SRC["10M checks/day
probes · assertions · anomaly"] DET["Detector swarm
per-check FP≈0.1%"] ORA["Independent Oracle
differential/deterministic"] TRI["Triage · denoise
dedup · aggregate · precision floor"] PAGE["Page a human
<10/day"] ARCH[("Archive / silence
queryable, non-paging")] SRC -->|massive| DET -->|10k candidates| ORA ORA -->|independent verdict| TRI -->|precision≥90%| PAGE TRI -.demoted noise.-> ARCH DET -.low confidence.-> ARCH classDef src fill:#1a2530,stroke:#64c8ff,color:#e8eef5 classDef det fill:#1a1a30,stroke:#ffb450,color:#e8eef5 classDef ora fill:#0e2030,stroke:#5eead4,color:#e8eef5 classDef page fill:#2a1530,stroke:#ff7ab6,color:#e8eef5 class SRC src class DET,TRI det class ORA ora class PAGE page class ARCH src

Each stage of the funnel raises precision: detectors preserve recall, the independent oracle breaks correlation, triage guards the precision floor, and only <10/day reach human attention

Key Technical Points

1. The Bayesian math of the base-rate fallacy: precision crushed by the base rate

Principle: An alerting system's usability is set by P(true│alert) (the Bayesian detection rate / precision), which depends not only on recall but is dominated by the base rate. Bayes: P(true│alert) = TPR·B / (TPR·B + FPR·(1−B)). When base rate B is tiny, the denominator is dominated by FPR·(1−B) — even at TPR=99%, unless FPR is driven down to the order of B, precision approaches 0. This is exactly what Axelsson proved for intrusion detection: the limiting factor of an IDS was never the detection rate, but the false-alarm rate.

Trade-off (three responses, each with a cost):
# How the base rate crushes precision (compute before you design)
def precision(tpr, fpr, base_rate):
    tp = tpr * base_rate
    fp = fpr * (1 - base_rate)
    return tp / (tp + fp)

# "Accurate" detector: recall 99%, per-check FP 0.1%
precision(0.99, 0.001, 5e-7)   # ≈ 0.000495 —— 1 real in 2000 reports
# Shrink domain to high-risk surface, base rate up to 1e-4:
precision(0.99, 0.001, 1e-4)   # ≈ 0.090   —— still not enough
# Chain an independent oracle, effective FPR down to 1e-5:
precision(0.99, 1e-5, 1e-4)    # ≈ 0.908   —— clears the precision floor
Real-world cases:

2. The precision floor: triage is the gatekeeper of usability

Principle: Since single detectors can't move the base rate, hand precision to the triage / denoise layer — it produces no new signal, only dedups, aggregates, confidence-scores and cross-detector-votes candidates, admitting only verdicts above a precision floor (e.g. precision≥90%) into human view. Everything below is demoted to a "queryable but non-paging" archive. Key mindset: better a miss into the archive than noise into the pager — a single false positive's cost isn't itself, it's that it erodes trust in every subsequent alert.

Trade-off:
# Triage: cross-detector voting + aggregation + precision floor
def triage(candidates, floor=0.9, window="5m"):
    groups = group_by_root_cause(candidates, window)   # same-cause grouping, anti-spam
    for g in groups:
        # more independent detectors firing together = higher confidence (approx. multiplicative)
        conf = calibrated_confidence(g.detectors, g.signals)
        if conf >= floor:
            page(dedup_key=g.root_cause, evidence=g.top_signals)
        else:
            archive(g)        # queryable, in metrics, but never bothers on-call
Real-world cases:

3. Independent oracle backstop vs LLM self-judging: correlation is the invisible killer

Principle: The most tempting shortcut at low base rate is to let the model that generates the alert judge itself. But a backstop's denoise only holds when its failure modes are independent of the layer it backs. Chaining two layers to multiply FPR (0.1%×1%) presumes independence; if judge and detector share blind spots (same model, same prompt, same training distribution), they err together on the same inputs — false positives correlate, misses correlate, and effective FPR degrades from "multiply" to "barely moves." A real backstop needs an independent oracle: a deterministic assertion, differential re-check (run a reference path on the same input and compare, see Day 52), or a second judge of a different architecture / signal source.

Trade-off:
# A backstop's value depends on independence, not on "adding a layer"
def confirm(alert):
    # ❌ anti-pattern: same model self-confirms, correlated failure
    # return llm_judge(alert)                # hallucinate -> self-stamp

    # ✅ independent oracle: deterministic + differential, orthogonal failure modes
    if deterministic_assert(alert) is FAIL:            # hard rules first
        return True
    ref = run_reference_path(alert.input)              # independent reference path
    return normalize(alert.observed) != normalize(ref) # differential compare (Day 52)
Real-world cases:

4. The metrics trap: report-level vs decision-level FP rate

Principle: Teams love to report "our detector's FP rate is only 0.1%" — that's the per-decision FP rate, pretty but deceptive. What engineers actually bear is per-report precision: of the N alerts they open in the pager, how many are real. A per-decision 0.1% at base rate 10⁻⁷ corresponds to a report-level precision of 0.05% — same system, one metric shows "excellent," another "unusable." Deeper still is activity vs outcome metrics (following Day 50): alert count, coverage, detector count are all activity metrics — rising doesn't mean better; the true outcome metrics are the fraction of real incidents caught in time and on-call's trust in the pager.

MetricTypeTrapWhat to watch
Per-check FP 0.1%decision-levelreport-level collapses after multiplying by volumeconvert to precision before reporting
Total alerts / coverageactivityrising looks like progress, actually making noisetrue-report share, miss count
Detector countactivitymore detectors likely lower overall precisionmarginal precision contribution
Does on-call still read the pageroutcomeno one measures it, yet it's the system's life-or-death truthack rate / ignore rate
Real-world cases:

Scaling & Optimization

Common Pitfalls + Interview Follow-ups

1. "My detector is 99% accurate" ≠ usable system. The interviewer will press: what's the base rate? Compute the precision. Failing to do the Bayesian conversion = not understanding the essence, the classic point-loser.
2. Using an LLM to judge its own output. Correlated failure — it rubber-stamps its own hallucinations. Follow-up: is your backstop oracle independent of the generator? Where does that independence come from?
3. Treating recall as the sole goal. At low base rate, maxing recall = noise drowns the system = humans stop reading = actual recall goes to zero. Here recall and precision aren't opposed; rather, "recall is void if precision misses the floor."
4. Using alert count/coverage to prove progress. Activity metrics. Follow-up: what are the ack rate, ignore rate, true-report share? A system no one reads is dead at 100% coverage.
5. Ignoring the double edge of the aggregation window. Aggregation raises precision, but too wide a window merges two independent incidents or delays the first report. Follow-up: in a cascading avalanche failure, will your aggregation hide the root cause?

Deep-Dive Resources

Deeper Reflection (click to expand)

1. At base rate 10⁻⁷, to reach report-level precision 90%, how low must a single detector's FPR go? Is it realistic? Give the order of magnitude and alternatives.

Let TPR≈1. For precision≥0.9, i.e. B / (B + FPR·(1−B)) ≥ 0.9, solve FPR ≤ B/9 ≈ 1.1×10⁻⁸ — that's at most one error per hundred million checks. A single statistical/ML detector essentially cannot do this; calibration error alone far exceeds it.

So the right path isn't hard-pressing single-detector FPR, but:

  • Raise the base rate: shrink the decision domain from "all checks" to "SLO-violation/high-risk surface," lifting B three orders (10⁻⁷ → 10⁻⁴), relaxing the required FPR to ~10⁻⁵.
  • Chain independent layers: two independent FPRs multiply, 0.1%×0.1%=10⁻⁶, which with a raised base rate clears 90%.
  • Aggregate: fuse a single incident's N candidates into 1 report, effectively dropping the FP count from "per-check" to "per-event."

Key insight: low-base-rate problems are almost always solved by "raise B + independent layers," not by making some detector godlike.

2. Why does "just add another LLM judge" sometimes barely reduce false positives? Explain it through correlation.

The math premise of chained denoise is conditional independence: FPR_total = FPR₁ × FPR₂ holds only when the two layers' errors on the same input are uncorrelated. If both are the same base model, same prompt context, their errors are highly correlated — the "incidents" the generator hallucinates are exactly the kind the judge also endorses (same distributional blind spot). At the extreme, correlation approaches 1 and FPR_total ≈ FPR₁; the second layer adds nothing.

This is why a real backstop must change the failure mode: a deterministic assertion's errors come from missing rules (orthogonal to model hallucination); a differential oracle's errors come from reference-path bugs (independent implementation); a different-architecture second detector has different blind spots. Independence isn't "is there a second layer," it's "will the second layer err in the same places."

3. You shrank the decision domain from 10M to 10k high-risk checks and precision improved a lot — but where is the cost hidden? How do you quantify what you missed?

The cost is recall: real incidents outside the domain simply aren't judged, so they're missed outright. Shrinking = trading recall for precision, which at low base rate is often worth it (recall was illusory anyway when noise drowns it).

How to quantify what you missed:

  • Post-mortem back-check: at each real incident's review, check whether it originally fell inside the domain. Outside = your shrinking missed it; record an "out-of-domain miss."
  • Chaos injection (Day 44): inject known faults both inside and outside the domain, directly measuring both sides' recall. At low base rate this is the only way to get enough true samples.
  • Shadow judging: keep judging the excluded domain in the background without alerting, offline counting "how many real incidents would have been reported," estimating the shrink's miss rate.

Decision rule: are there "high-severity" ones among the out-of-domain misses? Trading precision for recall is fine, but you can't trade a Sev1 out — those must remain judged no matter how low the base rate.

4. A cascading avalanche failure trips hundreds or thousands of detectors at once. Is your aggregation/dedup layer a friend or an accomplice?

It's both. Friend: aggregation fuses an avalanche's thousands of alerts into a few "incidents," avoiding spam and stopping the real root cause from drowning in downstream symptoms — precisely the triage layer's value.

Accomplice risks:

  • Over-aggregation hides the cause: crude merging by "time window + similarity" may fuse the root-cause alert with downstream chained symptoms, so on-call sees only symptoms and can't find the source.
  • An independent incident mixed into the window: a second unrelated failure happening during the avalanche gets sucked into the same aggregate and suppressed as noise.
  • First-report delay: waiting a window to aggregate delays the first alert — and an avalanche most needs speed.

Countermeasures: aggregate by causal/dependency topology (not pure temporal similarity), preserve and highlight the "most upstream" alert; for high-severity signals use "report then aggregate" rather than "aggregate then report," defining same-cause by topology not time window.

5. On-call has stopped reading the pager — what does this "trust collapse" state look like in metrics? How do you catch it before the collapse?

Collapse's metric portrait: ack latency lengthens, ack rate drops, "acked but no action" share rises, bulk silence/snooze frequency spikes, the same alert re-fires with no one handling it. Note: alert volume and coverage may still look great here — precisely how activity metrics deceive.

How to catch it early (before collapse):

  • Treat "human behavior" as a first-class metric: monitor ack rate, ignore rate, false-positive feedback rate, not just the system. Humans starting to ignore is the earliest leading signal.
  • FP budget + circuit breaker: budget "false positives" like an error budget; a detector that burns past budget in a week auto-downgrades/retires, preventing it from eroding overall trust.
  • Sampled audit: periodically hand-label a batch of pages true/false, directly measuring report-level precision drift.

Core mindset: an alerting system's true SLO isn't on the machine side, it's on the human side — "do people still trust it." Once trust burns through, rebuilding costs far more than the denoising work you saved.