Review ask

As a clinical trial statistician or programmer, challenge anything in this benchmark: describe a problem in Comments, or propose a fix with the Edit button on any source file.

  1. Task: Would a real study need this derivation, specified this way? Does it follow CDISC ADaM: variable names, controlled terms, record structure? Flag any rule that is ambiguous or that a sponsor would handle differently.
  2. Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
  3. Expected output: Would independent QC programming reproduce every value from these inputs and rules?

The other tabs beside Comments are background for assessment, not under review.

Summary

Lifecycle: reviewedEditUnder review
What this benchmark means
README.md

Goal: derive TR01SDT, TR01EDT, TR02SDT, TR02EDT, TRT01A, TRT02A, and WASHDUR to capture each treatment period and the washout between them, with one record per subject.

Input: demographics listing each subject, plus exposure records carrying the epoch (EPOCH), the treatment given (EXTRT), start and end dates (EXSTDTC, EXENDTC), and a sequence number (EXSEQ).

Variables:

  • TR01SDT is the earliest EXSTDTC among the subject's exposure records in the TREATMENT 1 epoch and a recorded start date; empty when the subject has no such record.
  • TR01EDT is the latest EXENDTC among the subject's exposure records in the TREATMENT 1 epoch and a recorded end date; empty when the subject has no such record.
  • TR02SDT is the earliest EXSTDTC among the subject's exposure records in the TREATMENT 2 epoch and a recorded start date; empty when the subject has no such record.
  • TR02EDT is the latest EXENDTC among the subject's exposure records in the TREATMENT 2 epoch and a recorded end date; empty when the subject has no such record.
  • TRT01A is the EXTRT from the subject's earliest exposure record in the TREATMENT 1 epoch, earliest by EXSTDTC with the lower EXSEQ breaking ties on the same day and a record with no start date sorting last; either VITAMIN D3 or PLACEBO, and empty when the subject has no period-one exposure.
  • TRT02A is the EXTRT from the subject's earliest exposure record in the TREATMENT 2 epoch, chosen the same way; either VITAMIN D3 or PLACEBO, and empty when the subject has no period-two exposure.
  • WASHDUR is the number of days strictly between TR01EDT and TR02SDT, counting neither endpoint, so periods that touch give zero; empty when either date is missing.

Note: every value comes only from exposure records in its own period, so period-one dates and treatment never mix with period-two records. A subject with no period-two exposure leaves TR02SDT, TR02EDT, TRT02A, and WASHDUR empty together.

Inputs

Under review
2 source files

input/dm.csv

Edit
6 rows
input/dm.csv
STUDYIDUSUBJID
YAMAA-01
YAMAA-01
YAMAA-01
YAMAA-01
YAMAA-01
YAMAA-01

input/ex.csv

Edit
17 rows
input/ex.csv
STUDYIDUSUBJIDEXSEQEPOCHEXTRTEXSTDTCEXENDTC
YAMAA-011TREATMENT 1VITAMIN D32025-01-062025-01-16
YAMAA-012TREATMENT 1VITAMIN D32025-01-172025-01-26
YAMAA-013TREATMENT 2PLACEBO2025-02-102025-03-02
YAMAA-013TREATMENT 2PLACEBO2025-02-102025-03-02
YAMAA-011TREATMENT 1PLACEBO2025-01-082025-01-28
YAMAA-012TREATMENT 2VITAMIN D32025-02-122025-02-22
YAMAA-013TREATMENT 2VITAMIN D32025-02-232025-03-04
YAMAA-011TREATMENT 1VITAMIN D32025-01-102025-01-20
YAMAA-012TREATMENT 1VITAMIN D32025-03-102025-03-15
YAMAA-015TREATMENT 1PLACEBO2025-03-102025-03-20
YAMAA-011TREATMENT 2PLACEBO2025-04-012025-04-15
YAMAA-011TREATMENT 1PLACEBO2025-04-012025-04-10
YAMAA-012TREATMENT 2VITAMIN D32025-04-112025-04-25
YAMAA-011TREATMENT 1VITAMIN D32025-05-012025-05-10
YAMAA-012TREATMENT 1PLACEBO—2025-05-12
YAMAA-013TREATMENT 2PLACEBO2025-06-01—
YAMAA-014TREATMENT 2PLACEBO2025-06-022025-06-20

Expected output

Under review
6 expected rows

expected/adsl.csv

Edit
6 rows
expected/adsl.csv
STUDYIDUSUBJIDTR01SDTTR01EDTTR02SDTTR02EDTTRT01ATRT02AWASHDUR
YAMAA-012025-01-062025-01-262025-02-102025-03-02VITAMIN D3PLACEBO14
YAMAA-012025-01-082025-01-282025-02-122025-03-04PLACEBOVITAMIN D314
YAMAA-012025-01-102025-01-20——VITAMIN D3——
YAMAA-012025-03-102025-03-202025-04-012025-04-15VITAMIN D3PLACEBO11
YAMAA-012025-04-012025-04-102025-04-112025-04-25PLACEBOVITAMIN D30
YAMAA-012025-05-012025-05-122025-06-012025-06-20VITAMIN D3PLACEBO19
Shaded columns are derived— = missing value

Comments and assessment materials

Comments

Signed in with GitHub; saved in repository Discussions

Mapping spec

Generated from the benchmark YAML; do not edit by hand

Dataset NameVariable NameVariable LabelTypeCodelist/Controlled TermsCoreComputational MethodOrigin
ADSLSTUDYIDStudy IdentifiercharacterCopy value from DM.STUDYID.Predecessor
ADSLUSUBJIDUnique Subject IdentifiercharacterCopy value from DM.USUBJID.Predecessor
ADSLTR01SDTDate of First Exposure in Period 01numericISO 8601Aggregate MIN(EX.EXSTDTC) over records matching on the shared output keys, where EX.EPOCH = 'TREATMENT 1'.Derived
ADSLTR01EDTDate of Last Exposure in Period 01numericISO 8601Aggregate MAX(EX.EXENDTC) over records matching on the shared output keys, where EX.EPOCH = 'TREATMENT 1'.Derived
ADSLTR02SDTDate of First Exposure in Period 02numericISO 8601Aggregate MIN(EX.EXSTDTC) over records matching on the shared output keys, where EX.EPOCH = 'TREATMENT 2'.Derived
ADSLTR02EDTDate of Last Exposure in Period 02numericISO 8601Aggregate MAX(EX.EXENDTC) over records matching on the shared output keys, where EX.EPOCH = 'TREATMENT 2'.Derived
ADSLTRT01AActual Treatment for Period 01characterVITAMIN D3, PLACEBOCopy value from EX.EXTRT where EX.EPOCH = 'TREATMENT 1', keeping the first ordered by EX.EXSTDTC then EX.EXSEQ.Predecessor
ADSLTRT02AActual Treatment for Period 02characterVITAMIN D3, PLACEBOCopy value from EX.EXTRT where EX.EPOCH = 'TREATMENT 2', keeping the first ordered by EX.EXSTDTC then EX.EXSEQ.Predecessor
ADSLWASHDURWashout Duration (Days)numericWhole days from TR01EDT to TR02SDT, counting neither endpoint.Derived
VariablePermitted values
TRT01AVITAMIN D3, PLACEBO
TRT02AVITAMIN D3, PLACEBO
VersionDateAuthorDescriptionReviewerSign-off
1.32026-09-29yamaa docs buildShow study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata
1.22026-09-29yamaa docs buildClassify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived
1.12026-09-29yamaa docs buildResolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML
1.0yamaa docs buildInitial generation from spec.yaml

Agent prompt

The request an agent receives with the input files; each tier states no more than the one above

prompts/adam-adsl-crossover-periods/full.md

Edit
28 lines

Following CDISC ADaM standards, use the provided DM and EX datasets to create an ADSL dataset with one record per subject.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, TR01SDT, TR01EDT, TR02SDT, TR02EDT, TRT01A, TRT02A, WASHDUR

TR01SDT is the earliest recorded start date among the subject's exposure records in the TREATMENT 1 epoch; it has no value when the subject has no such record. TR01EDT is the latest recorded end date there. TR02SDT and TR02EDT are the same for the TREATMENT 2 epoch. Every value comes only from exposure records in its own period.

TRT01A is the treatment given on the subject's earliest exposure record in the TREATMENT 1 epoch, earliest by start date with the lower sequence number breaking ties on the same day and a record with no start date sorting last; either VITAMIN D3 or PLACEBO, and empty when the subject has no period-one exposure. TRT02A is chosen the same way in the TREATMENT 2 epoch.

WASHDUR is the number of days strictly between the end of period one and the start of period two, counting neither endpoint, so periods that touch give zero; it has no value when either date is missing. A subject with no period-two exposure leaves the period-two dates and treatment and the washout empty together.

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.csv.

prompts/adam-adsl-crossover-periods/conventions.md

Edit
13 lines

Following CDISC ADaM standards, use the provided DM and EX datasets to create an ADSL dataset with one record per subject.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, TR01SDT, TR01EDT, TR02SDT, TR02EDT, TRT01A, TRT02A, WASHDUR

TRT01A and TRT02A are VITAMIN D3 or PLACEBO, or empty.

WASHDUR counts neither the end of period one nor the start of period two.

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.csv.

prompts/adam-adsl-crossover-periods/brief.md

Edit
9 lines

Following CDISC ADaM standards, use the provided DM and EX datasets to create an ADSL dataset with one record per subject.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, TR01SDT, TR01EDT, TR02SDT, TR02EDT, TRT01A, TRT02A, WASHDUR

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.csv.

yamaa code

Runs the yamaa specification; its result must match the expected output

run.R

Edit
4 lines
library(yamaa)adsl <- yamaa_domain("spec.yaml")$outputadsl

run.py

Edit
4 lines
import yamaaadsl = yamaa.yamaa_domain("spec.yaml").outputadsl

Solution without yamaa

Written from the full prompt and the inputs alone; CI checks it reproduces the expected output

solutions/adam-adsl-crossover-periods/result.R

Edit
66 lines
# Reference solution for the yamaa benchmark adam-adsl-crossover-periods (R track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.library(dplyr, warn.conflicts = FALSE)library(readr)dm <- read_csv(  "/app/input/dm.csv",  col_types = cols(.default = col_character()))ex <- read_csv(  "/app/input/ex.csv",  col_types = cols(    EXSEQ = col_integer(),    EXSTDTC = col_date(),    EXENDTC = col_date(),    .default = col_character()  ))ex1 <- ex |> filter(EPOCH %in% "TREATMENT 1")ex2 <- ex |> filter(EPOCH %in% "TREATMENT 2")agg1 <- ex1 |>  group_by(STUDYID, USUBJID) |>  summarise(    TR01SDT = if (all(is.na(EXSTDTC))) as.Date(NA) else min(EXSTDTC, na.rm = TRUE),    TR01EDT = if (all(is.na(EXENDTC))) as.Date(NA) else max(EXENDTC, na.rm = TRUE),    .groups = "drop"  )agg2 <- ex2 |>  group_by(STUDYID, USUBJID) |>  summarise(    TR02SDT = if (all(is.na(EXSTDTC))) as.Date(NA) else min(EXSTDTC, na.rm = TRUE),    TR02EDT = if (all(is.na(EXENDTC))) as.Date(NA) else max(EXENDTC, na.rm = TRUE),    .groups = "drop"  )# The treatment on the earliest record in each period: earliest start date# with the lower sequence number breaking ties, records without a start# date sorting last.first1 <- ex1 |>  arrange(STUDYID, USUBJID, is.na(EXSTDTC), EXSTDTC, EXSEQ) |>  distinct(STUDYID, USUBJID, .keep_all = TRUE) |>  transmute(STUDYID, USUBJID, TRT01A = EXTRT)first2 <- ex2 |>  arrange(STUDYID, USUBJID, is.na(EXSTDTC), EXSTDTC, EXSEQ) |>  distinct(STUDYID, USUBJID, .keep_all = TRUE) |>  transmute(STUDYID, USUBJID, TRT02A = EXTRT)adsl <- dm |>  left_join(agg1, by = c("STUDYID", "USUBJID")) |>  left_join(agg2, by = c("STUDYID", "USUBJID")) |>  left_join(first1, by = c("STUDYID", "USUBJID")) |>  left_join(first2, by = c("STUDYID", "USUBJID")) |>  mutate(WASHDUR = as.integer(TR02SDT - TR01EDT) - 1L) |>  select(    STUDYID, USUBJID, TR01SDT, TR01EDT, TR02SDT, TR02EDT,    TRT01A, TRT02A, WASHDUR  )dir.create("/app/output", showWarnings = FALSE)write_csv(adsl, "/app/output/adsl.csv", na = "")

solutions/adam-adsl-crossover-periods/result.py

Edit
65 lines
# Reference solution for the yamaa benchmark adam-adsl-crossover-periods (Python track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.from pathlib import Pathimport polars as pldm = pl.read_csv("/app/input/dm.csv", infer_schema=False)ex = pl.read_csv("/app/input/ex.csv", infer_schema=False).with_columns(    pl.col("EXSEQ").cast(pl.Int64, strict=False),    pl.col("EXSTDTC").str.to_date(strict=False),    pl.col("EXENDTC").str.to_date(strict=False),)ex1 = ex.filter(pl.col("EPOCH") == "TREATMENT 1")ex2 = ex.filter(pl.col("EPOCH") == "TREATMENT 2")agg1 = ex1.group_by(["STUDYID", "USUBJID"]).agg(    TR01SDT=pl.col("EXSTDTC").min(), TR01EDT=pl.col("EXENDTC").max())agg2 = ex2.group_by(["STUDYID", "USUBJID"]).agg(    TR02SDT=pl.col("EXSTDTC").min(), TR02EDT=pl.col("EXENDTC").max())# The treatment on the earliest record in each period: earliest start date# with the lower sequence number breaking ties, records without a start# date sorting last.first1 = (    ex1.sort(["EXSTDTC", "EXSEQ"], nulls_last=True)    .unique(["STUDYID", "USUBJID"], keep="first", maintain_order=True)    .select("STUDYID", "USUBJID", TRT01A="EXTRT"))first2 = (    ex2.sort(["EXSTDTC", "EXSEQ"], nulls_last=True)    .unique(["STUDYID", "USUBJID"], keep="first", maintain_order=True)    .select("STUDYID", "USUBJID", TRT02A="EXTRT"))adsl = (    dm.join(agg1, on=["STUDYID", "USUBJID"], how="left", maintain_order="left")    .join(agg2, on=["STUDYID", "USUBJID"], how="left", maintain_order="left")    .join(first1, on=["STUDYID", "USUBJID"], how="left", maintain_order="left")    .join(first2, on=["STUDYID", "USUBJID"], how="left", maintain_order="left")    # Days strictly between the periods, counting neither endpoint.    .with_columns(        WASHDUR=(pl.col("TR02SDT") - pl.col("TR01EDT")).dt.total_days() - 1    )    .select(        "STUDYID",        "USUBJID",        "TR01SDT",        "TR01EDT",        "TR02SDT",        "TR02EDT",        "TRT01A",        "TRT02A",        "WASHDUR",    ))Path("/app/output").mkdir(exist_ok=True)adsl.write_csv("/app/output/adsl.csv")

solutions/adam-adsl-crossover-periods/result.sas

Edit
36 lines
/* opensas reference solution for the yamaa benchmark adam-adsl-crossover-periods.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data dm;  length STUDYID USUBJID $1024;  infile "/app/input/dm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024.;run;data ex;  length STUDYID USUBJID EPOCH EXTRT EXSTDTC EXENDTC $1024;  infile "/app/input/ex.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. EXSEQ EPOCH : $char1024. EXTRT : $char1024. EXSTDTC : $char1024. EXENDTC : $char1024.;run;data exposure; set ex; NODATE=missing(EXSTDTC); run;proc sort data=exposure; by STUDYID USUBJID EPOCH NODATE EXSTDTC EXSEQ; run;data firstex; set exposure; by STUDYID USUBJID EPOCH; if first.EPOCH; run;proc sql;create table starts1 as select STUDYID,USUBJID,min(EXSTDTC) as TR01SDT from ex where EPOCH='TREATMENT 1' and not missing(EXSTDTC) group by STUDYID,USUBJID;create table ends1 as select STUDYID,USUBJID,max(EXENDTC) as TR01EDT from ex where EPOCH='TREATMENT 1' group by STUDYID,USUBJID;create table starts2 as select STUDYID,USUBJID,min(EXSTDTC) as TR02SDT from ex where EPOCH='TREATMENT 2' and not missing(EXSTDTC) group by STUDYID,USUBJID;create table ends2 as select STUDYID,USUBJID,max(EXENDTC) as TR02EDT from ex where EPOCH='TREATMENT 2' group by STUDYID,USUBJID;create table joined as select a.*,b.TR01SDT,c.TR01EDT,d.TR02SDT,e.TR02EDT,f.EXTRT as TRT01A,g.EXTRT as TRT02A from dm aleft join starts1 b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJIDleft join ends1 c on a.STUDYID=c.STUDYID and a.USUBJID=c.USUBJIDleft join starts2 d on a.STUDYID=d.STUDYID and a.USUBJID=d.USUBJIDleft join ends2 e on a.STUDYID=e.STUDYID and a.USUBJID=e.USUBJIDleft join firstex f on a.STUDYID=f.STUDYID and a.USUBJID=f.USUBJID and f.EPOCH='TREATMENT 1'left join firstex g on a.STUDYID=g.STUDYID and a.USUBJID=g.USUBJID and g.EPOCH='TREATMENT 2'; quit;data result; set joined; WASHDUR=.; if not missing(TR01EDT) and not missing(TR02SDT) then WASHDUR=max(0,input(TR02SDT,yymmdd10.)-input(TR01EDT,yymmdd10.)-1); run;proc sql;  create table final as select STUDYID, USUBJID, TR01SDT, TR01EDT, TR02SDT, TR02EDT, TRT01A, TRT02A, WASHDUR from result;quit;proc export data=final outfile="/app/output/adsl.csv" dbms=csv replace; run;