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: 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:
LBSEQnumbers the records in the documented collection order within each subject (protein, creatine kinase, glucose, ketones, creatinine), then by form repeat.LBTESTCDis the test short name for the collection form:PROT(protein),GLUC(glucose),KETON(ketones),CREAT(creatinine), orCK(creatine kinase).LBTESTis the test name for the collection form.LBORRESis the result as collected: a dipstick grade such asNeg,tr, or1 plus, or a censored value such as<0.1.LBORRESUis the unit as reported on the collection form; empty when the form records no unit.LBSTRESCis the standardized character result: known dipstick spellings, in any letter case, fold toNEGATIVE,TRACE,1+, or2+; anything else is kept as collected.LBSTRESNis the standardized numeric result. It holds a number only for a true numeric such as0.9; it stays empty for grades and for censored values.LBSTRESUis the standard unit, carried from the reported unit.LBNRINDis the reference range indicator:LOWfor a result below the quantification limit,HIGHfor one above it, empty otherwise.LBDTCis 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 review1 source fileinput/odm.csv
Edit51 rows| StudyOID | MetaDataVersionOID | SubjectKey | StudyEventOID | StudyEventRepeatKey | FormOID | FormRepeatKey | ItemGroupOID | ItemGroupRepeatKey | ItemOID | Value |
|---|---|---|---|---|---|---|---|---|---|---|
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | NEGATIVE |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-01 |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.RESULT | Neg |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-01 |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.RESULT | TRACE |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-01 |
| STUDY01 | MDV.1 | STUDY01-101 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | neg |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-03 |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.RESULT | tr |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-03 |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.RESULT | Trace |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-03 |
| STUDY01 | MDV.1 | STUDY01-102 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | 1+ |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-05 |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.RESULT | +1 |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-05 |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.RESULT | 1 plus |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-05 |
| STUDY01 | MDV.1 | STUDY01-103 | V1 | 1 | FO.LB_KETON | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | 2+ |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-07 |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 1 | IG.LB | 1 | IT.LB.RESULT | <0.1 |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-07 |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 1 | IG.LB | 1 | IT.LB.LBORRESU | mg/dL |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 2 | IG.LB | 1 | IT.LB.RESULT | 0.9 |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 2 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-07 |
| STUDY01 | MDV.1 | STUDY01-104 | V1 | 1 | FO.LB_CREAT | 2 | IG.LB | 1 | IT.LB.LBORRESU | mg/dL |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_CK | 1 | IG.LB | 1 | IT.LB.RESULT | >500 |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_CK | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-09 |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_CK | 1 | IG.LB | 1 | IT.LB.LBORRESU | U/L |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.RESULT | +2 |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-09 |
| STUDY01 | MDV.1 | STUDY01-105 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | nEg |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-11 |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.RESULT | 1 Plus |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-11 |
| STUDY01 | MDV.1 | STUDY01-106 | V1 | 1 | FO.LB_GLUC | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
| STUDY01 | MDV.1 | STUDY01-107 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.RESULT | POSITIVE |
| STUDY01 | MDV.1 | STUDY01-107 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBDTC | 2026-08-12 |
| STUDY01 | MDV.1 | STUDY01-107 | V1 | 1 | FO.LB_PROT | 1 | IG.LB | 1 | IT.LB.LBORRESU | — |
Expected output
Under review17 expected rowsexpected/lb.csv
Edit17 rows| DOMAIN | STUDYID | USUBJID | LBSEQ | LBTESTCD | LBTEST | LBORRES | LBORRESU | LBSTRESC | LBSTRESN | LBSTRESU | LBNRIND | LBDTC |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LB | STUDY01 | 1 | PROT | Protein | NEGATIVE | — | NEGATIVE | — | — | — | 2026-08-01 | |
| LB | STUDY01 | 2 | GLUC | Glucose | Neg | — | NEGATIVE | — | — | — | 2026-08-01 | |
| LB | STUDY01 | 3 | KETON | Ketones | TRACE | — | TRACE | — | — | — | 2026-08-01 | |
| LB | STUDY01 | 1 | PROT | Protein | neg | — | NEGATIVE | — | — | — | 2026-08-03 | |
| LB | STUDY01 | 2 | GLUC | Glucose | tr | — | TRACE | — | — | — | 2026-08-03 | |
| LB | STUDY01 | 3 | KETON | Ketones | Trace | — | TRACE | — | — | — | 2026-08-03 | |
| LB | STUDY01 | 1 | PROT | Protein | 1+ | — | 1+ | — | — | — | 2026-08-05 | |
| LB | STUDY01 | 2 | GLUC | Glucose | +1 | — | 1+ | — | — | — | 2026-08-05 | |
| LB | STUDY01 | 3 | KETON | Ketones | 1 plus | — | 1+ | — | — | — | 2026-08-05 | |
| LB | STUDY01 | 1 | PROT | Protein | 2+ | — | 2+ | — | — | — | 2026-08-07 | |
| LB | STUDY01 | 2 | CREAT | Creatinine | <0.1 | mg/dL | <0.1 | — | mg/dL | LOW | 2026-08-07 | |
| LB | STUDY01 | 3 | CREAT | Creatinine | 0.9 | mg/dL | 0.9 | 0.9 | mg/dL | — | 2026-08-07 | |
| LB | STUDY01 | 1 | CK | Creatine Kinase | >500 | U/L | >500 | — | U/L | HIGH | 2026-08-09 | |
| LB | STUDY01 | 2 | GLUC | Glucose | +2 | — | 2+ | — | — | — | 2026-08-09 | |
| LB | STUDY01 | 1 | PROT | Protein | nEg | — | NEGATIVE | — | — | — | 2026-08-11 | |
| LB | STUDY01 | 2 | GLUC | Glucose | 1 Plus | — | 1+ | — | — | — | 2026-08-11 | |
| LB | STUDY01 | 1 | PROT | Protein | POSITIVE | — | POSITIVE | — | — | — | 2026-08-12 |
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 "LB". | 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 | ||||
| LBSEQ | Sequence Number | Num | Derived | Row number within each group of STUDYID, USUBJID, ordered by FORMORD then FORMRPT. | SDTM | 4 | ||||
| LBTESTCD | Lab Test or Examination Short Name | Char | Collected | Recode 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. | SDTM | 5 | ||||
| LBTEST | Lab Test or Examination Name | Char | Collected | Recode 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. | SDTM | 6 | ||||
| LBORRES | Result or Finding in Original Units | Char | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 7 | ||||
| LBORRESU | Original Units | Char | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 8 | ||||
| LBSTRESC | Character Result/Finding in Standard Units | Char | Derived | If 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". | SDTM | 9 | ||||
| LBSTRESN | Numeric Result/Finding in Standard Units | Num | Derived | Copy value from LBSTRESC; a value that cannot be converted → blank. | SDTM | 10 | ||||
| LBSTRESU | Standard Units | Char | Derived | Copy value from LBORRESU. | SDTM | 11 | ||||
| LBNRIND | Reference Range Indicator | Char | Derived | If LBSTRESC LIKE '<%' then "LOW"; If LBSTRESC LIKE '>%' then "HIGH"; otherwise blank. | SDTM | 12 | ||||
| LBDTC | Date/Time of Specimen Collection | Char | ISO 8601 | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 13 |
| Template | Include when | LBORRES | LBORRESU | LBDTC |
|---|---|---|---|---|
| lab-result | LBORRES IS NOT NULL | Aggregate 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' |
| 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-lb-character-results/full.md
Edit29 linesFollowing 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
Edit17 linesFollowing 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
Edit9 linesFollowing 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.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/sdtm-lb-character-results/result.R
Edit106 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
Edit150 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
Edit46 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;
yamaa