As a clinical trial statistician or programmer, answer three short questions in Your review: the task, the inputs and outputs, and the AI-assisted review. Agreeing with all three approves this benchmark; disagreeing with any needs a comment. Propose a fix with the Edit button on any source file, or add it to your comments.
We suggest spending no more than 5 minutes on a review. The other tabs beside Your review are background for assessment, not under review.
Summary
README.mdPublic entries: Python and R. Passing coverage requires an installed inventory.
Goal: 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,AVISITandAVISITN: the planned parameter,ITEMorTOTAL SCOREinPARCAT2, 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:PHANTOMon an item record the questionnaire data does not hold, and on the total score at a timepoint where neither item was collected; empty otherwise.QSSTATandQSREASND: the collected item's completion status and reason not done, empty on a phantom record and on the total score.AREASND: empty whenAVALhas a value. On a phantom record,NOT TREATEDfor 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, orNOT CALCULABLEwhen they carry none.PROEX1FL:Yat every timepoint not planned after the subject's death.PROEX2FL:Yfor a subject in the safety population at a timepoint planned after neither the end of treatment nor death; empty otherwise.PROSCMFL:YwhenAVALhas 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 review3 source filesinput/adsl.csv
Edit4 rows| STUDYID | USUBJID | RANDDT | SAFFL | EOTDT | DCTREAS | DTHDT |
|---|---|---|---|---|---|---|
| YAMAA-01 | 2024-01-08 | Y | — | — | — | |
| YAMAA-01 | 2024-01-15 | Y | 2024-02-05 | DEATH | 2024-02-05 | |
| YAMAA-01 | 2024-01-22 | Y | 2024-02-23 | PROGRESSIVE DISEASE | — | |
| YAMAA-01 | 2024-01-29 | N | — | — | — |
input/qs.csv
Edit12 rows| STUDYID | USUBJID | QSSEQ | QSCAT | QSTESTCD | QSTEST | QSSTRESN | QSSTAT | QSREASND | VISITNUM | VISIT |
|---|---|---|---|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | FATIGUE SCALE | FAT01 | Fatigue severity | 2 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 2 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | 3 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 3 | FATIGUE SCALE | FAT01 | Fatigue severity | — | NOT DONE | — | 2 | CYCLE 2 DAY 1 | |
| YAMAA-01 | 4 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | 1 | — | — | 2 | CYCLE 2 DAY 1 | |
| YAMAA-01 | 5 | FATIGUE SCALE | FAT01 | Fatigue severity | — | NOT DONE | PATIENT REFUSAL | 3 | CYCLE 3 DAY 1 | |
| YAMAA-01 | 6 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | — | NOT DONE | PATIENT REFUSAL | 3 | CYCLE 3 DAY 1 | |
| YAMAA-01 | 1 | FATIGUE SCALE | FAT01 | Fatigue severity | 1 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 2 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | 1 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 1 | FATIGUE SCALE | FAT01 | Fatigue severity | 0 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 2 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | 2 | — | — | 1 | CYCLE 1 DAY 1 | |
| YAMAA-01 | 3 | FATIGUE SCALE | FAT01 | Fatigue severity | 3 | — | — | 3 | CYCLE 3 DAY 1 | |
| YAMAA-01 | 4 | FATIGUE SCALE | FAT02 | Fatigue interference with daily activities | 4 | — | — | 3 | CYCLE 3 DAY 1 |
input/schedule.csv
Edit9 rows| STUDYID | VISITNUM | VISIT | VISITDY | PARCAT2 | PARAMCD | PARAM |
|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | CYCLE 1 DAY 1 | 1 | ITEM | FAT01 | Fatigue severity |
| YAMAA-01 | 1 | CYCLE 1 DAY 1 | 1 | ITEM | FAT02 | Fatigue interference with daily activities |
| YAMAA-01 | 1 | CYCLE 1 DAY 1 | 1 | TOTAL SCORE | FATTOT | Fatigue total score |
| YAMAA-01 | 2 | CYCLE 2 DAY 1 | 22 | ITEM | FAT01 | Fatigue severity |
| YAMAA-01 | 2 | CYCLE 2 DAY 1 | 22 | ITEM | FAT02 | Fatigue interference with daily activities |
| YAMAA-01 | 2 | CYCLE 2 DAY 1 | 22 | TOTAL SCORE | FATTOT | Fatigue total score |
| YAMAA-01 | 3 | CYCLE 3 DAY 1 | 43 | ITEM | FAT01 | Fatigue severity |
| YAMAA-01 | 3 | CYCLE 3 DAY 1 | 43 | ITEM | FAT02 | Fatigue interference with daily activities |
| YAMAA-01 | 3 | CYCLE 3 DAY 1 | 43 | TOTAL SCORE | FATTOT | Fatigue total score |
Expected output
Under review36 expected rowsexpected/adqs.csv
Edit36 rows| STUDYID | USUBJID | PARCAT2 | PARAMCD | PARAM | AVISIT | AVISITN | AVAL | DTYPE | QSSTAT | QSREASND | AREASND | PROEX1FL | PROEX2FL | PROSCMFL |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 1 DAY 1 | 1 | 2 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 1 DAY 1 | 1 | 3 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 1 DAY 1 | 1 | 5 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 2 DAY 1 | 2 | — | — | NOT DONE | — | — | Y | Y | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 2 DAY 1 | 2 | 1 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 2 DAY 1 | 2 | — | — | — | — | NOT CALCULABLE | Y | Y | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 3 DAY 1 | 3 | — | — | NOT DONE | PATIENT REFUSAL | PATIENT REFUSAL | Y | Y | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 3 DAY 1 | 3 | — | — | NOT DONE | PATIENT REFUSAL | PATIENT REFUSAL | Y | Y | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 3 DAY 1 | 3 | — | — | — | — | PATIENT REFUSAL | Y | Y | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 1 DAY 1 | 1 | 1 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 1 DAY 1 | 1 | 1 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 1 DAY 1 | 1 | 2 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | DEATH | — | — | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | DEATH | — | — | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | DEATH | — | — | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 1 DAY 1 | 1 | 0 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 1 DAY 1 | 1 | 2 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 1 DAY 1 | 1 | 2 | — | — | — | — | Y | Y | Y | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | — | Y | Y | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 3 DAY 1 | 3 | 3 | — | — | — | — | Y | — | Y | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 3 DAY 1 | 3 | 4 | — | — | — | — | Y | — | Y | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 3 DAY 1 | 3 | 7 | — | — | — | — | Y | — | Y | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 1 DAY 1 | 1 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 1 DAY 1 | 1 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 1 DAY 1 | 1 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 2 DAY 1 | 2 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | ITEM | FAT01 | Fatigue severity | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | ITEM | FAT02 | Fatigue interference with daily activities | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | NOT TREATED | Y | — | — | |
| YAMAA-01 | TOTAL SCORE | FATTOT | Fatigue total score | CYCLE 3 DAY 1 | 3 | — | PHANTOM | — | — | NOT TREATED | Y | — | — |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADQS | STUDYID | Study Identifier | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | USUBJID | Unique Subject Identifier | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | PARCAT2 | Parameter Category 2 | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | PARAMCD | Parameter Code | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | PARAM | Parameter | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | AVISIT | Analysis Visit | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | AVISITN | Analysis Visit (N) | numeric | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADQS | PROEX1FL | Clinical Benefit PRO Expected Flag | character | Y | Set per constructed row; see the Row construction sheet. | Derived | |
| ADQS | PROEX2FL | Safety/Tolerability PRO Expected Flag | character | Y | Set per constructed row; see the Row construction sheet. | Derived | |
| ADQS | QSSTAT | Completion Status | character | Take 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 | ||
| ADQS | QSREASND | Reason Not Performed | character | Take 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 | ||
| ADQS | AVAL | Analysis Value | numeric | If PARCAT2 = 'ITEM' then "ITEMRESN"; If ITEMANS = 2 then "ITEMSUM"; otherwise blank. | Derived | ||
| ADQS | DTYPE | Derivation Type | character | PHANTOM | If PARCAT2 = 'ITEM' AND ITEMSEQ IS NULL then "PHANTOM"; If PARCAT2 <> 'ITEM' AND ITEMCOL = 0 then "PHANTOM"; otherwise blank. | Derived | |
| ADQS | AREASND | Analysis Reason Not Performed | character | If 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 | ||
| ADQS | PROSCMFL | PRO Score Completed Flag | character | If AVAL IS NOT NULL then "Y"; otherwise blank. | Derived |
| Template | Include when | STUDYID | USUBJID | PARCAT2 | PARAMCD | PARAM | AVISIT | AVISITN | PROEX1FL | PROEX2FL | PHANREAS |
|---|---|---|---|---|---|---|---|---|---|---|---|
| planned | Copy 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 blank | Copy 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 blank | Copy PARCAT2 from the SUBJ record matching on STUDYID, where PARCAT2 is derived as SCHED.PARCAT2, and no match yields blank | Copy PARAMCD from the SUBJ record matching on STUDYID, where PARAMCD is derived as SCHED.PARAMCD, and no match yields blank | Copy PARAM from the SUBJ record matching on STUDYID, where PARAM is derived as SCHED.PARAM, and no match yields blank | Copy VISIT from the SUBJ record matching on STUDYID, where VISIT is derived as SCHED.VISIT, and no match yields blank | Copy VISITNUM from the SUBJ record matching on STUDYID, where VISITNUM is derived as SCHED.VISITNUM, and no match yields blank | If 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 blank | If 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 |
| Variable | Permitted values |
|---|---|
| PROEX1FL | Y |
| PROEX2FL | Y |
| DTYPE | PHANTOM |
| Version | Date | Author | Description | Reviewer | Sign-off |
|---|---|---|---|---|---|
| 1.7 | 2026-10-20 | yamaa docs build | Read a specification in the 1.0 surface as its 0.5 counterpart; state that missing values stay missing where a mapping declares no missing handler, and leave out a declared missing answer that restates a handler's default | ||
| 1.6 | 2026-10-17 | yamaa docs build | Describe collected date parts written as ISO 8601 date text and times of day counted in seconds since midnight | ||
| 1.5 | 2026-10-17 | yamaa docs build | Name collected precision in words instead of letter codes; state imputation fallbacks, minimum source precision, bounds ignored outside the collected period, and invalid text with no handler as an error; describe date and datetime shifts, imputation flag codes, and partial text giving no date part | ||
| 1.4 | 2026-10-15 | yamaa docs build | State datetime precision codes through the hour and minute, and complete only the time parts a collected datetime lacks | ||
| 1.3 | 2026-09-29 | yamaa docs build | Show study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata | ||
| 1.2 | 2026-09-29 | yamaa docs build | Classify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived | ||
| 1.1 | 2026-09-29 | yamaa docs build | Resolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML | ||
| 1.0 | yamaa docs build | Initial generation from spec.yaml |
prompts/adam-adqs-pro-phantom-records/full.md
Edit42 linesFollowing 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
Edit17 linesFollowing 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
Edit10 linesFollowing 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.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adqs-pro-phantom-records/result.R
Edit114 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
Edit151 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
Edit88 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;
yamaa