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 SDTM: 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: draftEditUnder review
What this benchmark means
README.md

Goal: build one Subject Elements (SE) record per subject per element the subject entered, carrying SESEQ, ETCD, ELEMENT, TAETORD, EPOCH, SESTDTC, SEENDTC, SESTDY, SEENDY, and SEUPDES.

Input: two sources:

  • dm.csv: subject-level demographics (one row per subject) with the planned arm code and the reference dates (informed consent, reference start, end of study participation).
  • odm.csv: a long-format Operational Data Model (ODM) extract, one row per subject per study event per item, carrying each element's trial-design attributes (description, planned order, epoch) as IT.TE.* / IT.TA.* items and the first and last dosing dates as IT.EX.* items.

The extract repeats each subject's trial-design attributes and dosing dates on every study event, so each SE record derives from its own event context without reading across events. The reference dates come from dm.csv, matched on the subject keys the two datasets share (STUDYID, USUBJID).

Variables:

  • SESEQ numbers the subject's elements in planned order, so it equals TAETORD.
  • ETCD is the element code, taken from the subject's study event.
  • ELEMENT is the element description, e.g. Screening.
  • TAETORD is the planned order of the element within the subject's arm.
  • EPOCH is the epoch the element belongs to, e.g. TREATMENT.
  • SESTDTC is the date the subject actually started the element: informed consent for screening, first dosing date for treatment, last dosing date for follow-up (and for any other or missing epoch).
  • SEENDTC is the date the subject actually ended the element: first dosing date for screening, last dosing date for treatment, end of study participation for follow-up (and for any other or missing epoch).
  • SESTDY is the study day of the element start, counting the reference start date as day 1 with no day 0; blank when either date is missing.
  • SEENDY is the study day of the element end, counted the same way.
  • SEUPDES is always blank: the records are built only from planned elements, and the column describes only an unplanned one.

Note: elements are recorded back to back: an element ends on the day the next one starts. The treatment element ends on the actual last dosing date, so a subject who stopped dosing early has a shorter treatment element and an earlier follow-up start than the plan described.

Provenance: the trial design, subjects, dates, and dosing dates are invented fixtures.

Inputs

Under review
2 source files

input/dm.csv

Edit
2 rows
input/dm.csv
STUDYIDUSUBJIDARMCDRFICDTCRFSTDTCRFPENDTC
YAMAA-01A2026-01-022026-01-052026-02-17
YAMAA-01B2026-01-032026-01-052026-02-17

input/odm.csv

Edit
30 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
YAMAA-01MDV.1001SCRN1IG.TE1IT.TE.ELEMENTScreening
YAMAA-01MDV.1001SCRN1IG.TE1IT.TA.TAETORD1
YAMAA-01MDV.1001SCRN1IG.TE1IT.TA.EPOCHSCREENING
YAMAA-01MDV.1001SCRN1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1001SCRN1IG.EX1IT.EX.LASTDTC2026-02-02
YAMAA-01MDV.1001TRTA1IG.TE1IT.TE.ELEMENTTreatment A
YAMAA-01MDV.1001TRTA1IG.TE1IT.TA.TAETORD2
YAMAA-01MDV.1001TRTA1IG.TE1IT.TA.EPOCHTREATMENT
YAMAA-01MDV.1001TRTA1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1001TRTA1IG.EX1IT.EX.LASTDTC2026-02-02
YAMAA-01MDV.1001FUP1IG.TE1IT.TE.ELEMENTFollow-up
YAMAA-01MDV.1001FUP1IG.TE1IT.TA.TAETORD3
YAMAA-01MDV.1001FUP1IG.TE1IT.TA.EPOCHFOLLOW-UP
YAMAA-01MDV.1001FUP1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1001FUP1IG.EX1IT.EX.LASTDTC2026-02-02
YAMAA-01MDV.1002SCRN1IG.TE1IT.TE.ELEMENTScreening
YAMAA-01MDV.1002SCRN1IG.TE1IT.TA.TAETORD1
YAMAA-01MDV.1002SCRN1IG.TE1IT.TA.EPOCHSCREENING
YAMAA-01MDV.1002SCRN1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1002SCRN1IG.EX1IT.EX.LASTDTC2026-01-20
YAMAA-01MDV.1002TRTB1IG.TE1IT.TE.ELEMENTTreatment B
YAMAA-01MDV.1002TRTB1IG.TE1IT.TA.TAETORD2
YAMAA-01MDV.1002TRTB1IG.TE1IT.TA.EPOCHTREATMENT
YAMAA-01MDV.1002TRTB1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1002TRTB1IG.EX1IT.EX.LASTDTC2026-01-20
YAMAA-01MDV.1002FUP1IG.TE1IT.TE.ELEMENTFollow-up
YAMAA-01MDV.1002FUP1IG.TE1IT.TA.TAETORD3
YAMAA-01MDV.1002FUP1IG.TE1IT.TA.EPOCHFOLLOW-UP
YAMAA-01MDV.1002FUP1IG.EX1IT.EX.FIRSTDTC2026-01-06
YAMAA-01MDV.1002FUP1IG.EX1IT.EX.LASTDTC2026-01-20

Expected output

Under review
6 expected rows

expected/se.csv

Edit
6 rows
expected/se.csv
DOMAINSTUDYIDUSUBJIDSESEQETCDELEMENTTAETORDEPOCHSESTDTCSEENDTCSESTDYSEENDYSEUPDES
SEYAMAA-011SCRNScreening1SCREENING2026-01-022026-01-06-32—
SEYAMAA-012TRTATreatment A2TREATMENT2026-01-062026-02-02229—
SEYAMAA-013FUPFollow-up3FOLLOW-UP2026-02-022026-02-172944—
SEYAMAA-011SCRNScreening1SCREENING2026-01-032026-01-06-22—
SEYAMAA-012TRTBTreatment B2TREATMENT2026-01-062026-01-20216—
SEYAMAA-013FUPFollow-up3FOLLOW-UP2026-01-202026-02-171644—
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

Variable NameVariable LabelTypeLengthControlled Terms or FormatOriginCoreConversion DefinitionVariable TypeVariable OrderComments for Define
DOMAINDomain AbbreviationCharAssignedSet constant value "SE".SDTM1
STUDYIDStudy IdentifierCharCollectedCopy value from ODM.StudyOID.SDTM2
USUBJIDUnique Subject IdentifierCharCollectedCopy value from ODM.SubjectKey.SDTM3
ETCDElement CodeCharCollectedCopy value from ODM.StudyEventOID.SDTM4
ELEMENTDescription of ElementCharCollectedCopy value from ODM.Value where ODM.ItemOID = 'IT.TE.ELEMENT'.SDTM5
TAETORDPlanned Order of Element within ArmNumCollectedCopy value from ODM.Value where ODM.ItemOID = 'IT.TA.TAETORD'.SDTM6
SESEQSequence NumberNumDerivedCopy value from TAETORD.SDTM7
EPOCHEpochCharCollectedCopy value from ODM.Value where ODM.ItemOID = 'IT.TA.EPOCH'.SDTM8
SESTDTCStart Date/Time of ElementCharISO 8601DerivedIf EPOCH = 'SCREENING' then "DM.RFICDTC"; If EPOCH = 'TREATMENT' then "FIRSTDTC"; otherwise "LASTDTC".SDTM9
SEENDTCEnd Date/Time of ElementCharISO 8601DerivedIf EPOCH = 'SCREENING' then "FIRSTDTC"; If EPOCH = 'TREATMENT' then "LASTDTC"; otherwise "DM.RFPENDTC".SDTM10
SESTDYStudy Day of Start of ElementNumDerivedCDISC study day of SESTDTC against DM.RFSTDTC as day 1; there is no day zero.SDTM11
SEENDYStudy Day of End of ElementNumDerivedCDISC study day of SEENDTC against DM.RFSTDTC as day 1; there is no day zero.SDTM12
SEUPDESDescription of Unplanned ElementCharAssignedSet constant value "None".SDTM13
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/sdtm-se-subject-elements/full.md

Edit
30 lines

Following CDISC SDTM standards, use the provided DM and ODM datasets to create an SE dataset with one record per subject per element the subject entered.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, SESEQ, ETCD, ELEMENT, TAETORD, EPOCH, SESTDTC, SEENDTC, SESTDY, SEENDY, SEUPDES

STUDYID is the ODM StudyOID and USUBJID is the SubjectKey, each as given.

SESEQ numbers the subject's elements in planned order, so it equals TAETORD. ETCD is the element code from the subject's study event, ELEMENT its description, TAETORD its planned order, and EPOCH its epoch.

SESTDTC is the date the subject actually started the element: informed consent for screening, the first dosing date for treatment, and the last dosing date for follow-up (and for any other or missing epoch). SEENDTC is the date the subject actually ended the element: the first dosing date for screening, the last dosing date for treatment, and the end of study participation for follow-up (and for any other or missing epoch). SESTDY and SEENDY are the study days of the element start and end relative to the reference start date. SEUPDES always has no value: the records are built only from planned elements.

Elements are recorded back to back: an element ends on the day the next one starts, so a subject who stopped dosing early has a shorter treatment element and an earlier follow-up start.

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

prompts/sdtm-se-subject-elements/conventions.md

Edit
12 lines

Following CDISC SDTM standards, use the provided DM and ODM datasets to create an SE dataset with one record per subject per element the subject entered.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, SESEQ, ETCD, ELEMENT, TAETORD, EPOCH, SESTDTC, SEENDTC, SESTDY, SEENDY, SEUPDES

SEUPDES always has no value.

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

prompts/sdtm-se-subject-elements/brief.md

Edit
10 lines

Following CDISC SDTM standards, use the provided DM and ODM datasets to create an SE dataset with one record per subject per element the subject entered.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, SESEQ, ETCD, ELEMENT, TAETORD, EPOCH, SESTDTC, SEENDTC, SESTDY, SEENDY, SEUPDES

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

yamaa code

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

run.R

Edit
4 lines
library(yamaa)se <- yamaa_domain("spec.yaml")$outputse

run.py

Edit
4 lines
import yamaase = yamaa.yamaa_domain("spec.yaml").outputse

Solution without yamaa

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

solutions/sdtm-se-subject-elements/result.R

Edit
74 lines
# Reference solution for the yamaa benchmark sdtm-se-subject-elements (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)library(tidyr)dm <- read_csv(  "/app/input/dm.csv",  col_types = cols(.default = col_character()))odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character()))wide <- odm |>  mutate(Value = na_if(Value, "")) |>  pivot_wider(    id_cols = c(SubjectKey, StudyEventOID),    names_from = ItemOID,    values_from = Value,    values_fn = function(x) x[[1]]  )study_day <- function(ref, day) {  ifelse(    is.na(ref) | ref == "" | is.na(day) | day == "",    NA_character_,    {      delta <- as.integer(as.Date(day) - as.Date(ref))      as.character(ifelse(delta >= 0, delta + 1L, delta))    }  )}se <- wide |>  left_join(dm, by = c("SubjectKey" = "USUBJID")) |>  mutate(    DOMAIN = "SE",    STUDYID = STUDYID,    USUBJID = SubjectKey,    SESEQ = IT.TA.TAETORD,    ETCD = StudyEventOID,    ELEMENT = IT.TE.ELEMENT,    TAETORD = IT.TA.TAETORD,    EPOCH = IT.TA.EPOCH,    first_dtc = IT.EX.FIRSTDTC,    last_dtc = IT.EX.LASTDTC,    SESTDTC = case_when(      EPOCH == "SCREENING" ~ RFICDTC,      EPOCH == "TREATMENT" ~ first_dtc,      TRUE ~ last_dtc    ),    SEENDTC = case_when(      EPOCH == "SCREENING" ~ first_dtc,      EPOCH == "TREATMENT" ~ last_dtc,      TRUE ~ RFPENDTC    ),    SESTDY = study_day(RFSTDTC, SESTDTC),    SEENDY = study_day(RFSTDTC, SEENDTC),    SEUPDES = NA_character_  ) |>  arrange(USUBJID, suppressWarnings(as.integer(TAETORD))) |>  select(    DOMAIN, STUDYID, USUBJID, SESEQ, ETCD, ELEMENT, TAETORD, EPOCH,    SESTDTC, SEENDTC, SESTDY, SEENDY, SEUPDES  )dir.create("/app/output", showWarnings = FALSE)write_csv(se, "/app/output/se.csv", na = "")

solutions/sdtm-se-subject-elements/result.py

Edit
125 lines
# Reference solution for the yamaa benchmark sdtm-se-subject-elements (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 datetime import datefrom pathlib import Pathimport polars as pldef _present(value) -> bool:    return value is not None and str(value) != ""def _parse(value) -> date | None:    if not _present(value):        return None    return date.fromisoformat(str(value))def _study_day(ref: date | None, day: date | None) -> str | None:    if ref is None or day is None:        return None    delta = (day - ref).days    return str(delta + 1 if delta >= 0 else delta)dm = pl.read_csv("/app/input/dm.csv", infer_schema=False)odm = pl.read_csv("/app/input/odm.csv", infer_schema=False)# Planned element per study event, and dosing dates per subject.elements: dict[tuple, dict] = {}dosing: dict[str, dict] = {}for row in odm.to_dicts():    subj = str(row["SubjectKey"])    event = str(row["StudyEventOID"])    oid, value = str(row["ItemOID"]), row["Value"]    if oid == "IT.TE.ELEMENT":        elements.setdefault((subj, event), {})["element"] = str(value)    elif oid == "IT.TA.TAETORD":        elements.setdefault((subj, event), {})["order"] = str(value)    elif oid == "IT.TA.EPOCH":        elements.setdefault((subj, event), {})["epoch"] = str(value)    elif oid == "IT.EX.FIRSTDTC":        dosing.setdefault(subj, {})["first"] = str(value) if _present(value) else None    elif oid == "IT.EX.LASTDTC":        dosing.setdefault(subj, {})["last"] = str(value) if _present(value) else Nonedm_by_subj = {str(r["USUBJID"]): r for r in dm.to_dicts()}rows = []for (subj, etcd), info in elements.items():    subject = dm_by_subj.get(subj)    if subject is None:        continue    studyid = str(subject["STUDYID"])    usubjid = str(subject["USUBJID"])    epoch = info.get("epoch")    order = info.get("order")    first = (dosing.get(subj) or {}).get("first")    last = (dosing.get(subj) or {}).get("last")    rfic = str(subject.get("RFICDTC") or "")    rfic = rfic if _present(rfic) else None    rfstd = str(subject.get("RFSTDTC") or "")    rfstd = rfstd if _present(rfstd) else None    rfpen = str(subject.get("RFPENDTC") or "")    rfpen = rfpen if _present(rfpen) else None    if epoch == "SCREENING":        stdtc, endtc = rfic, first    elif epoch == "TREATMENT":        stdtc, endtc = first, last    else:        # Follow-up, and any other or missing epoch.        stdtc, endtc = last, rfpen    ref = _parse(rfstd)    rows.append(        {            "DOMAIN": "SE",            "STUDYID": studyid,            "USUBJID": usubjid,            "SESEQ": order,            "ETCD": etcd,            "ELEMENT": info.get("element"),            "TAETORD": order,            "EPOCH": epoch,            "SESTDTC": stdtc,            "SEENDTC": endtc,            "SESTDY": _study_day(ref, _parse(stdtc)),            "SEENDY": _study_day(ref, _parse(endtc)),            "SEUPDES": None,        }    )rows.sort(key=lambda r: (r["USUBJID"], int(r["TAETORD"] or 0)))se = (    pl.DataFrame(        rows,        schema={            "DOMAIN": pl.String,            "STUDYID": pl.String,            "USUBJID": pl.String,            "SESEQ": pl.String,            "ETCD": pl.String,            "ELEMENT": pl.String,            "TAETORD": pl.String,            "EPOCH": pl.String,            "SESTDTC": pl.String,            "SEENDTC": pl.String,            "SESTDY": pl.String,            "SEENDY": pl.String,            "SEUPDES": pl.String,        },    )    .sort(["USUBJID", "TAETORD"])    .select(        "DOMAIN", "STUDYID", "USUBJID", "SESEQ", "ETCD", "ELEMENT", "TAETORD",        "EPOCH", "SESTDTC", "SEENDTC", "SESTDY", "SEENDY", "SEUPDES",    ))Path("/app/output").mkdir(exist_ok=True)se.write_csv("/app/output/se.csv")

solutions/sdtm-se-subject-elements/result.sas

Edit
53 lines
/* opensas reference solution for the yamaa benchmark sdtm-se-subject-elements.   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 ARMCD RFICDTC RFSTDTC RFPENDTC $1024;  infile "/app/input/dm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. ARMCD : $char1024. RFICDTC : $char1024. RFSTDTC : $char1024. RFPENDTC : $char1024.;run;data odm;  length StudyOID MetaDataVersionOID SubjectKey StudyEventOID StudyEventRepeatKey ItemGroupOID ItemGroupRepeatKey ItemOID Value $1024;  infile "/app/input/odm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input StudyOID : $char1024. MetaDataVersionOID : $char1024. SubjectKey : $char1024. StudyEventOID : $char1024. StudyEventRepeatKey : $char1024. ItemGroupOID : $char1024. ItemGroupRepeatKey : $char1024. ItemOID : $char1024. Value : $char1024.;run;proc sort data=odm out=ordered; by StudyOID SubjectKey StudyEventOID; run;data elements;  length ELEMENT ORD EPOCH $1024;  retain ELEMENT ORD EPOCH;  set ordered;  by StudyOID SubjectKey StudyEventOID;  if first.StudyEventOID then do;    ELEMENT=''; ORD=''; EPOCH='';  end;    if ItemOID='IT.TE.ELEMENT' then ELEMENT=Value;  if ItemOID='IT.TA.TAETORD' then ORD=Value;  if ItemOID='IT.TA.EPOCH' then EPOCH=Value;  if last.StudyEventOID then output;run;proc sort data=odm out=ordered; by StudyOID SubjectKey; run;data dosing;  length FIRSTDTC LASTDTC $1024;  retain FIRSTDTC LASTDTC;  set ordered;  by StudyOID SubjectKey;  if first.SubjectKey then do;    FIRSTDTC=''; LASTDTC='';  end;    if ItemOID='IT.EX.FIRSTDTC' then FIRSTDTC=Value;  if ItemOID='IT.EX.LASTDTC' then LASTDTC=Value;  if last.SubjectKey then output;run;proc sql; create table joined as select a.*,b.FIRSTDTC,b.LASTDTC,c.RFICDTC,c.RFSTDTC,c.RFPENDTC from elements a left join dosing b on a.StudyOID=b.StudyOID and a.SubjectKey=b.SubjectKey inner join dm c on a.StudyOID=c.STUDYID and a.SubjectKey=c.USUBJID where not missing(a.ELEMENT); quit;data result; set joined; length DOMAIN $2 STUDYID USUBJID ETCD SESTDTC SEENDTC SEUPDES $1024; DOMAIN='SE'; STUDYID=StudyOID; USUBJID=SubjectKey; ETCD=StudyEventOID; TAETORD=input(ORD,best32.); SESEQ=TAETORD; SEUPDES=''; SESTDY=.; SEENDY=.;if EPOCH='SCREENING' then do; SESTDTC=RFICDTC; SEENDTC=FIRSTDTC; end; else if EPOCH='TREATMENT' then do; SESTDTC=FIRSTDTC; SEENDTC=LASTDTC; end; else do; SESTDTC=LASTDTC; SEENDTC=RFPENDTC; end;if not missing(RFSTDTC) then do; if not missing(SESTDTC) then do; SESTDY=input(SESTDTC,yymmdd10.)-input(RFSTDTC,yymmdd10.); if SESTDY>=0 then SESTDY+1; end; if not missing(SEENDTC) then do; SEENDY=input(SEENDTC,yymmdd10.)-input(RFSTDTC,yymmdd10.); if SEENDY>=0 then SEENDY+1; end; end; run;proc sql;  create table final as select DOMAIN, STUDYID, USUBJID, SESEQ, ETCD, ELEMENT, TAETORD, EPOCH, SESTDTC, SEENDTC, SESTDY, SEENDY, SEUPDES from result;quit;proc export data=final outfile="/app/output/se.csv" dbms=csv replace; run;