3 episode families · Real CDISC ADaM · Deterministic oracle · Hard gates

Can AI do the statistical programming that a regulator would accept?

Every pivotal trial’s read-out passes through the same bottleneck: a Statistical Analysis Plan is turned into analysis code, then into hundreds of tables, listings and figures — each one independently re-programmed by a second statistician and reconciled. This benchmark asks whether an AI system can do that work end to end and auditably: reproduce the pre-specified TLF from real CDISC data, with every cell traceable to the rows and code that produced it, and every gap in the SAP declared instead of quietly filled.

200–500

TLFs in a single Phase III package, each double-programmed

1.6–2.0×

the primary effort, by design — QC redundancy is 100% deliberate

Database lock

→ top-line sits on the critical path of every go/no-go

Computed

answers derive from SAP + ADaM — recall is useless, no cutoff needed

The bottleneck

Confirmatory analysis is slow, redundant, and hard to audit

When a trial’s database locks, the Statistical Analysis Plan — written and frozen before anyone sees the data — must be turned into executable analysis code and then into the top-line package: disposition, demographics, the primary efficacy table, key secondary endpoints, and the core safety tables. Nothing downstream moves until those numbers exist.

The industry’s QC standard for that step is double programming: two statisticians independently implement the same SAP clause and reconcile every discrepancy. It is twenty years old, deliberately 100% redundant, and the industry itself describes it as resource-heavy and low-efficiency — yet nothing has replaced it, because what regulators actually require is reproducibility, traceability, and a validated QC process (ICH E9, 21 CFR Part 11), not double programming per se.

That is the opening. An agent that can produce a SAP-faithful table and a cell-level evidence chain is not merely cheaper than a second programmer — it is more auditable than one. But nobody had a rigorous way to measure whether current systems can actually do it. This benchmark is that measurement.

And it is a hard measurement to fake. The answer is determined by the SAP and the data, so it must be computed rather than recalled — there is no temporal cutoff to defend and no memorised outcome to retrieve. What separates systems is fidelity, auditability, and discipline under ambiguity.

CDISCPILOT01

Real, public CDISC SDTM/ADaM pilot data (ADSL, ADQSADAS, ADAE, ADTTE) — zero data-use agreement, so the fixture is fully reproducible outside our walls.

Non-LLM oracle

Correctness is decided by a deterministic verifier: cell match against a hidden gold, plus a fresh re-execution of the submitted code. No judge model sits in the correctness path.

Declare, don’t guess

A real SAP is under-specified. Silently resolving a material ambiguity zeroes the score — even when every number is right. Documentation is the deliverable, not a courtesy.

The task

Reproduce the table and prove you earned every cell

An episode stages a SAP extract and the analysis datasets into an isolated VM at /work. The solver reads the SAP, writes and runs analysis code, calls the verifier for feedback, repairs, and submits. Five artifacts are graded — the table is only the first.

Artifact 1

Reproduced table

The TLF cells, written to disk by the solver’s own code so a fresh re-run reproduces them byte for byte.

Artifact 2

Execution code

The full analysis script. It is re-executed in a clean container by the verifier — reported numbers must match what the code actually produces.

Artifact 3

Evidence ledger

Per-arm lineage: the contributing USUBJIDs and population filter behind each cell, such that n recomputes from the rows named.

Artifact 4

Ambiguity ledger

One entry per analysis decision forced by an under-specified SAP — the field, how it was resolved, and which cells it affects.

Artifact 5

Findings

Deviations and defects actually found — and [] when the package is clean. Fabricating a defect costs points; restraint is scored.

Escape hatch

Specification query

ask_spec puts a question to the study statistician. Querying an already-specified point returns nothing and counts as noise — judging which points are open is the solver’s job.

Data reality

Data-quality ledger

Real ADaM is not clean. Duplicates, implausible flagged values, out-of-window visits and partial dates must be handled and recorded.

Feedback

Verifier loop

check localizes a wrong cell and a cause class — population, variable, or model — but never reveals the correct number. Three calls, maximum.

THE FOUR-ACTION LOOP

solver trajectory · inside the analysis VM

ew-act inspect_task # SAP extract, staged inputs, gates, feedback_mode

ew-act ask_spec --param question=’which population flag?’

ew-act bash --param cmd=’cat > /work/run.R <<R

# fit the SAP-specified model; write /work/reproduced_table.json

R

Rscript /work/run.R’

ew-act check # diagnostic: names the wrong cell + cause class

# … targeted fix, re-run, rebuild submission.json …

ew-act submit

Episode · runtime

STAGED INTO THE VM

task.json (SAP extract, required outputs, hard gates) + the ADaM CSVs

NEVER STAGED

the hidden gold table, the planted ambiguities, and their intended resolutions

ANALYSIS IMAGES

ew-analysis-py · numpy / scipy / statsmodels
ew-analysis-r · R + mmrm / emmeans / survival

FEEDBACK MODES

none · binary · diagnostic — set per instance, capped at 3 checks

RECORDED

the full trajectory, for outcome verification and the separate process judge

The episode pool

Three families, one world the hardest episode from each

The environment is a cube: analysis × discipline × stage. Fixing a point gives an episode; episodes built from the same source dataset form a family. The active pool takes the hardest episode from each of the three families, and every one of them carries an under-specified SAP — which is what removed the 1.0 saturation that clean episodes had.

Family 01 · ADQSADAS

Repeated-measures efficacy

ADAS-Cog change from baseline, MMRM in R. Population, covariance structure and df method are all left open.

mmrm_amb →

Family 02 · ADAE

Treatment-emergent AEs

Subjects with ≥1 TEAE over a safety denominator. Both the population flag and the TEAE definition are material and open.

teae_amb →

Family 03 · ADTTE

Time to event

Kaplan–Meier median, log-rank and Cox in R. Population, event definition and tie handling are open.

survival_amb →

Impact decides the penalty. Ambiguities are ranked. Material ones — which patients are analysed, what counts as an event — are gate-fatal if resolved silently. The “how to analyse” nuances — covariance structure, degrees-of-freedom method, tie handling — only dock rigor. That is deliberate: it gives graded discrimination instead of an all-or-nothing cliff.

Family 01

adascog_efficacy

R track · capability

Repeated-measures efficacy — mmrm_amb

The primary efficacy read-out of the CDISC pilot’s Alzheimer study: change from baseline in ADAS-Cog, analysed as a mixed model for repeated measures. This is the episode that separates systems on raw capability — it requires the solver to reach for R, fit the model correctly, and pull the right marginal means out of it.

WHAT THE SOLVER MUST PRODUCE

Week-24 LS-means per arm and each active arm’s difference versus placebo with its p-value, computed by fitting CHG ~ TRTP*AVISIT + BASE with an unstructured subject-level covariance, then extracting estimated marginal means and treatment-versus-control contrasts. The numbers must come out of an executed fit — not out of the model’s memory of what an ADAS-Cog table looks like.

WHERE THE SAP GOES QUIET

Population (material) — the SAP names “the analysis population” without pinning which flag defines it. Resolve it silently and the episode scores zero regardless of the table.

Covariance structure — unstructured, compound-symmetric, or something else? Left open; impact-2, so it docks rigor rather than zeroing.

Degrees-of-freedom method — Satterthwaite or Kenward–Roger? Also open, also impact-2.

Why this one is graded, not binary. Because two of its three ambiguities are non-material, strong solvers land in the 0.79–0.82 band rather than all collapsing to 0 or saturating at 1 — the impact policy converts a bimodal outcome into a continuous one.

mmrm_amb · spec

SOURCE DATA

ADQSADAS + ADSL — real CDISCPILOT01

ANALYSIS

MMRM · unstructured covariance · Satterthwaite df · EMMs and contrasts vs Placebo

CELL SHAPE

{ls_means, diffs} — per-arm LS-mean, difference vs placebo, p-value

RUNTIME

ew-analysis-r — R with mmrm, emmeans, jsonlite

PLANTED AMBIGUITIES

population (material)

covariance (impact 2)

df method (impact 2)

LIVE SPREAD

gpt-5.5 0.79 · deepseek-v4-pro 0.82 · deepseek-v4-flash 0.00 (budget timeout writing R)

Family 02

ae_safety

Python track · discipline

Treatment-emergent adverse events — teae_amb

Arithmetically the easiest table in the pool: count the subjects with at least one treatment-emergent adverse event, divide by the safety population. Every frontier model can compute it. That is exactly why it is here — with the computation trivially available, what remains to be measured is discipline.

WHAT THE SOLVER MUST PRODUCE

Per arm, the numerator (subjects with ≥1 TEAE), the denominator (the safety population), and the percentage — with the evidence ledger substantiating both sides of the fraction, not just the numerator. A table whose denominator cannot be recomputed from the rows named fails the evidence axis even if the percentage is right.

WHERE THE SAP GOES QUIET

Population (material) — which flag defines the safety denominator is not pinned.

TEAE definition (material) — what makes an event treatment-emergent is left to the programmer. Both traps are material, so this episode has two independent ways to zero.

Because the arithmetic is not the obstacle, the score here is almost entirely a read on ledger quality: recall against the planted ambiguities, precision (no invented ones), impact ranking, and whether each entry is linked to the cells it affects.

De-signposted on purpose. The SAP clauses read as ordinary under-specified prose. Nothing flags them as traps — the duty to ask or document is stated once, in the solver’s standing SOP, exactly as it would be in a real statistical programming group.

teae_amb · spec

SOURCE DATA

ADAE + ADSL — real CDISCPILOT01

ANALYSIS

subjects with ≥1 TEAE over the safety denominator, per arm

CELL SHAPE

{n_with_teae, n_safety, pct} per arm

RUNTIME

ew-analysis-py — Python with numpy / scipy / statsmodels

PLANTED AMBIGUITIES

population (material)

TEAE definition (material)

LIVE SPREAD

gpt-5.5 0.94 · deepseek-v4-pro 0.79 · deepseek-v4-flash 0.90 — the pool’s tightest band, and its purest discipline signal

Family 03

time_to_event

R track · capability + gate

Time to event — survival_amb

Kaplan–Meier medians, a log-rank test, and a Cox proportional-hazards model against placebo. This episode combines the two things the pool is built to separate: it demands real statistical capability in R, and it plants two material ambiguities — so a system can be perfectly competent and still score zero.

WHAT THE SOLVER MUST PRODUCE

Per-arm KM medians (with “NE” where the median is never reached — a small but revealing detail, since a solver that reports a number there has fabricated it), the log-rank p-value, and the hazard ratio with its 95% confidence interval and p-value for each active arm versus placebo.

WHERE THE SAP GOES QUIET

Population (material) — again unpinned.

Event definition (material) — which records constitute an event versus a censor. Getting this wrong changes every cell; assuming it silently zeroes the episode outright.

Tie handling — Efron, Breslow, or exact? Open, impact-2.

This is where the gate visibly fires. In the live matrix, two of three configurations scored 0.00 here — not because their tables were wrong, but because they silently resolved a material choice. The third declared its assumptions and scored 0.91. Same computation, opposite outcome.

survival_amb · spec

SOURCE DATA

ADTTE + ADSL — real CDISCPILOT01

ANALYSIS

KM median · log-rank · Cox HR vs Placebo with 95% CI

CELL SHAPE

{km_median, logrank_p, cox} — medians may be “NE”

RUNTIME

ew-analysis-r — R survival: survfit · survdiff · coxph

PLANTED AMBIGUITIES

population (material)

event definition (material)

tie handling (impact 2)

LIVE SPREAD

gpt-5.5 0.91 · deepseek-v4-pro 0.00 · deepseek-v4-flash 0.00 — the widest separation in the pool

Scoring

Correctness is the gate. The score is judgment rigor

The verifier computes verified_outcome_score = gate ? rigor : 0. Getting the table right earns you the right to be scored; it does not earn you a score. This is the design decision that makes the benchmark measure something other than arithmetic.

STAGE 1 · HARD GATES — ONE VOTE NO, AND THE EPISODE IS ZERO

gate

Outcome match

Every cell matches the hidden gold — counts exact, estimates and p-values within display-rounding tolerance.

code_must_compile

The code runs

The submitted script executes in a clean container. The re-executor is language-aware: Rscript on the R images, python3 on the Python one.

no_hallucinated_data

The table is the code’s output

A fresh re-execution must reproduce the reported table, and no arm’s n may exceed its population. Numbers from prose fail here.

no_unauthorized_api_call

No egress

Network and subprocess escape are detected in both the Python and R runtimes. The analysis happens where the data is.

no_silent_assumption

Nothing resolved quietly

Every material ambiguity must be asked via ask_spec or declared in the ambiguity ledger. Matching is alias-aware, so declaring it in natural language counts.

no_silent_dataquality

Nothing cleaned quietly

Each planted data issue must be handled and recorded. Fixing a duplicate without saying so is the same failure as ignoring it.

STAGE 2 · RIGOR — SCORED 0–1, ONLY ONCE THE GATE HOLDS

0.40

AMBIGUITY DISCIPLINE

Recall against the planted ambiguities, precision against invented ones, correct impact ranking, and linkage from each entry to the cells it touches.

0.35

EVIDENCE CHAIN

Per-arm lineage that actually recomputes the cell from the rows named — plus restraint: no fabricated findings on a clean package.

0.25

REPAIR

A targeted diff against the broken code — five lines or fewer, not a rewrite — followed by a fresh reproduction. Exercised in the S5 stage diagnostic.

Any episode can also be run in per-task mode, which scores a single workflow stage in isolation. This is a run mode on an existing episode, not a separate benchmark — it exists so a failure can be localised to a stage instead of collapsing into one number. Each stage maps to one process capability.

Axes an episode doesn’t exercise are reweighted away. A clean episode is scored on evidence alone; an ambiguity episode adds the ambiguity axis; the per-task repair stage adds repair. The weights are renormalised so no episode is penalised for a dimension it never presented.

DIAGNOSE ONE STAGE AT A TIME

STAGE

WHAT IT ASKS

ARTIFACT

CAPABILITY

S1

SAP → machine-readable spec — population, endpoint, method, covariates, missing-data rule, plus the ambiguity ledger

ars_spec · ledger

E

S2

Spec → code → table — write and run an analysis that reproduces the TLF

analysis · tlf

T

S3

Audit lineage — row lineage plus a step-reproducible computational chain for every cell

trace

C

S4

Deviation detection — find SAP↔data↔code inconsistencies, naming the specific variable; return empty on a clean package

findings

S

S5

Repair after QC feedback — localise the defect, fix it narrowly, re-verify

repair

R

S6

SAP coverage — check every required analysis was produced; emit a traceability matrix

coverage

E

The environment

An executable world, not a prompt

The world is parameterised into episodes along three axes the analysis, the discipline imposed on it, and the workflow stage under test. Fixing a point in that cube gives one runnable, reproducible, repairable episode.

THREE NESTED LAYERS

Environment — the isolated analysis VM: the four-action interface, the staged data, the statistical runtimes, and the budgets. The verifier, the hidden gold and the intended resolutions live here, outside the solver’s reach.

Episode — one parameterisation: an analysis crossed with a discipline. Discipline is a hidden spec field, not a separate world, so ambiguity, a broken starting point, and dirty data are all the same machinery.

Family — episodes built from the same source dataset. The active pool takes the hardest member of each family; swapping a member changes what that family tests without touching the engine.

The trust boundary is runtime, not repository. Instances are committed complete — including each reference file of gold values and intended resolutions — but only the inputs directory is ever mounted into the VM, and every hidden field is blocked by explicit deny-keys. A clean clone can therefore build the instances and run the verifier self-test with no extra setup, and still not leak an answer to the solver.

What each episode fixes

ANALYSIS

descriptive · ANCOVA · MMRM · TEAE · time-to-event

DISCIPLINE

under-specified SAP · broken starting code · dirty ADaM

STAGE

end-to-end, or one workflow stage in per-task mode

RUNTIME

Python or R analysis image, egress-locked, deterministic

HIDDEN

gold cells · planted ambiguities and their intended resolutions · planted defects

RECORDED

trajectory · result · workspace · full solver log

The verifier is settled before any solver runs. A self-test drives hand-built correct and deliberately-broken submissions through the scorer with no container and no model involved, covering both run modes. Correctness of the measurement comes first; the models come second.

Beyond the benchmark

The same loop, inside a sponsor’s enclave

The benchmark’s public fixture is CDISC pilot data. The same environment runs against a sponsor’s own locked study, where the oracle stops being a curated gold table and becomes the client’s validated answer key — the TLF their two programmers already reconciled.

In that mode the work happens where the data already lives. Nothing leaves the enclave, the audit log is the one their QA group already reads, and the deliverable slots into the existing SOP: define.xml and ARS for the spec contract, the ledger for sign-off, the lineage for the auditor.

The buyers are the biostatistics and statistical-programming teams at CROs and sponsors — the people who write the SAP, run double programming, and own the TLF package. The gap they face is specific: existing tools verify a finished table, or check CDISC conformance, or supply infrastructure. None of them go SAP → code → TLF with an audit chain attached.

Promotion is conditional, and the conditions are written down. Every hard gate passing, per-cell agreement with the client’s validated key, full traceability with fresh reproduction, deviation recall at or above 90% with a low false-positive rate on clean packages — and interoperation with their existing audit trail. Short of that, it stays a benchmark.

Public fixture vs client pilot

PUBLIC DEV

CDISCPILOT01 — public ADaM, zero DUA, curated hidden gold

PRIVATE TEST

client locked study — validated answer key, hidden

ORACLE STRENGTH

tolerance-based match → per-cell agreement with an official key

AMBIGUITY SOURCE

planted, curated → real SAP wording and real house conventions

DEVIATION TRUTH

synthetic planted set → the client’s historical QC discrepancies

COMPLIANCE

runs in-enclave · no PHI leaves · 21 CFR Part 11 audit logging