Case study · all five pillars
HYPER-3: the trial that has to stop an arm
Adults with elevated blood pressure are randomized to standard care or to one of three daily doses of an investigational agent. Seated systolic pressure is measured weekly for 24 weeks. Everything below is computed with axiom against a synthetic world whose truth we know — which is the only way to check that a method catches what it is supposed to catch.
The protocol
400 patients, four arms, three age bands
Randomization is stratified by age band in 2:1:1:1 blocks, so the shared control arm is twice the size of each dose arm. Enrollment is staggered over 16 calendar weeks, which is what makes an interim review a real event: at any given review some patients have months of follow-up and some have weeks.
ARMS
Four
standard of care, 10 mg, 20 mg and 40 mg daily.
80 patients in each dose arm; 160 on standard care
STRATA
Three age bands
25-35, 36-50 and 51+, in shares 25% / 40% / 35%.
clearance is slowest in the oldest band — which is where the trouble is
REVIEWS
6 safety looks
At calendar weeks 12, 14, 16, 18, 20, 22, monitoring 12 contrasts — every dose arm, pooled and inside each age band.
12 × 6 chances to stop something early
The truth the trial is trying to find
Benefit in the young cancels harm in the old
The agent lowers blood pressure through a saturating dose–response. It also has a superlinear pressor effect that is negligible below 20 mg and concentrated in the oldest band, where clearance is slowest. Below is what that does to the net benefit — and why looking only at the arm average is not a conservative choice.
Change in systolic pressure versus control at week 12
Below zero is a fall in blood pressure, which is the point of the drug. Above zero is harm. This is the intention-to-treat contrast — the quantity randomization actually licenses, averaged over how much medication people take.
hyper3.intent_to_treat_contrast(dose, stratum, week=12) — the data-generating process itself, integrated over the adherence distribution. In a real trial this curve is exactly what you do not have.
Why the average is the dangerous statistic here
Follow the grey line to 40 mg. Pooled over the whole trial population the contrast is -0.64 mmHg — which is to say the top dose looks indistinguishable from doing nothing. Unremarkable, not alarming. A monitoring rule watching that number has nothing to fire on.
Now split it. At the same dose the youngest band is at -5.90 — a substantial benefit — while the oldest is at +5.85, a substantial rise in blood pressure. The average is near zero because the two are cancelling. Averaging is not the conservative choice here; it is the choice that destroys the only signal that matters.
Worth noting what the dose is doing to the pooled curve even so: at 20 mg the pooled contrast is -4.30 mmHg, and going up to 40 mg moves it to -0.64. The higher dose is worse on average — but “worse than the middle dose” is not the same alarm as “harming a quarter of the patients”, and only one of those triggers a stopping rule.
The rule, written before the data
A boundary the statistic has to cross
The protocol commits in advance: stop an arm when there is at least a 95% posterior probability that it is worse than control by more than 2 mmHg. axiom turns that sentence into a Z threshold at every scheduled review, accounting for how much information has accumulated by then.
information = information_fractions(enrolled_by_look, n_total) schedule = LookSchedule(labels=..., information=information) rule = StoppingRule( name="harm|age_51_plus|dose_40", looks=schedule, boundaries=(harm_boundary( 0.95, # posterior probability that fires the rule information, margin=2.0, # mmHg worse than control se_at_full_information=se, ),), ) path = monitor(rule, observed_z, effects=effects, ses=ses)
Two things are worth noticing. The boundary is
derived from the protocol sentence rather than chosen from a table — change
the margin or the probability and the thresholds move accordingly. And the same
StoppingRule object is used both to check the realized data and to compute
the rule’s operating characteristics before the trial opens, so the thing being
evaluated is the thing that will actually run.
| true drift | P(stop at some review) | expected reviews taken | expected information used |
|---|---|---|---|
| +0.0 — no harm | 1.0% | 5.96 | 0.995 |
| -1.0 | 6.0% | 5.78 | 0.972 |
| -2.0 | 24.3% | 5.18 | 0.897 |
| -3.0 | 58.2% | 3.98 | 0.748 |
| -4.0 — severe | 87.4% | 2.60 | 0.575 |
The rule's power curve
How often the rule stops an arm, against how much harm is genuinely there. The table states five points; this is the shape between them, which is what says whether a rule is trigger-happy or asleep.
axiom.design.operating_characteristics(rule, drift) at each drift — computed exactly, not simulated
Under no harm at all the rule fires 1.0% of the time — a low false-stop rate is what buys the right to look six times. Under severe harm it fires 87% of the time and, on average, after 2.6 reviews rather than all 6.
The trial runs
Pooled it is fine. Split by age it is not.
All four panels below monitor the same 40 mg arm against the same protocol rule, on the same vertical scale. The only difference is who is counted.
Safety monitoring of the 40 mg arm, four ways
Shaded region is harm. Faint dotted continuations are reviews the arm never reached, because it had already been stopped.
axiom.design.monitor(rule, z, effects, ses) per contrast · effects from axiom.identify.ols with ANCOVA covariates at each look · seed 20260821
The crossing, in the protocol’s own words
P(40 mg is worse than control by more than 2 mmHg | data) — against a rule that fires at 0.95
Stopping early costs you something, and axiom records what
The decision writes a ledger line: “harm|age_51_plus|dose_40: harm boundary crossed at look 1 (week_12), Z = -3.221 against -2.527, at information fraction 0.375”
The assumption it carries is
stopped_estimate_bias —
an estimate reported at the look that stopped the study is biased away from the null; its naive interval does not have the nominal coverage It is challenged by:
a median-unbiased or stage-wise-ordered estimate computed for the same path
This matters more than it sounds. An arm stopped at a boundary crossing has a biased effect estimate by construction: you stopped precisely because the reading was extreme, so the reading overstates the effect. The right thing for a library to do is not to hide that, and not to silently correct it either, but to record it as an assumption attached to the number so that whoever writes the report knows a stage-wise-ordered estimate is owed.
Every monitored contrast
Twelve contrasts, six reviews each. Exactly one fired.
The lowest Z each contrast ever reached
Sorted worst first. Eleven of the twelve never leave the middle of the range; the one in red is the 40 mg arm inside the oldest band, and it is the reason the trial has a stratum-level rule at all.
axiom.design.monitor(rule, z, effects, ses) per contrast · hover a row for what it did
| stratum | arm | reviews taken | lowest Z | decision | stopped at |
|---|---|---|---|---|---|
| all units | 10 mg | 6 | +1.91 | completed | — |
| all units | 20 mg | 6 | +1.55 | completed | — |
| all units | 40 mg | 6 | +0.45 | completed | — |
| 25-35 | 10 mg | 6 | +1.31 | completed | — |
| 25-35 | 20 mg | 6 | +1.12 | completed | — |
| 25-35 | 40 mg | 6 | +1.86 | completed | — |
| 36-50 | 10 mg | 6 | +1.29 | completed | — |
| 36-50 | 20 mg | 6 | +1.04 | completed | — |
| 36-50 | 40 mg | 6 | +1.08 | completed | — |
| 51+ | 10 mg | 6 | +0.33 | completed | — |
| 51+ | 20 mg | 6 | -0.13 | completed | — |
| 51+ | 40 mg | 1 | -3.22 | stop_harm | week 12 |
What it took
Every pillar showed up
The stopping decision is one line of output. Getting to a trial where that line means something used the whole toolkit.
01 · IDENTIFY
What randomization buys
Randomization licenses the contrast, but the analysis still conditions on baseline and stratum. axiom states which adjustment set the graph licenses and what per-protocol versus intention-to-treat actually changes.
See the pillar
02 · DESIGN
Sizing and the boundary
The 400-patient size, the information fractions at each review, the boundary derived from the protocol sentence, and the operating characteristics that justify looking six times.
See the pillar
04 · SURFACE
The dose–response
An Emax curve in dose with an exponential onset, which is what makes 20 mg and 40 mg nearly indistinguishable on benefit while their harm profiles diverge sharply.
See the pillar
The six notebooks
This page is a summary. The full case study lives in
the repository at nbs/case-studies/hypertension/ — six executed
notebooks over one synthetic world, from the causal graph through sizing, the
dose–response, the sequential design, and the review that stops the arm. Its design
notes are in docs/notes/0004-case-study-hypertension.md.