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: pick the reference range the way a multi-lab study does -- by lab, test, sex and age band, and by the period the range was in effect -- and flag each result: LBSTNRLO, LBSTNRHI, and LBNRIND.

Input: lab results as Operational Data Model (ODM) item data -- one row per collected item, with the test code, the collecting lab, the collection date and the numeric result as IT.LB.* items of a repeating IG.LB panel, and recorded sex and birth date as IT.DM.* items -- plus a reference dictionary keyed by lab, test code, sex and age band, each entry carrying the dates it was in effect, the unit and the lower and upper limits.

Variables:

  • LBTESTCD is the test code from the IT.LB.LBTESTCD item.
  • SEX is recorded sex from the IT.DM.SEX item.
  • LBNAM is the collecting lab from the IT.LB.LBNAM item; different labs may carry different ranges for the same test.
  • LBSTRESN is the numeric result in standard units from the IT.LB.LBSTRESN item; missing when the result was not collected.
  • LBORRESU is the unit of the reference entry for the result's lab, test code and sex whose age band holds the subject's age at collection and which was in effect on the collection date; blank, like both limits, when no entry matches.
  • LBSTNRLO is the lower reference limit in standard units from that entry.
  • LBSTNRHI is the upper reference limit in standard units from that entry.
  • LBNRIND is LOW when the result is below the lower limit, HIGH when it is above the upper limit, and NORMAL otherwise; blank when the result or either reference limit is missing.

Note: when a lab revises a range, the entry in effect on the collection date wins -- a result collected on the first day of the new range is judged against the new limits -- and a subject whose age falls on an age-band boundary takes the band that starts at that age. Each output row is built from one IT.LB.LBDTC item, with the other items of its panel read alongside; LBSEQ is the panel's repeat key.

Inputs

Under review
2 source files

input/lbrange.csv

Edit
10 rows
input/lbrange.csv
LBNAMLBTESTCDSEXAGELOAGEHIEFFSTDTEFFENDTUNITNRLONRHI
Central LabALTM18992024-01-012025-06-30U/L040
Central LabALTM18992025-07-01—U/L045
Central LabALTF18992024-01-012025-06-30U/L040
Central LabALTF18992025-07-01—U/L038
Central LabCREATM18992024-01-01—mg/dL0.71.3
Local Lab AHGBF0172024-01-01—g/dL11.515.5
Local Lab AHGBF18992024-01-01—g/dL1216
Local Lab AHGBM0172024-01-01—g/dL1216.5
Local Lab AHGBM18992024-01-01—g/dL13.517.5
Local Lab APOTASSIUMF18992024-01-01—mmol/L3.55.1

input/odm.csv

Edit
48 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
PILOT7MDV.1P7-501SCREENING1IG.DM1IT.DM.SEXM
PILOT7MDV.1P7-501SCREENING1IG.DM1IT.DM.BRTHDTC1980-05-01
PILOT7MDV.1P7-501LAB1IG.LB1IT.LB.LBTESTCDALT
PILOT7MDV.1P7-501LAB1IG.LB1IT.LB.LBNAMCentral Lab
PILOT7MDV.1P7-501LAB1IG.LB1IT.LB.LBDTC2025-03-15
PILOT7MDV.1P7-501LAB1IG.LB1IT.LB.LBSTRESN15
PILOT7MDV.1P7-502SCREENING1IG.DM1IT.DM.SEXF
PILOT7MDV.1P7-502SCREENING1IG.DM1IT.DM.BRTHDTC1990-02-10
PILOT7MDV.1P7-502LAB1IG.LB1IT.LB.LBTESTCDALT
PILOT7MDV.1P7-502LAB1IG.LB1IT.LB.LBNAMCentral Lab
PILOT7MDV.1P7-502LAB1IG.LB1IT.LB.LBDTC2025-07-01
PILOT7MDV.1P7-502LAB1IG.LB1IT.LB.LBSTRESN39
PILOT7MDV.1P7-503SCREENING1IG.DM1IT.DM.SEXF
PILOT7MDV.1P7-503SCREENING1IG.DM1IT.DM.BRTHDTC2017-09-20
PILOT7MDV.1P7-503LAB1IG.LB1IT.LB.LBTESTCDHGB
PILOT7MDV.1P7-503LAB1IG.LB1IT.LB.LBNAMLocal Lab A
PILOT7MDV.1P7-503LAB1IG.LB1IT.LB.LBDTC2025-02-01
PILOT7MDV.1P7-503LAB1IG.LB1IT.LB.LBSTRESN12.5
PILOT7MDV.1P7-504SCREENING1IG.DM1IT.DM.SEXM
PILOT7MDV.1P7-504SCREENING1IG.DM1IT.DM.BRTHDTC2007-06-01
PILOT7MDV.1P7-504LAB1IG.LB1IT.LB.LBTESTCDHGB
PILOT7MDV.1P7-504LAB1IG.LB1IT.LB.LBNAMLocal Lab A
PILOT7MDV.1P7-504LAB1IG.LB1IT.LB.LBDTC2025-06-01
PILOT7MDV.1P7-504LAB1IG.LB1IT.LB.LBSTRESN12.5
PILOT7MDV.1P7-505SCREENING1IG.DM1IT.DM.SEXM
PILOT7MDV.1P7-505SCREENING1IG.DM1IT.DM.BRTHDTC1975-11-11
PILOT7MDV.1P7-505LAB1IG.LB1IT.LB.LBTESTCDCREAT
PILOT7MDV.1P7-505LAB1IG.LB1IT.LB.LBNAMCentral Lab
PILOT7MDV.1P7-505LAB1IG.LB1IT.LB.LBDTC2025-04-01
PILOT7MDV.1P7-505LAB1IG.LB1IT.LB.LBSTRESN0.9
PILOT7MDV.1P7-506SCREENING1IG.DM1IT.DM.SEXM
PILOT7MDV.1P7-506SCREENING1IG.DM1IT.DM.BRTHDTC1988-03-03
PILOT7MDV.1P7-506LAB1IG.LB1IT.LB.LBTESTCDALT
PILOT7MDV.1P7-506LAB1IG.LB1IT.LB.LBNAMCentral Lab
PILOT7MDV.1P7-506LAB1IG.LB1IT.LB.LBDTC2025-08-01
PILOT7MDV.1P7-506LAB1IG.LB1IT.LB.LBSTRESN—
PILOT7MDV.1P7-507SCREENING1IG.DM1IT.DM.SEXF
PILOT7MDV.1P7-507SCREENING1IG.DM1IT.DM.BRTHDTC1995-12-12
PILOT7MDV.1P7-507LAB1IG.LB1IT.LB.LBTESTCDPOTASSIUM
PILOT7MDV.1P7-507LAB1IG.LB1IT.LB.LBNAMLocal Lab A
PILOT7MDV.1P7-507LAB1IG.LB1IT.LB.LBDTC2025-05-05
PILOT7MDV.1P7-507LAB1IG.LB1IT.LB.LBSTRESN5.5
PILOT7MDV.1P7-508SCREENING1IG.DM1IT.DM.SEXM
PILOT7MDV.1P7-508SCREENING1IG.DM1IT.DM.BRTHDTC1980-01-01
PILOT7MDV.1P7-508LAB1IG.LB1IT.LB.LBTESTCDGLUCOSE
PILOT7MDV.1P7-508LAB1IG.LB1IT.LB.LBNAMCentral Lab
PILOT7MDV.1P7-508LAB1IG.LB1IT.LB.LBDTC2025-05-05
PILOT7MDV.1P7-508LAB1IG.LB1IT.LB.LBSTRESN5

Expected output

Under review
8 expected rows

expected/lb.csv

Edit
8 rows
expected/lb.csv
DOMAINSTUDYIDUSUBJIDLBSEQLBTESTCDSEXLBNAMLBSTRESNLBORRESULBSTNRLOLBSTNRHILBNRIND
LBPILOT71ALTMCentral Lab15U/L040NORMAL
LBPILOT71ALTFCentral Lab39U/L038HIGH
LBPILOT71HGBFLocal Lab A12.5g/dL11.515.5NORMAL
LBPILOT71HGBMLocal Lab A12.5g/dL13.517.5LOW
LBPILOT71CREATMCentral Lab0.9mg/dL0.71.3NORMAL
LBPILOT71ALTMCentral Lab—U/L045—
LBPILOT71POTASSIUMFLocal Lab A5.5mmol/L3.55.1HIGH
LBPILOT71GLUCOSEMCentral Lab5————
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 NumberNumCollectedCopy value from ODM.ItemGroupRepeatKey.SDTM4
LBTESTCDLab Test or Examination Short NameCharDerivedTake Value from the ODM record whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID, ItemGroupOID equals ODM.ItemGroupOID and ItemGroupRepeatKey equals ODM.ItemGroupRepeatKey, where ODM.ItemOID = 'IT.LB.LBTESTCD', and a row with no match is an error.SDTM5
SEXSexCharDerivedTake Value from the ODM record whose StudyOID equals ODM.StudyOID and SubjectKey equals ODM.SubjectKey, where ODM.ItemOID = 'IT.DM.SEX', and a row with no match is an error.SDTM6
LBNAMLab NameCharDerivedTake Value from the ODM record whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID, ItemGroupOID equals ODM.ItemGroupOID and ItemGroupRepeatKey equals ODM.ItemGroupRepeatKey, where ODM.ItemOID = 'IT.LB.LBNAM', and a row with no match is an error.SDTM7
LBSTRESNNumeric Result/Finding in Standard UnitsNumDerivedTake Value from the ODM record whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID, ItemGroupOID equals ODM.ItemGroupOID and ItemGroupRepeatKey equals ODM.ItemGroupRepeatKey, where ODM.ItemOID = 'IT.LB.LBSTRESN', and a row with no match is an error.SDTM8
LBORRESUOriginal UnitsCharDerivedTake UNIT from the LBRANGE record matching on LBNAM, LBTESTCD and SEX, where AGE falls within AGELO to AGEHI, where LBRANGE.EFFSTDT <= ODM.Value AND (LBRANGE.EFFENDT IS NULL OR ODM.Value <= LBRANGE.EFFENDT), and no match yields blank.SDTM9
LBSTNRLOReference Range Lower Limit-Std UnitsNumDerivedTake NRLO from the LBRANGE record matching on LBNAM, LBTESTCD and SEX, where AGE falls within AGELO to AGEHI, where LBRANGE.EFFSTDT <= ODM.Value AND (LBRANGE.EFFENDT IS NULL OR ODM.Value <= LBRANGE.EFFENDT), and no match yields blank.SDTM10
LBSTNRHIReference Range Upper Limit-Std UnitsNumDerivedTake NRHI from the LBRANGE record matching on LBNAM, LBTESTCD and SEX, where AGE falls within AGELO to AGEHI, where LBRANGE.EFFSTDT <= ODM.Value AND (LBRANGE.EFFENDT IS NULL OR ODM.Value <= LBRANGE.EFFENDT), and no match yields blank.SDTM11
LBNRINDReference Range IndicatorCharDerivedIf LBSTRESN IS NULL OR LBSTNRLO IS NULL OR LBSTNRHI IS NULL then "None"; If LBSTRESN < LBSTNRLO then "LOW"; If LBSTRESN > LBSTNRHI then "HIGH"; otherwise "NORMAL".SDTM12
TemplateInclude when
resultODM.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-lab-specific-ranges/full.md

Edit
32 lines

Following CDISC SDTM standards, use the provided LBRANGE and ODM datasets to create an LB dataset with one record per collected lab result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, SEX, LBNAM, LBSTRESN, LBORRESU, LBSTNRLO, LBSTNRHI, LBNRIND

DOMAIN is "LB". STUDYID is the ODM study OID and USUBJID is the ODM subject key, each as given. Each record is built from one IT.LB.LBDTC item of a lab panel, with the panel's other items read alongside it, and LBSEQ is the panel's repeat key. LBTESTCD is the test code from the IT.LB.LBTESTCD item. SEX is the recorded sex from the IT.DM.SEX item. LBNAM is the collecting lab from the IT.LB.LBNAM item. LBSTRESN is the numeric result in standard units from the IT.LB.LBSTRESN item, with no value when the result was not collected. The reference entry for a result is the LBRANGE entry for the result's lab, test code, and sex whose age band holds the subject's age in whole years at collection, counted from the subject's birth date to the collection date, and which was in effect on the collection date. A subject whose age falls on an age-band boundary takes the band that starts at that age. An entry is in effect on a date from its first effective date through its last effective date, and an entry with no last effective date remains in effect. When a lab revises a range, the entry in effect on the collection date wins, so a result collected on the first day of the new range is judged against the new limits. LBORRESU is the matched entry's unit, LBSTNRLO is its lower reference limit in standard units, and LBSTNRHI is its upper reference limit in standard units; all three have no value when no entry matches. LBNRIND is "LOW" when the result is below the lower limit, "HIGH" when it is above the upper limit, and "NORMAL" otherwise, and it has no value when the result or either reference limit is missing.

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

prompts/sdtm-lb-lab-specific-ranges/conventions.md

Edit
13 lines

Following CDISC SDTM standards, use the provided LBRANGE and ODM datasets to create an LB dataset with one record per collected lab result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, SEX, LBNAM, LBSTRESN, LBORRESU, LBSTNRLO, LBSTNRHI, LBNRIND

DOMAIN is "LB". The subject's age counts in whole years at collection, counted from the subject's birth date to the collection date. LBNRIND is "LOW", "HIGH", or "NORMAL", or has no value.

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

prompts/sdtm-lb-lab-specific-ranges/brief.md

Edit
9 lines

Following CDISC SDTM standards, use the provided LBRANGE and ODM datasets to create an LB dataset with one record per collected lab result.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, SEX, LBNAM, LBSTRESN, LBORRESU, LBSTNRLO, LBSTNRHI, LBNRIND

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-lab-specific-ranges/result.R

Edit
117 lines
# Reference solution for the yamaa benchmark sdtm-lb-lab-specific-ranges (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)library(purrr)library(lubridate)odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character())) |>  mutate(Value = na_if(Value, ""))lbrange <- read_csv(  "/app/input/lbrange.csv",  col_types = cols(    AGELO = col_integer(), AGEHI = col_integer(),    NRLO = col_double(), NRHI = col_double(),    .default = col_character()  ))age_years <- function(birth, collected) {  b <- ymd(birth)  c <- ymd(collected)  years <- year(c) - year(b)  before_birthday <- (month(c) < month(b)) |    (month(c) == month(b) & day(c) < day(b))  years - if_else(before_birthday, 1L, 0L)}dm <- odm |>  filter(ItemGroupOID == "IG.DM") |>  select(SubjectKey, ItemOID, Value) |>  pivot_wider(names_from = ItemOID, values_from = Value)labs <- odm |>  filter(ItemGroupOID == "IG.LB") |>  distinct(StudyOID, SubjectKey, ItemGroupRepeatKey)one_lab <- function(study, subj, igrep) {  sub <- odm |>    filter(      SubjectKey == subj, ItemGroupOID == "IG.LB",      ItemGroupRepeatKey == igrep    )  get <- function(oid) {    hit <- sub |> filter(ItemOID == oid)    if (nrow(hit) == 0) return(NA_character_)    hit$Value[[1]]  }  dsub <- dm |> filter(SubjectKey == subj)  sex <- if (nrow(dsub) == 0) NA_character_ else dsub$`IT.DM.SEX`[[1]]  birth <- if (nrow(dsub) == 0) NA_character_ else dsub$`IT.DM.BRTHDTC`[[1]]  code <- get("IT.LB.LBTESTCD")  lbnam <- get("IT.LB.LBNAM")  lbdtc <- get("IT.LB.LBDTC")  raw <- get("IT.LB.LBSTRESN")  stresn <- suppressWarnings(as.numeric(raw))  unit <- NA_character_  lo <- NA_real_  hi <- NA_real_  if (!is.na(birth) && !is.na(lbdtc) && !is.na(code) && !is.na(lbnam) && !is.na(sex)) {    age <- age_years(birth, lbdtc)    hit <- lbrange |>      filter(        LBNAM == lbnam, LBTESTCD == code, SEX == sex,        AGELO <= age, age < AGEHI,        EFFSTDT <= lbdtc,        is.na(EFFENDT) | lbdtc <= EFFENDT      ) |>      arrange(desc(EFFSTDT), desc(AGELO)) |>      slice_head(n = 1)    if (nrow(hit) == 1) {      unit <- hit$UNIT[[1]]      lo <- hit$NRLO[[1]]      hi <- hit$NRHI[[1]]    }  }  nrind <- if (is.na(stresn) || is.na(lo) || is.na(hi)) {    NA_character_  } else if (stresn < lo) {    "LOW"  } else if (stresn > hi) {    "HIGH"  } else {    "NORMAL"  }  tibble(    DOMAIN = "LB",    STUDYID = study,    USUBJID = subj,    LBSEQ = as.integer(igrep),    LBTESTCD = code,    SEX = sex,    LBNAM = lbnam,    LBSTRESN = stresn,    LBORRESU = unit,    LBSTNRLO = lo,    LBSTNRHI = hi,    LBNRIND = nrind  )}lb <- pmap_dfr(  list(labs$StudyOID, labs$SubjectKey, labs$ItemGroupRepeatKey),  one_lab) |>  arrange(STUDYID, USUBJID, LBSEQ)dir.create("/app/output", showWarnings = FALSE)write_csv(lb, "/app/output/lb.csv", na = "")

solutions/sdtm-lb-lab-specific-ranges/result.py

Edit
123 lines
# Reference solution for the yamaa benchmark sdtm-lb-lab-specific-ranges (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 datetime import datefrom pathlib import Pathimport polars as plodm = pl.read_csv("/app/input/odm.csv", infer_schema=False)lbrange = pl.read_csv("/app/input/lbrange.csv", infer_schema=False).with_columns(    pl.col("AGELO").cast(pl.Int64),    pl.col("AGEHI").cast(pl.Int64),    pl.col("NRLO").cast(pl.Float64, strict=False),    pl.col("NRHI").cast(pl.Float64, strict=False),)subjects = {}labs = {}study_of = {}for r in odm.to_dicts():    study_of[r["SubjectKey"]] = r["StudyOID"]    if r["ItemGroupOID"] == "IG.DM":        subjects.setdefault(r["SubjectKey"], {})[r["ItemOID"]] = (            r["Value"] if r["Value"] not in (None, "") else None        )    elif r["ItemGroupOID"] == "IG.LB":        key = (r["SubjectKey"], r["ItemGroupRepeatKey"])        labs.setdefault(key, {})[r["ItemOID"]] = (            r["Value"] if r["Value"] not in (None, "") else None        )def age_years(birth, collected):    b = date.fromisoformat(birth)    c = date.fromisoformat(collected)    years = c.year - b.year    if (c.month, c.day) < (b.month, b.day):        years -= 1    return yearsranges = sorted(lbrange.to_dicts(), key=lambda r: (r["EFFSTDT"], r["AGELO"]), reverse=True)rows = []for (subj, repkey), lab in labs.items():    dm = subjects.get(subj, {})    sex = dm.get("IT.DM.SEX")    birth = dm.get("IT.DM.BRTHDTC")    code = lab.get("IT.LB.LBTESTCD")    lbnam = lab.get("IT.LB.LBNAM")    lbdtc = lab.get("IT.LB.LBDTC")    raw = lab.get("IT.LB.LBSTRESN")    try:        stresn = float(raw) if raw not in (None, "") else None    except ValueError:        stresn = None    unit = lo = hi = None    if birth and lbdtc and code and lbnam and sex:        age = age_years(birth, lbdtc)        best = None        for rg in ranges:            if rg["LBNAM"] != lbnam or rg["LBTESTCD"] != code or rg["SEX"] != sex:                continue            if not (rg["AGELO"] <= age < rg["AGEHI"]):                continue            eff_start = rg["EFFSTDT"]            eff_end = rg["EFFENDT"] if rg["EFFENDT"] not in (None, "") else None            if lbdtc < eff_start:                continue            if eff_end is not None and lbdtc > eff_end:                continue            best = rg            break        if best is not None:            unit, lo, hi = best["UNIT"], best["NRLO"], best["NRHI"]    if stresn is None or lo is None or hi is None:        nrind = None    elif stresn < lo:        nrind = "LOW"    elif stresn > hi:        nrind = "HIGH"    else:        nrind = "NORMAL"    rows.append(        {            "DOMAIN": "LB",            "STUDYID": study_of[subj],            "USUBJID": subj,            "LBSEQ": int(repkey),            "LBTESTCD": code,            "SEX": sex,            "LBNAM": lbnam,            "LBSTRESN": stresn,            "LBORRESU": unit,            "LBSTNRLO": lo,            "LBSTNRHI": hi,            "LBNRIND": nrind,        }    )lb = pl.DataFrame(    rows,    schema={        "DOMAIN": pl.String,        "STUDYID": pl.String,        "USUBJID": pl.String,        "LBSEQ": pl.Int64,        "LBTESTCD": pl.String,        "SEX": pl.String,        "LBNAM": pl.String,        "LBSTRESN": pl.Float64,        "LBORRESU": pl.String,        "LBSTNRLO": pl.Float64,        "LBSTNRHI": pl.Float64,        "LBNRIND": pl.String,    },).sort(["STUDYID", "USUBJID", "LBSEQ"])Path("/app/output").mkdir(parents=True, exist_ok=True)lb.write_csv("/app/output/lb.csv")

solutions/sdtm-lb-lab-specific-ranges/result.sas

Edit
57 lines
/* opensas reference solution for the yamaa benchmark sdtm-lb-lab-specific-ranges.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data lbrange;  length LBNAM LBTESTCD SEX EFFSTDT EFFENDT UNIT $1024;  infile "/app/input/lbrange.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input LBNAM : $char1024. LBTESTCD : $char1024. SEX : $char1024. AGELO AGEHI EFFSTDT : $char1024. EFFENDT : $char1024. UNIT : $char1024. NRLO NRHI;  RANGE_ORDER=_n_;run;data odm;  length StudyOID MetaDataVersionOID SubjectKey StudyEventOID StudyEventRepeatKey ItemGroupOID ItemGroupRepeatKey ItemOID Value $1024;  infile "/app/input/odm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input StudyOID : $char1024. MetaDataVersionOID : $char1024. SubjectKey : $char1024. StudyEventOID : $char1024. StudyEventRepeatKey : $char1024. ItemGroupOID : $char1024. ItemGroupRepeatKey : $char1024. ItemOID : $char1024. Value : $char1024.;run;proc sort data=odm out=ordered; by StudyOID SubjectKey; run;data subjects;  length SEX BRTHDTC $1024;  retain SEX BRTHDTC;  set ordered;  where ItemGroupOID='IG.DM';  by StudyOID SubjectKey;  if first.SubjectKey then do;    SEX=''; BRTHDTC='';  end;    if ItemOID='IT.DM.SEX' then SEX=Value;  if ItemOID='IT.DM.BRTHDTC' then BRTHDTC=Value;  if last.SubjectKey then output;run;proc sort data=odm out=ordered; by StudyOID SubjectKey ItemGroupRepeatKey; run;data panels;  length LBDTC LBNAM LBTESTCD RESULTTEXT $1024;  retain LBDTC LBNAM LBTESTCD RESULTTEXT;  set ordered;  where ItemGroupOID='IG.LB';  by StudyOID SubjectKey ItemGroupRepeatKey;  if first.ItemGroupRepeatKey then do;    LBDTC=''; LBNAM=''; LBTESTCD=''; RESULTTEXT='';  end;    if ItemOID='IT.LB.LBDTC' then LBDTC=Value;  if ItemOID='IT.LB.LBNAM' then LBNAM=Value;  if ItemOID='IT.LB.LBTESTCD' then LBTESTCD=Value;  if ItemOID='IT.LB.LBSTRESN' then RESULTTEXT=Value;  if last.ItemGroupRepeatKey then output;run;proc sql; create table withbirth as select a.*,b.SEX,b.BRTHDTC from panels a left join subjects b on a.StudyOID=b.StudyOID and a.SubjectKey=b.SubjectKey; quit;data collected; set withbirth; AGE=.; if lengthn(BRTHDTC)=10 and lengthn(LBDTC)=10 then AGE=intck('year',input(BRTHDTC,yymmdd10.),input(LBDTC,yymmdd10.),'continuous'); LBSTRESN=input(RESULTTEXT,?? best32.); LBSEQ=input(ItemGroupRepeatKey,best32.); run;proc sql; create table joined as select a.*,b.UNIT as LBORRESU,b.NRLO as LBSTNRLO,b.NRHI as LBSTNRHI,b.EFFSTDT as RANGE_START,b.AGELO as RANGE_AGE,b.RANGE_ORDER from collected a left join lbrange b on a.LBNAM=b.LBNAM and a.LBTESTCD=b.LBTESTCD and a.SEX=b.SEX and a.AGE>=b.AGELO and a.AGE<b.AGEHI and a.LBDTC>=b.EFFSTDT and (missing(b.EFFENDT) or a.LBDTC<=b.EFFENDT); quit;proc sort data=joined; by StudyOID SubjectKey LBSEQ descending RANGE_START descending RANGE_AGE RANGE_ORDER; run;data selected; set joined; by StudyOID SubjectKey LBSEQ; if first.LBSEQ; run;data result; set selected; length DOMAIN $2 STUDYID USUBJID $1024 LBNRIND $8; DOMAIN='LB'; STUDYID=StudyOID; USUBJID=SubjectKey; LBNRIND=''; if not missing(LBSTRESN) and not missing(LBSTNRLO) and not missing(LBSTNRHI) then do; if LBSTRESN<LBSTNRLO then LBNRIND='LOW'; else if LBSTRESN>LBSTNRHI then LBNRIND='HIGH'; else LBNRIND='NORMAL'; end; run;proc sql;  create table final as select DOMAIN, STUDYID, USUBJID, LBSEQ, LBTESTCD, SEX, LBNAM, LBSTRESN, LBORRESU, LBSTNRLO, LBSTNRHI, LBNRIND from result;quit;proc export data=final outfile="/app/output/lb.csv" dbms=csv replace; run;