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.
- 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.
- Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
- 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
README.mdGoal: carry each collected vital-signs result with its actual
collection datetime, and add the planned time point it was
scheduled for: VSTESTCD, VSORRES, VSTPT, VSTPTNUM,
VSELTM, VSDTC, and VSDY.
Input: long-form Operational Data Model (ODM) data with one row
per collected item, carrying the collection form (FormOID), the
item (ItemOID), and the stored value (Value); a small test
dictionary (vs_mapping.csv) translating each vital-signs item
into its test short name, test name, and unit; plus
demographics rows carrying the reference start date. Each planned
time point arrives as its own vital-signs form, so the form tells
which scheduled reading a result belongs to. The first-dose
datetime rides along on the dosing form FO.EX.
Variables:
VSSEQnumbers the records per subject in schedule order, then pulse, systolic, and diastolic blood pressure within each time point.VSTPTis the planned time point name for the collection form:PRE-DOSE,30 MIN POST-DOSE,1 H POST-DOSE, or4 H POST-DOSE.VSTPTNUMis the planned time point number for the form: 1 through 4 in schedule order.VSELTMis the planned elapsed time since the first dose for the form, in ISO 8601 duration form:-PT15Mbefore the dose, thenPT30M,PT1H, andPT4Hafter it.VSTESTCDis the test short name, looked up from the test dictionary by item:PULSE,SYSBP, orDIABP. Items with no dictionary entry (the datetime and dosing items) give no record.VSORRESis the result in original units, the value on the test's item row within the form.VSORRESUis the original unit from the test dictionary:beats/minormmHg.VSSTRESNis the numeric result in standard units; pulse and blood pressure were collected in standard units, so it equals the original result.VSSTRESUis the standard unit, carried from the original unit.VSDTCis the actual collection datetime from the form's datetime item row, shared by every result on that form; a form with no datetime item stops the run.VSDYis the study day of the collection datetime, counted from the subject's reference start date: that date is day 1, there is no day zero, and dates before it count back from -1; missing when the subject has no reference start date.
Note: the planned elapsed time stays in duration form next to the actual collection datetime, so a reading taken later than planned keeps its late actual datetime and the gap shows when the two are compared. Each reading is also checked against the first-dose datetime: a post-dose reading dated before the dose, or a pre-dose reading dated after it, stops the run.
Inputs
Under review3 source filesinput/dm.csv
Edit2 rows| STUDYID | USUBJID | RFSTDTC |
|---|---|---|
| YAMAA-01 | 2025-01-08 | |
| YAMAA-01 | 2025-01-15 |
input/odm.csv
Edit34 rows| StudyOID | MetaDataVersionOID | SubjectKey | StudyEventOID | StudyEventRepeatKey | FormOID | FormRepeatKey | ItemGroupOID | ItemGroupRepeatKey | ItemOID | Value |
|---|---|---|---|---|---|---|---|---|---|---|
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-08T08:45:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.PULSE | 72 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.SYSBP | 118 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.DIABP | 76 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-08T09:31:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.PULSE | 74 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.SYSBP | 120 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.DIABP | 78 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-08T10:02:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.PULSE | 70 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.SYSBP | 116 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.DIABP | 74 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-08T13:05:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.PULSE | 68 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.SYSBP | 114 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.DIABP | 72 |
| YAMAA-01 | MDV.1 | YAMAA-01-001 | SE.D1 | 1 | FO.EX | 1 | IG.EX | 1 | IT.EX.EXSTDTC | 2025-01-08T09:00:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-15T08:44:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.PULSE | 80 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.SYSBP | 124 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_PREDOSE | 1 | IG.VS | 1 | IT.VS.DIABP | 82 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-15T09:52:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.PULSE | 88 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.SYSBP | 128 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_30MIN | 1 | IG.VS | 1 | IT.VS.DIABP | 84 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-15T10:03:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.PULSE | 78 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.SYSBP | 122 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_1H | 1 | IG.VS | 1 | IT.VS.DIABP | 80 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.VSDTC | 2025-01-15T13:01:00 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.PULSE | 76 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.SYSBP | 120 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.VS_4H | 1 | IG.VS | 1 | IT.VS.DIABP | 78 |
| YAMAA-01 | MDV.1 | YAMAA-01-002 | SE.D1 | 1 | FO.EX | 1 | IG.EX | 1 | IT.EX.EXSTDTC | 2025-01-15T09:00:00 |
input/vs_mapping.csv
Edit3 rows| ItemOID | VSTESTCD | VSTEST | UNIT |
|---|---|---|---|
| IT.VS.PULSE | PULSE | Pulse | beats/min |
| IT.VS.SYSBP | SYSBP | Systolic Blood Pressure | mmHg |
| IT.VS.DIABP | DIABP | Diastolic Blood Pressure | mmHg |
Expected output
Under review24 expected rowsexpected/vs.csv
Edit24 rows| DOMAIN | STUDYID | USUBJID | VSSEQ | VISIT | VSTPT | VSTPTNUM | VSELTM | VSTESTCD | VSORRES | VSORRESU | VSSTRESN | VSSTRESU | VSDTC | VSDY |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| VS | YAMAA-01 | 1 | DAY 1 | PRE-DOSE | 1 | -PT15M | PULSE | 72 | beats/min | 72 | beats/min | 2025-01-08T08:45:00 | 1 | |
| VS | YAMAA-01 | 2 | DAY 1 | PRE-DOSE | 1 | -PT15M | SYSBP | 118 | mmHg | 118 | mmHg | 2025-01-08T08:45:00 | 1 | |
| VS | YAMAA-01 | 3 | DAY 1 | PRE-DOSE | 1 | -PT15M | DIABP | 76 | mmHg | 76 | mmHg | 2025-01-08T08:45:00 | 1 | |
| VS | YAMAA-01 | 4 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | PULSE | 74 | beats/min | 74 | beats/min | 2025-01-08T09:31:00 | 1 | |
| VS | YAMAA-01 | 5 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | SYSBP | 120 | mmHg | 120 | mmHg | 2025-01-08T09:31:00 | 1 | |
| VS | YAMAA-01 | 6 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | DIABP | 78 | mmHg | 78 | mmHg | 2025-01-08T09:31:00 | 1 | |
| VS | YAMAA-01 | 7 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | PULSE | 70 | beats/min | 70 | beats/min | 2025-01-08T10:02:00 | 1 | |
| VS | YAMAA-01 | 8 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | SYSBP | 116 | mmHg | 116 | mmHg | 2025-01-08T10:02:00 | 1 | |
| VS | YAMAA-01 | 9 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | DIABP | 74 | mmHg | 74 | mmHg | 2025-01-08T10:02:00 | 1 | |
| VS | YAMAA-01 | 10 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | PULSE | 68 | beats/min | 68 | beats/min | 2025-01-08T13:05:00 | 1 | |
| VS | YAMAA-01 | 11 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | SYSBP | 114 | mmHg | 114 | mmHg | 2025-01-08T13:05:00 | 1 | |
| VS | YAMAA-01 | 12 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | DIABP | 72 | mmHg | 72 | mmHg | 2025-01-08T13:05:00 | 1 | |
| VS | YAMAA-01 | 1 | DAY 1 | PRE-DOSE | 1 | -PT15M | PULSE | 80 | beats/min | 80 | beats/min | 2025-01-15T08:44:00 | 1 | |
| VS | YAMAA-01 | 2 | DAY 1 | PRE-DOSE | 1 | -PT15M | SYSBP | 124 | mmHg | 124 | mmHg | 2025-01-15T08:44:00 | 1 | |
| VS | YAMAA-01 | 3 | DAY 1 | PRE-DOSE | 1 | -PT15M | DIABP | 82 | mmHg | 82 | mmHg | 2025-01-15T08:44:00 | 1 | |
| VS | YAMAA-01 | 4 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | PULSE | 88 | beats/min | 88 | beats/min | 2025-01-15T09:52:00 | 1 | |
| VS | YAMAA-01 | 5 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | SYSBP | 128 | mmHg | 128 | mmHg | 2025-01-15T09:52:00 | 1 | |
| VS | YAMAA-01 | 6 | DAY 1 | 30 MIN POST-DOSE | 2 | PT30M | DIABP | 84 | mmHg | 84 | mmHg | 2025-01-15T09:52:00 | 1 | |
| VS | YAMAA-01 | 7 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | PULSE | 78 | beats/min | 78 | beats/min | 2025-01-15T10:03:00 | 1 | |
| VS | YAMAA-01 | 8 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | SYSBP | 122 | mmHg | 122 | mmHg | 2025-01-15T10:03:00 | 1 | |
| VS | YAMAA-01 | 9 | DAY 1 | 1 H POST-DOSE | 3 | PT1H | DIABP | 80 | mmHg | 80 | mmHg | 2025-01-15T10:03:00 | 1 | |
| VS | YAMAA-01 | 10 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | PULSE | 76 | beats/min | 76 | beats/min | 2025-01-15T13:01:00 | 1 | |
| VS | YAMAA-01 | 11 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | SYSBP | 120 | mmHg | 120 | mmHg | 2025-01-15T13:01:00 | 1 | |
| VS | YAMAA-01 | 12 | DAY 1 | 4 H POST-DOSE | 4 | PT4H | DIABP | 78 | mmHg | 78 | mmHg | 2025-01-15T13:01:00 | 1 |
Comments and assessment materials
| 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 "VS". | 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 | ||||
| VISIT | Visit Name | Char | Collected | Recode ODM.StudyEventOID: "SE.D1" → "DAY 1"; missing or unlisted values are errors. | SDTM | 4 | ||||
| VSTPT | Planned Time Point Name | Char | Collected | Recode ODM.FormOID: "FO.VS_PREDOSE" → "PRE-DOSE", "FO.VS_30MIN" → "30 MIN POST-DOSE", "FO.VS_1H" → "1 H POST-DOSE", "FO.VS_4H" → "4 H POST-DOSE"; missing or unlisted values are errors. | SDTM | 5 | ||||
| VSTPTNUM | Planned Time Point Number | Num | Collected | Recode ODM.FormOID: "FO.VS_PREDOSE" → "1", "FO.VS_30MIN" → "2", "FO.VS_1H" → "3", "FO.VS_4H" → "4"; missing or unlisted values are errors. | SDTM | 6 | ||||
| VSELTM | Planned Elapsed Time | Char | Collected | Recode ODM.FormOID: "FO.VS_PREDOSE" → "-PT15M", "FO.VS_30MIN" → "PT30M", "FO.VS_1H" → "PT1H", "FO.VS_4H" → "PT4H"; missing or unlisted values are errors. | SDTM | 7 | ||||
| VSTESTCD | Vital Signs Test Short Name | Char | PULSE, SYSBP, DIABP | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 8 | |||
| VSSEQ | Sequence Number | Num | Derived | Row number within each group of STUDYID, USUBJID, ordered by VSTPTNUM then ORD_TEST. | SDTM | 9 | ||||
| VSORRES | Result or Finding in Original Units | Char | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 10 | ||||
| VSORRESU | Original Units | Char | beats/min, mmHg | Derived | Set per constructed row; see the Row construction sheet. | SDTM | 11 | |||
| VSSTRESN | Numeric Result/Finding in Standard Units | Num | Derived | Copy value from VSORRES. | SDTM | 12 | ||||
| VSSTRESU | Standard Units | Char | Derived | Copy value from VSORRESU. | SDTM | 13 | ||||
| VSDTC | Date/Time of Measurements | Char | ISO 8601 | Derived | Aggregate ONLY(ODM.Value) over records whose StudyOID equals ODM.StudyOID, SubjectKey equals ODM.SubjectKey, StudyEventOID equals ODM.StudyEventOID and FormOID equals ODM.FormOID, where ODM.ItemOID = 'IT.VS.VSDTC'. | SDTM | 14 | |||
| VSDY | Study Day of Vital Signs | Num | Derived | CDISC study day of VSDT against RFSTDTC as day 1; there is no day zero. | SDTM | 15 |
| Template | Include when | VSTESTCD | VSORRES | VSORRESU |
|---|---|---|---|---|
| vs_reading | VSTESTCD IS NOT NULL | Copy VSTESTCD from the VS_MAPPING record whose ItemOID equals ODM.ItemOID, and no match yields blank | Aggregate ONLY(ODM.Value) over records matching on the shared output keys | Copy UNIT from the VS_MAPPING record whose ItemOID equals ODM.ItemOID, and no match yields blank |
| Variable | Permitted values |
|---|---|
| VSTESTCD | PULSE, SYSBP, DIABP |
| VSORRESU | beats/min, mmHg |
| Version | Date | Author | Description | Reviewer | Sign-off |
|---|---|---|---|---|---|
| 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-vs-planned-time-points/full.md
Edit35 linesFollowing CDISC SDTM standards, use the provided ODM, DM, and VS_MAPPING datasets to create a VS dataset with one record per collected vital-signs result.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, VSSEQ, VISIT, VSTPT, VSTPTNUM, VSELTM, VSTESTCD, VSORRES, VSORRESU, VSSTRESN, VSSTRESU, VSDTC, VSDY
STUDYID is the ODM StudyOID and USUBJID is the SubjectKey, each as given.
VSSEQ numbers the records per subject from 1 in schedule order, then pulse, systolic, and diastolic blood pressure within each time point. VISIT is DAY 1 for study event SE.D1. VSTPT is the planned time point name for the collection form: PRE-DOSE, 30 MIN POST-DOSE, 1 H POST-DOSE, or 4 H POST-DOSE. VSTPTNUM is the planned time point number for the form: 1 through 4 in schedule order. VSELTM is the planned elapsed time since the first dose for the form: -PT15M before the dose, then PT30M, PT1H, and PT4H after it.
VSTESTCD is the test short name from the test dictionary: PULSE, SYSBP, or DIABP. Items with no dictionary entry give no record. VSORRES is the result in original units, and VSORRESU the original unit from the test dictionary (beats/min or mmHg). VSSTRESN is the numeric result in standard units; pulse and blood pressure were collected in standard units, so it equals the original result. VSSTRESU is the standard unit, carried from the original unit. VSDTC is the actual collection datetime from the form's datetime item row, shared by every result on that form. VSDY is the study day of the collection datetime relative to the subject's reference start date.
The planned elapsed time stays next to the actual collection datetime, so a reading taken later than planned keeps its late actual datetime.
Read the source datasets from /app/input and save the completed dataset as /app/output/vs.csv.
prompts/sdtm-vs-planned-time-points/conventions.md
Edit16 linesFollowing CDISC SDTM standards, use the provided ODM, DM, and VS_MAPPING datasets to create a VS dataset with one record per collected vital-signs result.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, VSSEQ, VISIT, VSTPT, VSTPTNUM, VSELTM, VSTESTCD, VSORRES, VSORRESU, VSSTRESN, VSSTRESU, VSDTC, VSDY
VISIT is DAY 1 for study event SE.D1. VSTPT is PRE-DOSE, 30 MIN POST-DOSE, 1 H POST-DOSE, or 4 H POST-DOSE. VSTPTNUM is 1 through 4 in schedule order. VSELTM is -PT15M before the dose, then PT30M, PT1H, and PT4H after it.
VSTESTCD is PULSE, SYSBP, or DIABP.
Read the source datasets from /app/input and save the completed dataset as /app/output/vs.csv.
prompts/sdtm-vs-planned-time-points/brief.md
Edit10 linesFollowing CDISC SDTM standards, use the provided ODM, DM, and VS_MAPPING datasets to create a VS dataset with one record per collected vital-signs result.
The output dataset should contain the following columns in this order: DOMAIN, STUDYID, USUBJID, VSSEQ, VISIT, VSTPT, VSTPTNUM, VSELTM, VSTESTCD, VSORRES, VSORRESU, VSSTRESN, VSSTRESU, VSDTC, VSDY
Read the source datasets from /app/input and save the completed dataset as /app/output/vs.csv.
run.R
Edit4 lineslibrary(yamaa)vs <- yamaa_domain("spec.yaml")$outputvsrun.py
Edit4 linesimport yamaavs = yamaa.yamaa_domain("spec.yaml").outputvssolutions/sdtm-vs-planned-time-points/result.R
Edit106 lines# Reference solution for the yamaa benchmark sdtm-vs-planned-time-points (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(lubridate)library(readr)library(tidyr)study_day <- function(ref, coll) { if (is.na(ref) || is.na(coll)) { return(NA_integer_) } delta <- as.integer(coll - ref) if (delta >= 0L) delta + 1L else delta}time_point <- function(form) { name <- toupper(form) if (grepl("PREDOSE", name, fixed = TRUE)) { list(vstpt = "PRE-DOSE", vstptnum = 1L, vseltm = "-PT15M") } else if (grepl("30MIN", name, fixed = TRUE)) { list(vstpt = "30 MIN POST-DOSE", vstptnum = 2L, vseltm = "PT30M") } else if (grepl("1H", name, fixed = TRUE)) { list(vstpt = "1 H POST-DOSE", vstptnum = 3L, vseltm = "PT1H") } else if (grepl("4H", name, fixed = TRUE)) { list(vstpt = "4 H POST-DOSE", vstptnum = 4L, vseltm = "PT4H") } else { NULL }}dm <- read_csv( "/app/input/dm.csv", col_types = cols(.default = col_character()))odm <- read_csv( "/app/input/odm.csv", col_types = cols(.default = col_character()))mapping <- read_csv( "/app/input/vs_mapping.csv", col_types = cols(.default = col_character()))ref_map <- setNames(suppressWarnings(ymd(dm$RFSTDTC)), dm$USUBJID)test_map <- setNames(mapping$VSTESTCD, mapping$ItemOID)unit_map <- setNames(mapping$UNIT, mapping$ItemOID)forms <- odm |> filter(StudyEventOID == "SE.D1", grepl("^FO\\.VS_", FormOID)) |> group_by(SubjectKey, FormOID, StudyOID) |> summarise( vsdtc = Value[ItemOID == "IT.VS.VSDTC"][1], .groups = "drop" )results <- odm |> filter( StudyEventOID == "SE.D1", grepl("^FO\\.VS_", FormOID), ItemOID %in% names(test_map), !is.na(Value) & Value != "" ) |> left_join(forms, by = c("SubjectKey", "FormOID", "StudyOID")) |> rowwise() |> mutate( point = list(time_point(FormOID)), VSTPT = point$vstpt, VSTPTNUM = point$vstptnum, VSELTM = point$vseltm, VSTESTCD = test_map[[ItemOID]], VSORRES = Value, VSORRESU = unit_map[[ItemOID]], VSSTRESN = suppressWarnings(as.numeric(Value)), VSSTRESU = VSORRESU, VSDTC = vsdtc, ref = ref_map[[SubjectKey]], VSDY = study_day(ref, suppressWarnings(as_date(ymd_hms(VSDTC)))), test_ord = case_when( VSTESTCD == "PULSE" ~ 1L, VSTESTCD == "SYSBP" ~ 2L, .default = 3L ) ) |> ungroup() |> filter(!is.na(VSTPT), !is.na(VSDTC) & VSDTC != "") |> arrange(SubjectKey, VSTPTNUM, test_ord) |> group_by(SubjectKey) |> mutate(VSSEQ = row_number()) |> ungroup() |> arrange(SubjectKey, VSSEQ) |> transmute( DOMAIN = "VS", STUDYID = StudyOID, USUBJID = SubjectKey, VSSEQ, VISIT = "DAY 1", VSTPT, VSTPTNUM, VSELTM, VSTESTCD, VSORRES, VSORRESU, VSSTRESN, VSSTRESU, VSDTC, VSDY )dir.create("/app/output", showWarnings = FALSE)write_csv(results, "/app/output/vs.csv", na = "")solutions/sdtm-vs-planned-time-points/result.py
Edit139 lines# Reference solution for the yamaa benchmark sdtm-vs-planned-time-points (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 date, datetimefrom pathlib import Pathimport polars as pldef parse_date(text: str | None) -> date | None: if text is None or text == "": return None try: return datetime.fromisoformat(text).date() except ValueError: try: return date.fromisoformat(text[:10]) except ValueError: return Nonedef study_day(ref: date | None, coll: date | None) -> int | None: if ref is None or coll is None: return None delta = (coll - ref).days return delta + 1 if delta >= 0 else deltadef time_point(form: str) -> tuple[str, int, str] | None: # The planned time point for each collection form, in schedule order. name = form.upper() if "PREDOSE" in name: return ("PRE-DOSE", 1, "-PT15M") if "30MIN" in name: return ("30 MIN POST-DOSE", 2, "PT30M") if "1H" in name: return ("1 H POST-DOSE", 3, "PT1H") if "4H" in name: return ("4 H POST-DOSE", 4, "PT4H") return Nonedm = pl.read_csv("/app/input/dm.csv", infer_schema=False)odm = pl.read_csv("/app/input/odm.csv", infer_schema=False)mapping = pl.read_csv("/app/input/vs_mapping.csv", infer_schema=False)ref_map = {row["USUBJID"]: parse_date(row["RFSTDTC"]) for row in dm.to_dicts()}test_map = { row["ItemOID"]: (row["VSTESTCD"], row["UNIT"]) for row in mapping.to_dicts()}# Within each time point: pulse, systolic, then diastolic.test_order = {"PULSE": 1, "SYSBP": 2, "DIABP": 3}# One form per subject per planned time point: its datetime and results.forms: dict[tuple[str, str], dict] = {}for row in odm.to_dicts(): if row["StudyEventOID"] != "SE.D1": continue if not row["FormOID"].startswith("FO.VS_"): continue key = (row["SubjectKey"], row["FormOID"]) entry = forms.setdefault( key, { "subject": row["SubjectKey"], "study": row["StudyOID"], "form": row["FormOID"], "dt": None, "results": {}, }, ) if row["ItemOID"] == "IT.VS.VSDTC": entry["dt"] = row["Value"] elif row["ItemOID"] in test_map: entry["results"][row["ItemOID"]] = row["Value"]records = []for entry in forms.values(): point = time_point(entry["form"]) if point is None or entry["dt"] in (None, ""): continue vstpt, vstptnum, vseltm = point coll = parse_date(entry["dt"]) vsdy = study_day(ref_map.get(entry["subject"]), coll) for item, value in entry["results"].items(): if value is None or value == "": continue testcd, unit = test_map[item] records.append( { "DOMAIN": "VS", "STUDYID": entry["study"], "USUBJID": entry["subject"], "VISIT": "DAY 1", "VSTPT": vstpt, "VSTPTNUM": vstptnum, "VSELTM": vseltm, "VSTESTCD": testcd, "_tord": test_order.get(testcd, 9), "VSORRES": value, "VSORRESU": unit, "VSSTRESN": float(value), "VSSTRESU": unit, "VSDTC": entry["dt"], "VSDY": vsdy, } )vs = ( pl.DataFrame(records) .sort(["USUBJID", "VSTPTNUM", "_tord"]) .with_columns( VSSEQ=pl.int_range(1, pl.len() + 1).over("USUBJID"), ) .sort(["USUBJID", "VSSEQ"]) .select( "DOMAIN", "STUDYID", "USUBJID", "VSSEQ", "VISIT", "VSTPT", "VSTPTNUM", "VSELTM", "VSTESTCD", "VSORRES", "VSORRESU", "VSSTRESN", "VSSTRESU", "VSDTC", "VSDY", ))Path("/app/output").mkdir(exist_ok=True)vs.write_csv("/app/output/vs.csv")solutions/sdtm-vs-planned-time-points/result.sas
Edit53 lines/* opensas reference solution for the yamaa benchmark sdtm-vs-planned-time-points. Reads the benchmark inputs and derives the requested output. Execute with the existing opensas runtime: opensas /app/output/result.sas. */data dm; length STUDYID USUBJID RFSTDTC $1024; infile "/app/input/dm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. RFSTDTC : $char1024.;run;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;data vs_mapping; length ItemOID VSTESTCD VSTEST UNIT $1024; infile "/app/input/vs_mapping.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input ItemOID : $char1024. VSTESTCD : $char1024. VSTEST : $char1024. UNIT : $char1024.;run;proc sort data=odm out=ordered; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey FormOID FormRepeatKey; run;data dates; length VSDTC $1024; retain VSDTC; set ordered; where ItemGroupOID='IG.VS'; by StudyOID SubjectKey StudyEventOID StudyEventRepeatKey FormOID FormRepeatKey; if first.FormRepeatKey then do; VSDTC=''; end; if ItemOID='IT.VS.VSDTC' then VSDTC=Value; if last.FormRepeatKey then output;run;proc sql; create table joined as select a.*,b.VSDTC,c.VSTESTCD,c.UNIT,d.RFSTDTC 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.FormOID=b.FormOID and a.FormRepeatKey=b.FormRepeatKey inner join vs_mapping c on a.ItemOID=c.ItemOID left join dm d on a.StudyOID=d.STUDYID and a.SubjectKey=d.USUBJID; quit;data prepared; set joined; length DOMAIN $2 STUDYID USUBJID VISIT VSTPT VSELTM VSORRES VSORRESU VSSTRESU $1024; DOMAIN='VS'; STUDYID=StudyOID; USUBJID=SubjectKey; VISIT='DAY 1'; VSTPT=''; VSELTM=''; VSTPTNUM=.;if FormOID='FO.VS_PREDOSE' then do; VSTPT='PRE-DOSE'; VSTPTNUM=1; VSELTM='-PT15M'; end;if FormOID='FO.VS_30MIN' then do; VSTPT='30 MIN POST-DOSE'; VSTPTNUM=2; VSELTM='PT30M'; end;if FormOID='FO.VS_1H' then do; VSTPT='1 H POST-DOSE'; VSTPTNUM=3; VSELTM='PT1H'; end;if FormOID='FO.VS_4H' then do; VSTPT='4 H POST-DOSE'; VSTPTNUM=4; VSELTM='PT4H'; end;VSORRES=Value; VSORRESU=UNIT; VSSTRESU=UNIT; VSSTRESN=input(Value,best32.); VSDY=.; if lengthn(VSDTC)>=10 and lengthn(RFSTDTC)>=10 then do; VSDY=input(substr(VSDTC,1,10),yymmdd10.)-input(substr(RFSTDTC,1,10),yymmdd10.); if VSDY>=0 then VSDY+1; end;TESTORD=3; if VSTESTCD='PULSE' then TESTORD=1; if VSTESTCD='SYSBP' then TESTORD=2; run;proc sort data=prepared; by STUDYID USUBJID VSTPTNUM TESTORD; run;data result; set prepared; by STUDYID USUBJID; retain VSSEQ; if first.USUBJID then VSSEQ=0; VSSEQ+1;run;proc sql; create table final as select DOMAIN, STUDYID, USUBJID, VSSEQ, VISIT, VSTPT, VSTPTNUM, VSELTM, VSTESTCD, VSORRES, VSORRESU, VSSTRESN, VSSTRESU, VSDTC, VSDY from result;quit;proc export data=final outfile="/app/output/vs.csv" dbms=csv replace; run;
yamaa
Comments
Signed in with GitHub; saved in repository Discussions