This page is the repository's own document docs/omakase.md, shown as it is, so that it never drifts from the product.
Sapling chooses a complete, coherent stack and its conventions for you, like Ruby on Rails. You start from a protocol and a folder of SDTM data, and a few commands take you to the study dashboard. Every choice it makes is written in the open, in the SAP, where you keep it or change it.
The doctrine#
- The SAP says what; the specs and the code say how.
sap.qmd(Markdown for Quarto, docs/markdown.md) is the statistician's plan. The ADaM and TLF specifications (adam-spec.qmd,tlf-spec.qmd) are the programmer's: made from the SAP, edited by the programmer, checked against the SAP. The programs are made from the specs and are never edited. - Convention over configuration. A new study has one layout, one set of populations, one way to window the
visits, one standard table set, one engine. A
sapling.ymlexists only when the study departs from a default. - Nothing hidden. Every default is a block of the SAP, marked
<!-- default: omakase (<the convention>) -->(or<!-- default: company/<template> -->when your company's standards made it), with a citation of the protocol sentence that supports it. The PDF shows it like any other block. - Nobody decides for the statistician. A default is a proposal. The open decisions stay open until a person
answers them (
sapling decide); the AI drafts, it never approves or decides. - Every step says what it did. One verdict line per step, like Rails' generators;
sapling statusshows each step as fresh, stale, missing, edited or failed, and no step silently uses a stale product. - One bad analysis stops only its own output. An analysis no engine computes as the plan asks is an error of
sapling checkat its place (the method registry, docs/markdown.md "The methods and what they report");sapling buildandsapling runmake every other output, and the verdict oftlf runnames the output that is not made with its first cause (NOT MADE T14_3_2 (analysis T14_3_2_MMRM (sap.qmd:342): no engine computes ...)).sapling statusshowstlf runas failed with that output and its cause; the steps after it (the dashboard, define, the package) say which output stopped them. - Everything is editable, in the easiest place for its owner (the table "Edit anything" below).
Getting started#
sapling new cdiscpilot01 --protocol usdm.json --sdtm sdtm/ # the study folder, a first SAP, the omakase defaults
cd cdiscpilot01
sapling generate table teae-severity # one more conventional table, in its section
sapling ai "state how missing data are handled in the primary analysis" # your AI drafts; you apply
sapling decide # the open decisions; a person answers them
sapling build # the SAP (PDF by Quarto), the specs, the programs
sapling run # the ADaM data, the TLFs, the ARD
sapling dashboard # the study dashboard on a free port of 3100-3199
sapling doctor checks the machine first (Node, R and its packages, the R engine, DataStep Studio for R, Python,
Quarto, the AI provider, the editor), each line with the exact command that fixes it.
The study folder#
| Path | What | Who |
|---|---|---|
sap.qmd | the SAP in Markdown for Quarto (docs/markdown.md) | the statistician |
adam-spec.qmd, tlf-spec.qmd | the specifications, made by sapling adam spec / sapling tlf spec, then edited | the programmer |
_extensions/sapling/ | the Quarto extension of Sapling (a Lua filter): the blocks as boxes in the PDF and in a Quarto preview; the plan names it (filters: [sapling]) | |
protocol/usdm.json, protocol/protocol.json | the protocol (CDISC USDM) and its page index (what a citation resolves against) | |
sdtm/ | links to the SDTM .xpt files, and sdtm.json (their metadata) | |
custom/ | code of your own, when a spec cannot say it (docs/customize.md); Sapling never writes there | the programmer |
standards/ | the study's own templates of the defaults (below) | the study team |
out/ | every product (never edited: change the SAP or a spec) | |
.sapling/ | what Sapling keeps for itself: the step state, the facts read from the protocol and the data, the bases of the specs, the R engine's view of the study | |
.Rprofile | the study's R session loads the R engine (DataStep Studio for R: dsr start here) | |
sapling.yml | only when departing from a default (engine:, ai.provider:, reference.adam:, reference.tlf:, dashboard.filters:, standards:) |
The commands#
Every step has its own command, its products in a folder of out/ named like it, and one verdict line. A step knows
what it needs: when an input is missing or older than its source, it says which command to run first, or makes it
with --with-deps. sapling build and sapling run are these steps in order (one code path).
| Command | Products | Needs |
|---|---|---|
sapling new <study> --protocol F [--sdtm DIR] | the study folder and sap.qmd | |
sapling generate <thing> ... (alias g) | blocks in their section of the plan (in sap.qmd: written in Markdown and inserted at the end of the section's own text, the rest byte for byte) | |
sapling ai "<plain English>" [--section TITLE] / --apply FILE / --draft-all | a drafted block, compiled in its place, applied when you say so (--yes); else saved in out/ai/ (below) | the AI provider |
sapling check [--strict] [--json] | the five passes (below) | |
sapling review / sapling lab <block> | out/review/, out/lab/ | |
sapling decide [n "answer" --by NAME] | the answer, the name and the date in the plan (in sap.qmd: the attributes answer, by, on of the .decision div) | a person |
sapling sap structured | out/sap/sap.md, sap.html, blocks.json | |
sapling sap json | out/sap/sap.json | |
sapling sap pdf | out/sap/sap.pdf | Quarto (Typst) |
sapling sap html | out/sap/quarto/sap.html (not a step of build) | Quarto |
sapling sap sync | the rules of the specs written into the plan, each marked for review | |
sapling adam spec | adam-spec.qmd (made or merged), out/adam/spec/ (the JSON specification, define.md, define.csv) | |
sapling adam programs | out/adam/programs/ (admiral, one per data set, build_order.txt) | adam spec |
sapling adam data [DS] | out/adam/data/*.xpt | the R engine |
sapling adam check | out/adam/check.txt: the data against reference.adam | adam data |
sapling tlf spec | tlf-spec.qmd (made or merged), out/tlf/spec/ (the analysis contract, CDISC ARS) | the R engine (its Python tools) |
sapling tlf shells / tlf qc-spec | out/tlf/shells/, out/tlf/qc-spec/ | tlf spec |
sapling tlf programs | out/tlf/programs/ | tlf spec |
sapling tlf run [OUTPUT] | out/tlf/listings/, out/ard/ (every number, ars_results.json) | tlf programs, adam data |
sapling tlf check | out/tlf/check.txt: the listings against reference.tlf | tlf run |
sapling define | out/define/define.xml | adam spec, tlf spec |
sapling dashboard [--check] [--port N] | out/dashboard/ (the configuration, the contract, the gate: DASHBOARD PASS), then the app | tlf run, adam data |
sapling package | out/package/ with MANIFEST.txt | the products |
sapling build | sap structured, json, pdf; adam spec, programs; tlf spec, shells, qc-spec, programs | |
sapling run | adam data, adam check, tlf run, tlf check | |
sapling status | each step: fresh, stale, missing, edited or failed (the outputs a step did not make, each with its first cause); the specs against the SAP; the next command | |
sapling doctor | the machine, with the fix of each problem; which layer each default comes from | |
sapling templates list / copy <name> [--to study|company] | the defaults and their layer; a default copied to edit | |
sapling export adam-spec --csv | out/export/adam-spec.csv (an export: the spec stays the source) | |
sapling open | the study in VS Code or Positron (Sapling Visual) |
Options of the steps: --with-deps (make what is missing or stale first), --force (overwrite an edited product),
--keep-session (leave the study's R session running).
sapling generate#
| Thing | What it adds, and where |
|---|---|
table <template> | populations, disposition (Trial population); demographics (Baseline characteristics); ancova-primary, byvisit-efficacy (Analysis methods); mmrm (Additional analyses); exposure, teae-overview, teae, sae, teae-severity (by the maximum severity), teae-grade, teae-frequent, teae-riskdiff, teae-rate (docs/markdown.md, "Adverse event tables"), labs-summary, labs-shift, vitals, km (Harms). Options: --id, --endpoint N, --dataset DS, --event COND, --population ID |
population ITT|SAF|EFF|FAS|MITT|PP | the population and, when it is standard, its ADSL flag; for FAS, MITT and PP (no standard rule) a decision |
estimand [ANALYSIS] | the ICH E9(R1) estimand of the next endpoint |
multiplicity [--procedure P] | the protocol's rule for the primary hypotheses (co-primary when the protocol says both must be significant, else a hierarchy) |
sensitivity | a tipping-point analysis of the primary estimand |
derivation DS.VAR "<rule>" | a .derive div in the section of its data set (a rule is never invented: without one, only the standard derivations) |
Each generator fills its defaults from the plan (its populations, data sets, variables) and from the facts sapling new
read from the protocol and the data (.sapling/facts.json). The block is compiled in its place first: a generator that
would add an error adds nothing. A second generate of the same block is an error that says where the first is
(the line in the plan; --id adds another one). The same convention for the same endpoint (--endpoint N) is the same
block. The visual editor offers the same generators in its insert palette, under "Ready-made from your protocol": one code path
(src/omakase_service.ts).
The generators write Markdown directly. sapling new (src/init.ts, then src/omakase.ts), the skeleton from a
protocol document (src/start.ts) and every sapling generate read the plan by its nodes (the Markdown reader) and
write each block in its form of docs/markdown.md (mdCmd of src/qmd_plan.ts: a command of src/commands.ts from its
arguments and options). A block a generator adds is inserted at the end of the own text of its section, with one
empty line around it; an open decision it answers is removed with its citation. npm test compares the plan of
sapling new and the block of each generator with their golden references (test/golden/).
sapling ai: the AI drafts, you apply#
sapling ai "state the primary estimand with its intercurrent events" # the part of its kind: Estimands
sapling ai "a table of serious adverse events" --section Harms # the part you name
sapling ai --apply out/ai/20261111-210550-state-the-primary-estimand.qmd # a saved draft, applied later
Your AI provider (sapling.yml: ai.provider, src/ai.ts) writes the block from your words, the protocol's
sentences, the names of the plan and the study, and the language reference. The block is compiled in its place; when it
does not compile, the AI gets the compiler's messages and tries again (two times at most).
| What | How |
|---|---|
| where the block goes | the part of the SAP of its kind, the table of parts that the skeleton and sapling generate use (an estimand in Estimands, a table of safety data in Harms, a sensitivity analysis in Additional analyses, ...); --section TITLE puts it in the part you name. The output says where it went and why |
| the open decision of the part | the draft takes its place (the skeleton has one per part); the output names the decision that goes. The decision is removed only when the draft is applied |
| a draft with an error | never written into the plan, also with --yes (an error the plan had before the draft too): the output shows the first error, and the draft is saved |
without --yes (and no y) | the draft is saved as out/ai/<time>-<words>.qmd; sapling ai --apply <file> applies it later (in its part, in the place of the decision if it is still open; checked again) |
| the names | the AI may write only the data sets, variables and parameters the plan names, the ADaM specification (or the SDTM metadata of a plan that derives its ADaM) has, or, without a specification, the standard names of the ADaM IG. A name it makes up is sent back to it; one it keeps is listed: "names the statistician must confirm" |
| choices | a value the request and the protocol do not give (the number of imputations, a seed, an assumption, the degrees of freedom, the ties) is not chosen silently: the AI asks for it in its one decision, or cites where it comes from; the output lists "choices left to you" |
| never | an approval, the answer of a decision, or a waiver: such a draft is refused |
sapling new --protocol protocol.pdf: a skeleton from the protocol document#
Without a USDM file, sapling new reads the text of the protocol (a PDF through pdftotext, or a text file) into
protocol/protocol.txt and writes a skeleton sap.qmd: one part per item of Gamble et al. 2017, each an open decision
that names the section of the protocol that speaks of it (by the words of its headings, the statistics chapter first
for a part about the analysis; else the section whose text says it most). A part whose section is not found cites
nothing, and its decision says "The protocol is silent on it, or its section was not found". sapling new prints the
parts it did not find, and a note for each kind of page whose text cannot be read as it is: the pages without text
(pictures or scans), the letter-spaced pages and the lines of them it rebuilt, and the pages with cipher text
(docs/markdown.md, "A protocol PDF whose text is not its words").
sapling dummy: data before the first patient#
A study with a protocol and the metadata of its SDTM data (sdtm/sdtm.json: the domains, their variables, the values a
variable can take), and no data yet:
sapling dummy # sdtm/*.xpt (DUMMY data) and the report sdtm/DUMMY.md
sapling run # the ADaM data and the tables of the plan, on the dummy data
sapling dummy [--per-arm N] [--seed S] [--effect SD] [--endpoint DOMAIN.TESTCD] [--force] (src/dummy.ts) makes SAS
transport files of DM, EX, DS, SV, AE and every findings domain with a list of test codes. It writes only the variables
of the metadata, only values the metadata allow, no text longer than the metadata say. The same seed gives the same
bytes. Two things in the data are designed:
- The cases the imputation rules are for. The first subjects of every arm are one case each:
complete,dropout,missed-visit,two-in-window,tie(two records equally far from the target day),late-visit(a record in the next window),no-baseline,two-baselines,partial-dates(a start date without the day in the month of the first dose, one without the month, an event without an end, one before the first dose),not-dosed,open-exposure(no end date of exposure),death, and the screen failures.sdtm/DUMMY.mdlists each case with its subjects, and for each imputation or selection rule of the plan (a window, an analysis record flag, a baseline, a date imputation, LOCF, a population flag) the cases that reach it. A rule that no case reaches is named in the verdict. Look for the subjects in the ADaM data to see what the rule does with them. - The effect on the primary endpoint, in standard deviations of its test (
--effect, default -1; the arms between the reference and the last one have their share).--effect 0gives data under the null hypothesis. The generator tests the effect itself (Welch's t-test of the change from baseline to the last visit, observed cases; the same numbers as R'st.test) and prints the p-value: the default is significant for every seed tried. The primary test is the one whose name shares the most words with the primary endpoint of the plan, or--endpoint.
sapling dummy check (after sapling run, or sapling adam data --with-deps) reads the analysis data
(out/adam/data/*.xpt; no R) and says what each rule did with its designed subjects, against what the generator
knows of them (sdtm/dummy.json: an event recorded as 2025-01 started on study day 2). The report is
out/dummy/check.md; each row is
- OK: the result every convention agrees on (of two records in a window, the closest one is analysed; a subject without a dose is not in the safety population);
- RULE: the result of a choice of the plan, for the statistician to confirm (of two records equally far from the target day the earlier one; a late visit counts for the window of its day; after a dropout the analysis data have no record, or a record made by LOCF);
- LOOK: a result that loses or changes data. On the second protocol with the omakase defaults there are four: an
adverse event with the day missing in the month of the first dose is set to the first of the month, before the dose,
and is not treatment emergent; one with the year only is not imputed and not treatment emergent; an exposure without
an end date gives a treatment of 1 day (two subjects). They are defaults the statistician changes in the plan
(
impute=, the rule ofTRTEMFL, the rule ofTRTEDT); the check then shows the new result.
The guards: sapling dummy never replaces data it did not make (a sdtm/ with transport files and no DUMMY.md is
refused); sapling package refuses dummy data. Limits: a domain without a generator (CM, MH, the trial design, the
supplemental qualifiers) and a findings domain whose metadata list no test codes are not made, and the verdict says so;
the visits are the scheduled ones the metadata name (Screening, Baseline, Week n).
The omakase defaults#
| Topic | The default | Where it comes from |
|---|---|---|
| populations | ITT (randomized: ADSL.ITTFL), SAF (randomized and dosed: SAFFL), EFF (dosed, with a value of each primary endpoint after baseline: EFFFL) when the protocol names an efficacy population, the completers of Week n (COMPnnFL) when it names them; PP as an open decision | the protocol's sentences (sapc init) |
| treatment columns | efficacy and the subject tables by the planned treatment (TRT01P, ICH E9 5.2.1); safety by the actual one (TRT01A) | |
| TRT01PN | the planned dose of the arm when each arm has one (the highest EX.EXDOSE of its subjects), else 1, 2, ... in the protocol's order | the data |
| estimand | per objective (ICH E9(R1)): the population, the treatments, the variable, the protocol's intercurrent events and a permanent discontinuation under treatment policy; it names the primary analysis | the USDM estimand |
| first and last dose | the first and the last exposure record (EX) | |
| baseline | the last non-missing value on or before the day of the first dose | |
| visit windows | by study day, from the protocol's schedule (7 days a week after the first dose): Baseline day 1 or before; each visit to the midpoint between its target day and the next one; the last open-ended. ANL01FL: in a window, the value closest to the target day (the earlier of two). For an endpoint, the visits at which it is measured (the data) | the USDM encounters |
| change from baseline | CHG and PCHG after baseline | |
| missing data | LOCF only when the protocol says so (a .impute div with method="LOCF") | the protocol's words |
| primary model | ANCOVA of the change with the baseline (ANOVA of the value for a score without baseline), the pooled site when the protocol's model has the investigator, the treatment as its number (the test of linear dose response with doses; the treatment test with two arms) | the protocol |
| pooled sites | a site with fewer than n patients in a group pooled, n from the protocol | the protocol |
| age groups | <65, 65-<75, >=75 (ICH E7) | |
| TEAE | on or after the first dose; the first occurrence flags | |
| tests on baseline and safety | only those the protocol names (ICH E9: baseline tests are not informative after randomization) | the protocol |
| the standard table set | 14.1.1 populations, 14.1.2 disposition, 14.2.1 demographics, 14.3.x the primary analysis of each primary endpoint, 14.4.1 exposure, 14.5.1 TEAE overview, 14.5.2 TEAE by SOC and PT, 14.5.3 SAE, 14.6.1 labs by visit, 14.6.2 lab shifts, 14.7.1 vital signs (ICH E3 section 14, numbered as the CDISC pilot's study report) | |
| ADaM | ADSL, ADAE (OCCDS), one BDS per primary endpoint, ADVS, ADLBC and ADLBH, derived in the SAP from SDTM with the ADaM IG's names | the data |
| codelists | CDISC Controlled Terminology decodes (SEX) | |
| the stack | R, admiral, CDISC ARS, the study dashboard (r-agentic, used in place), Sapling Visual as the editor, the knowledge rules on | |
| the dashboard | filters SEX, AGEGR1, RACE; a data cut on the first dose; menus Efficacy, Safety, Subjects; the roles of the exploratory tools | sapling.yml dashboard.filters, or your dashboard.md |
sapling check: a compiler with passes#
| Pass | What it checks |
|---|---|
| 1 syntax | the parse of the plan (Markdown) and the Sapling subset: the commands, their arguments and options |
| 2 structure | the sections in the order of the SAP guideline (Gamble et al. 2017); the required sections (objectives, estimands, populations, analysis methods, missing data, multiplicity); unique ids; every reference resolves; every output has an analysis (and so a shell); every analysis a population, data and a method; a primary estimand names its analysis; every citation resolves; no orphan block; the specs against the SAP (a rule of a spec that differs from the SAP; merge conflicts) |
| 3 data | the variables against the derivations, the ADaM IG and SDTM (used before made, made twice), codelist values |
| 4 knowledge | the rules of the knowledge base (ICH E9(R1), Gamble, FDA, EMA findings) |
| 5 chain | what the chain needs for sapling adam spec and sapling tlf spec |
Each pass has its verdict line (PASS, WARN, FAIL) and file:line:col diagnostics with their fix. Exit code 0
(pass), 1 (--strict: warnings are errors, for CI and before approval), 2 (errors). --json for tools. Every step
command runs the check first and refuses on errors ("fix these first").
How to change what Sapling made#
THE SAP SAYS WHAT, THE SPEC SAYS HOW. Change a thing at its source:
| You want to change | Where | Who | What happens |
|---|---|---|---|
| a population, an analysis, a test, a table's rows, an estimand, a derivation rule | sap.qmd (Sapling Visual's forms, or the Markdown) | the statistician | sapling status marks the steps stale; sapling adam spec / tlf spec merge the change into the specs |
| a variable's label, length, type; the order of rows; a title; a footnote | adam-spec.qmd, tlf-spec.qmd | the programmer | allowed: the products follow the spec; sapling check notes it |
| a derivation rule, a condition, a method, in the spec | adam-spec.qmd, tlf-spec.qmd | the programmer | the products follow the spec; sapling check reports "the spec's rule differs from the SAP's" with the exact SAP line; sapling sap sync writes it into the SAP, marked for a person to review; --strict refuses until the SAP and the spec agree |
| what a rule language cannot say | custom/ (docs/customize.md), declared in the SAP with a .derive div with custom="..." | the programmer | the hook is how; the SAP's words are what |
| a default of the whole company or study | standards/ (below) | the standards team | new blocks and specs use it; the block says <!-- default: company/<template> --> |
a generated program or product in out/ | never | the next run refuses to overwrite it ("you edited out/...: make the change in the spec") |
When the SAP changes after you edited a spec, sapling adam spec (or tlf spec) makes the SAP's new spec and merges
it with yours, 3-way (the spec the SAP gave before, yours, the new one): your edits are kept, the SAP's change comes
in, and a line both changed is marked as a conflict (<<<<<<< yours (the spec) ... >>>>>>> new (the SAP now)),
never resolved silently. With no edit, the products are byte for byte what the SAP alone makes.
Edit anything#
| What you want to change | Where | Who |
|---|---|---|
| the plan | sap.qmd: Sapling Visual (forms) or the Markdown | statistician |
| the ADaM variables (labels, lengths, rules, R) | adam-spec.qmd | programmer |
| the tables (order of rows, titles, footnotes) | tlf-spec.qmd | programmer |
| a default for every study of the company | ~/.sapling/standards/ (or sapling.yml standards:, SAPLING_STANDARDS): defaults.yml, templates/table-<name>.qmd | standards team |
| a default for one study | <study>/standards/ (study > company > omakase) | study team |
| the dashboard (filters, menus) | sapling.yml dashboard.filters, or dashboard.md next to the plan | programmer |
| the engine, the AI provider, the references to compare with | sapling.yml | anyone |
| a step's product | nowhere: it is made again; change its source |
sapling templates list shows every default and its layer; sapling templates copy defaults or copy table-<name>
starts a file to edit. defaults.yml keys: age_groups, tables (the standard set of sapling new),
lab_categories.
A table template is Markdown (docs/markdown.md), templates/table-<name>.qmd: the table as the generator writes it, with
placeholders {{id}} (the id of the table), {{n}} (its number, 14.5.3), {{itt}}, {{saf}}, {{eff}} (the
populations of the plan) and {{row:X}} (the id <id>_X of a row). The comment at its top is left out; the block
gets the mark of its layer (<!-- default: study/table-sae -->):
<!-- The template of sapling generate table sae (study), Markdown (docs/markdown.md): {{id}}, {{n}}, ... -->
:::: {.tlf #{{id}} population="{{saf}}"}
**Table {{n}} Incidence of Serious Treatment-Emergent Adverse Events**
::: {.incidence #{{row:SER}} condition='ADAE.TRTEMFL = "Y" and ADAE.AESER = "Y"' rows="soc;pt"}
Serious treatment-emergent adverse events
:::
::::
A table template of the earlier LaTeX form (table-<name>.tex) is not taken. sapling generate refuses the table
with a message that says how to replace the template: sapling templates copy table-<name> --force [--to company]
writes the built-in table as table-<name>.qmd in the same folder; make the changes of the old template in it again,
then remove the old file. sapling new leaves the table out with that note, and sapling templates list marks the
file.
The R engine#
The R engine is r-agentic (its R library, its Python tools, its standards, the study dashboard), used in place from
its installed location: no copy, and nothing written there. sapling doctor finds it (in this order):
SAPLING_ENGINE, sapling.yml engine:, ~/.sapling/engine, r-agentic next to Sapling.
The study folder holds the engine's view of the study in .sapling/: links to the engine's R/, tools/ and
standards/, the app's files linked in a real folder .sapling/apps/dashboard/, and the study as the engine reads it
(.sapling/studies/<id>/: study.json, data/sdtm linked to sdtm/, the protocol's index, the structured SAP, the
specifications, the programs, the engine's output). The study's .Rprofile loads the engine's R packages and library
with options(sma.root = ".sapling"); r-agentic's harness finds its root by that option, and its Python tools run with
their root set to .sapling (tools/engine_tool.py). R runs only through DataStep Studio for R (dsr start in the
study folder, dsr batch, dsr eval), as r-agentic's AGENTS.md asks. r-agentic's own files and proofs are unchanged.
The proofs#
| Proof | Command | Result |
|---|---|---|
the pilot with the plan in Markdown, against the stored digest of a proven run (tools/pilot_digest.json) | zsh tools/pilot_qmd.sh | new, check, DEMO decisions, build, run, adam conform, status, dashboard gate, define, package, sap pdf and html by Quarto; DASHBOARD PASS (18,662 numbers); against the digest: the same 7 ADaM data sets (3,678,731 values), 12 listings (5,023 lines), ARD (485,536 CSV values, 1,475 ARS results) and contract; PILOT QMD PASS |
the MVP, replayable for a recording (timed transcript steps.tsv, --pause before each step, relative paths) | zsh tools/mvp_demo.sh [--pause] | 9 steps (doctor, new, generate, a real AI request, DEMO decisions, build, status, run, dashboard) in 221 s; DASHBOARD PASS (24,875 numbers); MVP DEMO PASS |
| the Rails demo: a fresh folder to the dashboard | zsh tools/omakase_demo.sh | 8 sapling commands in 212 s (the AI request about 20 s); 7 ADaM programs and 13 TLF programs BATCH: CLEAN; DASHBOARD PASS, 13 pages, 24,875 numbers equal to the ARD; OMAKASE DEMO PASS |
the omakase result against the hand-tuned pilot (pilot-adam.qmd) | python3 tools/omakase_compare.py <study>/out/tlf/listings examples/cdiscpilot01/out/adam/results/tlf | the primary analyses equal: ADAS-Cog (11) N 79/81/74, dose-response p = 0.245; CIBIC+ p = 0.960; demographics N 86/84/84/254, all 71 of its numbers; no listing line-equal (other titles, ids, rows); 30-95% of each pilot listing's numbers, every difference with its cause (the age groups, the first dose from EX instead of the Visit 3 date, windows by study day for labs and vital signs, the rows of the overview, no pairwise LS means) |
a second protocol (synthetic, examples/synthetic-htn/) | sapling new synhtn01 --protocol examples/synthetic-htn/usdm.json --sdtm examples/synthetic-htn/sdtm/sdtm.json, then sapling build | SAPLING CHECK PASS, SAPLING BUILD PASS; then sapling dummy and sapling run: 3 ADaM and 9 TLF programs BATCH: CLEAN, SAPLING RUN PASS; Table 14.3.1 (the ANCOVA of the change in systolic blood pressure at Week 12) with its p-value, the same as lm() on the ADaM records |
| the command line | node --test test/sapling.test.ts (in npm test) | new, generate (once), the passes and the structure rules, the steps (fresh, stale, missing, edited), the specs (compare, sync, merge, byte for byte), the templates (Markdown; one of the earlier LaTeX form refused with how to replace it), decide |
| the writer of the generators | node --test test/golden.test.ts test/qmd_study.test.ts test/init.test.ts (in npm test) | sapling new, each generate thing and sapc init make what their golden references (test/golden/) hold |
The MVP, and after it#
The MVP is the smallest end-to-end product a statistician and a programmer can use: sapling new (the study folder,
the omakase SAP, Sapling Visual opens), the SAP edited in Sapling Visual or with sapling ai, sapling generate, the
step commands with sapling status and sapling doctor, adam-spec.qmd made from the SAP with a basic grid in the
editor (Sapling: Open ADaM Spec Grid: filter, sort, inline edit of the label, the length and the rule, the mark of a
rule that differs from the SAP with the SAP line to write), the programs made from it, and the pilot from an empty
folder to DASHBOARD PASS in 8 commands.
Built beyond the MVP and kept (tested in test/sapling.test.ts, small and self-contained): tlf-spec.qmd,
sapling sap sync, the 3-way merge of a spec after an SAP change (conflicts marked), the company and study template
layers (standards/, sapling templates), sapling export adam-spec --csv.
After the MVP (not built):
sapling eject/sapling update(superseded by the specs as the editable source);- the full panel set of the editor: the study settings and
sapling.ymlas forms, a template browser, a visual 3-way merge (Take / Keep / Edit), a steps panel with Run buttons, the R code cell of the grid with "test this expression on the data", a codelist editor, add / remove / duplicate rows in the grid; - the TLF spec grid;
sapling import adam-spec spec.xlsx; an Excel export (CSV only today); - the SAP's declaration of every hook enforced by
sapling check(a block for a custom step, a filter withcustom=, a table withlayout="custom:...": not in the language yet), and the custom-code demo (examples/customize) rewritten around the spec; - the proof cases of the spec on the pilot with every gate (DURDIS in calendar months through a spec edit, an R-code flag and its subgroup table, a TLF layout edit, an SAP amendment merged);
- efficacy endpoints measured in the laboratory data or as a time to an event (an endpoint of the questionnaires is matched by its category; a vital sign by its name, to its parameter of ADVS; a vital sign measured at several time points per visit is not matched: the statistician says which record);
- the p-value of a table is printed with the pattern
x.xxxby the R engine: a very small one shows as0.000, not<0.001(the engine's formatting; r-agentic is read only here); - the regulatory (FDA) work.