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: give each electrocardiogram (ECG) reading collected at a planned time point one record per test, the QT interval as a number and the overall interpretation as text, with the replicate number (EGREPNUM), the planned time point (EGTPT, EGTPTNUM) and the result in standard character form (EGSTRESC) on every record.

Input: long-form Operational Data Model (ODM) rows, one row per collected item. Each planned time point has its own ECG form, and each replicate ECG taken there is one repeat of the ECG item group, numbered in the item-group repeat key. A repeat holds the ECG's date and time, its QT interval in milliseconds, and the reader's interpretation.

Variables:

  • EGSEQ numbers every record of a subject in one sequence: by time point number, then replicate number, then test code (INTP before QTAG).
  • EGTESTCD and EGTEST name the test: QTAG, "QT Interval, Aggregate", for the interval and INTP, "Interpretation", for the interpretation.
  • EGORRES is the result as collected: the interval in milliseconds, or the interpretation, NORMAL or ABNORMAL.
  • EGORRESU is msec for the interval and empty for the interpretation.
  • EGSTRESC is the result in standard form, always as text, for both tests: the collected result, since the interval was collected in standard units and the interpretation in standard terms.
  • EGSTRESN is the interval as a number; empty for the interpretation, which has no numeric result.
  • EGSTRESU is msec for the interval and empty for the interpretation.
  • EGREPNUM is the item-group repeat key: it numbers the replicate ECGs of one time point, starting again at each time point.
  • EGDTC is the date and time of the ECG, shared by both of its records; empty when the repeat holds none.
  • EGTPT is the planned time point of the form: PRE-DOSE or 2 H POST-DOSE.
  • EGTPTNUM is the planned time point number: 1 before the dose and 2 after it.

Note: a result left blank on the form gives no record, while the other result of the same ECG keeps its record.

Inputs

Under review
1 source file

input/odm.csv

Edit
36 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyFormOIDFormRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.EGDTC2026-03-02T07:50:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.QT402
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.EGDTC2026-03-02T07:52:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.QT398
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.EGDTC2026-03-02T07:54:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.QT405
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG1IT.EG.EGDTC2026-03-02T10:00:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG1IT.EG.QT418
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG1IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG2IT.EG.EGDTC2026-03-02T10:02:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG2IT.EG.QT424
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG2IT.EG.INTPABNORMAL
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG3IT.EG.EGDTC2026-03-02T10:04:00
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG3IT.EG.QT421
YAMAA-01MDV.1YAMAA-01-001SE.DAY11FO.EG_2H1IG.EG3IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.EGDTC2026-03-03T07:46:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.QT386
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG1IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.EGDTC2026-03-03T07:48:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.QT391
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG2IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.EGDTC2026-03-03T07:50:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.QT389
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_PREDOSE1IG.EG3IT.EG.INTPNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG1IT.EG.EGDTC2026-03-03T10:01:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG1IT.EG.QT437
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG1IT.EG.INTPABNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG2IT.EG.EGDTC2026-03-03T10:03:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG2IT.EG.QT—
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG2IT.EG.INTPABNORMAL
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG3IT.EG.EGDTC2026-03-03T10:05:00
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG3IT.EG.QT433
YAMAA-01MDV.1YAMAA-01-002SE.DAY11FO.EG_2H1IG.EG3IT.EG.INTPABNORMAL

Expected output

Under review
23 expected rows

expected/eg.csv

Edit
23 rows
expected/eg.csv
DOMAINSTUDYIDUSUBJIDEGSEQEGTESTCDEGTESTEGORRESEGORRESUEGSTRESCEGSTRESNEGSTRESUEGREPNUMVISITEGDTCEGTPTEGTPTNUM
EGYAMAA-011INTPInterpretationNORMAL—NORMAL——1DAY 12026-03-02T07:50:00PRE-DOSE1
EGYAMAA-012QTAGQT Interval, Aggregate402msec402402msec1DAY 12026-03-02T07:50:00PRE-DOSE1
EGYAMAA-013INTPInterpretationNORMAL—NORMAL——2DAY 12026-03-02T07:52:00PRE-DOSE1
EGYAMAA-014QTAGQT Interval, Aggregate398msec398398msec2DAY 12026-03-02T07:52:00PRE-DOSE1
EGYAMAA-015INTPInterpretationNORMAL—NORMAL——3DAY 12026-03-02T07:54:00PRE-DOSE1
EGYAMAA-016QTAGQT Interval, Aggregate405msec405405msec3DAY 12026-03-02T07:54:00PRE-DOSE1
EGYAMAA-017INTPInterpretationNORMAL—NORMAL——1DAY 12026-03-02T10:00:002 H POST-DOSE2
EGYAMAA-018QTAGQT Interval, Aggregate418msec418418msec1DAY 12026-03-02T10:00:002 H POST-DOSE2
EGYAMAA-019INTPInterpretationABNORMAL—ABNORMAL——2DAY 12026-03-02T10:02:002 H POST-DOSE2
EGYAMAA-0110QTAGQT Interval, Aggregate424msec424424msec2DAY 12026-03-02T10:02:002 H POST-DOSE2
EGYAMAA-0111INTPInterpretationNORMAL—NORMAL——3DAY 12026-03-02T10:04:002 H POST-DOSE2
EGYAMAA-0112QTAGQT Interval, Aggregate421msec421421msec3DAY 12026-03-02T10:04:002 H POST-DOSE2
EGYAMAA-011INTPInterpretationNORMAL—NORMAL——1DAY 12026-03-03T07:46:00PRE-DOSE1
EGYAMAA-012QTAGQT Interval, Aggregate386msec386386msec1DAY 12026-03-03T07:46:00PRE-DOSE1
EGYAMAA-013INTPInterpretationNORMAL—NORMAL——2DAY 12026-03-03T07:48:00PRE-DOSE1
EGYAMAA-014QTAGQT Interval, Aggregate391msec391391msec2DAY 12026-03-03T07:48:00PRE-DOSE1
EGYAMAA-015INTPInterpretationNORMAL—NORMAL——3DAY 12026-03-03T07:50:00PRE-DOSE1
EGYAMAA-016QTAGQT Interval, Aggregate389msec389389msec3DAY 12026-03-03T07:50:00PRE-DOSE1
EGYAMAA-017INTPInterpretationABNORMAL—ABNORMAL——1DAY 12026-03-03T10:01:002 H POST-DOSE2
EGYAMAA-018QTAGQT Interval, Aggregate437msec437437msec1DAY 12026-03-03T10:01:002 H POST-DOSE2
EGYAMAA-019INTPInterpretationABNORMAL—ABNORMAL——2DAY 12026-03-03T10:03:002 H POST-DOSE2
EGYAMAA-0110INTPInterpretationABNORMAL—ABNORMAL——3DAY 12026-03-03T10:05:002 H POST-DOSE2
EGYAMAA-0111QTAGQT Interval, Aggregate433msec433433msec3DAY 12026-03-03T10:05:002 H POST-DOSE2
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 "EG".SDTM1
STUDYIDStudy IdentifierCharCollectedCopy value from ODM.StudyOID.SDTM2
USUBJIDUnique Subject IdentifierCharCollectedCopy value from ODM.SubjectKey.SDTM3
VISITVisit NameCharCollectedRecode ODM.StudyEventOID: "SE.DAY1" → "DAY 1"; missing values stay missing; unlisted values are errors.SDTM4
EGTPTPlanned Time Point NameCharCollectedRecode ODM.FormOID: "FO.EG_PREDOSE" → "PRE-DOSE", "FO.EG_2H" → "2 H POST-DOSE"; missing values stay missing; unlisted values are errors.SDTM5
EGTPTNUMPlanned Time Point NumberNumCollectedRecode ODM.FormOID: "FO.EG_PREDOSE" → "1", "FO.EG_2H" → "2"; missing values stay missing; unlisted values are errors.SDTM6
EGREPNUMRepetition NumberNumCollectedCopy value from ODM.ItemGroupRepeatKey.SDTM7
EGTESTCDECG Test or Examination Short NameCharQTAG, INTPDerivedSet per constructed row; see the Row construction sheet.SDTM8
EGTESTECG Test or Examination NameCharDerivedSet per constructed row; see the Row construction sheet.SDTM9
EGORRESResult or Finding in Original UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM10
EGORRESUOriginal UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM11
EGSTRESCCharacter Result/Finding in Std FormatCharDerivedCopy value from EGORRES.SDTM12
EGSTRESNNumeric Result/Finding in Standard UnitsNumDerivedSet per constructed row; see the Row construction sheet.SDTM13
EGSTRESUStandard UnitsCharDerivedCopy value from EGORRESU.SDTM14
EGDTCDate/Time of ECGCharISO 8601DerivedSet per constructed row; see the Row construction sheet.SDTM15
EGSEQSequence NumberNumDerivedRow number within each group of STUDYID, USUBJID, ordered by EGTPTNUM then EGREPNUM then EGTESTCD.SDTM16
TemplateInclude whenEGTESTCDEGTESTEGORRESEGORRESUEGSTRESNEGDTC
qt-intervalEGORRES IS NOT NULLConstant "QTAG"Constant "QT Interval, Aggregate"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.EG.QT'Constant "msec"Copy EGORRESAggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.EG.EGDTC'
interpretationEGORRES IS NOT NULLConstant "INTP"Constant "Interpretation"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.EG.INTP'Constant "None"Constant "None"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.EG.EGDTC'
VariablePermitted values
EGTESTCDQTAG, INTP
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-eg-ecg-findings/full.md

Edit
31 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an EG dataset with one record per test of each replicate ECG taken at a planned time point.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EGSEQ, EGTESTCD, EGTEST, EGORRES, EGORRESU, EGSTRESC, EGSTRESN, EGSTRESU, EGREPNUM, VISIT, EGDTC, EGTPT, EGTPTNUM

DOMAIN is "EG". STUDYID is the ODM study OID and USUBJID is the ODM subject key, each as given. VISIT is "DAY 1", the visit the ECGs were taken at. Each planned time point has its own ECG form: EGTPT is "PRE-DOSE" with EGTPTNUM 1 on the pre-dose form, and "2 H POST-DOSE" with EGTPTNUM 2 on the two-hour post-dose form. Each replicate ECG taken at a time point is one repeat of the ECG item group, and EGREPNUM is that item-group repeat key: it numbers the replicate ECGs of one time point, starting again at each time point. Each ECG gives one record for its QT interval, with EGTESTCD "QTAG" and EGTEST "QT Interval, Aggregate", and one for its overall interpretation, with EGTESTCD "INTP" and EGTEST "Interpretation". A result left blank on the form gives no record, while the other result of the same ECG keeps its record. EGORRES is the result as collected: the interval in milliseconds, or the interpretation. EGSTRESC is the collected result as text for both tests, since the interval was collected in standard units and the interpretation in standard terms. EGSTRESN is the interval as a number and has no value for the interpretation. EGORRESU and EGSTRESU are "msec" for the interval and have no value for the interpretation. EGDTC is the ECG's date and time, from the same repeat, on both of its records. EGSEQ numbers each subject's records from 1 by time point number, then replicate number, then test code (INTP before QTAG).

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

prompts/sdtm-eg-ecg-findings/conventions.md

Edit
15 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an EG dataset with one record per test of each replicate ECG taken at a planned time point.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EGSEQ, EGTESTCD, EGTEST, EGORRES, EGORRESU, EGSTRESC, EGSTRESN, EGSTRESU, EGREPNUM, VISIT, EGDTC, EGTPT, EGTPTNUM

DOMAIN is "EG". VISIT is "DAY 1". EGTPT is "PRE-DOSE" with EGTPTNUM 1, or "2 H POST-DOSE" with EGTPTNUM 2. EGTESTCD is "QTAG" with EGTEST "QT Interval, Aggregate", or "INTP" with EGTEST "Interpretation". EGORRESU and EGSTRESU are "msec" or have no value.

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

prompts/sdtm-eg-ecg-findings/brief.md

Edit
10 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an EG dataset with one record per test of each replicate ECG taken at a planned time point.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EGSEQ, EGTESTCD, EGTEST, EGORRES, EGORRESU, EGSTRESC, EGSTRESN, EGSTRESU, EGREPNUM, VISIT, EGDTC, EGTPT, EGTPTNUM

Read the source datasets from /app/input and save the completed dataset as /app/output/eg.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-eg-ecg-findings/result.R

Edit
67 lines
# Reference solution for the yamaa benchmark sdtm-eg-ecg-findings (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)odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character()),  na = "")# One replicate ECG is one repeat of the ECG item group on a time point's# form; its items become columns.ecgs <- odm |>  filter(FormOID %in% c("FO.EG_PREDOSE", "FO.EG_2H")) |>  select(StudyOID, SubjectKey, StudyEventOID, FormOID, ItemGroupRepeatKey,         ItemOID, Value) |>  pivot_wider(names_from = ItemOID, values_from = Value)# One record per test; a blank result gives no record.qt <- ecgs |>  transmute(StudyOID, SubjectKey, StudyEventOID, FormOID, ItemGroupRepeatKey,            EGDTC = IT.EG.EGDTC,            EGTESTCD = "QTAG", EGTEST = "QT Interval, Aggregate",            EGORRES = IT.EG.QT, EGORRESU = "msec")interpretation <- ecgs |>  transmute(StudyOID, SubjectKey, StudyEventOID, FormOID, ItemGroupRepeatKey,            EGDTC = IT.EG.EGDTC,            EGTESTCD = "INTP", EGTEST = "Interpretation",            EGORRES = IT.EG.INTP, EGORRESU = NA_character_)eg <- bind_rows(qt, interpretation) |>  filter(!is.na(EGORRES), trimws(EGORRES) != "") |>  mutate(    DOMAIN = "EG",    STUDYID = StudyOID,    USUBJID = SubjectKey,    EGSTRESC = EGORRES,    EGSTRESN = if_else(EGTESTCD == "QTAG", suppressWarnings(as.numeric(EGORRES)),                       NA_real_),    EGSTRESU = EGORRESU,    EGREPNUM = as.integer(ItemGroupRepeatKey),    VISIT = case_when(StudyEventOID == "SE.DAY1" ~ "DAY 1"),    EGTPT = case_when(      FormOID == "FO.EG_PREDOSE" ~ "PRE-DOSE",      FormOID == "FO.EG_2H" ~ "2 H POST-DOSE"    ),    EGTPTNUM = case_when(      FormOID == "FO.EG_PREDOSE" ~ 1L,      FormOID == "FO.EG_2H" ~ 2L    )  ) |>  arrange(STUDYID, USUBJID, EGTPTNUM, EGREPNUM, EGTESTCD) |>  group_by(STUDYID, USUBJID) |>  mutate(EGSEQ = row_number()) |>  ungroup() |>  select(DOMAIN, STUDYID, USUBJID, EGSEQ, EGTESTCD, EGTEST, EGORRES, EGORRESU,         EGSTRESC, EGSTRESN, EGSTRESU, EGREPNUM, VISIT, EGDTC, EGTPT,         EGTPTNUM)dir.create("/app/output", showWarnings = FALSE)write_csv(eg, "/app/output/eg.csv", na = "")

solutions/sdtm-eg-ecg-findings/result.py

Edit
97 lines
# Reference solution for the yamaa benchmark sdtm-eg-ecg-findings (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.import csvODM_CSV = "/app/input/odm.csv"OUTPUT_CSV = "/app/output/eg.csv"VISITS = {"SE.DAY1": "DAY 1"}TIME_POINTS = {    "FO.EG_PREDOSE": ("PRE-DOSE", 1),    "FO.EG_2H": ("2 H POST-DOSE", 2),}# Item OID: test code, test name, unit (None for a character result).TESTS = {    "IT.EG.QT": ("QTAG", "QT Interval, Aggregate", "msec"),    "IT.EG.INTP": ("INTP", "Interpretation", None),}DATETIME_ITEM = "IT.EG.EGDTC"COLUMNS = [    "DOMAIN", "STUDYID", "USUBJID", "EGSEQ", "EGTESTCD", "EGTEST", "EGORRES",    "EGORRESU", "EGSTRESC", "EGSTRESN", "EGSTRESU", "EGREPNUM", "VISIT",    "EGDTC", "EGTPT", "EGTPTNUM",]def main():    with open(ODM_CSV, newline="") as f:        odm = list(csv.DictReader(f))    # One replicate ECG is one repeat of the ECG item group on a time point's    # form; collect its items.    ecgs = {}    for row in odm:        if row["FormOID"] not in TIME_POINTS:            continue        key = (            row["StudyOID"],            row["SubjectKey"],            row["StudyEventOID"],            row["FormOID"],            int(row["ItemGroupRepeatKey"]),        )        ecgs.setdefault(key, {})[row["ItemOID"]] = row["Value"].strip()    records = []    for (study, subject, event, form, repeat), items in ecgs.items():        tpt, tptnum = TIME_POINTS[form]        for item, (testcd, test, unit) in TESTS.items():            result = items.get(item, "")            if result == "":                continue  # a blank result gives no record            records.append(                {                    "DOMAIN": "EG",                    "STUDYID": study,                    "USUBJID": subject,                    "EGTESTCD": testcd,                    "EGTEST": test,                    "EGORRES": result,                    "EGORRESU": unit or "",                    "EGSTRESC": result,                    "EGSTRESN": result if unit else "",                    "EGSTRESU": unit or "",                    "EGREPNUM": repeat,                    "VISIT": VISITS[event],                    "EGDTC": items.get(DATETIME_ITEM, ""),                    "EGTPT": tpt,                    "EGTPTNUM": tptnum,                }            )    records.sort(        key=lambda r: (            r["STUDYID"], r["USUBJID"], r["EGTPTNUM"], r["EGREPNUM"],            r["EGTESTCD"],        )    )    sequence = {}    for record in records:        subject = (record["STUDYID"], record["USUBJID"])        sequence[subject] = sequence.get(subject, 0) + 1        record["EGSEQ"] = sequence[subject]    with open(OUTPUT_CSV, "w", newline="") as f:        writer = csv.DictWriter(f, fieldnames=COLUMNS)        writer.writeheader()        writer.writerows(records)if __name__ == "__main__":    main()

solutions/sdtm-eg-ecg-findings/result.sas

Edit
60 lines
/* opensas reference solution for the yamaa benchmark sdtm-eg-ecg-findings.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data odm;  length StudyOID MetaDataVersionOID SubjectKey StudyEventOID StudyEventRepeatKey FormOID FormRepeatKey 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. FormOID : $char1024. FormRepeatKey : $char1024. ItemGroupOID : $char1024. ItemGroupRepeatKey : $char1024. ItemOID : $char1024. Value : $char1024.;run;/* One replicate ECG is one repeat of the ECG item group on a time point's   form: collect its date and time, QT interval and interpretation. */proc sort data=odm out=ordered; by StudyOID SubjectKey StudyEventOID FormOID ItemGroupRepeatKey; run;data wide;  length EGDTC QT INTP $1024;  retain EGDTC QT INTP;  set ordered;  where FormOID in ('FO.EG_PREDOSE','FO.EG_2H');  by StudyOID SubjectKey StudyEventOID FormOID ItemGroupRepeatKey;  if first.ItemGroupRepeatKey then do;    EGDTC=''; QT=''; INTP='';  end;  if ItemOID='IT.EG.EGDTC' then EGDTC=Value;  if ItemOID='IT.EG.QT' then QT=Value;  if ItemOID='IT.EG.INTP' then INTP=Value;  if last.ItemGroupRepeatKey then output;run;/* One record per test; a blank result gives no record. */data prepared; set wide;  length DOMAIN $2 STUDYID USUBJID EGTESTCD EGTEST EGORRES EGORRESU EGSTRESC EGSTRESU VISIT EGTPT $1024;  DOMAIN='EG'; STUDYID=StudyOID; USUBJID=SubjectKey;  VISIT=''; if StudyEventOID='SE.DAY1' then VISIT='DAY 1';  EGTPT=''; EGTPTNUM=.;  if FormOID='FO.EG_PREDOSE' then do; EGTPT='PRE-DOSE'; EGTPTNUM=1; end;  if FormOID='FO.EG_2H' then do; EGTPT='2 H POST-DOSE'; EGTPTNUM=2; end;  EGREPNUM=input(ItemGroupRepeatKey,best32.);  if not missing(QT) then do;    EGTESTCD='QTAG'; EGTEST='QT Interval, Aggregate';    EGORRES=strip(QT); EGORRESU='msec'; EGSTRESC=EGORRES;    EGSTRESN=input(EGORRES,best32.); EGSTRESU='msec';    output;  end;  if not missing(INTP) then do;    EGTESTCD='INTP'; EGTEST='Interpretation';    EGORRES=strip(INTP); EGORRESU=''; EGSTRESC=EGORRES;    EGSTRESN=.; EGSTRESU='';    output;  end;run;proc sort data=prepared; by STUDYID USUBJID EGTPTNUM EGREPNUM EGTESTCD; run;data result; set prepared; by STUDYID USUBJID; retain EGSEQ;  if first.USUBJID then EGSEQ=0;  EGSEQ+1;run;proc sql;  create table final as select DOMAIN, STUDYID, USUBJID, EGSEQ, EGTESTCD, EGTEST, EGORRES, EGORRESU, EGSTRESC, EGSTRESN, EGSTRESU, EGREPNUM, VISIT, EGDTC, EGTPT, EGTPTNUM from result;quit;proc export data=final outfile="/app/output/eg.csv" dbms=csv replace; run;