Review ask

As a clinical trial statistician or programmer, answer three short questions in Your review: the task, the inputs and outputs, and the AI-assisted review. Agreeing with all three approves this benchmark; disagreeing with any needs a comment. Propose a fix with the Edit button on any source file, or add it to your comments.

We suggest spending no more than 5 minutes on a review. The other tabs beside Your review are background for assessment, not under review.

Summary

Lifecycle: draftEditUnder review
What this benchmark means
README.md

Public entries: Python and R. Passing coverage requires an installed inventory.

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

Human review and assessment

Your review

3 questions and your comments · View benchmark 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.72026-10-20yamaa docs buildRead a specification in the 1.0 surface as its 0.5 counterpart; state that missing values stay missing where a mapping declares no missing handler, and leave out a declared missing answer that restates a handler's default
1.62026-10-17yamaa docs buildDescribe collected date parts written as ISO 8601 date text and times of day counted in seconds since midnight
1.52026-10-17yamaa docs buildName collected precision in words instead of letter codes; state imputation fallbacks, minimum source precision, bounds ignored outside the collected period, and invalid text with no handler as an error; describe date and datetime shifts, imputation flag codes, and partial text giving no date part
1.42026-10-15yamaa docs buildState datetime precision codes through the hour and minute, and complete only the time parts a collected datetime lacks
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
31 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

DOMAIN is "SE". STUDYID is the study OID as given in ODM and USUBJID is the subject key as given in ODM. ETCD is the code of the study event the record is built from. ELEMENT is the element description the extract gives for that event (the IT.TE.ELEMENT item). TAETORD is the planned order of the element within the subject's arm (the IT.TA.TAETORD item). EPOCH is the epoch the extract gives for that event (the IT.TA.EPOCH item). SESEQ numbers the subject's elements in planned order and equals TAETORD. The extract gives each subject a first dosing date and a last dosing date, the same on every study event of the subject. SESTDTC is the subject's informed consent date in DM when EPOCH is "SCREENING", the subject's first dosing date when EPOCH is "TREATMENT", and the subject's last dosing date for any other epoch or when EPOCH has no value. SEENDTC is the subject's first dosing date when EPOCH is "SCREENING", the subject's last dosing date when EPOCH is "TREATMENT", and the subject's end of study participation date in DM for any other epoch or when EPOCH has no value. SESTDY is the study day of SESTDTC counted from the subject's reference start date in DM, and has no value when either date has no value. SEENDY is the study day of SEENDTC counted the same way, and has no value when either date has no value. SEUPDES has no value on every record.

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
14 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

DOMAIN is "SE". SESTDY is the study day of SESTDTC counted from the subject's reference start date in DM, and SEENDY is the study day of SEENDTC counted the same way. SEUPDES has no value on every record.

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

Public calls generated from benchmark.yaml; results must match the expected output

R

Edit
3 lines
library(yamaa)yamaa_spec("spec.yaml")$run()$output

Python

Edit
3 lines
import yamaayamaa.spec("spec.yaml").run().output

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
154 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."""Build the SDTM SE (Subject Elements) dataset from DM and ODM extracts.One record per subject per element the subject entered. Dates and studydays follow the epoch-based rules described in the task."""import csvfrom datetime import dateINPUT_DIR = "/app/input"OUTPUT_DIR = "/app/output"def parse_date(value):    """Parse an ISO date (or the date part of an ISO datetime).    Returns a datetime.date, or None when there is no usable value.    """    if value is None:        return None    text = str(value).strip()    if not text:        return None    try:        return date.fromisoformat(text[:10])    except ValueError:        return Nonedef study_day(target, reference):    """SDTM study day of target counted from the reference start date.    No value when either date has no value. There is no day zero: the    reference date itself is day 1 and the day before it is day -1.    """    if target is None or reference is None:        return ""    diff = (target - reference).days    return diff + 1 if diff >= 0 else diffdef main():    # DM: one row per subject with the dates the SE rules draw on.    dm = {}    with open(f"{INPUT_DIR}/dm.csv", newline="", encoding="utf-8") as handle:        for row in csv.DictReader(handle):            dm[row["USUBJID"]] = {                "rficdtc": (row.get("RFICDTC") or "").strip(),                "rfstdtc": parse_date(row.get("RFSTDTC")),                "rfpendtc": (row.get("RFPENDTC") or "").strip(),            }    # ODM extract: one element record per subject per study event, plus    # the subject's first and last dosing dates (the same on every event).    elements = {}    subject_order = []    with open(f"{INPUT_DIR}/odm.csv", newline="", encoding="utf-8") as handle:        for row in csv.DictReader(handle):            subject = row["SubjectKey"]            if subject not in elements:                elements[subject] = {}                subject_order.append(subject)            record = elements[subject].setdefault(                row["StudyEventOID"],                {                    "studyid": row["StudyOID"],                    "usubjid": subject,                    "etcd": row["StudyEventOID"],                    "element": "",                    "taetord": "",                    "epoch": "",                    "firstdtc": "",                    "lastdtc": "",                },            )            item = row["ItemOID"]            value = (row.get("Value") or "").strip()            if item == "IT.TE.ELEMENT":                record["element"] = value            elif item == "IT.TA.TAETORD":                record["taetord"] = value            elif item == "IT.TA.EPOCH":                record["epoch"] = value            elif item == "IT.EX.FIRSTDTC":                record["firstdtc"] = value            elif item == "IT.EX.LASTDTC":                record["lastdtc"] = value    rows = []    for subject in subject_order:        subject_dm = dm.get(subject, {})        reference = subject_dm.get("rfstdtc")        records = sorted(            elements[subject].values(),            key=lambda rec: (int(rec["taetord"]) if rec["taetord"] else 0),        )        for record in records:            epoch = record["epoch"]            if epoch == "SCREENING":                sestdtc = subject_dm.get("rficdtc", "")                seendtc = record["firstdtc"]            elif epoch == "TREATMENT":                sestdtc = record["firstdtc"]                seendtc = record["lastdtc"]            else:                sestdtc = record["lastdtc"]                seendtc = subject_dm.get("rfpendtc", "")            rows.append(                {                    "DOMAIN": "SE",                    "STUDYID": record["studyid"],                    "USUBJID": record["usubjid"],                    "SESEQ": record["taetord"],                    "ETCD": record["etcd"],                    "ELEMENT": record["element"],                    "TAETORD": record["taetord"],                    "EPOCH": epoch,                    "SESTDTC": sestdtc,                    "SEENDTC": seendtc,                    "SESTDY": study_day(parse_date(sestdtc), reference),                    "SEENDY": study_day(parse_date(seendtc), reference),                    "SEUPDES": "",                }            )    columns = [        "DOMAIN",        "STUDYID",        "USUBJID",        "SESEQ",        "ETCD",        "ELEMENT",        "TAETORD",        "EPOCH",        "SESTDTC",        "SEENDTC",        "SESTDY",        "SEENDY",        "SEUPDES",    ]    with open(f"{OUTPUT_DIR}/se.csv", "w", newline="", encoding="utf-8") as handle:        writer = csv.DictWriter(handle, fieldnames=columns)        writer.writeheader()        writer.writerows(rows)if __name__ == "__main__":    main()

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;