Review ask

As a clinical trial statistician or programmer, challenge anything in this benchmark: describe a problem in Comments, or propose a fix with the Edit button on any source file.

  1. Task: Would a real study need this derivation, specified this way? Does it follow CDISC ADaM: variable names, controlled terms, record structure? Flag any rule that is ambiguous or that a sponsor would handle differently.
  2. Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
  3. Expected output: Would independent QC programming reproduce every value from these inputs and rules?

The other tabs beside Comments are background for assessment, not under review.

Summary

Lifecycle: draftEditUnder review
What this benchmark means
README.md

Goal: carry each questionnaire item response into PARAMCD, PARAM, and AVAL, and add one scale score record per scale per visit, coded F1SCORE, F2SCORE, SSCORE, and GSCORE, holding the mean answered-item response transformed to a zero to one hundred scale.

Input: collected questionnaire responses for a ten-item instrument, with study, subject, and visit (STUDYID, USUBJID, VISIT), category (QSCAT), test code and test name (QSTESTCD, QSTEST), and numeric result (QSSTRESN): four physical functioning items and two role functioning items on the one to four answer scale, three fatigue and sleep symptom items on the one to four answer scale, and two global health items on the one to seven answer scale.

Variables:

  • PARAMCD: the test code (F101 through F104, F201, F202, S01, S02, G01, G02) on an item record, or the scale code (F1SCORE, F2SCORE, SSCORE, GSCORE) on an added score record.
  • PARAM: the test name on an item record, or the scale name on the added score record.
  • AVAL: on an item record, the numeric result on its answer scale, empty when the item was not answered; on a score record, the scale's mean answered-item response on a zero to one hundred scale, where the functioning scales read higher for better functioning, the symptom scale reads higher for worse symptoms, and the global scale reads higher for better health; empty when too few of the scale's items were answered.

Note: every visit with an anchor item record (F101, F201, S01, or G01), answered or not, carries that scale's score record, so a visit with too few answers to score keeps an empty score and stays apart from a visit with no records at all. One item, F104, belongs to two scales and feeds both of their scores.

Inputs

Under review
1 source file

input/qs.csv

Edit
14 rows
input/qs.csv
STUDYIDUSUBJIDVISITQSSEQQSCATQSTESTCDQSTESTQSSTRESN
MSQ1BASELINE1PHYSICAL FUNCTIONING SCALEF101Difficulty lifting heavy objects2
MSQ1BASELINE2PHYSICAL FUNCTIONING SCALEF102Difficulty walking a long distance2
MSQ1BASELINE3PHYSICAL FUNCTIONING SCALEF103Difficulty walking a short distance2
MSQ1BASELINE4PHYSICAL FUNCTIONING SCALEF104Needing to rest during the day2
MSQ1BASELINE5ROLE FUNCTIONING SCALEF201Limits in doing work or daily activities2
MSQ1BASELINE6ROLE FUNCTIONING SCALEF202Limits in pursuing hobbies or leisure activities3
MSQ1BASELINE7FATIGUE AND SLEEP SYMPTOM SCALES01Feeling tired during the day2
MSQ1BASELINE8FATIGUE AND SLEEP SYMPTOM SCALES02Trouble falling asleep2
MSQ1BASELINE9GLOBAL HEALTH SCALEG01Rating of overall health4
MSQ1BASELINE10GLOBAL HEALTH SCALEG02Rating of overall quality of life5
MSQ1BASELINE1PHYSICAL FUNCTIONING SCALEF101Difficulty lifting heavy objects3
MSQ1BASELINE2ROLE FUNCTIONING SCALEF201Limits in doing work or daily activities4
MSQ1BASELINE3FATIGUE AND SLEEP SYMPTOM SCALES01Feeling tired during the day—
MSQ1BASELINE4GLOBAL HEALTH SCALEG01Rating of overall health7

Expected output

Under review
22 expected rows

expected/adqs.csv

Edit
22 rows
expected/adqs.csv
STUDYIDUSUBJIDVISITPARAMCDPARAMAVAL
MSQ1BASELINEF101Difficulty lifting heavy objects2
MSQ1BASELINEF102Difficulty walking a long distance2
MSQ1BASELINEF103Difficulty walking a short distance2
MSQ1BASELINEF104Needing to rest during the day2
MSQ1BASELINEF201Limits in doing work or daily activities2
MSQ1BASELINEF202Limits in pursuing hobbies or leisure activities3
MSQ1BASELINES01Feeling tired during the day2
MSQ1BASELINES02Trouble falling asleep2
MSQ1BASELINEG01Rating of overall health4
MSQ1BASELINEG02Rating of overall quality of life5
MSQ1BASELINEF101Difficulty lifting heavy objects3
MSQ1BASELINEF201Limits in doing work or daily activities4
MSQ1BASELINES01Feeling tired during the day—
MSQ1BASELINEG01Rating of overall health7
MSQ1BASELINEF1SCOREPhysical Functioning Scale Score66.66666666666667
MSQ1BASELINEF1SCOREPhysical Functioning Scale Score—
MSQ1BASELINEF2SCORERole Functioning Scale Score50
MSQ1BASELINEF2SCORERole Functioning Scale Score0
MSQ1BASELINESSCOREFatigue and Sleep Symptom Scale Score33.333333333333336
MSQ1BASELINESSCOREFatigue and Sleep Symptom Scale Score—
MSQ1BASELINEGSCOREGlobal Health Scale Score58.333333333333336
MSQ1BASELINEGSCOREGlobal Health Scale Score100
Shaded columns are derived— = missing value

Comments and assessment materials

Comments

Signed in with GitHub; saved in repository Discussions

Mapping spec

Generated from the benchmark YAML; do not edit by hand

Dataset NameVariable NameVariable LabelTypeCodelist/Controlled TermsCoreComputational MethodOrigin
ADQSSTUDYIDStudy IdentifiercharacterCopy value from QS.STUDYID.Predecessor
ADQSUSUBJIDUnique Subject IdentifiercharacterCopy value from QS.USUBJID.Predecessor
ADQSVISITVisit NamecharacterCopy value from QS.VISIT.Predecessor
ADQSPARAMCDParameter CodecharacterF101, F102, F103, F104, F201, F202, S01, S02, G01, G02, F1SCORE, F2SCORE, SSCORE, GSCORESet per constructed row; see the Row construction sheet.Derived
ADQSPARAMParametercharacterSet per constructed row; see the Row construction sheet.Derived
ADQSAVALAnalysis ValuenumericSet per constructed row; see the Row construction sheet.Derived
TemplateInclude whenPARAMCDPARAMITEMRESNSCALE_ANCHORSCALE_SUMSCALE_ANSAVALSTUDYIDUSUBJIDVISIT
itemCopy QS.QSTESTCDCopy QS.QSTESTCopy QS.QSSTRESNConstant "None"Constant "None"Constant "None"Copy ITEMRESNNoneNoneNone
f1scoreSCALE_ANCHOR >= 1Constant "F1SCORE"Constant "Physical Functioning Scale Score"Constant "None"Aggregate COUNT(QS.QSTESTCD) over records matching on the shared output keys, where QS.QSTESTCD = 'F101'Aggregate SUM(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('F101', 'F102', 'F103', 'F104')Aggregate COUNT(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('F101', 'F102', 'F103', 'F104')If SCALE_ANS >= 2 then compute 100 * (1 - (SCALE_SUM / SCALE_ANS - 1) / 3); otherwise "None"Copy QS.STUDYIDCopy QS.USUBJIDCopy QS.VISIT
f2scoreSCALE_ANCHOR >= 1Constant "F2SCORE"Constant "Role Functioning Scale Score"Constant "None"Aggregate COUNT(QS.QSTESTCD) over records matching on the shared output keys, where QS.QSTESTCD = 'F201'Aggregate SUM(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('F201', 'F202')Aggregate COUNT(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('F201', 'F202')If SCALE_ANS >= 1 then compute 100 * (1 - (SCALE_SUM / SCALE_ANS - 1) / 3); otherwise "None"Copy QS.STUDYIDCopy QS.USUBJIDCopy QS.VISIT
sscoreSCALE_ANCHOR >= 1Constant "SSCORE"Constant "Fatigue and Sleep Symptom Scale Score"Constant "None"Aggregate COUNT(QS.QSTESTCD) over records matching on the shared output keys, where QS.QSTESTCD = 'S01'Aggregate SUM(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('S01', 'S02', 'F104')Aggregate COUNT(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('S01', 'S02', 'F104')If SCALE_ANS >= 2 then compute 100 * (SCALE_SUM / SCALE_ANS - 1) / 3; otherwise "None"Copy QS.STUDYIDCopy QS.USUBJIDCopy QS.VISIT
gscoreSCALE_ANCHOR >= 1Constant "GSCORE"Constant "Global Health Scale Score"Constant "None"Aggregate COUNT(QS.QSTESTCD) over records matching on the shared output keys, where QS.QSTESTCD = 'G01'Aggregate SUM(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('G01', 'G02')Aggregate COUNT(QS.QSSTRESN) over records matching on the shared output keys, where QS.QSTESTCD IN ('G01', 'G02')If SCALE_ANS >= 1 then compute 100 * (SCALE_SUM / SCALE_ANS - 1) / 6; otherwise "None"Copy QS.STUDYIDCopy QS.USUBJIDCopy QS.VISIT
VariablePermitted values
PARAMCDF101, F102, F103, F104, F201, F202, S01, S02, G01, G02, F1SCORE, F2SCORE, SSCORE, GSCORE
VersionDateAuthorDescriptionReviewerSign-off
1.32026-09-29yamaa docs buildShow study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata
1.22026-09-29yamaa docs buildClassify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived
1.12026-09-29yamaa docs buildResolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML
1.0yamaa docs buildInitial generation from spec.yaml

Agent prompt

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

prompts/adam-adqs-multi-scale-scoring/full.md

Edit
40 lines

Following CDISC ADaM standards, use the provided QS dataset to create an ADQS dataset with one record per subject per visit per item, plus one score record per scale per visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL

Carry each item response into PARAMCD (its test code), PARAM (its test name), and AVAL, keeping the numeric result on its answer scale, empty when the item was not answered.

The instrument has four scales. The physical functioning scale holds items F101 through F104, with score PARAMCD "F1SCORE" and PARAM "Physical Functioning Scale Score". The role functioning scale holds items F201 and F202, with score PARAMCD "F2SCORE" and PARAM "Role Functioning Scale Score". The fatigue and sleep symptom scale holds items S01, S02, and F104, with score PARAMCD "SSCORE" and PARAM "Fatigue and Sleep Symptom Scale Score"; item F104 also belongs to the physical functioning scale and feeds both scores. The global health scale holds items G01 and G02, with score PARAMCD "GSCORE" and PARAM "Global Health Scale Score".

A scale score is the mean of the scale's answered items at that visit, reported on a zero to one hundred scale. On the functioning scales a higher score means better functioning: the score is 100 times one minus the raw mean minus one over the range. On the symptom and global scales a higher score means worse symptoms or better health: the score is 100 times the raw mean minus one over the range. The range is 3 for items answered one to four and 6 for items answered one to seven.

A score needs at least two answered items for the physical functioning and symptom scales and at least one for the role functioning and global scales; with too few answered items the score has no value. Add one score record per scale for each visit that has the scale's anchor item record (F101, F201, S01, or G01), answered or not. A visit with too few answers still gets its score record, with no value, while a visit with no records at all gets none.

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

prompts/adam-adqs-multi-scale-scoring/conventions.md

Edit
20 lines

Following CDISC ADaM standards, use the provided QS dataset to create an ADQS dataset with one record per subject per visit per item, plus one score record per scale per visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL

The physical functioning items are PARAMCD F101 through F104, the role functioning items F201 and F202, the fatigue and sleep symptom items S01, S02, and F104, and the global health items G01 and G02. Score records have PARAMCD "F1SCORE", "F2SCORE", "SSCORE", and "GSCORE" with PARAM "Physical Functioning Scale Score", "Role Functioning Scale Score", "Fatigue and Sleep Symptom Scale Score", and "Global Health Scale Score". Item responses are on a one to four answer scale, except the global health items on a one to seven scale. Scale scores are reported on a zero to one hundred scale: higher for better functioning on the functioning scales, higher for worse symptoms on the symptom scale, and higher for better health on the global scale.

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

prompts/adam-adqs-multi-scale-scoring/brief.md

Edit
9 lines

Following CDISC ADaM standards, use the provided QS dataset to create an ADQS dataset with one record per subject per visit per item, plus one score record per scale per visit.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL

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

yamaa code

Runs the yamaa specification; its result must match the expected output

run.R

Edit
4 lines
library(yamaa)adqs <- yamaa_domain("spec.yaml")$outputadqs

run.py

Edit
4 lines
import yamaaadqs = yamaa.yamaa_domain("spec.yaml").outputadqs

Solution without yamaa

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

solutions/adam-adqs-multi-scale-scoring/result.R

Edit
75 lines
# Reference solution for the yamaa benchmark adam-adqs-multi-scale-scoring (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(purrr, warn.conflicts = FALSE)library(readr)qs <- read_csv(  "/app/input/qs.csv",  col_types = cols(    QSSEQ = col_integer(),    QSSTRESN = col_double(),    .default = col_character()  ))# Each item response, on its own answer scale.items <- qs |>  mutate(PARAMCD = QSTESTCD, PARAM = QSTEST, AVAL = QSSTRESN) |>  select(STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL)# The four scales: member items, anchor item, minimum answered items,# direction (down = higher score means better, up = higher means worse),# and the answer range.scales <- tribble(  ~pcode, ~pname, ~members, ~anchor, ~min_ans, ~direction, ~rng,  "F1SCORE", "Physical Functioning Scale Score",  list(c("F101", "F102", "F103", "F104")), "F101", 2L, "down", 3,  "F2SCORE", "Role Functioning Scale Score",  list(c("F201", "F202")), "F201", 1L, "down", 3,  "SSCORE", "Fatigue and Sleep Symptom Scale Score",  list(c("S01", "S02", "F104")), "S01", 2L, "up", 3,  "GSCORE", "Global Health Scale Score",  list(c("G01", "G02")), "G01", 1L, "up", 6)score_one <- function(pcode, pname, members, anchor, min_ans, direction, rng) {  # `members` arrives as a one-element list from the tribble's list-column;  # `%in%` against a list never matches, so unwrap it first.  members <- unlist(members)  member_rows <- items |> filter(PARAMCD %in% members)  visits <- items |>    filter(PARAMCD %in% anchor) |>    distinct(STUDYID, USUBJID, VISIT)  stats <- member_rows |>    group_by(STUDYID, USUBJID, VISIT) |>    summarise(      n_answered = sum(!is.na(AVAL)),      raw_mean = mean(AVAL, na.rm = TRUE),      .groups = "drop"    )  visits |>    left_join(stats, by = c("STUDYID", "USUBJID", "VISIT")) |>    mutate(      AVAL = dplyr::case_when(        is.na(n_answered) | n_answered < min_ans ~ NA_real_,        direction == "down" ~ 100 * (1 - (raw_mean - 1) / rng),        TRUE ~ 100 * (raw_mean - 1) / rng      ),      PARAMCD = pcode,      PARAM = pname    ) |>    select(STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL)}scores <- pmap_dfr(scales, score_one)adqs <- bind_rows(items, scores) |>  arrange(STUDYID, USUBJID, VISIT, PARAMCD)dir.create("/app/output", showWarnings = FALSE)write_csv(adqs, "/app/output/adqs.csv", na = "")

solutions/adam-adqs-multi-scale-scoring/result.py

Edit
78 lines
# Reference solution for the yamaa benchmark adam-adqs-multi-scale-scoring (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 plqs = pl.read_csv("/app/input/qs.csv", infer_schema=False).with_columns(    pl.col("QSSEQ").cast(pl.Int64),    pl.col("QSSTRESN").cast(pl.Float64),)# Each item response, on its own answer scale.items = qs.with_columns(    PARAMCD=pl.col("QSTESTCD"), PARAM=pl.col("QSTEST"), AVAL=pl.col("QSSTRESN")).select("STUDYID", "USUBJID", "VISIT", "PARAMCD", "PARAM", "AVAL")# The four scales: member items, anchor item, minimum answered items,# direction (down = higher score means better, up = higher means worse),# and the answer range.scales = [    (        "F1SCORE",        "Physical Functioning Scale Score",        ["F101", "F102", "F103", "F104"],        "F101",        2,        "down",        3,    ),    ("F2SCORE", "Role Functioning Scale Score", ["F201", "F202"], "F201", 1, "down", 3),    (        "SSCORE",        "Fatigue and Sleep Symptom Scale Score",        ["S01", "S02", "F104"],        "S01",        2,        "up",        3,    ),    ("GSCORE", "Global Health Scale Score", ["G01", "G02"], "G01", 1, "up", 6),]score_frames = []for pcode, pname, members, anchor, min_ans, direction, rng in scales:    member_rows = items.filter(pl.col("PARAMCD").is_in(members))    visits = (        items.filter(pl.col("PARAMCD") == anchor)        .select("STUDYID", "USUBJID", "VISIT")        .unique(maintain_order=True)    )    stats = member_rows.group_by(["STUDYID", "USUBJID", "VISIT"]).agg(        n_answered=pl.col("AVAL").drop_nulls().len(),        raw_mean=pl.col("AVAL").mean(),    )    if direction == "down":        value = 100 * (1 - (pl.col("raw_mean") - 1) / rng)    else:        value = 100 * (pl.col("raw_mean") - 1) / rng    scores = (        visits.join(stats, on=["STUDYID", "USUBJID", "VISIT"], how="left")        .with_columns(            AVAL=pl.when(pl.col("n_answered") >= min_ans).then(value).otherwise(None)        )        .with_columns(PARAMCD=pl.lit(pcode), PARAM=pl.lit(pname))        .select("STUDYID", "USUBJID", "VISIT", "PARAMCD", "PARAM", "AVAL")    )    score_frames.append(scores)adqs = pl.concat([items, *score_frames], how="diagonal").sort(    ["STUDYID", "USUBJID", "VISIT", "PARAMCD"])Path("/app/output").mkdir(exist_ok=True)adqs.write_csv("/app/output/adqs.csv")

solutions/adam-adqs-multi-scale-scoring/result.sas

Edit
36 lines
/* opensas reference solution for the yamaa benchmark adam-adqs-multi-scale-scoring.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data qs;  length STUDYID USUBJID VISIT QSCAT QSTESTCD QSTEST $1024;  infile "/app/input/qs.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. VISIT : $char1024. QSSEQ QSCAT : $char1024. QSTESTCD : $char1024. QSTEST : $char1024. QSSTRESN;run;proc sql; create table collected as select STUDYID,USUBJID,VISIT,QSTESTCD as PARAMCD,QSTEST as PARAM,QSSTRESN as AVAL from qs; quit;proc sql;create table anchor0 as select distinct STUDYID,USUBJID,VISIT from collected where PARAMCD='F101';create table members0 as select * from collected where PARAMCD in ('F101','F102','F103','F104');create table stats0 as select a.STUDYID,a.USUBJID,a.VISIT,count(b.AVAL) as NANSWER,mean(b.AVAL) as RAWMEAN from anchor0 a left join members0 b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.VISIT=b.VISIT group by a.STUDYID,a.USUBJID,a.VISIT; quit;data score0; set stats0; length PARAMCD PARAM $1024; PARAMCD='F1SCORE'; PARAM='Physical Functioning Scale Score'; AVAL=.; if NANSWER>=2 then AVAL=100*(1-(RAWMEAN-1)/3); run;proc sql;create table anchor1 as select distinct STUDYID,USUBJID,VISIT from collected where PARAMCD='F201';create table members1 as select * from collected where PARAMCD in ('F201','F202');create table stats1 as select a.STUDYID,a.USUBJID,a.VISIT,count(b.AVAL) as NANSWER,mean(b.AVAL) as RAWMEAN from anchor1 a left join members1 b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.VISIT=b.VISIT group by a.STUDYID,a.USUBJID,a.VISIT; quit;data score1; set stats1; length PARAMCD PARAM $1024; PARAMCD='F2SCORE'; PARAM='Role Functioning Scale Score'; AVAL=.; if NANSWER>=1 then AVAL=100*(1-(RAWMEAN-1)/3); run;proc sql;create table anchor2 as select distinct STUDYID,USUBJID,VISIT from collected where PARAMCD='S01';create table members2 as select * from collected where PARAMCD in ('S01','S02','F104');create table stats2 as select a.STUDYID,a.USUBJID,a.VISIT,count(b.AVAL) as NANSWER,mean(b.AVAL) as RAWMEAN from anchor2 a left join members2 b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.VISIT=b.VISIT group by a.STUDYID,a.USUBJID,a.VISIT; quit;data score2; set stats2; length PARAMCD PARAM $1024; PARAMCD='SSCORE'; PARAM='Fatigue and Sleep Symptom Scale Score'; AVAL=.; if NANSWER>=2 then AVAL=100*(RAWMEAN-1)/3; run;proc sql;create table anchor3 as select distinct STUDYID,USUBJID,VISIT from collected where PARAMCD='G01';create table members3 as select * from collected where PARAMCD in ('G01','G02');create table stats3 as select a.STUDYID,a.USUBJID,a.VISIT,count(b.AVAL) as NANSWER,mean(b.AVAL) as RAWMEAN from anchor3 a left join members3 b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.VISIT=b.VISIT group by a.STUDYID,a.USUBJID,a.VISIT; quit;data score3; set stats3; length PARAMCD PARAM $1024; PARAMCD='GSCORE'; PARAM='Global Health Scale Score'; AVAL=.; if NANSWER>=1 then AVAL=100*(RAWMEAN-1)/6; run;data result; length PARAMCD PARAM $1024; set collected score0 score1 score2 score3; run;proc sql;  create table final as select STUDYID, USUBJID, VISIT, PARAMCD, PARAM, AVAL from result;quit;proc export data=final outfile="/app/output/adqs.csv" dbms=csv replace; run;