Skip to content

Motivation

Two AI agents use the same specification to derive ADaM data. Both report completion, but their outputs differ. The specification leaves decisions for the agents to make during implementation. A human reviewer must find the gap, settle the rule, and rerun the derivation.

Review takes longer, and confidence in the result falls.

Introduction

yamaa is a domain-specific language (DSL) for clinical trial data standardization. A yamaa specification transforms ODM XML data, extracted from an EDC system, into SDTM and ADaM datasets following CDISC standards, and on to define.xml. yamaa's rules fix what every item means, so the same specification with the same inputs always produces the same dataset, whether the R package, the Python package or the yamaa command line runs it.

The language is designed for AI-agent and human collaboration: people and agents build the specification together at the planning stage, so nothing is left for the coding stage to guess.

Why specifications disagree

An Excel specification has three layers, but only two of them are written down:

Layer What it answers Where an Excel spec keeps it
Structure What exists -- which datasets, which variables, which types Dataset sheet, Variable sheet
Algorithm What to compute The free-text derivation column
Semantics What that computation means when the data does not cooperate Nowhere. It lives in each programmer's experience

yamaa writes each layer into a different kind of file:

Layer of yamaa File
Structure the schema under yaml/ -- what each file type may contain
Algorithm your specification (type: spec) -- one specification produces one dataset
Semantics rules/ -- normative contracts for every written item
Worked proof benchmarks/ -- runnable specifications with byte-exact expected outputs

In one sentence: an Excel spec is written to be understood; a yamaa spec is written to be executed the same way twice.

One execution

A yamaa specification has exactly one execution. Where it would have two, yamaa fails instead of choosing.

Think of execution as a function:

output_dataset = derive(input_datasets, spec)

The contract covers two things: spec, the closed vocabulary saying what you can write and nothing more, and derive, the semantics saying what each written item means. See Principles for why this moves agreement to the planning stage.

The four components

Component Purpose Start here
Schema Declares what each file type may contain; anything it does not declare is rejected before execution. Schema introduction
Rules Fix the meaning of every written item, so no host can read the same spec in two ways. Rules
Engine Runs specifications from Python, R and the command line; the same specification with the same inputs produces the same output dataset. R, Python and command-line API
Benchmark Runnable specifications with input data and byte-exact expected outputs. Benchmark

What a specification looks like

One specification produces one dataset. yamaa first constructs the output rows, then derives columns onto those rows. Column derivation never changes the row count. See Derivation for the full keys / rows / columns walkthrough.

Every yamaa file starts with the same two fields: yamaa, the oldest language version the file needs, always quoted, and type, the kind of file it is. This specification builds SDTM DM from a raw table:

yamaa: "1.0"
type: spec
model: SDTM
domain: DM
keys: [STUDYID, USUBJID]
input:
  RAW: raw/dm.csv

output:
  path: dm.csv
  columns: [STUDYID, DOMAIN, USUBJID, SEX]

columns:
  - name: STUDYID
    type: str
    label: Study Identifier
    derivation: RAW.STUDYID

  - name: DOMAIN
    type: str
    label: Domain Abbreviation
    derivation: "'DM'"      # quoted: the text DM, not a column

  - name: USUBJID
    type: str
    label: Unique Subject Identifier
    derivation: RAW.USUBJID

  - name: SEX
    type: str
    label: Sex
    derivation:
      mapping:
        source: RAW.SEX
        dict: {M: M, F: F}
        missing: U      # "if not collected, set to U"
        unmapped: U     # "if not in codelist, set to U"

Derivations use only expressions and registered verbs -- no free-text derivation column. In an expression, a bare name such as RAW.STUDYID reads a column, and text is quoted, so "'DM'" is the text DM. A verb's value fields, such as dict and its handlers, hold only constants, so U there is the text U.

Every verb declares what happens when the data does not cooperate. Omit a handler and its condition stops the run. "Not collected" and "collected but unrecognised" are two conditions, answered by missing and unmapped. Write null as the answer to leave the value missing instead.

Four file types

Every file declares one of four types in its type line:

Type What one file holds
spec One dataset: its inputs, keys, rows, columns, checks and output, as above. A specification may inherit from a parent layer and add reusable fragments under include, each a spec file too.
project A study: the specifications of each section (sdtm, adam or send), the codelists and function files they share, the lock of each host language, and the metadata define.xml needs.
codelists The codelists of one terminology source, such as a CDISC Controlled Terminology release or the sponsor's own lists.
function One project function: its parameters, return type and tests, and the callable that implements it in each host language, so R and Python share one contract and one set of tests.

An engine reads a file that declares its own language version or an older 1.x one, and refuses a newer file with one condition that names the version the file needs.

Working in plain text

Every input to a run is a file named in YAML, so the project directory is the unit of review: people and agents edit its files, and one call runs them, such as yamaa run project.yaml. Four calls help along the way, and none of them reads study data:

Step Command line R Python
Lint, and fix the layout yamaa lint --fix PATH... yamaa_lint(paths, fix = TRUE) yamaa.lint(paths, fix=True)
Get a file type's JSON Schema yamaa schema spec yamaa_schema("spec") yamaa.schema("spec")
Find problems before any data yamaa check --format json PATH yamaa_spec(path)$check()$issues yamaa.spec(path).check().issues
See the composed specification yamaa resolve PATH yamaa_spec(path)$resolve() yamaa.spec(path).resolve()
  • Lint puts each file in one canonical layout and keeps its comments, so an edit by a person or an agent shows as the smallest diff.
  • Schema gives the JSON Schema of the spec, project, codelists or function type; an editor that reads it validates and completes a file as it is typed.
  • Check reports each problem it can find without data: its condition, the rule it breaks, its spec path and a message, and the file, line and column where that path is written. Problems that need the data, such as a column no input has, appear when the specification runs.
  • Resolve shows the specification after its layers are composed, and where each field came from.

Sign-off happens on the YAML diff, with comments carrying the rationale next to the fields they explain. The R, Python and command-line API describes the R and Python calls and the command line.

Programs in a study submission

The sponsor chooses the program format and its location in the submission package with its submission team. The program can be the YAML specification with the driver that runs it, an R or Python script or a yamaa command, or a readable R or Python implementation, including code produced with AI assistance. The sponsor prepares and reviews any separate implementation as part of its own development process.

Retain the exact inputs, specification and inherited files, project file, codelist files and other referenced resources, function files with the packages that implement them, the lock files the project names, and the driver used for the build. Record the yamaa version and the host and dependency versions. The sponsor determines which of these accompany the submitted programs alongside the datasets, define.xml and reviewer's guide.

Verify a separate implementation independently against the yamaa results. Match records by their declared keys and compare every output cell, including missing values and types; state any numeric tolerance in the comparison record. Retain the comparison report and resolve discrepancies before using the implementation as the submitted program. Use a separately implemented derivation for this verification: the R and Python packages and the command line share one engine.

The reviewer's guide can explain the approach with wording such as:

The YAML specifications state the dataset structure and each variable's derivation. The accompanying driver runs those specifications with the recorded yamaa version and environment. The program inventory identifies the source files used for each dataset and the retained verification evidence.

Adapt this text to the chosen program format; for a separate R or Python implementation, identify its cell-level comparison evidence. The derivation descriptions and computational methods in define.xml should agree with the specification, and the reviewer's guide should explain where to find each. This guide documents no filed-study precedent or agency acceptance; the sponsor owns the submission decision and supporting evidence.

Where to go next