Review ask

As a clinical trial statistician or programmer, answer three short questions in Your review: the task, the inputs and outputs, and the AI-assisted review. Agreeing with all three approves this benchmark; disagreeing with any needs a comment. Propose a fix with the Edit button on any source file, or add it to your comments.

We suggest spending no more than 5 minutes on a review. The other tabs beside Your review are background for assessment, not under review.

Summary

Lifecycle: draftEditUnder review
What this benchmark means
README.md

Public entries: Python and R. Passing coverage requires an installed inventory.

Goal: following the FDA technical specification for patient-reported outcome (PRO) data in cancer trials, give every randomized subject a record for each item and the total score of a two-item fatigue measure at each planned assessment, adding a phantom record for each one not collected, with the analysis value (AVAL), the record type (DTYPE), the analysis reason not performed (AREASND), the PRO expected flags for clinical benefit (PROEX1FL) and for safety and tolerability (PROEX2FL), and the score completed flag (PROSCMFL).

Input: one subject-level record per randomized subject with the randomization date, the safety population flag, the date and reason treatment ended, and the death date; the planned PRO schedule, one record per planned timepoint and parameter, with its planned day counted from randomization as day 1; and the collected questionnaire item records, each at its planned visit, holding a response on a 0 to 4 scale or a not-done status with its reason.

Variables:

  • PARCAT2, PARAMCD, PARAM, AVISIT and AVISITN: the planned parameter, ITEM or TOTAL SCORE in PARCAT2, and the planned visit, from the schedule.
  • AVAL: the item's response; on the total score (FATTOT), the sum of both items, empty unless both were answered.
  • DTYPE: PHANTOM on an item record the questionnaire data does not hold, and on the total score at a timepoint where neither item was collected; empty otherwise.
  • QSSTAT and QSREASND: the collected item's completion status and reason not done, empty on a phantom record and on the total score.
  • AREASND: empty when AVAL has a value. On a phantom record, NOT TREATED for a subject outside the safety population, the reason treatment ended at a timepoint planned after the end of treatment, and empty otherwise. On a collected item, its reason not done; on the total score, the reason its items carry, or NOT CALCULABLE when they carry none.
  • PROEX1FL: Y at every timepoint not planned after the subject's death.
  • PROEX2FL: Y for a subject in the safety population at a timepoint planned after neither the end of treatment nor death; empty otherwise.
  • PROSCMFL: Y when AVAL has a value.

Note: a timepoint is planned after an event when its planned day is later than the day of the event, both counted from randomization as day 1, so a timepoint planned on the day of the event is still expected. A subject whose treatment has not ended is expected for safety at every timepoint before death, and records collected after the end of treatment are kept.

Inputs

Under review
3 source files

input/adsl.csv

Edit
4 rows
input/adsl.csv
STUDYIDUSUBJIDRANDDTSAFFLEOTDTDCTREASDTHDT
YAMAA-012024-01-08Y———
YAMAA-012024-01-15Y2024-02-05DEATH2024-02-05
YAMAA-012024-01-22Y2024-02-23PROGRESSIVE DISEASE—
YAMAA-012024-01-29N———

input/qs.csv

Edit
12 rows
input/qs.csv
STUDYIDUSUBJIDQSSEQQSCATQSTESTCDQSTESTQSSTRESNQSSTATQSREASNDVISITNUMVISIT
YAMAA-011FATIGUE SCALEFAT01Fatigue severity2——1CYCLE 1 DAY 1
YAMAA-012FATIGUE SCALEFAT02Fatigue interference with daily activities3——1CYCLE 1 DAY 1
YAMAA-013FATIGUE SCALEFAT01Fatigue severity—NOT DONE—2CYCLE 2 DAY 1
YAMAA-014FATIGUE SCALEFAT02Fatigue interference with daily activities1——2CYCLE 2 DAY 1
YAMAA-015FATIGUE SCALEFAT01Fatigue severity—NOT DONEPATIENT REFUSAL3CYCLE 3 DAY 1
YAMAA-016FATIGUE SCALEFAT02Fatigue interference with daily activities—NOT DONEPATIENT REFUSAL3CYCLE 3 DAY 1
YAMAA-011FATIGUE SCALEFAT01Fatigue severity1——1CYCLE 1 DAY 1
YAMAA-012FATIGUE SCALEFAT02Fatigue interference with daily activities1——1CYCLE 1 DAY 1
YAMAA-011FATIGUE SCALEFAT01Fatigue severity0——1CYCLE 1 DAY 1
YAMAA-012FATIGUE SCALEFAT02Fatigue interference with daily activities2——1CYCLE 1 DAY 1
YAMAA-013FATIGUE SCALEFAT01Fatigue severity3——3CYCLE 3 DAY 1
YAMAA-014FATIGUE SCALEFAT02Fatigue interference with daily activities4——3CYCLE 3 DAY 1

input/schedule.csv

Edit
9 rows
input/schedule.csv
STUDYIDVISITNUMVISITVISITDYPARCAT2PARAMCDPARAM
YAMAA-011CYCLE 1 DAY 11ITEMFAT01Fatigue severity
YAMAA-011CYCLE 1 DAY 11ITEMFAT02Fatigue interference with daily activities
YAMAA-011CYCLE 1 DAY 11TOTAL SCOREFATTOTFatigue total score
YAMAA-012CYCLE 2 DAY 122ITEMFAT01Fatigue severity
YAMAA-012CYCLE 2 DAY 122ITEMFAT02Fatigue interference with daily activities
YAMAA-012CYCLE 2 DAY 122TOTAL SCOREFATTOTFatigue total score
YAMAA-013CYCLE 3 DAY 143ITEMFAT01Fatigue severity
YAMAA-013CYCLE 3 DAY 143ITEMFAT02Fatigue interference with daily activities
YAMAA-013CYCLE 3 DAY 143TOTAL SCOREFATTOTFatigue total score

Expected output

Under review
36 expected rows

expected/adqs.csv

Edit
36 rows
expected/adqs.csv
STUDYIDUSUBJIDPARCAT2PARAMCDPARAMAVISITAVISITNAVALDTYPEQSSTATQSREASNDAREASNDPROEX1FLPROEX2FLPROSCMFL
YAMAA-01ITEMFAT01Fatigue severityCYCLE 1 DAY 112————YYY
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 1 DAY 113————YYY
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 1 DAY 115————YYY
YAMAA-01ITEMFAT01Fatigue severityCYCLE 2 DAY 12——NOT DONE——YY—
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 2 DAY 121————YYY
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 2 DAY 12————NOT CALCULABLEYY—
YAMAA-01ITEMFAT01Fatigue severityCYCLE 3 DAY 13——NOT DONEPATIENT REFUSALPATIENT REFUSALYY—
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 3 DAY 13——NOT DONEPATIENT REFUSALPATIENT REFUSALYY—
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 3 DAY 13————PATIENT REFUSALYY—
YAMAA-01ITEMFAT01Fatigue severityCYCLE 1 DAY 111————YYY
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 1 DAY 111————YYY
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 1 DAY 112————YYY
YAMAA-01ITEMFAT01Fatigue severityCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01ITEMFAT01Fatigue severityCYCLE 3 DAY 13—PHANTOM——DEATH———
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 3 DAY 13—PHANTOM——DEATH———
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 3 DAY 13—PHANTOM——DEATH———
YAMAA-01ITEMFAT01Fatigue severityCYCLE 1 DAY 110————YYY
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 1 DAY 112————YYY
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 1 DAY 112————YYY
YAMAA-01ITEMFAT01Fatigue severityCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 2 DAY 12—PHANTOM———YY—
YAMAA-01ITEMFAT01Fatigue severityCYCLE 3 DAY 133————Y—Y
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 3 DAY 134————Y—Y
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 3 DAY 137————Y—Y
YAMAA-01ITEMFAT01Fatigue severityCYCLE 1 DAY 11—PHANTOM——NOT TREATEDY——
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 1 DAY 11—PHANTOM——NOT TREATEDY——
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 1 DAY 11—PHANTOM——NOT TREATEDY——
YAMAA-01ITEMFAT01Fatigue severityCYCLE 2 DAY 12—PHANTOM——NOT TREATEDY——
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 2 DAY 12—PHANTOM——NOT TREATEDY——
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 2 DAY 12—PHANTOM——NOT TREATEDY——
YAMAA-01ITEMFAT01Fatigue severityCYCLE 3 DAY 13—PHANTOM——NOT TREATEDY——
YAMAA-01ITEMFAT02Fatigue interference with daily activitiesCYCLE 3 DAY 13—PHANTOM——NOT TREATEDY——
YAMAA-01TOTAL SCOREFATTOTFatigue total scoreCYCLE 3 DAY 13—PHANTOM——NOT TREATEDY——
Shaded columns are derived— = missing value

Human review and assessment

Your review

3 questions and your comments · View benchmark discussions

Mapping spec

Generated from the benchmark YAML; do not edit by hand

Dataset NameVariable NameVariable LabelTypeCodelist/Controlled TermsCoreComputational MethodOrigin
ADQSSTUDYIDStudy IdentifiercharacterSet per constructed row; see the Row construction sheet.Derived
ADQSUSUBJIDUnique Subject IdentifiercharacterSet per constructed row; see the Row construction sheet.Derived
ADQSPARCAT2Parameter Category 2characterSet per constructed row; see the Row construction sheet.Derived
ADQSPARAMCDParameter CodecharacterSet per constructed row; see the Row construction sheet.Derived
ADQSPARAMParametercharacterSet per constructed row; see the Row construction sheet.Derived
ADQSAVISITAnalysis VisitcharacterSet per constructed row; see the Row construction sheet.Derived
ADQSAVISITNAnalysis Visit (N)numericSet per constructed row; see the Row construction sheet.Derived
ADQSPROEX1FLClinical Benefit PRO Expected FlagcharacterYSet per constructed row; see the Row construction sheet.Derived
ADQSPROEX2FLSafety/Tolerability PRO Expected FlagcharacterYSet per constructed row; see the Row construction sheet.Derived
ADQSQSSTATCompletion StatuscharacterTake QSSTAT from the QS record whose STUDYID equals STUDYID, USUBJID equals USUBJID, VISITNUM equals AVISITN and QSTESTCD equals PARAMCD, and no match yields blank.Derived
ADQSQSREASNDReason Not PerformedcharacterTake QSREASND from the QS record whose STUDYID equals STUDYID, USUBJID equals USUBJID, VISITNUM equals AVISITN and QSTESTCD equals PARAMCD, and no match yields blank.Derived
ADQSAVALAnalysis ValuenumericIf PARCAT2 = 'ITEM' then "ITEMRESN"; If ITEMANS = 2 then "ITEMSUM"; otherwise blank.Derived
ADQSDTYPEDerivation TypecharacterPHANTOMIf PARCAT2 = 'ITEM' AND ITEMSEQ IS NULL then "PHANTOM"; If PARCAT2 <> 'ITEM' AND ITEMCOL = 0 then "PHANTOM"; otherwise blank.Derived
ADQSAREASNDAnalysis Reason Not PerformedcharacterIf AVAL IS NOT NULL then "None"; If DTYPE = 'PHANTOM' then "PHANREAS"; If PARCAT2 = 'ITEM' then "QSREASND"; If ITEMREAS IS NOT NULL then "ITEMREAS"; otherwise "NOT CALCULABLE".Derived
ADQSPROSCMFLPRO Score Completed FlagcharacterIf AVAL IS NOT NULL then "Y"; otherwise blank.Derived
TemplateInclude whenSTUDYIDUSUBJIDPARCAT2PARAMCDPARAMAVISITAVISITNPROEX1FLPROEX2FLPHANREAS
plannedCopy STUDYID from the SUBJ record matching on STUDYID, where STUDYID is derived as STUDYID from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blankCopy USUBJID from the SUBJ record matching on STUDYID, where USUBJID is derived as USUBJID from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blankCopy PARCAT2 from the SUBJ record matching on STUDYID, where PARCAT2 is derived as SCHED.PARCAT2, and no match yields blankCopy PARAMCD from the SUBJ record matching on STUDYID, where PARAMCD is derived as SCHED.PARAMCD, and no match yields blankCopy PARAM from the SUBJ record matching on STUDYID, where PARAM is derived as SCHED.PARAM, and no match yields blankCopy VISIT from the SUBJ record matching on STUDYID, where VISIT is derived as SCHED.VISIT, and no match yields blankCopy VISITNUM from the SUBJ record matching on STUDYID, where VISITNUM is derived as SCHED.VISITNUM, and no match yields blankIf VISITDY > DTHDY, with VISITDY taken from the SUBJ record matching on STUDYID, where VISITDY is derived as SCHED.VISITDY, and no match yields blank and DTHDY taken from the SUBJ record matching on STUDYID, where DTHDY is derived as DTHDY from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank then "None"; otherwise "Y"If SAFFL = 'Y' AND (EOTDY IS NULL OR VISITDY <= EOTDY) AND (DTHDY IS NULL OR VISITDY <= DTHDY), with SAFFL taken from the SUBJ record matching on STUDYID, where SAFFL is derived as SAFFL from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank, EOTDY taken from the SUBJ record matching on STUDYID, where EOTDY is derived as EOTDY from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank, VISITDY taken from the SUBJ record matching on STUDYID, where VISITDY is derived as SCHED.VISITDY, and no match yields blank and DTHDY taken from the SUBJ record matching on STUDYID, where DTHDY is derived as DTHDY from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank then "Y"; otherwise blankIf SAFFL = 'N', with SAFFL taken from the SUBJ record matching on STUDYID, where SAFFL is derived as SAFFL from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank then "NOT TREATED"; If VISITDY > EOTDY, with VISITDY taken from the SUBJ record matching on STUDYID, where VISITDY is derived as SCHED.VISITDY, and no match yields blank and EOTDY taken from the SUBJ record matching on STUDYID, where EOTDY is derived as EOTDY from the ADSL record matching on the shared output keys, blank when unmatched, and no match yields blank then "PLANNED.DCTREAS"; otherwise blank
VariablePermitted values
PROEX1FLY
PROEX2FLY
DTYPEPHANTOM
VersionDateAuthorDescriptionReviewerSign-off
1.72026-10-20yamaa docs buildRead a specification in the 1.0 surface as its 0.5 counterpart; state that missing values stay missing where a mapping declares no missing handler, and leave out a declared missing answer that restates a handler's default
1.62026-10-17yamaa docs buildDescribe collected date parts written as ISO 8601 date text and times of day counted in seconds since midnight
1.52026-10-17yamaa docs buildName collected precision in words instead of letter codes; state imputation fallbacks, minimum source precision, bounds ignored outside the collected period, and invalid text with no handler as an error; describe date and datetime shifts, imputation flag codes, and partial text giving no date part
1.42026-10-15yamaa docs buildState datetime precision codes through the hour and minute, and complete only the time parts a collected datetime lacks
1.32026-09-29yamaa docs buildShow study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata
1.22026-09-29yamaa docs buildClassify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived
1.12026-09-29yamaa docs buildResolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML
1.0yamaa docs buildInitial generation from spec.yaml

Agent prompt

The request an agent receives with the input files; each tier states no more than the one above

prompts/adam-adqs-pro-phantom-records/full.md

Edit
42 lines

Following CDISC ADaM standards, use the provided ADSL, QS, and SCHEDULE datasets to create an ADQS dataset with one record per subject per parameter per planned visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARCAT2, PARAMCD, PARAM, AVISIT, AVISITN, AVAL, DTYPE, QSSTAT, QSREASND, AREASND, PROEX1FL, PROEX2FL, PROSCMFL

Every ADSL subject, in the safety population or not, has a record for each SCHEDULE parameter (items FAT01 and FAT02, total score FATTOT) at each planned visit. PARCAT2 ("ITEM" or "TOTAL SCORE"), PARAMCD and PARAM are the planned parameter's, and AVISIT and AVISITN its VISIT and VISITNUM, from SCHEDULE. An item is collected at a visit when QS holds a record of the subject with that VISITNUM and with QSTESTCD equal to the item's PARAMCD, answered or not done. A phantom record is an item record not collected, or a FATTOT record at a visit where neither item was collected.

  • AVAL is the item's QSSTRESN; on FATTOT it is the sum of the FAT01 and FAT02 responses at the visit, with no value unless both were answered.
  • DTYPE is "PHANTOM" on a phantom record and has no value otherwise.
  • QSSTAT and QSREASND are the collected item's, with no value on a phantom record and on FATTOT.
  • AREASND has no value when AVAL has a value. Otherwise, on a phantom record it is "NOT TREATED" for a subject whose SAFFL is "N", else the reason treatment ended (DCTREAS) at a visit planned after the end of treatment, else no value. On a collected item it is its QSREASND; on a FATTOT record that is not phantom, the QSREASND its items carry, or "NOT CALCULABLE" when neither carries one.
  • PROEX1FL is "Y" for every subject at each visit not planned after death.
  • PROEX2FL is "Y" for a subject whose SAFFL is "Y" at a visit planned after neither the end of treatment nor death.
  • PROSCMFL is "Y" when AVAL has a value.
  • PROEX1FL, PROEX2FL and PROSCMFL have no value where they are not "Y".

A visit is planned after an event when its VISITDY is later than the event's day, both counted from RANDDT as day 1, with EOTDT the end of treatment and DTHDT the death. No visit is planned after an event with no date, and a visit planned on the event's day is still expected. Items collected at a visit planned after the end of treatment are reported with their responses.

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

prompts/adam-adqs-pro-phantom-records/conventions.md

Edit
17 lines

Following CDISC ADaM standards, use the provided ADSL, QS, and SCHEDULE datasets to create an ADQS dataset with one record per subject per parameter per planned visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARCAT2, PARAMCD, PARAM, AVISIT, AVISITN, AVAL, DTYPE, QSSTAT, QSREASND, AREASND, PROEX1FL, PROEX2FL, PROSCMFL

The parameters are items FAT01 and FAT02 and total score FATTOT, and PARCAT2 is "ITEM" or "TOTAL SCORE". DTYPE is "PHANTOM" or has no value. AREASND is "NOT TREATED", the reason treatment ended (DCTREAS), a QSREASND, "NOT CALCULABLE", or has no value. PROEX1FL, PROEX2FL and PROSCMFL are "Y" or have no value. VISITDY and an event's day are both counted from RANDDT as day 1.

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

prompts/adam-adqs-pro-phantom-records/brief.md

Edit
10 lines

Following CDISC ADaM standards, use the provided ADSL, QS, and SCHEDULE datasets to create an ADQS dataset with one record per subject per parameter per planned visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARCAT2, PARAMCD, PARAM, AVISIT, AVISITN, AVAL, DTYPE, QSSTAT, QSREASND, AREASND, PROEX1FL, PROEX2FL, PROSCMFL

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

yamaa code

Public calls generated from benchmark.yaml; results must match the expected output

R

Edit
3 lines
library(yamaa)yamaa_spec("spec.yaml")$run()$output

Python

Edit
3 lines
import yamaayamaa.spec("spec.yaml").run().output

Solution without yamaa

Written from the full prompt and the inputs alone; CI checks it reproduces the expected output

solutions/adam-adqs-pro-phantom-records/result.R

Edit
114 lines
# Reference solution for the yamaa benchmark adam-adqs-pro-phantom-records (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.suppressPackageStartupMessages(library(readr))in_dir <- "/app/input"out_dir <- "/app/output"dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)read_chr <- function(name) {  d <- as.data.frame(read_csv(file.path(in_dir, name),                              col_types = cols(.default = col_character()),                              na = character(), trim_ws = TRUE))  d[] <- lapply(d, function(x) ifelse(is.na(x) | trimws(x) == "", NA_character_, x))  d}adsl  <- read_chr("adsl.csv")qs    <- read_chr("qs.csv")sched <- read_chr("schedule.csv")sched$VISITNUM  <- as.numeric(sched$VISITNUM)sched$VISITDY   <- as.numeric(sched$VISITDY)sched$sched_ord <- seq_len(nrow(sched))qs$VISITNUM <- as.numeric(qs$VISITNUM)qs$QSSTRESN <- as.numeric(qs$QSSTRESN)qs$QSSEQ    <- as.numeric(qs$QSSEQ)items <- unique(sched$PARAMCD[sched$PARCAT2 == "ITEM"])tot_cd <- unique(sched$PARAMCD[sched$PARCAT2 == "TOTAL SCORE"])# Event study days with RANDDT as day 1randdt <- as.Date(adsl$RANDDT)adsl$EOTDY <- as.numeric(as.Date(adsl$EOTDT) - randdt) + 1adsl$DTHDY <- as.numeric(as.Date(adsl$DTHDT) - randdt) + 1# Frame: every ADSL subject x every planned parameter/visitsubj <- adsl[, c("USUBJID", "SAFFL", "DCTREAS", "EOTDY", "DTHDY")]fr <- merge(subj, sched, by = NULL)fr$after_eot <- !is.na(fr$EOTDY) & fr$VISITDY > fr$EOTDYfr$after_dth <- !is.na(fr$DTHDY) & fr$VISITDY > fr$DTHDY# Collected item records: one per subject/visit/item (first by QSSEQ)qi <- qs[qs$QSTESTCD %in% items, ]qi <- qi[order(qi$USUBJID, qi$VISITNUM, qi$QSTESTCD, qi$QSSEQ), ]qi <- qi[!duplicated(qi[, c("USUBJID", "VISITNUM", "QSTESTCD")]), ]qi <- data.frame(USUBJID = qi$USUBJID, VISITNUM = qi$VISITNUM, PARAMCD = qi$QSTESTCD,                 QSSTRESN = qi$QSSTRESN, QS_STAT = qi$QSSTAT, QS_REASND = qi$QSREASND,                 collected = TRUE, stringsAsFactors = FALSE)# Total score summary per subject/visitkeys <- unique(qi[, c("USUBJID", "VISITNUM")])tot <- do.call(rbind, lapply(seq_len(nrow(keys)), function(i) {  g <- qi[qi$USUBJID == keys$USUBJID[i] & qi$VISITNUM == keys$VISITNUM[i], ]  g <- g[match(items, g$PARAMCD), ]  answered <- !is.na(g$PARAMCD) & !is.na(g$QSSTRESN)  r <- unique(g$QS_REASND[!is.na(g$QS_REASND)])  data.frame(USUBJID = keys$USUBJID[i], VISITNUM = keys$VISITNUM[i],             n_coll = sum(!is.na(g$PARAMCD)),             tot_val = if (all(answered)) sum(g$QSSTRESN) else NA_real_,             tot_reasnd = if (length(r) == 0) NA_character_ else r[1],             stringsAsFactors = FALSE)}))d <- merge(fr, qi, by = c("USUBJID", "VISITNUM", "PARAMCD"), all.x = TRUE)d <- merge(d, tot, by = c("USUBJID", "VISITNUM"), all.x = TRUE)d$collected[is.na(d$collected)] <- FALSEd$n_coll[is.na(d$n_coll)] <- 0is_tot  <- d$PARAMCD %in% tot_cdphantom <- ifelse(is_tot, d$n_coll == 0, !d$collected)aval <- ifelse(is_tot, d$tot_val, d$QSSTRESN)aval[phantom] <- NA_real_qsstat   <- ifelse(phantom | is_tot, NA_character_, d$QS_STAT)qsreasnd <- ifelse(phantom | is_tot, NA_character_, d$QS_REASND)areasnd <- rep(NA_character_, nrow(d))need <- is.na(aval)ph_nt  <- need & phantom & d$SAFFL %in% "N"ph_eot <- need & phantom & !ph_nt & d$after_eotareasnd[ph_nt]  <- "NOT TREATED"areasnd[ph_eot] <- d$DCTREAS[ph_eot]ci <- need & !phantom & !is_totareasnd[ci] <- d$QS_REASND[ci]ct <- need & !phantom & is_totareasnd[ct] <- ifelse(is.na(d$tot_reasnd[ct]), "NOT CALCULABLE", d$tot_reasnd[ct])out <- data.frame(  STUDYID  = d$STUDYID,  USUBJID  = d$USUBJID,  PARCAT2  = d$PARCAT2,  PARAMCD  = d$PARAMCD,  PARAM    = d$PARAM,  AVISIT   = d$VISIT,  AVISITN  = d$VISITNUM,  AVAL     = aval,  DTYPE    = ifelse(phantom, "PHANTOM", NA_character_),  QSSTAT   = qsstat,  QSREASND = qsreasnd,  AREASND  = areasnd,  PROEX1FL = ifelse(!d$after_dth, "Y", NA_character_),  PROEX2FL = ifelse(d$SAFFL %in% "Y" & !d$after_eot & !d$after_dth, "Y", NA_character_),  PROSCMFL = ifelse(!is.na(aval), "Y", NA_character_),  stringsAsFactors = FALSE)out <- out[order(out$USUBJID, out$AVISITN, d$sched_ord), ]rownames(out) <- NULLwrite_csv(out, file.path(out_dir, "adqs.csv"), na = "")

solutions/adam-adqs-pro-phantom-records/result.py

Edit
151 lines
# Reference solution for the yamaa benchmark adam-adqs-pro-phantom-records (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 plINPUT = Path("/app/input")OUTPUT = Path("/app/output")COLUMNS = [    "STUDYID", "USUBJID", "PARCAT2", "PARAMCD", "PARAM", "AVISIT", "AVISITN",    "AVAL", "DTYPE", "QSSTAT", "QSREASND", "AREASND", "PROEX1FL", "PROEX2FL",    "PROSCMFL",]ITEMS = ["FAT01", "FAT02"]TOTAL = "FATTOT"STR = pl.Utf8adsl = pl.read_csv(    INPUT / "adsl.csv",    schema_overrides={c: STR for c in                      ["STUDYID", "USUBJID", "RANDDT", "SAFFL", "EOTDT", "DCTREAS", "DTHDT"]},).with_columns(    [pl.col(c).str.strip_chars().replace("", None).str.to_date("%Y-%m-%d", strict=False)     for c in ["RANDDT", "EOTDT", "DTHDT"]])qs = pl.read_csv(    INPUT / "qs.csv",    schema_overrides={        "STUDYID": STR, "USUBJID": STR, "QSTESTCD": STR, "QSSTAT": STR,        "QSREASND": STR, "VISIT": STR, "QSSTRESN": pl.Float64, "VISITNUM": pl.Float64,    },)sched = pl.read_csv(    INPUT / "schedule.csv",    schema_overrides={"VISITNUM": pl.Float64, "VISITDY": pl.Float64,                      "VISIT": STR, "PARCAT2": STR, "PARAMCD": STR, "PARAM": STR},).with_row_index("_SCHORD")def blank_to_null(df, cols):    return df.with_columns(        [pl.when(pl.col(c).str.strip_chars() == "").then(None).otherwise(pl.col(c)).alias(c)         for c in cols]    )adsl = blank_to_null(adsl, ["SAFFL", "DCTREAS"])qs = blank_to_null(qs, ["QSSTAT", "QSREASND", "QSTESTCD"])# Study day of an event, counted from RANDDT as day 1.adsl = adsl.with_columns(    EOTDY=((pl.col("EOTDT") - pl.col("RANDDT")).dt.total_days() + 1).cast(pl.Float64),    DTHDY=((pl.col("DTHDT") - pl.col("RANDDT")).dt.total_days() + 1).cast(pl.Float64),)# Skeleton: every ADSL subject x every SCHEDULE parameter/visit.skel = adsl.select(    "STUDYID", "USUBJID", "SAFFL", "DCTREAS", "EOTDY", "DTHDY").join(    sched.select("_SCHORD", "VISITNUM", "VISIT", "VISITDY", "PARCAT2", "PARAMCD", "PARAM"),    how="cross",)# Collected items: one QS record per subject/visit/testcd (first by QSSEQ if duplicated).qs_items = (    qs.filter(pl.col("QSTESTCD").is_in(ITEMS))    .sort(["USUBJID", "VISITNUM", "QSTESTCD", "QSSEQ"])    .unique(subset=["USUBJID", "VISITNUM", "QSTESTCD"], keep="first", maintain_order=True)    .select(        "USUBJID", "VISITNUM",        pl.col("QSTESTCD").alias("PARAMCD"),        "QSSTRESN", "QSSTAT", "QSREASND",        pl.lit(True).alias("_COLL"),    ))# Visit-level summary for the total score.wide = skel.select("USUBJID", "VISITNUM").unique()for item in ITEMS:    sub = qs_items.filter(pl.col("PARAMCD") == item).select(        "USUBJID", "VISITNUM",        pl.col("QSSTRESN").alias(f"_{item}_N"),        pl.col("QSREASND").alias(f"_{item}_R"),        pl.col("_COLL").alias(f"_{item}_C"),    )    wide = wide.join(sub, on=["USUBJID", "VISITNUM"], how="left")wide = wide.with_columns(    [pl.col(f"_{i}_C").fill_null(False) for i in ITEMS]).with_columns(    _TOT_N=pl.when(pl.all_horizontal([pl.col(f"_{i}_N").is_not_null() for i in ITEMS]))    .then(pl.sum_horizontal([pl.col(f"_{i}_N") for i in ITEMS]))    .otherwise(None),    _TOT_C=pl.any_horizontal([pl.col(f"_{i}_C") for i in ITEMS]),    _TOT_R=pl.coalesce([pl.col(f"_{i}_R") for i in ITEMS]),).select("USUBJID", "VISITNUM", "_TOT_N", "_TOT_C", "_TOT_R")df = skel.join(qs_items, on=["USUBJID", "VISITNUM", "PARAMCD"], how="left").join(    wide, on=["USUBJID", "VISITNUM"], how="left")is_tot = pl.col("PARAMCD") == TOTALafter_eot = pl.col("EOTDY").is_not_null() & (pl.col("VISITDY") > pl.col("EOTDY"))after_dth = pl.col("DTHDY").is_not_null() & (pl.col("VISITDY") > pl.col("DTHDY"))df = df.with_columns(    _COLL=pl.when(is_tot).then(pl.col("_TOT_C")).otherwise(pl.col("_COLL").fill_null(False)),    AVAL=pl.when(is_tot).then(pl.col("_TOT_N")).otherwise(pl.col("QSSTRESN")),    _AFTER_EOT=after_eot,    _AFTER_DTH=after_dth,)phantom = ~pl.col("_COLL")df = df.with_columns(    DTYPE=pl.when(phantom).then(pl.lit("PHANTOM")).otherwise(None),    QSSTAT=pl.when(phantom | is_tot).then(None).otherwise(pl.col("QSSTAT")),    QSREASND=pl.when(phantom | is_tot).then(None).otherwise(pl.col("QSREASND")),    AREASND=pl.when(pl.col("AVAL").is_not_null()).then(None)    .when(phantom & (pl.col("SAFFL") == "N")).then(pl.lit("NOT TREATED"))    .when(phantom & pl.col("_AFTER_EOT")).then(pl.col("DCTREAS"))    .when(phantom).then(None)    .when(is_tot).then(pl.coalesce([pl.col("_TOT_R"), pl.lit("NOT CALCULABLE")]))    .otherwise(pl.col("QSREASND")),    PROEX1FL=pl.when(~pl.col("_AFTER_DTH")).then(pl.lit("Y")).otherwise(None),    PROEX2FL=pl.when((pl.col("SAFFL") == "Y") & ~pl.col("_AFTER_EOT") & ~pl.col("_AFTER_DTH"))    .then(pl.lit("Y")).otherwise(None),    PROSCMFL=pl.when(pl.col("AVAL").is_not_null()).then(pl.lit("Y")).otherwise(None),    AVISIT=pl.col("VISIT"),    AVISITN=pl.col("VISITNUM"),)# Integer-valued numerics are written without a trailing ".0".def as_int_if_whole(df, col):    s = df[col]    nn = s.drop_nulls()    if nn.len() == 0 or (nn == nn.round(0)).all():        return df.with_columns(pl.col(col).cast(pl.Int64))    return dfdf = df.sort(["USUBJID", "PARAMCD", "AVISITN"]).select(COLUMNS)df = as_int_if_whole(df, "AVAL")df = as_int_if_whole(df, "AVISITN")OUTPUT.mkdir(parents=True, exist_ok=True)df.write_csv(OUTPUT / "adqs.csv")

solutions/adam-adqs-pro-phantom-records/result.sas

Edit
88 lines
/* opensas reference solution for the yamaa benchmark adam-adqs-pro-phantom-records.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data adsl;  length STUDYID USUBJID SAFFL DCTREAS $1024;  infile "/app/input/adsl.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. RANDDT : yymmdd10. SAFFL : $char1024. EOTDT : yymmdd10. DCTREAS : $char1024. DTHDT : yymmdd10.;  /* Days counted from randomization as day 1, as the planned days are. */  if not missing(EOTDT) then EOTDY = EOTDT - RANDDT + 1;  if not missing(DTHDT) then DTHDY = DTHDT - RANDDT + 1;run;data schedule;  length STUDYID VISIT PARCAT2 PARAMCD PARAM $1024;  infile "/app/input/schedule.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. VISITNUM VISIT : $char1024. VISITDY PARCAT2 : $char1024. PARAMCD : $char1024. PARAM : $char1024.;run;data qs;  length STUDYID USUBJID QSCAT QSTESTCD QSTEST QSSTAT QSREASND VISIT $1024;  infile "/app/input/qs.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. QSSEQ QSCAT : $char1024. QSTESTCD : $char1024. QSTEST : $char1024. QSSTRESN QSSTAT : $char1024. QSREASND : $char1024. VISITNUM VISIT : $char1024.;run;/* Every randomized subject at every planned timepoint and parameter, with   the collected item record when there is one. */proc sql;  create table frame as select a.STUDYID,a.USUBJID,a.SAFFL,a.DCTREAS,a.EOTDY,a.DTHDY,b.VISITNUM,b.VISIT,b.VISITDY,b.PARCAT2,b.PARAMCD,b.PARAM from adsl as a inner join schedule as b on a.STUDYID=b.STUDYID;  create table joined as select f.*,i.QSSEQ,i.QSSTRESN,i.QSSTAT,i.QSREASND from frame as f left join qs as i on f.STUDYID=i.STUDYID and f.USUBJID=i.USUBJID and f.VISITNUM=i.VISITNUM and f.PARAMCD=i.QSTESTCD;quit;/* Per subject and visit: the sum and count of the answered items, how many   items were collected, and the reason the items carry. */proc sort data=qs out=items; by USUBJID VISITNUM; run;data visitagg;  length ITEMREAS $1024;  set items;  by USUBJID VISITNUM;  retain ITEMSUM ITEMANS ITEMCOL ITEMREAS;  if first.VISITNUM then do; ITEMSUM=.; ITEMANS=0; ITEMCOL=0; ITEMREAS=''; end;  ITEMCOL=ITEMCOL+1;  if not missing(QSSTRESN) then do; ITEMSUM=sum(ITEMSUM,QSSTRESN); ITEMANS=ITEMANS+1; end;  if not missing(QSREASND) and QSREASND > ITEMREAS then ITEMREAS=QSREASND;  if last.VISITNUM then output;  keep USUBJID VISITNUM ITEMSUM ITEMANS ITEMCOL ITEMREAS;run;proc sort data=joined; by USUBJID VISITNUM PARAMCD; run;data final;  length DTYPE AREASND PROEX1FL PROEX2FL PROSCMFL $1024;  merge joined(in=f) visitagg;  by USUBJID VISITNUM;  if f;  AVAL=.;  DTYPE='';  if PARCAT2='ITEM' then do;    AVAL=QSSTRESN;    if missing(QSSEQ) then DTYPE='PHANTOM';  end;  else do;    if ITEMANS=2 then AVAL=ITEMSUM;    if missing(ITEMCOL) or ITEMCOL=0 then DTYPE='PHANTOM';  end;  AREASND='';  if missing(AVAL) then do;    if DTYPE='PHANTOM' then do;      if SAFFL='N' then AREASND='NOT TREATED';      else if not missing(EOTDY) and VISITDY > EOTDY then AREASND=DCTREAS;    end;    else if PARCAT2='ITEM' then AREASND=QSREASND;    else if not missing(ITEMREAS) then AREASND=ITEMREAS;    else AREASND='NOT CALCULABLE';  end;  /* Clinical benefit expects the measure until death; safety and     tolerability from a treated subject until treatment ends or death. */  PROEX1FL='Y';  if not missing(DTHDY) and VISITDY > DTHDY then PROEX1FL='';  PROEX2FL='';  if SAFFL='Y' and (missing(EOTDY) or VISITDY <= EOTDY) and (missing(DTHDY) or VISITDY <= DTHDY) then PROEX2FL='Y';  PROSCMFL='';  if not missing(AVAL) then PROSCMFL='Y';run;proc sql;  create table out as select STUDYID, USUBJID, PARCAT2, PARAMCD, PARAM, VISIT as AVISIT, VISITNUM as AVISITN, AVAL, DTYPE, QSSTAT, QSREASND, AREASND, PROEX1FL, PROEX2FL, PROSCMFL from final order by USUBJID, AVISITN, PARAMCD;quit;proc export data=out outfile="/app/output/adqs.csv" dbms=csv replace; run;