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.
- 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.
- Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
- 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
README.mdGoal: build the administered exposure records from the exposure
as collected: tablet counts, a weight-based dose, a blinded kit, and
an AUC target, adding EXSEQ, EXTRT, EXDOSE, EXDOSU,
EXSTDTC, and EXENDTC.
Input: long-form Operational Data Model (ODM) data with one row
per collected item, carrying the study, subject, visit, form, and
form repeat (StudyOID, SubjectKey, StudyEventOID, FormOID,
FormRepeatKey), the item (ItemOID), and the stored value
(Value). Exposure items live on the FO.EC form
(IT.EC.ECOCCUR, IT.EC.ECTRT, IT.EC.FORM, IT.EC.TABLETS,
IT.EC.STRENGTHMG, IT.EC.DOSEMKG, IT.EC.AUCTARGET,
IT.EC.KIT, IT.EC.ECSTDTC, IT.EC.ECENDTC); body weight is the
IT.VS.WEIGHT item on the FO.VS form at the same visit. The kit
list comes from the interactive response technology (IRT) system,
not electronic data capture (EDC), so it stays a separate input
(input/kit_list.csv) mapping kit numbers to treatments and doses.
Variables:
EXSEQis the order of the administration within the subject, numbered by start date and then by the form's repeat number within the visit.EXTRTis the administered treatment: the kit list's treatment for a blinded kit, otherwise as collected.EXDOSEis the administered dose: tablets times strength, themg/kgdose times the visit's body weight, the kit list's dose for a blinded kit, and the AUC target as collected.EXDOSUismg, except the AUC target keepsAUC.
Note: only a form whose occurrence item (IT.EC.ECOCCUR) is Y
gives an exposure record, so a skipped dose leaves none. The placebo
kit keeps its zero dose.
Inputs
Under review2 source filesinput/kit_list.csv
Edit2 rows| KIT | TREATMENT | DOSE_MG |
|---|---|---|
| KIT-104 | DRUG X | 100 |
| KIT-207 | PLACEBO | 0 |
input/odm.csv
Edit55 rows| StudyOID | MetaDataVersionOID | SubjectKey | StudyEventOID | StudyEventRepeatKey | FormOID | FormRepeatKey | ItemGroupOID | ItemGroupRepeatKey | ItemOID | Value |
|---|---|---|---|---|---|---|---|---|---|---|
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECTRT | FINASTERIDE |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | TABLET |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.TABLETS | 2 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.STRENGTHMG | 5 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-01-02 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-01-02 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.ECOCCUR | N |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.ECTRT | FINASTERIDE |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.FORM | TABLET |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.TABLETS | 2 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.STRENGTHMG | 5 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-01-09 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.EC | 2 | IG.EC | 1 | IT.EC.ECENDTC | 2025-01-09 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 70 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.HEIGHT | 170 |
| STUDY01 | MDV.1 | 001 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-02 |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECTRT | TRASTUZUMAB |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | INFUSION |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.DOSEMKG | 6 |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-01-23 |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-01-23 |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 72 |
| STUDY01 | MDV.1 | 001 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-23 |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | KITDOSE |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.KIT | KIT-104 |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-02-05 |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-02-05 |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 80 |
| STUDY01 | MDV.1 | 002 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-02-05 |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECTRT | TRASTUZUMAB |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | INFUSION |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.DOSEMKG | 6 |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-02-26 |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-02-26 |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 80 |
| STUDY01 | MDV.1 | 002 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-02-26 |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | KITDOSE |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.KIT | KIT-207 |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-03-04 |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-03-04 |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 65 |
| STUDY01 | MDV.1 | 003 | V1 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-03-04 |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECOCCUR | Y |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECTRT | CARBOPLATIN |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.FORM | AUCDOSE |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.AUCTARGET | 5 |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECSTDTC | 2025-03-25 |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.EC | 1 | IG.EC | 1 | IT.EC.ECENDTC | 2025-03-25 |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.WEIGHT | 65 |
| STUDY01 | MDV.1 | 003 | V2 | 1 | FO.VS | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-03-25 |
Expected output
Under review6 expected rowsexpected/ex.csv
Edit6 rows| DOMAIN | STUDYID | USUBJID | EXSEQ | EXTRT | EXDOSE | EXDOSU | EXSTDTC | EXENDTC |
|---|---|---|---|---|---|---|---|---|
| EX | STUDY01 | 1 | FINASTERIDE | 10 | mg | 2025-01-02 | 2025-01-02 | |
| EX | STUDY01 | 2 | TRASTUZUMAB | 432 | mg | 2025-01-23 | 2025-01-23 | |
| EX | STUDY01 | 1 | DRUG X | 100 | mg | 2025-02-05 | 2025-02-05 | |
| EX | STUDY01 | 2 | TRASTUZUMAB | 480 | mg | 2025-02-26 | 2025-02-26 | |
| EX | STUDY01 | 1 | PLACEBO | 0 | mg | 2025-03-04 | 2025-03-04 | |
| EX | STUDY01 | 2 | CARBOPLATIN | 5 | AUC | 2025-03-25 | 2025-03-25 |
Comments and assessment materials
| Variable Name | Variable Label | Type | Length | Controlled Terms or Format | Origin | Core | Conversion Definition | Variable Type | Variable Order | Comments for Define |
|---|---|---|---|---|---|---|---|---|---|---|
| DOMAIN | Domain Abbreviation | Char | Assigned | Set constant value "EX". | SDTM | 1 | ||||
| STUDYID | Study Identifier | Char | Collected | Copy value from ODM.StudyOID. | SDTM | 2 | ||||
| USUBJID | Unique Subject Identifier | Char | Collected | Copy value from ODM.SubjectKey. | SDTM | 3 | ||||
| EXTRT | Name of Treatment | Char | Derived | If FORM = 'KITDOSE' then "KITMAP.TREATMENT"; otherwise "EC_ECTRT.Value". | SDTM | 4 | ||||
| EXDOSE | Dose | Num | Derived | If FORM = 'TABLET' then compute TABLETS * STRENGTHMG; If FORM = 'INFUSION' then compute DOSEMKG * WT_KG; If FORM = 'AUCDOSE' then "AUCTARGET"; otherwise "KIT_DOSE". | SDTM | 5 | ||||
| EXDOSU | Dose Units | Char | mg, AUC | Derived | If FORM = 'AUCDOSE' then "AUC"; otherwise "mg". | SDTM | 6 | |||
| EXSTDTC | Start Date/Time of Treatment | Char | ISO 8601 | Derived | Take Value from the ODM record whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID, FormOID equals ODM.FormOID and FormRepeatKey equals ODM.FormRepeatKey, where ODM.ItemOID = 'IT.EC.ECSTDTC', and a row with no match is an error. | SDTM | 7 | |||
| EXENDTC | End Date/Time of Treatment | Char | ISO 8601 | Derived | Take Value from the ODM record whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID, FormOID equals ODM.FormOID and FormRepeatKey equals ODM.FormRepeatKey, where ODM.ItemOID = 'IT.EC.ECENDTC', and a row with no match is an error. | SDTM | 8 | |||
| EXSEQ | Sequence Number | Num | Derived | Row number within each group of STUDYID, USUBJID, ordered by EXSTDTC then FRMREP. | SDTM | 9 |
| Template | Include when |
|---|---|
| administered | ODM.ItemOID = 'IT.EC.ECOCCUR' AND ODM.Value = 'Y' |
| Variable | Permitted values |
|---|---|
| EXDOSU | mg, AUC |
| Version | Date | Author | Description | Reviewer | Sign-off |
|---|---|---|---|---|---|
| 1.3 | 2026-09-29 | yamaa docs build | Show study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata | ||
| 1.2 | 2026-09-29 | yamaa docs build | Classify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived | ||
| 1.1 | 2026-09-29 | yamaa docs build | Resolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML | ||
| 1.0 | yamaa docs build | Initial generation from spec.yaml |
prompts/sdtm-ex-from-ec/full.md
Edit23 linesFollowing CDISC SDTM standards, use the provided ODM and KITLIST datasets to create an EX dataset with one record per administered exposure.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC
STUDYID is the ODM StudyOID and USUBJID is the SubjectKey, each as given.
Only a form whose occurrence is Y gives a record, so a skipped dose leaves none; the placebo kit keeps its zero dose. EXSEQ numbers the administrations within the subject by start date and then by the form repeat number within the visit. EXTRT is the kit list treatment for a blinded kit, and the collected treatment otherwise. EXDOSE is the administered dose: for tablets, the tablets taken at the collected strength; for infusion, the collected amount per kilogram at the visit body weight; for a blinded kit, the kit list dose; and for an AUC target, the collected target. EXDOSU is mg, except the AUC target keeps AUC. EXSTDTC and EXENDTC are the collected start and end dates. Body weight comes from the VS form at the same visit.
Read the source datasets from /app/input and save the completed dataset as /app/output/ex.csv.
prompts/sdtm-ex-from-ec/conventions.md
Edit9 linesFollowing CDISC SDTM standards, use the provided ODM and KITLIST datasets to create an EX dataset with one record per administered exposure.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC
Read the source datasets from /app/input and save the completed dataset as /app/output/ex.csv.
prompts/sdtm-ex-from-ec/brief.md
Edit9 linesFollowing CDISC SDTM standards, use the provided ODM and KITLIST datasets to create an EX dataset with one record per administered exposure.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC
Read the source datasets from /app/input and save the completed dataset as /app/output/ex.csv.
run.R
Edit4 lineslibrary(yamaa)ex <- yamaa_domain("spec.yaml")$outputexrun.py
Edit4 linesimport yamaaex = yamaa.yamaa_domain("spec.yaml").outputexsolutions/sdtm-ex-from-ec/result.R
Edit73 lines# Reference solution for the yamaa benchmark sdtm-ex-from-ec (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()))kit <- read_csv( "/app/input/kit_list.csv", col_types = cols(.default = col_character()))kit_trt <- setNames(kit$TREATMENT, kit$KIT)kit_dose <- setNames(suppressWarnings(as.numeric(kit$DOSE_MG)), kit$KIT)ec_forms <- odm |> filter(FormOID == "FO.EC")vs_weight <- odm |> filter(FormOID == "FO.VS", ItemOID == "IT.VS.WEIGHT") |> group_by(StudyOID, SubjectKey, StudyEventOID, StudyEventRepeatKey) |> summarise(WEIGHT = suppressWarnings(as.numeric(first(Value))), .groups = "drop")ec_wide <- ec_forms |> group_by(StudyOID, SubjectKey, StudyEventOID, StudyEventRepeatKey, FormRepeatKey) |> summarise( ECOCCUR = Value[ItemOID == "IT.EC.ECOCCUR"][1], ECTRT = Value[ItemOID == "IT.EC.ECTRT"][1], FORM = Value[ItemOID == "IT.EC.FORM"][1], TABLETS = suppressWarnings(as.numeric(Value[ItemOID == "IT.EC.TABLETS"][1])), STRENGTHMG = suppressWarnings(as.numeric(Value[ItemOID == "IT.EC.STRENGTHMG"][1])), DOSEMKG = suppressWarnings(as.numeric(Value[ItemOID == "IT.EC.DOSEMKG"][1])), KIT = Value[ItemOID == "IT.EC.KIT"][1], AUCTARGET = suppressWarnings(as.numeric(Value[ItemOID == "IT.EC.AUCTARGET"][1])), ECSTDTC = Value[ItemOID == "IT.EC.ECSTDTC"][1], ECENDTC = Value[ItemOID == "IT.EC.ECENDTC"][1], .groups = "drop" ) |> left_join(vs_weight, by = c("StudyOID", "SubjectKey", "StudyEventOID", "StudyEventRepeatKey")) |> filter(ECOCCUR == "Y")ex <- ec_wide |> rowwise() |> mutate( DOMAIN = "EX", STUDYID = StudyOID, USUBJID = SubjectKey, EXTRT = if (FORM == "KITDOSE") unname(kit_trt[KIT]) else ECTRT, EXDOSE = case_when( FORM == "TABLET" ~ TABLETS * STRENGTHMG, FORM == "INFUSION" ~ DOSEMKG * WEIGHT, FORM == "KITDOSE" ~ unname(kit_dose[KIT]), FORM == "AUCDOSE" ~ AUCTARGET, .default = NA_real_ ), EXDOSU = ifelse(FORM == "AUCDOSE", "AUC", "mg"), FORMREPEAT = suppressWarnings(as.integer(FormRepeatKey)), EXSTDTC_DATE = suppressWarnings(as.Date(na_if(ECSTDTC, ""))) ) |> ungroup() |> arrange(STUDYID, USUBJID, EXSTDTC_DATE, FORMREPEAT) |> group_by(STUDYID, USUBJID) |> mutate(EXSEQ = row_number()) |> ungroup() |> select(DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC = ECSTDTC, EXENDTC = ECENDTC)dir.create("/app/output", showWarnings = FALSE)write_csv(ex, "/app/output/ex.csv", na = "")solutions/sdtm-ex-from-ec/result.py
Edit144 lines# Reference solution for the yamaa benchmark sdtm-ex-from-ec (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 plodm = pl.read_csv("/app/input/odm.csv", infer_schema=False)kit = pl.read_csv("/app/input/kit_list.csv", infer_schema=False)kit_map = {r["KIT"]: (r["TREATMENT"], r["DOSE_MG"]) for r in kit.to_dicts()}ec_forms = odm.filter(pl.col("FormOID") == "FO.EC")vs_forms = odm.filter(pl.col("FormOID") == "FO.VS")# Body weight per visit: SubjectKey + StudyEventOID + StudyEventRepeatKey.vs_wide = ( vs_forms.filter(pl.col("ItemOID") == "IT.VS.WEIGHT") .group_by("StudyOID", "SubjectKey", "StudyEventOID", "StudyEventRepeatKey") .agg(pl.col("Value").first().alias("WEIGHT")) .with_columns(pl.col("WEIGHT").cast(pl.Float64, strict=False)))ec_wide = ( ec_forms.group_by( "StudyOID", "SubjectKey", "StudyEventOID", "StudyEventRepeatKey", "FormRepeatKey", ) .agg( pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.ECOCCUR").first().alias("ECOCCUR"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.ECTRT").first().alias("ECTRT"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.FORM").first().alias("FORM"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.TABLETS").first().alias("TABLETS"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.STRENGTHMG").first().alias("STRENGTHMG"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.DOSEMKG").first().alias("DOSEMKG"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.KIT").first().alias("KIT"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.AUCTARGET").first().alias("AUCTARGET"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.ECSTDTC").first().alias("ECSTDTC"), pl.col("Value").filter(pl.col("ItemOID") == "IT.EC.ECENDTC").first().alias("ECENDTC"), ) .join( vs_wide, on=["StudyOID", "SubjectKey", "StudyEventOID", "StudyEventRepeatKey"], how="left", ) .filter(pl.col("ECOCCUR") == "Y"))def num(v): if v is None or v == "": return None try: return float(v) except ValueError: return Nonerecords: list[dict] = []for row in ec_wide.to_dicts(): study = row["StudyOID"] subj = row["SubjectKey"] form = row["FORM"] or "" usubjid = subj if form == "KITDOSE": treatment, dose_mg = kit_map.get(row["KIT"] or "", (None, None)) trt = treatment dose = num(dose_mg) dosu = "mg" elif form == "TABLET": trt = row["ECTRT"] or None tablets = num(row["TABLETS"]) strength = num(row["STRENGTHMG"]) dose = tablets * strength if tablets is not None and strength is not None else None dosu = "mg" elif form == "INFUSION": trt = row["ECTRT"] or None perkg = num(row["DOSEMKG"]) weight = row["WEIGHT"] dose = perkg * weight if perkg is not None and weight is not None else None dosu = "mg" elif form == "AUCDOSE": trt = row["ECTRT"] or None dose = num(row["AUCTARGET"]) dosu = "AUC" else: trt = row["ECTRT"] or None dose = None dosu = "mg" records.append( { "STUDYID": study, "USUBJID": usubjid, "EXTRT": trt, "EXDOSE": dose, "EXDOSU": dosu, "EXSTDTC": row["ECSTDTC"] or None, "EXENDTC": row["ECENDTC"] or None, "FORMREPEAT": int(row["FormRepeatKey"]) if row["FormRepeatKey"] else 0, } )ex = ( pl.DataFrame( records, schema={ "STUDYID": pl.String, "USUBJID": pl.String, "EXTRT": pl.String, "EXDOSE": pl.Float64, "EXDOSU": pl.String, "EXSTDTC": pl.String, "EXENDTC": pl.String, "FORMREPEAT": pl.Int64, }, ) .with_columns( DOMAIN=pl.lit("EX"), EXSTDTC_DT=pl.col("EXSTDTC").str.to_date(strict=False), ) .sort(["STUDYID", "USUBJID", "EXSTDTC_DT", "FORMREPEAT"], nulls_last=True) .with_columns(EXSEQ=pl.col("USUBJID").cum_count().over(["STUDYID", "USUBJID"])) .with_columns(EXSEQ=pl.col("EXSEQ").cast(pl.Int64)) .select( "DOMAIN", "STUDYID", "USUBJID", "EXSEQ", "EXTRT", "EXDOSE", "EXDOSU", "EXSTDTC", "EXENDTC", ))Path("/app/output").mkdir(exist_ok=True)ex.write_csv("/app/output/ex.csv")solutions/sdtm-ex-from-ec/result.sas
Edit70 lines/* opensas reference solution for the yamaa benchmark sdtm-ex-from-ec. Reads the benchmark inputs and derives the requested output. Execute with the existing opensas runtime: opensas /app/output/result.sas. */data kit_list; length KIT TREATMENT DOSE_MG $1024; infile "/app/input/kit_list.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input KIT : $char1024. TREATMENT : $char1024. DOSE_MG : $char1024.;run;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;proc sort data=odm out=ordered; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey FormRepeatKey; run;data doses; length ECTRT ECSTDTC ECENDTC ECOCCUR FORM TABLETS STRENGTHMG DOSEMKG KIT AUCTARGET $1024; retain ECTRT ECSTDTC ECENDTC ECOCCUR FORM TABLETS STRENGTHMG DOSEMKG KIT AUCTARGET; set ordered; where FormOID='FO.EC'; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey FormRepeatKey; if first.FormRepeatKey then do; ECTRT=''; ECSTDTC=''; ECENDTC=''; ECOCCUR=''; FORM=''; TABLETS=''; STRENGTHMG=''; DOSEMKG=''; KIT=''; AUCTARGET=''; end; if ItemOID='IT.EC.ECTRT' then ECTRT=Value; if ItemOID='IT.EC.ECSTDTC' then ECSTDTC=Value; if ItemOID='IT.EC.ECENDTC' then ECENDTC=Value; if ItemOID='IT.EC.ECOCCUR' then ECOCCUR=Value; if ItemOID='IT.EC.FORM' then FORM=Value; if ItemOID='IT.EC.TABLETS' then TABLETS=Value; if ItemOID='IT.EC.STRENGTHMG' then STRENGTHMG=Value; if ItemOID='IT.EC.DOSEMKG' then DOSEMKG=Value; if ItemOID='IT.EC.KIT' then KIT=Value; if ItemOID='IT.EC.AUCTARGET' then AUCTARGET=Value; if last.FormRepeatKey then output;run;proc sort data=odm out=ordered; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey; run;data weights; length VISITWEIGHT $1024; retain VISITWEIGHT; set ordered; where FormOID='FO.VS'; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey; if first.StudyEventRepeatKey then do; VISITWEIGHT=''; end; if ItemOID='IT.VS.WEIGHT' then VISITWEIGHT=Value; if last.StudyEventRepeatKey then output;run;proc sql;create table joined as select a.*,b.VISITWEIGHT,c.TREATMENT,c.DOSE_MG from doses a left join weights b on a.StudyOID=b.StudyOID and a.SubjectKey=b.SubjectKey and a.StudyEventOID=b.StudyEventOID and a.StudyEventRepeatKey=b.StudyEventRepeatKey left join kit_list c on a.KIT=c.KIT; quit;data prepared; set joined; if ECOCCUR='Y'; length DOMAIN $2 STUDYID USUBJID EXTRT EXDOSU EXSTDTC EXENDTC $1024;DOMAIN='EX'; STUDYID=StudyOID; USUBJID=SubjectKey; EXTRT=ECTRT; EXSTDTC=ECSTDTC; EXENDTC=ECENDTC; EXDOSU='mg'; EXDOSE=.;if FORM='TABLET' then EXDOSE=input(TABLETS,best32.)*input(STRENGTHMG,best32.);if FORM='INFUSION' then EXDOSE=input(DOSEMKG,best32.)*input(VISITWEIGHT,best32.);if FORM='KITDOSE' then do; EXTRT=TREATMENT; EXDOSE=input(DOSE_MG,best32.); end;if FORM='AUCDOSE' then do; EXDOSE=input(AUCTARGET,best32.); EXDOSU='AUC'; end;FORMREP=input(FormRepeatKey,best32.); NODATE=missing(EXSTDTC); run;proc sort data=prepared; by STUDYID USUBJID NODATE EXSTDTC FORMREP; run;data result; set prepared; by STUDYID USUBJID; retain EXSEQ; if first.USUBJID then EXSEQ=0; EXSEQ+1;run;proc sql; create table final as select DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC from result;quit;proc export data=final outfile="/app/output/ex.csv" dbms=csv replace; run;
yamaa
Comments
Signed in with GitHub; saved in repository Discussions