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: carry LBORRES into a standardized LBSTRESC, populate LBSTRESN for true numerics only, and state LBNRIND for results at the quantification limits.

Input: long-form Operational Data Model (ODM) data with one row per recorded item. Each laboratory result is one form occurrence carrying the reported result (IT.LB.RESULT), the collection date (IT.LB.LBDTC), and the reported unit (IT.LB.LBORRESU); the collection form names the test. A repeated form occurrence keeps a second result for the same test and visit separate.

Variables:

  • LBSEQ numbers the records in the documented collection order within each subject (protein, creatine kinase, glucose, ketones, creatinine), then by form repeat.
  • LBTESTCD is the test short name for the collection form: PROT (protein), GLUC (glucose), KETON (ketones), CREAT (creatinine), or CK (creatine kinase).
  • LBTEST is the test name for the collection form.
  • LBORRES is the result as collected: a dipstick grade such as Neg, tr, or 1 plus, or a censored value such as <0.1.
  • LBORRESU is the unit as reported on the collection form; empty when the form records no unit.
  • LBSTRESC is the standardized character result: known dipstick spellings, in any letter case, fold to NEGATIVE, TRACE, 1+, or 2+; anything else is kept as collected.
  • LBSTRESN is the standardized numeric result. It holds a number only for a true numeric such as 0.9; it stays empty for grades and for censored values.
  • LBSTRESU is the standard unit, carried from the reported unit.
  • LBNRIND is the reference range indicator: LOW for a result below the quantification limit, HIGH for one above it, empty otherwise.
  • LBDTC is the specimen collection date from the same form occurrence.

Note: a grade spelling outside the known spellings is kept as collected rather than dropped, so a new spelling shows up in the output instead of silently disappearing. No demographics input is needed: the subject identifier comes from the ODM subject key.

Inputs

Under review
1 source file

input/odm.csv

Edit
51 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyFormOIDFormRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
STUDY01MDV.1STUDY01-101V11FO.LB_PROT1IG.LB1IT.LB.RESULTNEGATIVE
STUDY01MDV.1STUDY01-101V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-01
STUDY01MDV.1STUDY01-101V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-101V11FO.LB_GLUC1IG.LB1IT.LB.RESULTNeg
STUDY01MDV.1STUDY01-101V11FO.LB_GLUC1IG.LB1IT.LB.LBDTC2026-08-01
STUDY01MDV.1STUDY01-101V11FO.LB_GLUC1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-101V11FO.LB_KETON1IG.LB1IT.LB.RESULTTRACE
STUDY01MDV.1STUDY01-101V11FO.LB_KETON1IG.LB1IT.LB.LBDTC2026-08-01
STUDY01MDV.1STUDY01-101V11FO.LB_KETON1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-102V11FO.LB_PROT1IG.LB1IT.LB.RESULTneg
STUDY01MDV.1STUDY01-102V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-03
STUDY01MDV.1STUDY01-102V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-102V11FO.LB_GLUC1IG.LB1IT.LB.RESULTtr
STUDY01MDV.1STUDY01-102V11FO.LB_GLUC1IG.LB1IT.LB.LBDTC2026-08-03
STUDY01MDV.1STUDY01-102V11FO.LB_GLUC1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-102V11FO.LB_KETON1IG.LB1IT.LB.RESULTTrace
STUDY01MDV.1STUDY01-102V11FO.LB_KETON1IG.LB1IT.LB.LBDTC2026-08-03
STUDY01MDV.1STUDY01-102V11FO.LB_KETON1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-103V11FO.LB_PROT1IG.LB1IT.LB.RESULT1+
STUDY01MDV.1STUDY01-103V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-05
STUDY01MDV.1STUDY01-103V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-103V11FO.LB_GLUC1IG.LB1IT.LB.RESULT+1
STUDY01MDV.1STUDY01-103V11FO.LB_GLUC1IG.LB1IT.LB.LBDTC2026-08-05
STUDY01MDV.1STUDY01-103V11FO.LB_GLUC1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-103V11FO.LB_KETON1IG.LB1IT.LB.RESULT1 plus
STUDY01MDV.1STUDY01-103V11FO.LB_KETON1IG.LB1IT.LB.LBDTC2026-08-05
STUDY01MDV.1STUDY01-103V11FO.LB_KETON1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-104V11FO.LB_PROT1IG.LB1IT.LB.RESULT2+
STUDY01MDV.1STUDY01-104V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-07
STUDY01MDV.1STUDY01-104V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT1IG.LB1IT.LB.RESULT<0.1
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT1IG.LB1IT.LB.LBDTC2026-08-07
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT1IG.LB1IT.LB.LBORRESUmg/dL
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT2IG.LB1IT.LB.RESULT0.9
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT2IG.LB1IT.LB.LBDTC2026-08-07
STUDY01MDV.1STUDY01-104V11FO.LB_CREAT2IG.LB1IT.LB.LBORRESUmg/dL
STUDY01MDV.1STUDY01-105V11FO.LB_CK1IG.LB1IT.LB.RESULT>500
STUDY01MDV.1STUDY01-105V11FO.LB_CK1IG.LB1IT.LB.LBDTC2026-08-09
STUDY01MDV.1STUDY01-105V11FO.LB_CK1IG.LB1IT.LB.LBORRESUU/L
STUDY01MDV.1STUDY01-105V11FO.LB_GLUC1IG.LB1IT.LB.RESULT+2
STUDY01MDV.1STUDY01-105V11FO.LB_GLUC1IG.LB1IT.LB.LBDTC2026-08-09
STUDY01MDV.1STUDY01-105V11FO.LB_GLUC1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-106V11FO.LB_PROT1IG.LB1IT.LB.RESULTnEg
STUDY01MDV.1STUDY01-106V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-11
STUDY01MDV.1STUDY01-106V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-106V11FO.LB_GLUC1IG.LB1IT.LB.RESULT1 Plus
STUDY01MDV.1STUDY01-106V11FO.LB_GLUC1IG.LB1IT.LB.LBDTC2026-08-11
STUDY01MDV.1STUDY01-106V11FO.LB_GLUC1IG.LB1IT.LB.LBORRESU—
STUDY01MDV.1STUDY01-107V11FO.LB_PROT1IG.LB1IT.LB.RESULTPOSITIVE
STUDY01MDV.1STUDY01-107V11FO.LB_PROT1IG.LB1IT.LB.LBDTC2026-08-12
STUDY01MDV.1STUDY01-107V11FO.LB_PROT1IG.LB1IT.LB.LBORRESU—

Expected output

Under review
17 expected rows

expected/lb.csv

Edit
17 rows
expected/lb.csv
DOMAINSTUDYIDUSUBJIDLBSEQLBTESTCDLBTESTLBORRESLBORRESULBSTRESCLBSTRESNLBSTRESULBNRINDLBDTC
LBSTUDY011PROTProteinNEGATIVE—NEGATIVE———2026-08-01
LBSTUDY012GLUCGlucoseNeg—NEGATIVE———2026-08-01
LBSTUDY013KETONKetonesTRACE—TRACE———2026-08-01
LBSTUDY011PROTProteinneg—NEGATIVE———2026-08-03
LBSTUDY012GLUCGlucosetr—TRACE———2026-08-03
LBSTUDY013KETONKetonesTrace—TRACE———2026-08-03
LBSTUDY011PROTProtein1+—1+———2026-08-05
LBSTUDY012GLUCGlucose+1—1+———2026-08-05
LBSTUDY013KETONKetones1 plus—1+———2026-08-05
LBSTUDY011PROTProtein2+—2+———2026-08-07
LBSTUDY012CREATCreatinine<0.1mg/dL<0.1—mg/dLLOW2026-08-07
LBSTUDY013CREATCreatinine0.9mg/dL0.90.9mg/dL—2026-08-07
LBSTUDY011CKCreatine Kinase>500U/L>500—U/LHIGH2026-08-09
LBSTUDY012GLUCGlucose+2—2+———2026-08-09
LBSTUDY011PROTProteinnEg—NEGATIVE———2026-08-11
LBSTUDY012GLUCGlucose1 Plus—1+———2026-08-11
LBSTUDY011PROTProteinPOSITIVE—POSITIVE———2026-08-12
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 "LB".SDTM1
STUDYIDStudy IdentifierCharCollectedCopy value from ODM.StudyOID.SDTM2
USUBJIDUnique Subject IdentifierCharCollectedCopy value from ODM.SubjectKey.SDTM3
LBSEQSequence NumberNumDerivedRow number within each group of STUDYID, USUBJID, ordered by FORMORD then FORMRPT.SDTM4
LBTESTCDLab Test or Examination Short NameCharCollectedRecode ODM.FormOID: "FO.LB_PROT" → "PROT", "FO.LB_GLUC" → "GLUC", "FO.LB_KETON" → "KETON", "FO.LB_CREAT" → "CREAT", "FO.LB_CK" → "CK"; missing values stay missing; unlisted values are errors.SDTM5
LBTESTLab Test or Examination NameCharCollectedRecode ODM.FormOID: "FO.LB_PROT" → "Protein", "FO.LB_GLUC" → "Glucose", "FO.LB_KETON" → "Ketones", "FO.LB_CREAT" → "Creatinine", "FO.LB_CK" → "Creatine Kinase"; missing values stay missing; unlisted values are errors.SDTM6
LBORRESResult or Finding in Original UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM7
LBORRESUOriginal UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM8
LBSTRESCCharacter Result/Finding in Standard UnitsCharDerivedIf LBORRES_U IN ('NEGATIVE','NEG','TRACE','TR','1+','+1','1 PLUS','2+','+2') then recode LBORRES: "NEGATIVE" → "NEGATIVE", "NEG" → "NEGATIVE", "TRACE" → "TRACE", "TR" → "TRACE", "1+" → "1+", "+1" → "1+", "1 PLUS" → "1+", "2+" → "2+", "+2" → "2+"; missing values stay missing; unlisted values are errors; matching ignores letter case; otherwise "LBORRES".SDTM9
LBSTRESNNumeric Result/Finding in Standard UnitsNumDerivedCopy value from LBSTRESC; a value that cannot be converted → blank.SDTM10
LBSTRESUStandard UnitsCharDerivedCopy value from LBORRESU.SDTM11
LBNRINDReference Range IndicatorCharDerivedIf LBSTRESC LIKE '<%' then "LOW"; If LBSTRESC LIKE '>%' then "HIGH"; otherwise blank.SDTM12
LBDTCDate/Time of Specimen CollectionCharISO 8601DerivedSet per constructed row; see the Row construction sheet.SDTM13
TemplateInclude whenLBORRESLBORRESULBDTC
lab-resultLBORRES IS NOT NULLAggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.LB.RESULT'Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.LB.LBORRESU'Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.LB.LBDTC'
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-lb-character-results/full.md

Edit
29 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per laboratory result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, LBTEST, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBNRIND, LBDTC

DOMAIN is "LB". STUDYID is the ODM study OID and USUBJID is the ODM subject key, each as given. A repeated form occurrence gives its own record, so a second result for the same test and visit stays a separate record. LBSEQ numbers each subject's records from 1 in the collection order protein, creatine kinase, glucose, ketones, creatinine, then by form repeat. LBTESTCD is the test short name for the collection form and LBTEST its test name: "PROT" and "Protein" for protein, "CK" and "Creatine Kinase" for creatine kinase, "GLUC" and "Glucose" for glucose, "KETON" and "Ketones" for ketones, and "CREAT" and "Creatinine" for creatinine. LBSTRESC folds the known dipstick spellings, in any letter case, to the standardized results: "NEGATIVE" and "NEG" give "NEGATIVE", "TRACE" and "TR" give "TRACE", "1+", "+1", and "1 PLUS" give "1+", and "2+" and "+2" give "2+". Any other collected result is kept as collected in LBSTRESC. LBSTRESN holds the number the standardized result states when it is a plain number such as "0.9", and has no value for grades and for censored values. LBSTRESU is the reported unit, with no value when the form records no unit. LBNRIND is "LOW" for a result below the quantification limit (its standardized result begins with "<"), "HIGH" for a result above the limit (beginning with ">"), and has no value otherwise. LBDTC is the collection date from the same form occurrence.

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

prompts/sdtm-lb-character-results/conventions.md

Edit
17 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per laboratory result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, LBTEST, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBNRIND, LBDTC

DOMAIN is "LB". LBTESTCD and LBTEST are "PROT" and "Protein" for protein, "CK" and "Creatine Kinase" for creatine kinase, "GLUC" and "Glucose" for glucose, "KETON" and "Ketones" for ketones, and "CREAT" and "Creatinine" for creatinine. LBSTRESC folds the known dipstick spellings, in any letter case, to the standardized results: "NEGATIVE" and "NEG" give "NEGATIVE", "TRACE" and "TR" give "TRACE", "1+", "+1", and "1 PLUS" give "1+", and "2+" and "+2" give "2+". LBNRIND is "LOW" or "HIGH", or has no value.

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

prompts/sdtm-lb-character-results/brief.md

Edit
9 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per laboratory result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, LBTEST, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBNRIND, LBDTC

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

Edit
106 lines
# Reference solution for the yamaa benchmark sdtm-lb-character-results (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(purrr)library(stringr)odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character())) |>  mutate(Value = na_if(Value, ""))form_order <- c(  "FO.LB_PROT" = 0L, "FO.LB_CK" = 1L, "FO.LB_GLUC" = 2L,  "FO.LB_KETON" = 3L, "FO.LB_CREAT" = 4L)form_test <- c(  "FO.LB_PROT" = "PROT", "FO.LB_CK" = "CK", "FO.LB_GLUC" = "GLUC",  "FO.LB_KETON" = "KETON", "FO.LB_CREAT" = "CREAT")test_name <- c(  PROT = "Protein", CK = "Creatine Kinase", GLUC = "Glucose",  KETON = "Ketones", CREAT = "Creatinine")standardize <- function(raw) {  if (is.na(raw)) return(NA_character_)  low <- str_to_lower(str_replace_all(str_trim(raw), " ", ""))  if (low %in% c("negative", "neg")) return("NEGATIVE")  if (low %in% c("trace", "tr")) return("TRACE")  if (low %in% c("1+", "+1", "1plus")) return("1+")  if (low %in% c("2+", "+2", "2plus")) return("2+")  raw}forms <- odm |>  distinct(StudyOID, SubjectKey, FormOID, FormRepeatKey) |>  filter(FormOID %in% names(form_order))one_form <- function(study, subj, formoid, formrep) {  sub <- odm |>    filter(      SubjectKey == subj, FormOID == formoid, FormRepeatKey == formrep    )  get <- function(oid) {    hit <- sub |> filter(ItemOID == oid)    if (nrow(hit) == 0) return(NA_character_)    hit$Value[[1]]  }  raw <- get("IT.LB.RESULT")  if (is.na(raw)) return(tibble())  code <- form_test[[formoid]]  stresc <- standardize(raw)  stresn <- suppressWarnings(as.numeric(raw))  if (!is.na(stresc) && stresc %in% c("NEGATIVE", "TRACE", "1+", "2+")) {    stresn <- NA_real_  }  if (str_starts(str_trim(raw), "<") || str_starts(str_trim(raw), ">")) {    stresn <- NA_real_  }  nrind <- if (str_starts(str_trim(raw), "<")) {    "LOW"  } else if (str_starts(str_trim(raw), ">")) {    "HIGH"  } else {    NA_character_  }  unit <- get("IT.LB.LBORRESU")  tibble(    DOMAIN = "LB",    STUDYID = study,    USUBJID = subj,    ORDER = form_order[[formoid]],    REP = as.integer(formrep),    LBTESTCD = code,    LBTEST = test_name[[code]],    LBORRES = raw,    LBORRESU = unit,    LBSTRESC = stresc,    LBSTRESN = stresn,    LBSTRESU = unit,    LBNRIND = nrind,    LBDTC = get("IT.LB.LBDTC")  )}lb <- pmap_dfr(  list(forms$StudyOID, forms$SubjectKey, forms$FormOID, forms$FormRepeatKey),  one_form) |>  arrange(STUDYID, USUBJID, ORDER, REP) |>  group_by(STUDYID, USUBJID) |>  mutate(LBSEQ = row_number()) |>  ungroup() |>  select(    DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, LBTEST, LBORRES,    LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBNRIND, LBDTC  )dir.create("/app/output", showWarnings = FALSE)write_csv(lb, "/app/output/lb.csv", na = "")

solutions/sdtm-lb-character-results/result.py

Edit
150 lines
# Reference solution for the yamaa benchmark sdtm-lb-character-results (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 LB dataset from the ODM extract in /app/input.One record per laboratory result (per form occurrence). Character(dipstick) results are folded to their standardized spellings, numericresults carry LBSTRESN, and censored results (< / >) drive LBNRIND."""import reimport polars as plINPUT = "/app/input"OUTPUT = "/app/output"# Collection order of the tests, with their short and long names.TEST_ORDER = {    "FO.LB_PROT": (1, "PROT", "Protein"),    "FO.LB_CK": (2, "CK", "Creatine Kinase"),    "FO.LB_GLUC": (3, "GLUC", "Glucose"),    "FO.LB_KETON": (4, "KETON", "Ketones"),    "FO.LB_CREAT": (5, "CREAT", "Creatinine"),}# Known dipstick spellings (matched case-insensitively) and the# standardized result each folds to.DIPSTICK_FOLDS = {    "NEGATIVE": "NEGATIVE",    "NEG": "NEGATIVE",    "TRACE": "TRACE",    "TR": "TRACE",    "1+": "1+",    "+1": "1+",    "1 PLUS": "1+",    "2+": "2+",    "+2": "2+",}PLAIN_NUMBER = re.compile(r"^[+-]?\d+(\.\d+)?$")COLUMNS = [    "DOMAIN",    "STUDYID",    "USUBJID",    "LBSEQ",    "LBTESTCD",    "LBTEST",    "LBORRES",    "LBORRESU",    "LBSTRESC",    "LBSTRESN",    "LBSTRESU",    "LBNRIND",    "LBDTC",]def standardize(collected):    """Fold known dipstick spellings; keep anything else as collected."""    return DIPSTICK_FOLDS.get(collected.strip().upper(), collected)def main():    odm = pl.read_csv(f"{INPUT}/odm.csv", infer_schema_length=0).fill_null("")    # Gather the items of each form occurrence.    occurrences = {}    subject_order = []    for row in odm.iter_rows(named=True):        key = (row["SubjectKey"], row["FormOID"], row["FormRepeatKey"])        if row["SubjectKey"] not in subject_order:            subject_order.append(row["SubjectKey"])        occ = occurrences.setdefault(            key,            {                "STUDYID": row["StudyOID"],                "USUBJID": row["SubjectKey"],                "FormOID": row["FormOID"],                "FormRepeatKey": row["FormRepeatKey"],                "LBORRES": "",                "LBORRESU": "",                "LBDTC": "",            },        )        if row["ItemOID"] == "IT.LB.RESULT":            occ["LBORRES"] = row["Value"]        elif row["ItemOID"] == "IT.LB.LBDTC":            occ["LBDTC"] = row["Value"]        elif row["ItemOID"] == "IT.LB.LBORRESU":            occ["LBORRESU"] = row["Value"]    # Order records: subject (first appearance), then collection order    # of the test, then form repeat.    records = sorted(        occurrences.values(),        key=lambda o: (            subject_order.index(o["USUBJID"]),            TEST_ORDER[o["FormOID"]][0],            int(o["FormRepeatKey"]),        ),    )    out = {name: [] for name in COLUMNS}    seq_by_subject = {}    for occ in records:        _, testcd, test = TEST_ORDER[occ["FormOID"]]        orres = occ["LBORRES"]        unit = occ["LBORRESU"]        stresc = standardize(orres)        stresn = stresc if PLAIN_NUMBER.match(stresc.strip()) else ""        if stresc.startswith("<"):            nrind = "LOW"        elif stresc.startswith(">"):            nrind = "HIGH"        else:            nrind = ""        seq_by_subject[occ["USUBJID"]] = seq_by_subject.get(occ["USUBJID"], 0) + 1        values = {            "DOMAIN": "LB",            "STUDYID": occ["STUDYID"],            "USUBJID": occ["USUBJID"],            "LBSEQ": seq_by_subject[occ["USUBJID"]],            "LBTESTCD": testcd,            "LBTEST": test,            "LBORRES": orres,            "LBORRESU": unit,            "LBSTRESC": stresc,            "LBSTRESN": stresn,            "LBSTRESU": unit,            "LBNRIND": nrind,            "LBDTC": occ["LBDTC"],        }        for name in COLUMNS:            out[name].append(values[name])    # Blank values go in as nulls so they are written as empty fields.    for name in COLUMNS:        if name != "LBSEQ":            out[name] = [v if v != "" else None for v in out[name]]    lb = pl.DataFrame(out).select(COLUMNS)    lb.write_csv(f"{OUTPUT}/lb.csv")if __name__ == "__main__":    main()

solutions/sdtm-lb-character-results/result.sas

Edit
46 lines
/* opensas reference solution for the yamaa benchmark sdtm-lb-character-results.   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;proc sort data=odm out=ordered; by StudyOID SubjectKey FormOID FormRepeatKey; run;data wide;  length LBORRES LBORRESU LBDTC $1024;  retain LBORRES LBORRESU LBDTC;  set ordered;  by StudyOID SubjectKey FormOID FormRepeatKey;  if first.FormRepeatKey then do;    LBORRES=''; LBORRESU=''; LBDTC='';  end;    if ItemOID='IT.LB.RESULT' then LBORRES=Value;  if ItemOID='IT.LB.LBORRESU' then LBORRESU=Value;  if ItemOID='IT.LB.LBDTC' then LBDTC=Value;  if last.FormRepeatKey then output;run;data prepared; set wide; if not missing(LBORRES); length DOMAIN $2 STUDYID USUBJID LBTESTCD LBTEST LBSTRESC LBSTRESU LBNRIND TOKEN $1024;DOMAIN='LB'; STUDYID=StudyOID; USUBJID=SubjectKey; LBSTRESU=LBORRESU; LBSTRESC=LBORRES; LBNRIND=''; LBSTRESN=.; TOKEN=lowcase(compress(LBORRES,' '));if TOKEN in ('negative','neg') then LBSTRESC='NEGATIVE'; if TOKEN in ('trace','tr') then LBSTRESC='TRACE'; if TOKEN in ('1+','+1','1plus') then LBSTRESC='1+'; if TOKEN in ('2+','+2','2plus') then LBSTRESC='2+';if prxmatch('/^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/',strip(LBORRES)) and not (LBSTRESC in ('NEGATIVE','TRACE','1+','2+')) then LBSTRESN=input(LBORRES,best32.);if substr(strip(LBORRES),1,1)='<' then LBNRIND='LOW'; if substr(strip(LBORRES),1,1)='>' then LBNRIND='HIGH';if FormOID='FO.LB_PROT' then do; LBTESTCD='PROT'; LBTEST='Protein'; TESTORD=1; end;else if FormOID='FO.LB_CK' then do; LBTESTCD='CK'; LBTEST='Creatine Kinase'; TESTORD=2; end;else if FormOID='FO.LB_GLUC' then do; LBTESTCD='GLUC'; LBTEST='Glucose'; TESTORD=3; end;else if FormOID='FO.LB_KETON' then do; LBTESTCD='KETON'; LBTEST='Ketones'; TESTORD=4; end;else if FormOID='FO.LB_CREAT' then do; LBTESTCD='CREAT'; LBTEST='Creatinine'; TESTORD=5; end; else delete;REP=input(FormRepeatKey,best32.); run;proc sort data=prepared; by STUDYID USUBJID TESTORD REP; run;data result; set prepared; by STUDYID USUBJID; retain LBSEQ;  if first.USUBJID then LBSEQ=0;  LBSEQ+1;run;proc sql;  create table final as select DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, LBTEST, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBNRIND, LBDTC from result;quit;proc export data=final outfile="/app/output/lb.csv" dbms=csv replace; run;