learn · improvement loop
Control limits and alert math
Baselines, control charts, sample-size gates, false alarms.
In Improvement loop
A threshold is only as good as its arithmetic. This page covers how to draw a baseline, how to put control limits around a rate, how many units a line needs before it can see anything, and how often a set of lines will cry wolf. The worked examples use small-account numbers, because that's where most of these choices go wrong.
metric → baseline · limits · gate → compare → fire or stay quiet
what it is, in industry terms
Statistical process control, for a growth pipeline.
Manufacturing has used control charts since the 1920s: measure a process, draw a center line and limits from its own history, and only intervene when a point lands outside the limits or a run of points drifts. The same idea separates a real drop in a play's reply rate from an unlucky week. Every rate in the pipeline is a share of units (replies per touch, real matches per label), so the workhorse here is the p-chart. Small counts get Wilson intervals; slow drifts get EWMA or CUSUM.
how it works
From history to a line.
- History — 4 to 8 weeks same version
- Clean it — drop incident days split by weekday?
- Center line — p̄ or median
- Limits — ±3σ act ±2σ watch
- Gate — min sample n > 9(1−p̄)/p̄
- Freeze — baseline row until re-fit
Take the history — Four to eight weeks of the metric on the current version of whatever it watches. Older history describes a different system.
Clean it — Drop days with known incidents (a dead source, a dispatcher outage). Note weekday patterns: a Monday dip in post reach isn't a drop.
Draw the center — The baseline rate over the clean history. For skewed costs, use the median.
Draw the limits — ±3σ for action, ±2σ for a watch note, with σ computed from this period's own n.
Set the gate — The smallest n where the lower limit clears zero, or 30 units, whichever is larger.
Freeze it — Store the baseline as a row. Re-fit on a schedule or when the watched version changes, never silently.
control limits for a rate
The p-chart: center line ± 3 standard errors.
center p̄ = baseline rate (e.g. replies ÷ touches over the last 4 to 8 weeks) sigma σ = √( p̄ · (1 − p̄) / n ) n = units in this period limits = p̄ ± 3σ (act) p̄ ± 2σ (watch)
Worked example: baseline rate p̄ = 8%, n = 30 units in the period, observed rate 3.0%.
8.0% — center line
0.0% — lower limit (floored at 0: blind)
22.9% — upper limit
noise — this period
With n = 30 the lower limit is below zero, so this line cannot catch a drop. It needs at least 104 units per period: widen the window or use a Wilson gate.
minimum sample
A lower limit below zero means the line is blind.
If p̄ − 3σ is under zero, no observed rate can ever fall below the limit: a drop to nothing still reads as noise. The lower limit only clears zero when n > 9 · (1 − p̄) / p̄. That's the real minimum sample for a floor on a rate, and it's why weekly limits on low rates rarely work for a small account.
| Baseline rate | Units needed for a usable lower limit | At 30 units a week, use a window of |
|---|---|---|
| 2% | at least 442 units | 15 weeks |
| 5% | at least 172 units | 6 weeks |
| 8% | at least 104 units | 4 weeks |
| 10% | at least 82 units | 3 weeks |
| 15% | at least 52 units | 2 weeks |
| 20% | at least 37 units | 2 weeks |
| 30% | at least 22 units | 1 week |
| 50% | at least 10 units | 1 week |
small counts
Wilson intervals: honest ranges for a handful of units.
With 3 replies out of 20, "15%" hides how little is known. The Wilson interval gives the range the true rate plausibly sits in. A threshold on a small count fires only when the whole interval is past the line, not when the point estimate is.
Worked example: 3 successes out of 20 units, against a floor of 10%.
15.0% — point estimate
5.2% – 36.0% — 95% Wilson range
not enough evidence yet — keep collecting
A fixed line at 10% on the point estimate alone would stay quiet. The interval says the data cannot tell yet.
false alarms add up
Many lines, checked often, will fire by chance.
A single 3-sigma line on a stable process fires by chance about 0.27% of the time per check (two-sided). Twenty lines checked daily is 140 checks a week. The worked example below shows how often at least one fires with nothing wrong.
Worked example: 20 active lines, each checked 7 times a week, at 3σ.
0.27% — chance per check
140 — checks a week
32% — weeks with at least one false alarm
0.38 — expected false alarms a week
Assumes independent checks on stable processes. Real metrics are correlated, which lowers the count a little; it doesn't change the lesson.
what to do about it
Use 3 sigma for anything that acts and 2 sigma only for a "watch" note. Require 2 consecutive periods outside the limit before a diagnostic line opens a hypothesis. Keep guardrails (stop requests, touched holdouts) as plain ceilings: there a false alarm is cheap and a miss is not.
the baseline object
Baselines are rows too.
A threshold compares against a frozen baseline, never a number recomputed at firing time. The alert stores the baseline it used.
| Field | Type | What it holds |
|---|---|---|
baseline_id | uuid | One row per baseline fit |
metric_id + scope | ref | What it describes, e.g. reply rate for one play |
watched_version | text | The prompt, detector or play version the history came from |
window | dates | The history used, after cleaning |
excluded | list | Days dropped and why (incident ids) |
center | number | p̄, mean or median |
sigma_basis | text | How σ is computed: per-period n, or pooled |
limits | object | Act and watch limits, and the minimum sample |
method | enum | fixed · p-chart · EWMA · CUSUM · Wilson gate |
fitted_at | timestamp | When it was frozen |
supersedes | baseline_id | The previous fit, kept for audit |
a reply-rate baseline for one play
{
"baseline_id": "bl_91a2…",
"metric_id": "reply_rate_per_touch_7d",
"scope": {
"kind": "play",
"id": "listen-and-reply"
},
"watched_version": "opener v5",
"window": {
"from": "2026-09-21",
"to": "2026-10-18"
},
"excluded": [
{
"day": "2026-10-02",
"why": "dispatcher outage"
}
],
"center": 0.11,
"sigma_basis": "per-period n",
"limits": {
"act_sigma": 3,
"watch_sigma": 2,
"min_sample": 73
},
"method": "p-chart",
"fitted_at": "2026-10-19T09:00:00Z",
"supersedes": "bl_77f0…",
"note": "example values"
}methods
Which method for which metric.
Fixed line
- use for
- Guardrails: stop requests, touched holdouts, error rates.
- good
- Simple, auditable, right when the line comes from policy, not history.
- catch
- Ignores noise; needs a minimum sample.
p-chart (Shewhart)
- use for
- Rates per period with enough units: reply rate per play, precision per detector.
- good
- Limits adapt to each period's n.
- catch
- Blind on low rates with small n; slow to see small drifts.
EWMA
- use for
- Slow drift in a rate: edit distance creeping up, reply rate sagging.
- good
- Weights recent periods more, catches small sustained moves.
- catch
- Needs a smoothing choice (λ around 0.2); lags sudden jumps.
CUSUM
- use for
- Detecting a shift as early as possible once it starts.
- good
- Accumulates small deviations; good for 'did something change this week'.
- catch
- Harder to explain; needs a target shift size.
Wilson gate
- use for
- Small counts: a new play's first 20 touches, a detector's first labels.
- good
- Honest ranges at tiny n; fire only when the whole range is past the line.
- catch
- Wide ranges mean late firing; that's the cost of small data.
c-chart (Poisson)
- use for
- Counts per day: matches per detector, signals per trigger.
- good
- Catches spikes and droughts in volume.
- catch
- Weekday seasonality breaks it unless split by weekday.
Change vs baseline
- use for
- Costs and durations: cost per lead, minutes per draft.
- good
- Robust with a median baseline; easy to read.
- catch
- Pick the percent with the noise in mind; gate it.
Staleness timer
- use for
- Freshness: source age, Hot lead age, overdue readouts.
- good
- No statistics needed: time since last event.
- catch
- Needs the event's own timestamp, not the capture time.
Beta posterior
- use for
- Comparing a new version to the old on few units.
- good
- Gives the chance the new version is worse, with a prior from learnings.
- catch
- Prior choice matters; write it down before looking.
illustrative example
Three lines, set properly on small numbers.
These are examples to show the arithmetic, not measured rates.
Reply rate on one play — Example: baseline 8%, about 30 touches a week. σ = √(0.08 × 0.92 / 30) ≈ 5 points, so the weekly lower limit is below zero: blind. A usable floor needs at least 104 touches, so read it on a rolling 4-week window (about 120 touches) or use EWMA weekly.
Detector precision — Example: baseline 71% over the last 50 labels. σ ≈ √(0.71 × 0.29 / 50) ≈ 6.4 points; the 3σ lower limit is about 52%. The catalog's starter floor of 60% sits inside that, so expect it to fire on noise sometimes; after a month of labeled firings, re-fit.
Stop requests — Example: a ceiling of 2 per 100 touches over 7 days, gated at 50 touches. This stays a fixed line on purpose: one bad week of complaints costs more than a false pause.
failure modes
How the math gets misused.
Limits on mixed versions — The baseline spans a prompt change, so the limits are too wide and miss the new version's drop. · Store watched_version on every baseline; reset on change.
Weekday seasonality — Posts on weekends always reach fewer people; a fixed limit fires every Saturday. · Split baselines by weekday for posting metrics, or compare to the same weekday.
Peeking at limits — Re-drawing limits after a bad week makes the bad week normal. · Re-fit on schedule only; the bad week stays outside the fit window.
Ignoring n — Using last month's σ for a week with a third of the volume. · σ always uses the current period's n.
One point, one action — Opening a test on one point outside a watch limit. · Act on 3σ, or 2 consecutive periods past 2σ.
data to capture
What the math needs stored.
record — Every baseline fit with its window, exclusions, method and watched version; every comparison's value, n and limits.
label — Whether the period was later confirmed abnormal (incident, real change) or normal.
trains — Better sigma widths per metric, and which method (p-chart, EWMA, CUSUM) catches real problems earliest on this account.
test — Back-test a method on the labeled history before switching a threshold to it.