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: reviewedEditUnder review
What this benchmark means
README.md

Goal: one record per reported result from serum, skin-biopsy, saliva, and tape-strip forms, carrying LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTAT, VISITNUM, and LBDTC.

Input: Operational Data Model (ODM) extract in long form, one record per collected item, with the collected entry in the value field; each form also carries its collection date as a sibling item record.

Variables:

  • VISITNUM is the visit number: 1 screening, 2 baseline, 3 day 21; each unscheduled visit is numbered after the baseline visit per occurrence (2.01, 2.02), so repeated unscheduled visits stay distinct. VISIT is the matching visit name.
  • LBTESTCD is the short test code for the collected item: VITD25OH, IL13, or CAMPPRO.
  • LBTEST is the full test name for that code: 25-Hydroxyvitamin D, Interleukin 13 mRNA, or Cathelicidin Protein.
  • LBCAT is the test category for that code: CHEMISTRY, GENE EXPRESSION, or ANTIMICROBIAL PEPTIDE.
  • LBSPEC is the specimen type of the form the result came from: SERUM, SKIN BIOPSY, SALIVA, or TAPE STRIP.
  • LBLOC is the sampled site where the form distinguishes one: LESIONAL or NON-LESIONAL for biopsy and tape-strip results; blank for serum and saliva results.
  • LBORRES is the collected result, exactly as reported; blank when the test was not done.
  • LBORRESU is the unit fixed for each form: CYCLE for biopsy results and ng/mL otherwise; blank when the test was not done.
  • LBSTRESC repeats the reported result in standard form; blank when the test was not done.
  • LBSTRESN is the numeric form of the reported result; missing when the test was not done.
  • LBSTRESU is the standard unit, the same as LBORRESU; blank when the test was not done.
  • LBSTAT is NOT DONE when the test was not done; blank otherwise.
  • LBDTC is the collection date from the date item on the same form.

Note: an item with no reported value produces no record, while a collected zero is kept as a real result; an entry of NOT DONE still produces a record, with the result and unit fields left blank and the status recorded. Each record keeps the date of the form it came from, so a form collected twice at one visit keeps each occurrence's own date.

Inputs

Under review
1 source file

input/odm.csv

Edit
38 rows
input/odm.csv
StudyOIDMetaDataVersionOIDSubjectKeyStudyEventOIDStudyEventRepeatKeyItemGroupOIDItemGroupRepeatKeyItemOIDValue
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.SCRN1IG.NCT00789880.DM1IT.NCT00789880.DM.DIAGGRPAD
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.SCRN1IG.NCT00789880.LB1IT.NCT00789880.LB.LBDTC2025-01-01
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.SCRN1IG.NCT00789880.LB1IT.NCT00789880.LB.VITD23.2
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.BX1IT.NCT00789880.BX.BXDTC2025-01-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_LES5.45
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_NONLES3.71
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SALDTC2025-01-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SAL_CAMP0
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.TS1IT.NCT00789880.TS.TSDTC2025-01-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_LES0.34
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.BL1IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_NONLES2.1
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.BX1IT.NCT00789880.BX.BXDTC2025-01-29
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_LES12.95
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_NONLES0
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.SAL1IT.NCT00789880.SAL.SALDTC2025-01-29
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.SAL1IT.NCT00789880.SAL.SAL_CAMPNOT DONE
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.TS1IT.NCT00789880.TS.TSDTC2025-01-29
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_LES0
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.D211IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_NONLES—
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.UNSCH1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SALDTC2025-01-15
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.UNSCH1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SAL_CAMP2.1
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.UNSCH1IG.NCT00789880.SAL2IT.NCT00789880.SAL.SALDTC2025-01-16
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0001SE.NCT00789880.UNSCH1IG.NCT00789880.SAL2IT.NCT00789880.SAL.SAL_CAMP2.3
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.SCRN1IG.NCT00789880.DM1IT.NCT00789880.DM.DIAGGRPNONAD
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.SCRN1IG.NCT00789880.LB1IT.NCT00789880.LB.LBDTC2025-02-01
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.SCRN1IG.NCT00789880.LB1IT.NCT00789880.LB.VITD18.4
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.BX1IT.NCT00789880.BX.BXDTC2025-02-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_NONLES1.22
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SALDTC2025-02-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.SAL1IT.NCT00789880.SAL.SAL_CAMP—
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.TS1IT.NCT00789880.TS.TSDTC2025-02-08
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.BL1IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_NONLES4.49
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.BX1IT.NCT00789880.BX.BXDTC2025-03-01
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.BX1IT.NCT00789880.BX.IL13_NONLES1.87
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.SAL1IT.NCT00789880.SAL.SALDTC2025-03-01
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.SAL1IT.NCT00789880.SAL.SAL_CAMP5.24
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.TS1IT.NCT00789880.TS.TSDTC2025-03-01
ST.NCT00789880MDV.NCT00789880.1CATH-UCSD-0002SE.NCT00789880.D211IG.NCT00789880.TS1IT.NCT00789880.TS.TS_CAMP_NONLES4.65

Expected output

Under review
18 expected rows

expected/lb.csv

Edit
18 rows
expected/lb.csv
DOMAINSTUDYIDUSUBJIDLBSEQVISITVISITNUMLBTESTCDLBTESTLBCATLBSPECLBLOCLBORRESLBORRESULBSTRESCLBSTRESNLBSTRESULBSTATLBDTC
LBST.NCT007898801SCREENING1VITD25OH25-Hydroxyvitamin DCHEMISTRYSERUM—23.2ng/mL23.223.2ng/mL—2025-01-01
LBST.NCT007898801SCREENING1VITD25OH25-Hydroxyvitamin DCHEMISTRYSERUM—18.4ng/mL18.418.4ng/mL—2025-02-01
LBST.NCT007898805BASELINE2IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYLESIONAL5.45CYCLE5.455.45CYCLE—2025-01-08
LBST.NCT0078988011DAY 213IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYLESIONAL12.95CYCLE12.9512.95CYCLE—2025-01-29
LBST.NCT007898806BASELINE2IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYNON-LESIONAL3.71CYCLE3.713.71CYCLE—2025-01-08
LBST.NCT0078988012DAY 213IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYNON-LESIONAL0CYCLE00CYCLE—2025-01-29
LBST.NCT007898803BASELINE2IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYNON-LESIONAL1.22CYCLE1.221.22CYCLE—2025-02-08
LBST.NCT007898806DAY 213IL13Interleukin 13 mRNAGENE EXPRESSIONSKIN BIOPSYNON-LESIONAL1.87CYCLE1.871.87CYCLE—2025-03-01
LBST.NCT007898802BASELINE2CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDESALIVA—0ng/mL00ng/mL—2025-01-08
LBST.NCT007898809DAY 213CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDESALIVA——————NOT DONE2025-01-29
LBST.NCT007898807UNSCHEDULED2.01CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDESALIVA—2.1ng/mL2.12.1ng/mL—2025-01-15
LBST.NCT007898808UNSCHEDULED2.02CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDESALIVA—2.3ng/mL2.32.3ng/mL—2025-01-16
LBST.NCT007898804DAY 213CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDESALIVA—5.24ng/mL5.245.24ng/mL—2025-03-01
LBST.NCT007898803BASELINE2CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDETAPE STRIPLESIONAL0.34ng/mL0.340.34ng/mL—2025-01-08
LBST.NCT0078988010DAY 213CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDETAPE STRIPLESIONAL0ng/mL00ng/mL—2025-01-29
LBST.NCT007898804BASELINE2CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDETAPE STRIPNON-LESIONAL2.1ng/mL2.12.1ng/mL—2025-01-08
LBST.NCT007898802BASELINE2CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDETAPE STRIPNON-LESIONAL4.49ng/mL4.494.49ng/mL—2025-02-08
LBST.NCT007898805DAY 213CAMPPROCathelicidin ProteinANTIMICROBIAL PEPTIDETAPE STRIPNON-LESIONAL4.65ng/mL4.654.65ng/mL—2025-03-01
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 AbbreviationCharLBAssignedSet constant value "LB".SDTM1
STUDYIDStudy IdentifierCharCollectedCopy value from ODM.StudyOID.SDTM2
USUBJIDUnique Subject IdentifierCharCollectedCopy value from ODM.SubjectKey.SDTM3
VISITVisit NameCharSCREENING, BASELINE, DAY 21, UNSCHEDULEDCollectedRecode ODM.StudyEventOID: "SE.NCT00789880.SCRN" → "SCREENING", "SE.NCT00789880.BL" → "BASELINE", "SE.NCT00789880.D21" → "DAY 21", "SE.NCT00789880.UNSCH" → "UNSCHEDULED"; missing or unlisted values are errors.SDTM4
VISITNUMVisit NumberNum1, 2, 3, 2.01, 2.02DerivedIf ODM.StudyEventOID = 'SE.NCT00789880.SCRN' then "1"; If ODM.StudyEventOID = 'SE.NCT00789880.BL' then "2"; If ODM.StudyEventOID = 'SE.NCT00789880.D21' then "3"; If ODM.StudyEventOID = 'SE.NCT00789880.UNSCH' then compute 2 + FORMREPEAT / 100; otherwise blank.SDTM5
LBTESTCDLab Test or Examination Short NameCharVITD25OH, IL13, CAMPPRODerivedSet per constructed row; see the Row construction sheet.SDTM6
LBTESTLab Test or Examination NameCharDerivedSet per constructed row; see the Row construction sheet.SDTM7
LBCATCategory for Lab TestCharDerivedSet per constructed row; see the Row construction sheet.SDTM8
LBSPECSpecimen Material TypeCharSERUM, SKIN BIOPSY, SALIVA, TAPE STRIPDerivedSet per constructed row; see the Row construction sheet.SDTM9
LBLOCLocation Used for MeasurementCharLESIONAL, NON-LESIONALDerivedSet per constructed row; see the Row construction sheet.SDTM10
LBORRESResult or Finding in Original UnitsCharDerivedIf RAWORRES = 'NOT DONE' then "None"; otherwise "RAWORRES".SDTM11
LBORRESUOriginal UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM12
LBSTRESCCharacter Result/Finding in Std FormatCharDerivedCopy value from LBORRES.SDTM13
LBSTRESNNumeric Result/Finding in Standard UnitsNumDerivedCopy value from LBORRES; a value that cannot be converted → blank.SDTM14
LBSTRESUStandard UnitsCharDerivedSet per constructed row; see the Row construction sheet.SDTM15
LBSTATCompletion StatusCharNOT DONEDerivedIf RAWORRES = 'NOT DONE' then "NOT DONE"; otherwise blank.SDTM16
LBDTCDate/Time of Specimen CollectionCharISO 8601DerivedSet per constructed row; see the Row construction sheet.SDTM17
LBSEQSequence NumberNumDerivedRow number within each group of STUDYID, USUBJID, ordered by LBDTC then LBTESTCD then LBSPEC.SDTM18
TemplateInclude whenLBTESTCDLBTESTLBCATLBSPECLBLOCRAWORRESLBORRESULBSTRESULBDTC
serum_vitamin_dRAWORRES IS NOT NULLConstant "VITD25OH"Constant "25-Hydroxyvitamin D"Constant "CHEMISTRY"Constant "SERUM"Constant "None"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.LB.VITD'If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.LB.LBDTC'
biopsy_il13_lesionalRAWORRES IS NOT NULLConstant "IL13"Constant "Interleukin 13 mRNA"Constant "GENE EXPRESSION"Constant "SKIN BIOPSY"Constant "LESIONAL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.BX.IL13_LES'If RAWORRES = 'NOT DONE' then "None"; otherwise "CYCLE"If RAWORRES = 'NOT DONE' then "None"; otherwise "CYCLE"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.BX.BXDTC'
biopsy_il13_nonlesionalRAWORRES IS NOT NULLConstant "IL13"Constant "Interleukin 13 mRNA"Constant "GENE EXPRESSION"Constant "SKIN BIOPSY"Constant "NON-LESIONAL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.BX.IL13_NONLES'If RAWORRES = 'NOT DONE' then "None"; otherwise "CYCLE"If RAWORRES = 'NOT DONE' then "None"; otherwise "CYCLE"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.BX.BXDTC'
saliva_cathelicidinRAWORRES IS NOT NULLConstant "CAMPPRO"Constant "Cathelicidin Protein"Constant "ANTIMICROBIAL PEPTIDE"Constant "SALIVA"Constant "None"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.SAL.SAL_CAMP'If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.SAL.SALDTC'
tape_cathelicidin_lesionalRAWORRES IS NOT NULLConstant "CAMPPRO"Constant "Cathelicidin Protein"Constant "ANTIMICROBIAL PEPTIDE"Constant "TAPE STRIP"Constant "LESIONAL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.TS.TS_CAMP_LES'If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.TS.TSDTC'
tape_cathelicidin_nonlesionalRAWORRES IS NOT NULLConstant "CAMPPRO"Constant "Cathelicidin Protein"Constant "ANTIMICROBIAL PEPTIDE"Constant "TAPE STRIP"Constant "NON-LESIONAL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.TS.TS_CAMP_NONLES'If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"If RAWORRES = 'NOT DONE' then "None"; otherwise "ng/mL"Aggregate ONLY(ODM.Value) over records matching on the shared output keys, where ODM.ItemOID = 'IT.NCT00789880.TS.TSDTC'
VariablePermitted values
DOMAINLB
VISITSCREENING, BASELINE, DAY 21, UNSCHEDULED
VISITNUM1, 2, 3, 2.01, 2.02
LBTESTCDVITD25OH, IL13, CAMPPRO
LBSPECSERUM, SKIN BIOPSY, SALIVA, TAPE STRIP
LBLOCLESIONAL, NON-LESIONAL
LBSTATNOT DONE
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-multiform/full.md

Edit
41 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per reported result from the serum, skin-biopsy, saliva, and tape-strip forms.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, VISIT, VISITNUM, LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTAT, LBDTC

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

LBSEQ numbers each subject's records from 1 by collection date, then test code and specimen; lesional results come before non-lesional ones from the same form. VISIT is SCREENING, BASELINE, DAY 21, or UNSCHEDULED for the SCRN, BL, D21, and UNSCH study events, and VISITNUM is 1, 2, and 3 for the planned visits. An unscheduled visit is numbered after the baseline visit by its form occurrence (the item group repeat key): 2.01 for the first occurrence and 2.02 for the second.

LBTESTCD is VITD25OH, IL13, or CAMPPRO. LBTEST is 25-Hydroxyvitamin D, Interleukin 13 mRNA, or Cathelicidin Protein. LBCAT is CHEMISTRY, GENE EXPRESSION, or ANTIMICROBIAL PEPTIDE. LBSPEC is SERUM, SKIN BIOPSY, SALIVA, or TAPE STRIP. LBLOC is LESIONAL or NON-LESIONAL for biopsy and tape-strip results, and has no value for serum and saliva results.

LBORRES is the collected result, exactly as reported, and has no value when the test was not done. LBORRESU is CYCLE for biopsy results and ng/mL otherwise, and has no value when the test was not done. LBSTRESC repeats the reported result exactly as LBORRES holds it; LBSTRESN is its numeric form; and LBSTRESU repeats the unit. All three have no value when the test was not done. LBSTAT is NOT DONE when the test was not done, and has no value otherwise. LBDTC is the collection date from the date item on the same form.

An item with no reported value produces no record, while a collected zero is kept as a real result. An entry of NOT DONE still produces a record, with no value in the result and unit fields. A form collected twice at one visit keeps each occurrence's own date.

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

prompts/sdtm-lb-multiform/conventions.md

Edit
19 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per reported result from the serum, skin-biopsy, saliva, and tape-strip forms.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, VISIT, VISITNUM, LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTAT, LBDTC

VISIT is SCREENING, BASELINE, DAY 21, or UNSCHEDULED for the SCRN, BL, D21, and UNSCH study events. VISITNUM is 1, 2, and 3 for the planned visits, and 2.01 and 2.02 for the first and second unscheduled visit after baseline. LBTESTCD is VITD25OH, IL13, or CAMPPRO; LBTEST is 25-Hydroxyvitamin D, Interleukin 13 mRNA, or Cathelicidin Protein; LBCAT is CHEMISTRY, GENE EXPRESSION, or ANTIMICROBIAL PEPTIDE. LBSPEC is SERUM, SKIN BIOPSY, SALIVA, or TAPE STRIP. LBLOC is LESIONAL or NON-LESIONAL, or has no value.

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

prompts/sdtm-lb-multiform/brief.md

Edit
11 lines

Following CDISC SDTM standards, use the provided ODM dataset to create an LB dataset with one record per reported result from the serum, skin-biopsy, saliva, and tape-strip forms.

The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, LBSEQ, VISIT, VISITNUM, LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTAT, 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-multiform/result.R

Edit
89 lines
# Reference solution for the yamaa benchmark sdtm-lb-multiform (R track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.library(dplyr, warn.conflicts = FALSE)library(readr)library(tidyr)odm <- read_csv(  "/app/input/odm.csv",  col_types = cols(.default = col_character()))test_meta <- tribble(  ~short, ~LBTESTCD, ~LBTEST, ~LBCAT, ~LBSPEC, ~LBLOC, ~unit,  "VITD", "VITD25OH", "25-Hydroxyvitamin D", "CHEMISTRY", "SERUM", NA_character_, "ng/mL",  "IL13_LES", "IL13", "Interleukin 13 mRNA", "GENE EXPRESSION", "SKIN BIOPSY", "LESIONAL", "CYCLE",  "IL13_NONLES", "IL13", "Interleukin 13 mRNA", "GENE EXPRESSION", "SKIN BIOPSY", "NON-LESIONAL", "CYCLE",  "SAL_CAMP", "CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "SALIVA", NA_character_, "ng/mL",  "TS_CAMP_LES", "CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "TAPE STRIP", "LESIONAL", "ng/mL",  "TS_CAMP_NONLES", "CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "TAPE STRIP", "NON-LESIONAL", "ng/mL")wide <- odm |>  mutate(    short = sub(".*\\.", "", ItemOID),    is_date = grepl("DTC$", short)  )dates <- wide |>  filter(is_date, !is.na(Value) & Value != "") |>  select(    StudyOID, SubjectKey, StudyEventOID, ItemGroupOID, ItemGroupRepeatKey,    LBDTC = Value  )results <- wide |>  filter(!is_date, short %in% test_meta$short, !is.na(Value) & Value != "") |>  left_join(dates,    by = c(      "StudyOID", "SubjectKey", "StudyEventOID", "ItemGroupOID",      "ItemGroupRepeatKey"    )  ) |>  left_join(test_meta, by = "short") |>  mutate(    event_short = sub(".*\\.", "", StudyEventOID),    VISIT = case_when(      event_short == "SCRN" ~ "SCREENING",      event_short == "BL" ~ "BASELINE",      event_short == "D21" ~ "DAY 21",      event_short == "UNSCH" ~ "UNSCHEDULED"    ),    VISITNUM = case_when(      event_short == "SCRN" ~ 1,      event_short == "BL" ~ 2,      event_short == "D21" ~ 3,      event_short == "UNSCH" ~ 2 + as.numeric(ItemGroupRepeatKey) / 100,      TRUE ~ NA_real_    ),    not_done = Value == "NOT DONE",    LBORRES = if_else(not_done, NA_character_, Value),    LBORRESU = if_else(not_done, NA_character_, unit),    LBSTRESC = if_else(not_done, NA_character_, Value),    LBSTRESN = suppressWarnings(as.numeric(if_else(not_done, NA_character_, Value))),    LBSTRESU = if_else(not_done, NA_character_, unit),    LBSTAT = if_else(not_done, "NOT DONE", NA_character_),    loc_rank = case_when(      LBLOC == "LESIONAL" ~ 0L,      LBLOC == "NON-LESIONAL" ~ 1L,      TRUE ~ 2L    )  ) |>  arrange(SubjectKey, LBDTC, LBTESTCD, LBSPEC, loc_rank) |>  group_by(SubjectKey) |>  mutate(LBSEQ = row_number()) |>  ungroup() |>  mutate(DOMAIN = "LB") |>  arrange(SubjectKey, LBSEQ) |>  select(    DOMAIN, STUDYID = StudyOID, USUBJID = SubjectKey, LBSEQ, VISIT, VISITNUM,    LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC,    LBSTRESN, LBSTRESU, LBSTAT, LBDTC  )dir.create("/app/output", showWarnings = FALSE)write_csv(results, "/app/output/lb.csv", na = "")

solutions/sdtm-lb-multiform/result.py

Edit
173 lines
# Reference solution for the yamaa benchmark sdtm-lb-multiform (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 pldef _present(value) -> bool:    return value is not None and str(value) != ""odm = pl.read_csv("/app/input/odm.csv", infer_schema=False)# Each form occurrence keeps its own date; lesional comes before# non-lesional from the same form.test_meta = {    "VITD": ("VITD25OH", "25-Hydroxyvitamin D", "CHEMISTRY", "SERUM", None, "ng/mL"),    "IL13_LES": ("IL13", "Interleukin 13 mRNA", "GENE EXPRESSION", "SKIN BIOPSY", "LESIONAL", "CYCLE"),    "IL13_NONLES": ("IL13", "Interleukin 13 mRNA", "GENE EXPRESSION", "SKIN BIOPSY", "NON-LESIONAL", "CYCLE"),    "SAL_CAMP": ("CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "SALIVA", None, "ng/mL"),    "TS_CAMP_LES": ("CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "TAPE STRIP", "LESIONAL", "ng/mL"),    "TS_CAMP_NONLES": ("CAMPPRO", "Cathelicidin Protein", "ANTIMICROBIAL PEPTIDE", "TAPE STRIP", "NON-LESIONAL", "ng/mL"),}visit_meta = {    "SCRN": ("SCREENING", 1.0),    "BL": ("BASELINE", 2.0),    "D21": ("DAY 21", 3.0),}def _result_key(itemoid: str) -> str | None:    short = itemoid.split(".")[-1]    return short if short in test_meta else Nonegroups: dict[tuple, dict] = {}for row in odm.to_dicts():    key = (        str(row["StudyOID"]),        str(row["SubjectKey"]),        str(row["StudyEventOID"]),        str(row["ItemGroupOID"]),        str(row["ItemGroupRepeatKey"]),    )    groups.setdefault(key, {})[str(row["ItemOID"])] = row["Value"]rows = []for (studyid, subj, event_oid, _group_oid, group_rep), values in groups.items():    event_short = event_oid.split(".")[-1]    # The collection date from the date item on the same form.    lbdtc = None    for oid, val in values.items():        if oid.split(".")[-1].endswith("DTC") and _present(val):            lbdtc = str(val).strip()    if event_short == "UNSCH":        visit, visitnum = "UNSCHEDULED", 2 + float(group_rep) / 100    else:        visit, visitnum = visit_meta.get(event_short, (None, None))    for oid, val in values.items():        rkey = _result_key(oid)        if rkey is None:            continue        text = str(val) if _present(val) else ""        # An item with no reported value produces no record.        if text == "":            continue        testcd, test, cat, spec, loc, unit = test_meta[rkey]        if text == "NOT DONE":            rows.append(                {                    "DOMAIN": "LB",                    "STUDYID": studyid,                    "USUBJID": subj,                    "VISIT": visit,                    "VISITNUM": visitnum,                    "LBTESTCD": testcd,                    "LBTEST": test,                    "LBCAT": cat,                    "LBSPEC": spec,                    "LBLOC": loc,                    "LBORRES": None,                    "LBORRESU": None,                    "LBSTRESC": None,                    "LBSTRESN": None,                    "LBSTRESU": None,                    "LBSTAT": "NOT DONE",                    "LBDTC": lbdtc,                }            )        else:            try:                number = float(text)            except ValueError:                number = None            rows.append(                {                    "DOMAIN": "LB",                    "STUDYID": studyid,                    "USUBJID": subj,                    "VISIT": visit,                    "VISITNUM": visitnum,                    "LBTESTCD": testcd,                    "LBTEST": test,                    "LBCAT": cat,                    "LBSPEC": spec,                    "LBLOC": loc,                    "LBORRES": text,                    "LBORRESU": unit,                    "LBSTRESC": text,                    "LBSTRESN": number,                    "LBSTRESU": unit,                    "LBSTAT": None,                    "LBDTC": lbdtc,                }            )loc_rank = {"LESIONAL": 0, "NON-LESIONAL": 1, None: 2}rows.sort(    key=lambda r: (        r["USUBJID"],        r["LBDTC"] or "",        r["LBTESTCD"],        r["LBSPEC"],        loc_rank[r["LBLOC"]],    ))seq_by_subject: dict[str, int] = {}for row in rows:    seq_by_subject[row["USUBJID"]] = seq_by_subject.get(row["USUBJID"], 0) + 1    row["LBSEQ"] = str(seq_by_subject[row["USUBJID"]])lb = (    pl.DataFrame(        rows,        schema={            "DOMAIN": pl.String,            "STUDYID": pl.String,            "USUBJID": pl.String,            "LBSEQ": pl.String,            "VISIT": pl.String,            "VISITNUM": pl.Float64,            "LBTESTCD": pl.String,            "LBTEST": pl.String,            "LBCAT": pl.String,            "LBSPEC": pl.String,            "LBLOC": pl.String,            "LBORRES": pl.String,            "LBORRESU": pl.String,            "LBSTRESC": pl.String,            "LBSTRESN": pl.Float64,            "LBSTRESU": pl.String,            "LBSTAT": pl.String,            "LBDTC": pl.String,        },    )    .with_columns(        pl.col("LBSEQ").cast(pl.Int64).alias("_seq"),        pl.col("VISITNUM").cast(pl.Float64).alias("_visitnum"),    )    .sort(["USUBJID", "_seq"])    .select(        "DOMAIN", "STUDYID", "USUBJID", "LBSEQ", "VISIT", "VISITNUM",        "LBTESTCD", "LBTEST", "LBCAT", "LBSPEC", "LBLOC", "LBORRES",        "LBORRESU", "LBSTRESC", "LBSTRESN", "LBSTRESU", "LBSTAT", "LBDTC",    ))Path("/app/output").mkdir(exist_ok=True)lb.write_csv("/app/output/lb.csv")

solutions/sdtm-lb-multiform/result.sas

Edit
34 lines
/* opensas reference solution for the yamaa benchmark sdtm-lb-multiform.   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 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;data dateitems; set odm; if scan(ItemOID,-1,'.') in ('LBDTC','BXDTC','SALDTC','TSDTC'); run;proc sql; create table dates as select StudyOID,SubjectKey,StudyEventOID,StudyEventRepeatKey,ItemGroupOID,ItemGroupRepeatKey,max(Value) as LBDTC from dateitems group by StudyOID,SubjectKey,StudyEventOID,StudyEventRepeatKey,ItemGroupOID,ItemGroupRepeatKey;create table joined as select a.*,b.LBDTC from odm a inner join dates b on a.StudyOID=b.StudyOID and a.SubjectKey=b.SubjectKey and a.StudyEventOID=b.StudyEventOID and a.StudyEventRepeatKey=b.StudyEventRepeatKey and a.ItemGroupOID=b.ItemGroupOID and a.ItemGroupRepeatKey=b.ItemGroupRepeatKey; quit;data prepared; set joined; length DOMAIN $2 STUDYID USUBJID VISIT LBTESTCD LBTEST LBCAT LBSPEC LBLOC LBORRES LBORRESU LBSTRESC LBSTRESU LBSTAT KEY EVENT $1024;KEY=scan(ItemOID,-1,'.'); EVENT=scan(StudyEventOID,-1,'.'); if not missing(Value) and KEY in ('VITD','IL13_LES','IL13_NONLES','SAL_CAMP','TS_CAMP_LES','TS_CAMP_NONLES');DOMAIN='LB'; STUDYID=StudyOID; USUBJID=SubjectKey; LBORRES=Value; LBSTRESC=Value; LBSTRESN=.; LBORRESU='ng/mL'; LBLOC=''; LBSTAT=''; LOCORD=2;if Value ne 'NOT DONE' then LBSTRESN=input(Value,?? best32.);if KEY='VITD' then do; LBTESTCD='VITD25OH'; LBTEST='25-Hydroxyvitamin D'; LBCAT='CHEMISTRY'; LBSPEC='SERUM'; end;else if KEY in ('IL13_LES','IL13_NONLES') then do; LBTESTCD='IL13'; LBTEST='Interleukin 13 mRNA'; LBCAT='GENE EXPRESSION'; LBSPEC='SKIN BIOPSY'; LBORRESU='CYCLE'; end;else do; LBTESTCD='CAMPPRO'; LBTEST='Cathelicidin Protein'; LBCAT='ANTIMICROBIAL PEPTIDE'; if KEY='SAL_CAMP' then LBSPEC='SALIVA'; else LBSPEC='TAPE STRIP'; end;if KEY in ('IL13_LES','TS_CAMP_LES') then do; LBLOC='LESIONAL'; LOCORD=0; end;if KEY in ('IL13_NONLES','TS_CAMP_NONLES') then do; LBLOC='NON-LESIONAL'; LOCORD=1; end;VISITNUM=.; if EVENT='SCRN' then do; VISIT='SCREENING'; VISITNUM=1; end; if EVENT='BL' then do; VISIT='BASELINE'; VISITNUM=2; end; if EVENT='D21' then do; VISIT='DAY 21'; VISITNUM=3; end; if EVENT='UNSCH' then do; VISIT='UNSCHEDULED'; VISITNUM=2+input(ItemGroupRepeatKey,best32.)/100; end;LBSTRESU=LBORRESU; if Value='NOT DONE' then do; LBORRES=''; LBORRESU=''; LBSTRESC=''; LBSTRESN=.; LBSTRESU=''; LBSTAT='NOT DONE'; end; run;proc sort data=prepared; by STUDYID USUBJID LBDTC LBTESTCD LBSPEC LOCORD; 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, VISIT, VISITNUM, LBTESTCD, LBTEST, LBCAT, LBSPEC, LBLOC, LBORRES, LBORRESU, LBSTRESC, LBSTRESN, LBSTRESU, LBSTAT, LBDTC from result;quit;proc export data=final outfile="/app/output/lb.csv" dbms=csv replace; run;