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: map a pre-specified medical history checklist into MH and carry any number of volunteered free-text conditions alongside it.

Input: the ODM extract has one item record for each checklist condition asked, with a blank value when it was not answered. An item-definition table gives each checklist item its reported term and form order. Each volunteered condition has its own repeated free-text record. Repeat numbers can be reused at another visit.

Variables:

  • MHTERM is the checklist condition from the item-definition table, or the volunteered text exactly as reported. A checklist item the table does not define stops the run.
  • MHCAT is DISEASE-SPECIFIC HISTORY for checklist records and GENERAL HISTORY for volunteered records.
  • MHPRESP is Y for checklist records and blank for volunteered records.
  • MHOCCUR is Y or N for an answered checklist condition and blank otherwise.
  • MHSTAT is NOT DONE for an unanswered checklist condition and blank otherwise.
  • MHSEQ orders checklist conditions as shown on the form, then volunteered conditions by visit and form repeat.

Note: an unanswered checklist item still has an ODM record. A question entirely absent from the extract is not assumed to have been asked. Each volunteered condition is its own MH record, even when its repeat number is reused at another visit.

Inputs

Under review
2 source files

input/mh_items.csv

Edit
3 rows
input/mh_items.csv
ItemOIDMHTERMSORTORD
IT.MH.DIABETESDIABETES MELLITUS1
IT.MH.HYPERTENSIONHYPERTENSION2
IT.MH.ASTHMAASTHMA3

input/odm.csv

Edit
10 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
YAMAA-01MDV.1YAMAA-01-0001SCREENING1IG.MH.CHECKLIST1IT.MH.DIABETESY
YAMAA-01MDV.1YAMAA-01-0001SCREENING1IG.MH.CHECKLIST1IT.MH.HYPERTENSIONN
YAMAA-01MDV.1YAMAA-01-0001SCREENING1IG.MH.CHECKLIST1IT.MH.ASTHMA—
YAMAA-01MDV.1YAMAA-01-0001SCREENING1IG.MH.FREETEXT1IT.MH.MHTERMSeasonal allergies
YAMAA-01MDV.1YAMAA-01-0002SCREENING1IG.MH.CHECKLIST1IT.MH.DIABETESN
YAMAA-01MDV.1YAMAA-01-0002SCREENING1IG.MH.CHECKLIST1IT.MH.HYPERTENSIONY
YAMAA-01MDV.1YAMAA-01-0002SCREENING1IG.MH.CHECKLIST1IT.MH.ASTHMAN
YAMAA-01MDV.1YAMAA-01-0001SCREENING1IG.MH.FREETEXT2IT.MH.MHTERMMigraine
YAMAA-01MDV.1YAMAA-01-0001BASELINE1IG.MH.FREETEXT1IT.MH.MHTERMBack pain
YAMAA-01MDV.1YAMAA-01-0002SCREENING1IG.MH.FREETEXT1IT.MH.MHTERMAppendectomy

Expected output

Under review
10 expected rows

expected/mh.csv

Edit
10 rows
expected/mh.csv
DOMAINSTUDYIDUSUBJIDMHSEQMHTERMMHCATMHPRESPMHOCCURMHSTAT
MHYAMAA-011DIABETES MELLITUSDISEASE-SPECIFIC HISTORYYY—
MHYAMAA-012HYPERTENSIONDISEASE-SPECIFIC HISTORYYN—
MHYAMAA-013ASTHMADISEASE-SPECIFIC HISTORYY—NOT DONE
MHYAMAA-014Seasonal allergiesGENERAL HISTORY———
MHYAMAA-015MigraineGENERAL HISTORY———
MHYAMAA-016Back painGENERAL HISTORY———
MHYAMAA-011DIABETES MELLITUSDISEASE-SPECIFIC HISTORYYN—
MHYAMAA-012HYPERTENSIONDISEASE-SPECIFIC HISTORYYY—
MHYAMAA-013ASTHMADISEASE-SPECIFIC HISTORYYN—
MHYAMAA-014AppendectomyGENERAL HISTORY———
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 "MH".SDTM1
STUDYIDStudy IdentifierCharCollectedCopy value from ODM.StudyOID.SDTM2
USUBJIDUnique Subject IdentifierCharCollectedCopy value from ODM.SubjectKey.SDTM3
MHTERMReported Term for the Medical HistoryCharDerivedSet per constructed row; see the Row construction sheet.SDTM4
MHCATCategory for Medical HistoryCharDerivedSet per constructed row; see the Row construction sheet.SDTM5
MHPRESPPre-Specified Medical History IndicatorCharYDerivedSet per constructed row; see the Row construction sheet.SDTM6
MHOCCURMedical History OccurrenceCharY, NDerivedSet per constructed row; see the Row construction sheet.SDTM7
MHSTATCompletion StatusCharNOT DONEDerivedSet per constructed row; see the Row construction sheet.SDTM8
MHSEQSequence NumberNumDerivedRow number within each group of STUDYID, USUBJID, ordered by SORTORD then EVENTORD then EVENTREPEAT then REPEATKEY.SDTM9
TemplateInclude whenMHTERMMHCATMHPRESPMHOCCURMHSTATSORTORDEVENTORD
checklistODM.StudyEventOID = 'SCREENING' AND ODM.ItemGroupOID = 'IG.MH.CHECKLIST'If ODM.ItemGroupOID = 'IG.MH.CHECKLIST' then "MH_MAP.MHTERM"; otherwise "None"Constant "DISEASE-SPECIFIC HISTORY"Constant "Y"If ODM.Value = 'Y' then "Y"; If ODM.Value = 'N' then "N"; otherwise "None"If ODM.Value IS NULL then "NOT DONE"; otherwise "None"If ODM.ItemGroupOID = 'IG.MH.CHECKLIST' then "MH_MAP.SORTORD"; otherwise "None"If ODM.StudyEventOID IN ('SCREENING', 'BASELINE') then recode ODM.StudyEventOID: "SCREENING" → "1", "BASELINE" → "2"; missing or unlisted values are errors; otherwise "99"
volunteeredODM.ItemGroupOID = 'IG.MH.FREETEXT' AND ODM.ItemOID = 'IT.MH.MHTERM' AND ODM.Value IS NOT NULLCopy ODM.ValueConstant "GENERAL HISTORY"Constant "None"Constant "None"Constant "None"Constant "4"If ODM.ItemGroupOID = 'IG.MH.FREETEXT' AND ODM.ItemOID = 'IT.MH.MHTERM' AND ODM.Value IS NOT NULL then recode ODM.StudyEventOID: "SCREENING" → "1", "BASELINE" → "2"; missing or unlisted values are errors; otherwise "99"
VariablePermitted values
MHPRESPY
MHOCCURY, N
MHSTATNOT DONE
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-mh-prespecified-conditions/full.md

Edit
26 lines

Following CDISC SDTM standards, use the provided ODM and MH_ITEMS datasets to create an MH dataset with one record per checklist condition and per volunteered free-text condition.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, MHSEQ, MHTERM, MHCAT, MHPRESP, MHOCCUR, MHSTAT

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

MHTERM is the checklist condition from the item-definition table, or the volunteered text exactly as reported. MHCAT is DISEASE-SPECIFIC HISTORY for checklist records and GENERAL HISTORY for volunteered records. MHPRESP is Y for checklist records and has no value for volunteered records. MHOCCUR is Y or N for an answered checklist condition and has no value otherwise. MHSTAT is NOT DONE for an unanswered checklist condition and has no value otherwise. MHSEQ numbers each subject's records from 1: checklist conditions in the table's SORTORD order, then volunteered conditions by visit (SCREENING before BASELINE) and form repeat.

An unanswered checklist item still has an extract record. A question entirely absent from the extract is not assumed to have been asked. Each volunteered condition is its own MH record, even when its repeat number is reused at another visit.

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

prompts/sdtm-mh-prespecified-conditions/conventions.md

Edit
13 lines

Following CDISC SDTM standards, use the provided ODM and MH_ITEMS datasets to create an MH dataset with one record per checklist condition and per volunteered free-text condition.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, MHSEQ, MHTERM, MHCAT, MHPRESP, MHOCCUR, MHSTAT

MHCAT is DISEASE-SPECIFIC HISTORY for checklist records and GENERAL HISTORY for volunteered records. MHPRESP is Y or has no value. MHOCCUR is Y or N, or has no value. MHSTAT is NOT DONE or has no value.

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

prompts/sdtm-mh-prespecified-conditions/brief.md

Edit
9 lines

Following CDISC SDTM standards, use the provided ODM and MH_ITEMS datasets to create an MH dataset with one record per checklist condition and per volunteered free-text condition.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, MHSEQ, MHTERM, MHCAT, MHPRESP, MHOCCUR, MHSTAT

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

yamaa code

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

run.R

Edit
4 lines
library(yamaa)mh <- yamaa_domain("spec.yaml")$outputmh

run.py

Edit
4 lines
import yamaamh = yamaa.yamaa_domain("spec.yaml").outputmh

Solution without yamaa

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

solutions/sdtm-mh-prespecified-conditions/result.R

Edit
76 lines
# Reference solution for the yamaa benchmark sdtm-mh-prespecified-conditions (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)items <- read_csv(  "/app/input/mh_items.csv",  col_types = cols(SORTORD = col_integer(), .default = col_character()))odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character()))checklist_oids <- intersect(items$ItemOID, unique(odm$ItemOID))checklist <- odm |>  filter(ItemOID %in% checklist_oids) |>  group_by(StudyOID, SubjectKey, ItemOID) |>  slice_tail(n = 1) |>  ungroup() |>  left_join(items |> select(ItemOID, MHTERM, SORTORD), by = "ItemOID") |>  mutate(    answered = Value %in% c("Y", "N"),    MHTERM = MHTERM,    MHCAT = "DISEASE-SPECIFIC HISTORY",    MHPRESP = "Y",    MHOCCUR = if_else(answered, Value, NA_character_),    MHSTAT = if_else(answered, NA_character_, "NOT DONE"),    visit_rank = 0L,    repeat_no = 0L  ) |>  select(    STUDYID = StudyOID, USUBJID = SubjectKey, ItemOID, MHTERM, MHCAT,    MHPRESP, MHOCCUR, MHSTAT, SORTORD, visit_rank, repeat_no  )volunteered <- odm |>  filter(!ItemOID %in% checklist_oids, !is.na(Value) & Value != "") |>  mutate(    MHTERM = Value,    MHCAT = "GENERAL HISTORY",    MHPRESP = NA_character_,    MHOCCUR = NA_character_,    MHSTAT = NA_character_,    SORTORD = NA_integer_,    visit_rank = case_when(      StudyEventOID == "SCREENING" ~ 0L,      StudyEventOID == "BASELINE" ~ 1L,      TRUE ~ 99L    ),    repeat_no = suppressWarnings(as.integer(ItemGroupRepeatKey))  ) |>  select(    STUDYID = StudyOID, USUBJID = SubjectKey, ItemOID, MHTERM, MHCAT,    MHPRESP, MHOCCUR, MHSTAT, SORTORD, visit_rank, repeat_no  )mh <- bind_rows(checklist, volunteered) |>  arrange(USUBJID, is.na(SORTORD), SORTORD, visit_rank, repeat_no) |>  group_by(USUBJID) |>  mutate(MHSEQ = row_number()) |>  ungroup() |>  mutate(DOMAIN = "MH") |>  arrange(USUBJID, MHSEQ) |>  select(    DOMAIN, STUDYID, USUBJID, MHSEQ, MHTERM, MHCAT, MHPRESP, MHOCCUR, MHSTAT  )dir.create("/app/output", showWarnings = FALSE)write_csv(mh, "/app/output/mh.csv", na = "")

solutions/sdtm-mh-prespecified-conditions/result.py

Edit
114 lines
# Reference solution for the yamaa benchmark sdtm-mh-prespecified-conditions (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 pldef _present(value) -> bool:    return value is not None and str(value) != ""items = pl.read_csv("/app/input/mh_items.csv", infer_schema=False)odm = pl.read_csv("/app/input/odm.csv", infer_schema=False)meta = {row["ItemOID"]: (row["MHTERM"], int(row["SORTORD"])) for row in items.to_dicts()}checklist_oids = {oid for oid in meta if (odm["ItemOID"] == oid).any()}visit_order = {"SCREENING": 0, "BASELINE": 1}records_by_subject: dict[tuple[str, str], dict] = {}for row in odm.to_dicts():    studyid, usubjid = str(row["StudyOID"]), str(row["SubjectKey"])    key = (studyid, usubjid)    records_by_subject.setdefault(key, {"checklist": {}, "volunteered": []})for row in odm.to_dicts():    key = (str(row["StudyOID"]), str(row["SubjectKey"]))    oid, value = str(row["ItemOID"]), row["Value"]    if oid in checklist_oids:        # Last collected answer wins when an item repeats, including blanks.        records_by_subject[key]["checklist"][oid] = value if _present(value) else None    elif not _present(value):        continue    else:        # Each volunteered condition is its own record, even when its        # repeat number is reused at another visit.        event = str(row["StudyEventOID"])        records_by_subject[key]["volunteered"].append(            {                "term": str(value),                "visit_rank": visit_order.get(event, 99),                "repeat": int(row["ItemGroupRepeatKey"] or 1),            }        )rows = []for (studyid, usubjid), parts in records_by_subject.items():    ordered = sorted(parts["checklist"].items(), key=lambda kv: meta[kv[0]][1])    seq = 0    for oid, answer in ordered:        seq += 1        term, _ = meta[oid]        answered = answer in ("Y", "N")        rows.append(            {                "DOMAIN": "MH",                "STUDYID": studyid,                "USUBJID": usubjid,                "MHSEQ": str(seq),                "MHTERM": term,                "MHCAT": "DISEASE-SPECIFIC HISTORY",                "MHPRESP": "Y",                "MHOCCUR": answer if answered else None,                "MHSTAT": None if answered else "NOT DONE",            }        )    volunteered = sorted(        parts["volunteered"], key=lambda v: (v["visit_rank"], v["repeat"])    )    for entry in volunteered:        seq += 1        rows.append(            {                "DOMAIN": "MH",                "STUDYID": studyid,                "USUBJID": usubjid,                "MHSEQ": str(seq),                "MHTERM": entry["term"],                "MHCAT": "GENERAL HISTORY",                "MHPRESP": None,                "MHOCCUR": None,                "MHSTAT": None,            }        )mh = (    pl.DataFrame(        rows,        schema={            "DOMAIN": pl.String,            "STUDYID": pl.String,            "USUBJID": pl.String,            "MHSEQ": pl.String,            "MHTERM": pl.String,            "MHCAT": pl.String,            "MHPRESP": pl.String,            "MHOCCUR": pl.String,            "MHSTAT": pl.String,        },    )    .with_columns(pl.col("MHSEQ").cast(pl.Int64).alias("_seq"))    .sort(["USUBJID", "_seq"])    .select(        "DOMAIN", "STUDYID", "USUBJID", "MHSEQ", "MHTERM", "MHCAT",        "MHPRESP", "MHOCCUR", "MHSTAT",    ))Path("/app/output").mkdir(exist_ok=True)mh.write_csv("/app/output/mh.csv")

solutions/sdtm-mh-prespecified-conditions/result.sas

Edit
33 lines
/* opensas reference solution for the yamaa benchmark sdtm-mh-prespecified-conditions.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data mh_items;  length ItemOID MHTERM SORTORD $1024;  infile "/app/input/mh_items.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input ItemOID : $char1024. MHTERM : $char1024. SORTORD : $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;data authored; set odm; ordinal=_n_; run;proc sql; create table checklist as select a.*,b.MHTERM,b.SORTORD from authored a inner join mh_items b on a.ItemOID=b.ItemOID; quit;proc sort data=checklist; by StudyOID SubjectKey ItemOID ordinal; run;data checklistlast; set checklist; by StudyOID SubjectKey ItemOID; if last.ItemOID; run;data checklistout; set checklistlast; length DOMAIN $2 STUDYID USUBJID MHCAT MHPRESP MHOCCUR MHSTAT $1024; DOMAIN='MH'; STUDYID=StudyOID; USUBJID=SubjectKey; MHCAT='DISEASE-SPECIFIC HISTORY'; MHPRESP='Y'; MHOCCUR=''; MHSTAT='NOT DONE'; if Value in ('Y','N') then do; MHOCCUR=Value; MHSTAT=''; end; SECTION=0; ORD=input(SORTORD,best32.); VISITORD=0; REP=0; run;data volunteered; set odm; where ItemOID='IT.MH.MHTERM' and not missing(Value); length DOMAIN $2 STUDYID USUBJID MHTERM MHCAT MHPRESP MHOCCUR MHSTAT $1024; DOMAIN='MH'; STUDYID=StudyOID; USUBJID=SubjectKey; MHTERM=Value; MHCAT='GENERAL HISTORY'; MHPRESP=''; MHOCCUR=''; MHSTAT=''; SECTION=1; ORD=0; VISITORD=0; if StudyEventOID='BASELINE' then VISITORD=1; REP=input(ItemGroupRepeatKey,best32.); run;data prepared; set checklistout volunteered; run;proc sort data=prepared; by STUDYID USUBJID SECTION ORD VISITORD REP; run;data result; set prepared; by STUDYID USUBJID; retain MHSEQ;  if first.USUBJID then MHSEQ=0;  MHSEQ+1;run;proc sql;  create table final as select DOMAIN, STUDYID, USUBJID, MHSEQ, MHTERM, MHCAT, MHPRESP, MHOCCUR, MHSTAT from result;quit;proc export data=final outfile="/app/output/mh.csv" dbms=csv replace; run;