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
README.mdPublic entries: Python and R. Passing coverage requires an installed inventory.
Goal: 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 |
Human review and assessment
| 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.7 | 2026-10-20 | yamaa docs build | Read 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.6 | 2026-10-17 | yamaa docs build | Describe collected date parts written as ISO 8601 date text and times of day counted in seconds since midnight | ||
| 1.5 | 2026-10-17 | yamaa docs build | Name 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.4 | 2026-10-15 | yamaa docs build | State datetime precision codes through the hour and minute, and complete only the time parts a collected datetime lacks | ||
| 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
Edit26 linesFollowing CDISC SDTM standards, use the provided KIT_LIST and ODM datasets to create an EX dataset with one record per administration.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC
DOMAIN is "EX". STUDYID is the ODM study OID and USUBJID is the ODM subject key, each as given. Only a form whose occurrence item (IT.EC.ECOCCUR) is "Y" gives a record; a skipped dose gives none. EXTRT is the treatment as collected, except that a record whose collected form is "KITDOSE" has the treatment the kit list holds for its kit number. A record whose collected form is "TABLET" has EXDOSE as the number of tablets times the tablet strength. A record whose collected form is "INFUSION" has EXDOSE as the collected mg/kg dose times the body weight collected at the same visit. A record whose collected form is "KITDOSE" has EXDOSE as the dose the kit list holds for its kit number, and a kit whose listed dose is zero keeps that zero. A record whose collected form is "AUCDOSE" has EXDOSE as the AUC target as collected. EXDOSE is not rounded. EXDOSU is "AUC" for a record whose collected form is "AUCDOSE" and "mg" for every other record. EXSTDTC and EXENDTC are the start and end dates collected on the form. EXSEQ numbers each subject's records from 1 by start date.
Read the source datasets from /app/input and save the completed dataset as /app/output/ex.csv.
prompts/sdtm-ex-from-ec/conventions.md
Edit10 linesFollowing CDISC SDTM standards, use the provided KIT_LIST and ODM datasets to create an EX dataset with one record per administration.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, EXSEQ, EXTRT, EXDOSE, EXDOSU, EXSTDTC, EXENDTC
DOMAIN is "EX". EXDOSE is not rounded.
Read the source datasets from /app/input and save the completed dataset as /app/output/ex.csv.
prompts/sdtm-ex-from-ec/brief.md
Edit8 linesFollowing CDISC SDTM standards, use the provided KIT_LIST and ODM datasets to create an EX dataset with one record per administration.
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.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/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