Sapling SAP
Reference

The Method Lab

For each analysis: its formula, its programs, its assumptions on blinded data, a simulation and an example.

This page is the repository's own document docs/method-lab.md, shown as it is, so that it never drifts from the product.

The statistician's harness for each analysis of the plan.

Why#

An AI coding agent is strong because it has a harness: it runs the code, tests it, debugs it and checks the result, and it acts through these tools. A statistician who writes a SAP has the same needs for each analysis: what exactly does the model say, which program computes it, do its assumptions hold, does the test do what the plan says (its type I error, its power), and what does the table look like. The Method Lab gives the statistician, and their AI agent through MCP, these tools for every analysis of the plan. Each result is data (JSON) with a verdict line, and files a reviewer can read.

What it gives for a block#

sapc lab <plan.qmd> <block-id> [--part formula|program|assumptions|simulate|example|all] [--data DIR] [--unblinded] [--nsim N] [--seed S]

A block is an analysis with a model or a test (P_ADAS), a sensitivity or supplementary analysis (SENS_TP), or a table, estimand or method that leads to one. sapc lab <plan.qmd> lists the blocks; sapc lab <plan.qmd> --all runs the lab for all of them and writes the report out/lab/index.html.

PartWhatMade fromNeeds R
formulaThe model with all its terms and the distribution of its errors; the link from the estimand to the estimator; H0 and H1 with the sidedness; the test statistic and its reference distribution (with the df); the estimator and the CI formula. ANCOVA/ANOVA (dose-response slope, overall F, pairwise LS means), MMRM (covariance, fallbacks, Kenward-Roger), chi-square, Fisher, CMH, logistic, Cox, KM/log-rank, t-test, Wilcoxon; for a sensitivity analysis also the imputation model, the delta shift and Rubin's rules.the plan's model (its own symbols when it wrote an equation), options and estimand; the knowledge base's method pageno
programRunnable R (the functions of the method page: stats::lm, emmeans, mmrm::mmrm, survival::coxph, ...), SAS (PROC GLM, MIXED, PHREG, LIFETEST, FREQ, LOGISTIC, MI, MIANALYZE) and pseudocode, for exactly this data set, population, records, terms and options; the R-vs-SAS default differences (df, ties, quantiles, rounding) from the method page.the planno
assumptionsEach assumption of the method page with automatic checks, a status and a plot (PNG): residual normality (Shapiro-Wilk, QQ plot), equal variance (Brown-Forsythe), linearity of each covariate, parallel slopes, a linear dose response, influence (Cook's distance), small levels of a factor, MMRM convergence of the planned covariance and of each fallback in order, the missing data pattern, MAR (not testable: are MNAR sensitivity analyses planned?), proportional hazards (cox.zph), censoring, expected counts (chi-square or Fisher), events per parameter.the data, BLINDEDyes
simulate"Test the test": the type I error under H0, the power at the plan's effect, the bias and coverage of the estimate, and the bias under MNAR shifts of the missing values, each with a Monte Carlo 95% CI. Cross-checked with the analytic power of the planned test and of the block samplesize block's test, with the reason for a difference. Seeded, the number of simulations an option, the runtime shown.the plan's effect; the SD, covariates, correlation and dropout of the blinded data (else the plan's)yes
exampleDummy data from the ADaM specification and the plan (seeded, made-up values, for programs before the data exist), and the generated R program run on it, or on the real data with a dummy randomisation: the table shell filled with numbers, labelled EXAMPLE.the ADaM specification, the planyes

The files go to out/lab/<block>/ next to the plan: formula.json, formula.md, formula.html, program.R, program.sas, pseudocode.txt, program.json, assumptions.json and its PNGs, simulation.json and its PNGs, example.json, example/shell.txt, example/data/*.csv (dummy), summary.json (one verdict per part), spec.json (the analysis as the lab read it) and data.R (the program's data steps, which the checks use too).

The last line of sapc lab is SAPLING LAB PASS | CHECK | FAIL: CHECK when a check asks for a look, FAIL when a part could not run.

ICH E9: blinded before unblinding#

ICH E9 (and its blind review) lets the statistician look at the data before unblinding only without the treatment groups. The lab keeps this rule; the mode is in every result and on every table.

ModeWhenWhat the lab does
BLINDED--data DIR (the default with data)Every treatment variable of every data set (TRT*, ARM*, ACTARM*, dose variables such as AVGDD, CUMDOSE) is set to one value: the model is fitted without its treatment terms (pooled). The checks that need the groups (equal variance across groups, parallel slopes, a linear dose response, proportional hazards of the treatment) are DEFERRED. The example run uses a dummy randomisation: the treatment columns are permuted across subjects in a temporary copy, which is removed after the run.
SIMULATED / DUMMY / PLANno dataThe checks and the example run use dummy data; the simulation uses the plan's assumptions only.
UNBLINDED--unblinded (MCP: unblinded=true)The real treatment groups. Only after the database lock and unblinding, and only when the statistician says so. Every result says UNBLINDED.

An agent never sets unblinded on its own: the tool's description says so.

The pilot (CDISCPILOT01, pilot-reviewed.qmd, the ADaM data of the chain)#

From the repository, R 4.4 with the packages of SAPLING_R_LIBS:

D=~/r-agentic/studies/cdiscpilot01/output/adam      # read only
node src/cli.ts lab examples/cdiscpilot01/pilot-reviewed.qmd --all --part all --data $D      # 11 blocks, about 1 minute
node src/cli.ts lab examples/cdiscpilot01/lab-mmrm.qmd P_ADAS_MMRM --part all --data $D --nsim 200

The MCP tools#

sapling_lab_formula, sapling_lab_program, sapling_lab_assumptions, sapling_lab_simulate, sapling_lab_example (mcp/server.ts, defined in src/lab/mcp.ts). Input: file, block (omit it for the list of lab blocks), and for the R parts data, unblinded, nsim, seed. Output: {block, analysis, method, dir, verdicts, verdict, <part>} with the part's JSON below. Verdicts:

FORMULA: ANCOVA, test for linear dose response; 5 part(s): model, estimand, hypotheses, statistic, estimator
PROGRAM: R (stats), SAS (PROC MEANS, PROC GLM), pseudocode
ASSUMPTIONS: CHECK (levels_SITEGR1) [BLINDED; 5 ok, 3 deferred to unblinding]
SIMULATION: power 0.950 (0.935–0.962), at the plan's assumptions 0.915; type I 0.057 (0.044–0.073); 1000 simulations, seed 20261110, 3.63 s [BLINDED]
EXAMPLE: the program ran on blinded data (dummy randomisation): n 234, p 0.607, estimate 0.005 (1.1 s) [EXAMPLE]

The assistant's context includes a block's lab results when the lab has run (src/lab/context.ts): "Explain" adds them, and a request that names a block (or has the cursor in it) gets them, for questions such as "Which covariance structure?".

The UI contract (for the editor's Method Lab panel)#

The visual editor shows the lab as tabs on each analysis card: Formula · Program · Assumptions · Simulation · Example. It uses only this API and these JSON files; no editor code is in src/lab/.

API (src/lab/index.ts, Node; the bundle vscode/sapling/src/lab/ is compiled from it):

FunctionReturnsCost
labTargets(c)[{id, kind, analysis, test, title, line, output}]: the cards that get the tabsinstant
runLab(c, id, parts, {data?, unblinded?, nsim?, seed?, out?})LabResult: {block, analysis, method, title, line, dir, formula?, program?, assumptions?, simulation?, example?, verdicts, summary}; writes out/lab/<id>/formula, program: instant; assumptions, example: 1–3 s; simulate: seconds (MMRM: about 15 s per 100 simulations)
readLab(c, id){summary, parts}: what is already on disk (show it at once, run on request)instant
labContext(c, id)the text for the assistantinstant
formulaHtml(formula)the formula tab's HTML (KaTeX, its CSS from katex/katex.min.css)instant

c is the compiled plan (analyze(path, text) of src/service.ts). Run the R parts off the UI thread (a child process: node src/cli.ts lab <plan> <id> --part <part>, or a worker) and show a progress state; R parts can be cancelled by killing the process.

JSON (each has schema, block, analysis, test, verdict):

File / fieldSchemaFields the panel shows
formula.json / formulasapling.lab.formula/1sections[]: {key, title, tex[] (KaTeX display math), text}, assumptions[]: {text, source}, sources, notes[]
program.json / programsapling.lab.program/1r, sas, pseudocode (three code views with copy and "open as file"), packages[], procedures[], r_vs_sas[], notes[]
assumptions.json / assumptionssapling.lab.assumptions/1mode, label, mode_note (always visible), assumptions[]: {text, source, checks[]} (group the checks under the method page's assumptions), checks[]: {id, title, status, detail, statistic, p, plot}, removed[], n
simulation.json / simulationsapling.lab.simulation/1mode, label, nsim, seed, seconds, scenarios[]: {scenario, effect, rejection, mc_ci[2], mean_estimate, bias, coverage}, type1, power, crosscheck: {samplesize, planned_power, analytic_planned_test, analytic_samplesize_test, simulated, explanation}, mnar[]: {delta, power, bias_vs_treatment_policy}, dgm, plots[]
example.json / examplesapling.lab.example/1mode, label (always visible, e.g. "EXAMPLE: DUMMY DATA: not results"), shell (monospace), results, log[] on error
summary.jsonsapling.lab.summary/1verdicts: {formula, program, assumptions, simulate, example}: the badge line of the card

Plots are PNG files named in plot/plots, relative to the block's folder. Statuses: OK (green), CHECK (amber: a person looks), DEFERRED (grey: needs unblinding), INFO (grey), ERROR (red). A verdict that starts with ...: ERROR is a part that could not run (no R, no data set): show its message.

Rules for the panel: show the data mode on every tab that has one; never offer "unblinded" as a default or remember it between sessions (ask the statistician each time); the example numbers are never shown without their label; the panel writes nothing into the plan. The bundle (vscode/scripts/bundle.mjs) must also copy r/lab/lab.R for the R parts.

Requirements and settings#

Limits#

See also docs/design.md: the design simulation (several scenarios, intercurrent events and dropout mechanisms, the true value of each estimand, the power curve over N) uses this lab's spec and the R twin's analyses, for a design plan that has no data yet.