Review ask

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.

  1. 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.
  2. Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
  3. 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

Lifecycle: draftEditUnder review
What this benchmark means
README.md

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

Comments and assessment materials

Comments

Signed in with GitHub; saved in repository 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 or 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 or 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.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
30 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per collected 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

STUDYID is the ODM StudyOID and USUBJID is the SubjectKey, each as given.

LBSEQ numbers each subject's records in collection order (protein, creatine kinase, glucose, ketones, creatinine), then by form repeat. The test code and name come from the collection form: PROT and Protein, GLUC and Glucose, KETON and Ketones, CREAT and Creatinine, or CK and Creatine Kinase.

LBORRES is the result as collected. LBORRESU is the reported unit, and has no value when the form records no unit. LBSTRESC folds known dipstick spellings, in any letter case, to NEGATIVE, TRACE, 1+, or 2+; anything else is kept as collected. LBSTRESN holds a number only for a true numeric result, and has no value for grades and censored values. LBSTRESU repeats the reported unit. LBNRIND is LOW for a result below the quantification limit and HIGH for one above it, and has no value otherwise. LBDTC is the collection date from the same form occurrence.

A form with no reported result produces no record; a repeated form occurrence keeps a second result for the same test and visit separate. A grade spelling outside the known spellings is kept as collected.

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
15 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per collected 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

The test code and name are PROT and Protein, GLUC and Glucose, KETON and Ketones, CREAT and Creatinine, or CK and Creatine Kinase.

LBSTRESC folds known dipstick spellings to NEGATIVE, TRACE, 1+, or 2+. LBNRIND is LOW, 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 collected 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

Runs the yamaa specification; its result must match the expected output

run.R

Edit
4 lines
library(yamaa)lb <- yamaa_domain("spec.yaml")$outputlb

run.py

Edit
4 lines
import yamaalb = yamaa.yamaa_domain("spec.yaml").outputlb

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
138 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.from pathlib import Pathimport polars as plodm = pl.read_csv("/app/input/odm.csv", infer_schema=False)forms = {}study_of = {}for r in odm.to_dicts():    study_of[r["SubjectKey"]] = r["StudyOID"]    key = (r["SubjectKey"], r["FormOID"], r["FormRepeatKey"])    forms.setdefault(key, {})[r["ItemOID"]] = r["Value"]form_info = {    "FO.LB_PROT": ("PROT", "Protein", 0),    "FO.LB_CK": ("CK", "Creatine Kinase", 1),    "FO.LB_GLUC": ("GLUC", "Glucose", 2),    "FO.LB_KETON": ("KETON", "Ketones", 3),    "FO.LB_CREAT": ("CREAT", "Creatinine", 4),}def standardize(raw):    if raw is None:        return None    t = raw.strip()    low = t.lower().replace(" ", "")    if low in ("negative", "neg"):        return "NEGATIVE"    if low in ("trace", "tr"):        return "TRACE"    if low in ("1+", "+1", "1plus"):        return "1+"    if low in ("2+", "+2", "2plus"):        return "2+"    return rawdef as_number(text):    if text is None or text == "":        return None    try:        return float(text)    except ValueError:        return Nonerows = []for (subj, formoid, formrep), items in forms.items():    if formoid not in form_info:        continue    code, name, order = form_info[formoid]    raw = items.get("IT.LB.RESULT")    if raw is None or raw == "":        continue    lbdtc = items.get("IT.LB.LBDTC")    unit = items.get("IT.LB.LBORRESU")    unit = unit if unit not in (None, "") else None    stresc = standardize(raw)    # A true numeric result only: grades and censored values have no number.    stresn = as_number(raw)    if stresc in ("NEGATIVE", "TRACE", "1+", "2+"):        stresn = None    if raw.strip().startswith("<") or raw.strip().startswith(">"):        stresn = None    nrind = None    if raw.strip().startswith("<"):        nrind = "LOW"    elif raw.strip().startswith(">"):        nrind = "HIGH"    rows.append(        {            "DOMAIN": "LB",            "STUDYID": study_of[subj],            "USUBJID": subj,            "ORDER": order,            "REP": int(formrep),            "LBTESTCD": code,            "LBTEST": name,            "LBORRES": raw,            "LBORRESU": unit,            "LBSTRESC": stresc,            "LBSTRESN": stresn,            "LBSTRESU": unit,            "LBNRIND": nrind,            "LBDTC": lbdtc,        }    )lb = (    pl.DataFrame(        rows,        schema={            "DOMAIN": pl.String,            "STUDYID": pl.String,            "USUBJID": pl.String,            "ORDER": pl.Int64,            "REP": pl.Int64,            "LBTESTCD": pl.String,            "LBTEST": pl.String,            "LBORRES": pl.String,            "LBORRESU": pl.String,            "LBSTRESC": pl.String,            "LBSTRESN": pl.Float64,            "LBSTRESU": pl.String,            "LBNRIND": pl.String,            "LBDTC": pl.String,        },    )    .sort(["STUDYID", "USUBJID", "ORDER", "REP"])    .with_columns(        pl.int_range(1, pl.len() + 1).over(["STUDYID", "USUBJID"]).alias("LBSEQ")    )    .select(        "DOMAIN",        "STUDYID",        "USUBJID",        "LBSEQ",        "LBTESTCD",        "LBTEST",        "LBORRES",        "LBORRESU",        "LBSTRESC",        "LBSTRESN",        "LBSTRESU",        "LBNRIND",        "LBDTC",    ))Path("/app/output").mkdir(parents=True, exist_ok=True)lb.write_csv("/app/output/lb.csv")

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;