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: prepare each collected overall response assessment for best overall response (BOR) selection, adding BORCAT, BORPRI, and BORSEQ.

Input: overall response assessments carrying the analysis date (ADT), the assessment day relative to randomization (RANDDY), and the collected overall response (AVALC), plus each subject's start date of new anti-cancer therapy, when one was started.

Variables:

  • BORCAT is the response category the record can support in the best overall response decision: complete response as CR, partial response as PR, stable disease as SD, neither complete response nor progressive disease as NON-CR/NON-PD, progressive disease as PD, or not evaluable as NE. Stable disease and neither-complete-nor-progressive disease count only on or after day 42 after randomization; earlier ones, or ones with no day, fall back to not evaluable. An assessment on or after the start of new anti-cancer therapy supports no category. Any other collected value, including a missing one, supports no category, so BORCAT stays empty.
  • BORPRI orders the supported categories as complete response (1), partial response (2), stable disease (3), neither-complete-nor-progressive disease (4), progressive disease (5), not evaluable (6); empty when the record supports no category.
  • BORSEQ numbers the usable records of each study and subject in category order, then by analysis date, then by assessment sequence. The record numbered 1 supplies the study-subject's best overall response and its supporting date.

Note: a record that supports no category takes no priority and no number, so numbering passes over it to the next usable record: it can never be numbered 1, and it never consumes a number a usable record would otherwise take.

Inputs

Under review
2 source files

input/adrs_raw.csv

Edit
21 rows
input/adrs_raw.csv
STUDYIDUSUBJIDASEQADTRANDDYAVALC
PILOT712025-02-1742PR
PILOT722025-04-0791CR
PILOT712025-02-1028SD
PILOT722025-04-1491SD
PILOT732025-05-12119SD
PILOT712025-02-1021SD
PILOT722025-03-0342PD
PILOT712025-02-2428SD
PILOT722025-04-0770NE
PILOT712025-03-3149NON-CR/NON-PD
PILOT712025-02-0321ND
PILOT722025-03-1763—
PILOT732025-04-28105PR
PILOT712025-03-01—SD
PILOT722025-04-1470CR
PILOT712025-04-0150SD
PILOT722025-04-0155SD
PILOT712025-02-1028PR
PILOT722025-04-0791CR
PILOT812025-03-1056SD
PILOT822025-04-2198PD

input/adsl.csv

Edit
10 rows
input/adsl.csv
STUDYIDUSUBJIDNTXSTDT
PILOT7—
PILOT7—
PILOT72025-03-03
PILOT7—
PILOT72025-05-01
PILOT7—
PILOT7—
PILOT7—
PILOT72025-03-01
PILOT8—

Expected output

Under review
21 expected rows

expected/adrs.csv

Edit
21 rows
expected/adrs.csv
STUDYIDUSUBJIDASEQADTRANDDYAVALCBORCATBORPRIBORSEQ
PILOT712025-02-1742PRPR22
PILOT722025-04-0791CRCR11
PILOT712025-02-1028SDNE63
PILOT722025-04-1491SDSD31
PILOT732025-05-12119SDSD32
PILOT712025-02-1021SDNE61
PILOT722025-03-0342PD———
PILOT712025-02-2428SDNE61
PILOT722025-04-0770NENE62
PILOT712025-03-3149NON-CR/NON-PDNON-CR/NON-PD41
PILOT712025-02-0321ND———
PILOT722025-03-1763————
PILOT732025-04-28105PRPR21
PILOT712025-03-01—SDNE62
PILOT722025-04-1470CRCR11
PILOT712025-04-0150SDSD31
PILOT722025-04-0155SDSD32
PILOT712025-02-1028PRPR21
PILOT722025-04-0791CR———
PILOT812025-03-1056SDSD31
PILOT822025-04-2198PDPD52
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
ADRSSTUDYIDStudy IdentifiercharacterCopy value from ADRS_RAW.STUDYID.Predecessor
ADRSUSUBJIDUnique Subject IdentifiercharacterCopy value from ADRS_RAW.USUBJID.Predecessor
ADRSASEQSequence NumbernumericCopy value from ADRS_RAW.ASEQ.Predecessor
ADRSADTAnalysis DatenumericISO 8601Copy value from ADRS_RAW.ADT.Predecessor
ADRSRANDDYAnalysis Day Relative to RandomizationnumericCopy value from ADRS_RAW.RANDDY.Predecessor
ADRSAVALCAnalysis Value (C)characterCopy value from ADRS_RAW.AVALC.Predecessor
ADRSBORCATBest Overall Response CategorycharacterCR, PR, SD, NON-CR/NON-PD, PD, NEIf NTXSTDT IS NOT NULL AND ADT >= NTXSTDT then "None"; If AVALC = 'CR' then "CR"; If AVALC = 'PR' then "PR"; If AVALC = 'SD' AND RANDDY >= 42 then "SD"; If AVALC = 'NON-CR/NON-PD' AND RANDDY >= 42 then "NON-CR/NON-PD"; If AVALC = 'PD' then "PD"; If AVALC IN ('SD', 'NON-CR/NON-PD', 'NE') then "NE"; otherwise "None".Derived
ADRSBORPRIBest Overall Response PrioritynumericRecode BORCAT: "CR" → "1", "PR" → "2", "SD" → "3", "NON-CR/NON-PD" → "4", "PD" → "5", "NE" → "6"; missing values stay missing; unlisted values are errors.Derived
ADRSBORSEQBest Overall Response SequencenumericRow number within each group of STUDYID, USUBJID, ordered by BORPRI then ADT then ASEQ.Derived
VariablePermitted values
BORCATCR, PR, SD, NON-CR/NON-PD, PD, NE
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-adrs-response-prep/full.md

Edit
30 lines

Following CDISC ADaM standards, use the provided ADRS_RAW and ADSL datasets to create an ADRS dataset with one record per collected overall response assessment.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, ADT, RANDDY, AVALC, BORCAT, BORPRI, BORSEQ

BORCAT is the response category the record can support in the best overall response decision, from its collected response: "CR" for complete response, "PR" for partial response, "SD" for stable disease, "NON-CR/NON-PD" for neither complete response nor progressive disease, "PD" for progressive disease, and "NE" for not evaluable. Stable disease and neither complete response nor progressive disease count only on or after day 42 after randomization; one earlier, or one with no day relative to randomization, falls back to not evaluable, so its BORCAT is "NE". An assessment on or after the subject's start of new anti-cancer therapy in ADSL supports no category, whatever response was collected, so its BORCAT has no value. Any other collected response, including a missing one, supports no category, so BORCAT has no value. BORPRI orders the supported categories: 1 for "CR", 2 for "PR", 3 for "SD", 4 for "NON-CR/NON-PD", 5 for "PD", and 6 for "NE". BORPRI has no value when the record supports no category. BORSEQ numbers the records of each study and subject that support a category, in BORPRI order, then by analysis date, then by assessment sequence, starting at 1. A record that supports no category takes no number: BORSEQ has no value on it, and numbering passes over it without consuming a number. The record numbered 1 supplies the study-subject's best overall response and its supporting date.

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

prompts/adam-adrs-response-prep/conventions.md

Edit
17 lines

Following CDISC ADaM standards, use the provided ADRS_RAW and ADSL datasets to create an ADRS dataset with one record per collected overall response assessment.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, ADT, RANDDY, AVALC, BORCAT, BORPRI, BORSEQ

BORCAT is "CR" for complete response, "PR" for partial response, "SD" for stable disease, "NON-CR/NON-PD" for neither complete response nor progressive disease, "PD" for progressive disease, and "NE" for not evaluable, or has no value. BORPRI is 1 for "CR", 2 for "PR", 3 for "SD", 4 for "NON-CR/NON-PD", 5 for "PD", and 6 for "NE", or has no value. BORSEQ numbers the records of each study and subject in BORPRI order, then by analysis date, then by assessment sequence, starting at 1.

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

prompts/adam-adrs-response-prep/brief.md

Edit
9 lines

Following CDISC ADaM standards, use the provided ADRS_RAW and ADSL datasets to create an ADRS dataset with one record per collected overall response assessment.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, ADT, RANDDY, AVALC, BORCAT, BORPRI, BORSEQ

Read the source datasets from /app/input and save the completed dataset as /app/output/adrs.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-adrs-response-prep/result.R

Edit
72 lines
# Reference solution for the yamaa benchmark adam-adrs-response-prep (R track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.library(dplyr, warn.conflicts = FALSE)library(readr)raw <- read_csv(  "/app/input/adrs_raw.csv",  col_types = cols(    ASEQ = col_integer(),    ADT = col_date(),    RANDDY = col_integer(),    .default = col_character()  ))therapy <- read_csv(  "/app/input/adsl.csv",  col_types = cols(    NTXSTDT = col_date(),    .default = col_character()  ))raw <- raw |>  left_join(therapy, by = c("STUDYID", "USUBJID"))# The response category each record can support; an assessment on or after# the subject's new anti-cancer therapy start supports no category. Stable# and neither-complete-nor-progressive disease count only on or after day# 42, and earlier ones, or ones with no day, fall back to not evaluable.based <- raw |>  mutate(    BORCAT = case_when(      !is.na(NTXSTDT) & ADT >= NTXSTDT ~ NA_character_,      AVALC %in% c("CR", "PR", "PD", "NE") ~ AVALC,      AVALC %in% "SD" & !is.na(RANDDY) & RANDDY >= 42L ~ "SD",      AVALC %in% "SD" ~ "NE",      AVALC %in% "NON-CR/NON-PD" & !is.na(RANDDY) & RANDDY >= 42L ~ "NON-CR/NON-PD",      AVALC %in% "NON-CR/NON-PD" ~ "NE",      .default = NA_character_    ),    BORPRI = case_when(      BORCAT == "CR" ~ 1L,      BORCAT == "PR" ~ 2L,      BORCAT == "SD" ~ 3L,      BORCAT == "NON-CR/NON-PD" ~ 4L,      BORCAT == "PD" ~ 5L,      BORCAT == "NE" ~ 6L    )  )# Usable records numbered from 1 in priority, date, then sequence order;# records supporting no category are passed over without a gap.order <- based |>  filter(!is.na(BORCAT)) |>  arrange(STUDYID, USUBJID, BORPRI, ADT, ASEQ) |>  group_by(STUDYID, USUBJID) |>  mutate(BORSEQ = row_number()) |>  ungroup() |>  select(STUDYID, USUBJID, ASEQ, BORSEQ)adrs <- based |>  left_join(order, by = c("STUDYID", "USUBJID", "ASEQ")) |>  select(STUDYID, USUBJID, ASEQ, ADT, RANDDY, AVALC, BORCAT, BORPRI, BORSEQ) |>  arrange(STUDYID, USUBJID, ASEQ)dir.create("/app/output", showWarnings = FALSE)write_csv(adrs, "/app/output/adrs.csv", na = "")

solutions/adam-adrs-response-prep/result.py

Edit
154 lines
# Reference solution for the yamaa benchmark adam-adrs-response-prep (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."""Build ADRS from ADRS_RAW and ADSL.One output record per collected overall response assessment, with thebest-overall-response category (BORCAT), its priority (BORPRI), and theper study-subject sequence (BORSEQ) of records that support a category."""import csvfrom datetime import datefrom pathlib import PathINPUT_DIR = Path("/app/input")OUTPUT_DIR = Path("/app/output")# Collected response -> supported category, before the day-42 and# new-therapy rules are applied.CATEGORY_BY_RESPONSE = {    "CR": "CR",    "PR": "PR",    "SD": "SD",    "NON-CR/NON-PD": "NON-CR/NON-PD",    "PD": "PD",    "NE": "NE",}PRIORITY_BY_CATEGORY = {    "CR": 1,    "PR": 2,    "SD": 3,    "NON-CR/NON-PD": 4,    "PD": 5,    "NE": 6,}# Categories that only count on or after day 42 after randomization.DAY_LIMITED = {"SD", "NON-CR/NON-PD"}MIN_DAY = 42def parse_date(text):    text = (text or "").strip()    if not text:        return None    return date.fromisoformat(text)def parse_int(text):    text = (text or "").strip()    if not text:        return None    return int(text)def main():    with open(INPUT_DIR / "adsl.csv", newline="", encoding="utf-8") as f:        adsl_rows = list(csv.DictReader(f))    new_therapy_start = {}    for row in adsl_rows:        key = (row["STUDYID"], row["USUBJID"])        new_therapy_start[key] = parse_date(row.get("NTXSTDT"))    with open(INPUT_DIR / "adrs_raw.csv", newline="", encoding="utf-8") as f:        raw_rows = list(csv.DictReader(f))    records = []    for row in raw_rows:        studyid = row["STUDYID"]        usubjid = row["USUBJID"]        adt = parse_date(row.get("ADT"))        randdy = parse_int(row.get("RANDDY"))        avalc = (row.get("AVALC") or "").strip()        borcat = CATEGORY_BY_RESPONSE.get(avalc)        if borcat is not None:            start = new_therapy_start.get((studyid, usubjid))            if start is not None and adt is not None and adt >= start:                # On or after new anti-cancer therapy: supports nothing.                borcat = None            elif borcat in DAY_LIMITED and (randdy is None or randdy < MIN_DAY):                borcat = "NE"        borpri = PRIORITY_BY_CATEGORY[borcat] if borcat is not None else None        records.append(            {                "STUDYID": studyid,                "USUBJID": usubjid,                "ASEQ": row["ASEQ"],                "ADT": (row.get("ADT") or "").strip(),                "RANDDY": (row.get("RANDDY") or "").strip(),                "AVALC": (row.get("AVALC") or "").strip(),                "BORCAT": borcat or "",                "BORPRI": borpri,                "_adt_sort": adt,                "_aseq_sort": int(row["ASEQ"]),                "BORSEQ": None,            }        )    groups = {}    for rec in records:        if rec["BORPRI"] is not None:            groups.setdefault((rec["STUDYID"], rec["USUBJID"]), []).append(rec)    for group in groups.values():        ordered = sorted(            group,            key=lambda r: (                r["BORPRI"],                r["_adt_sort"] is None,                r["_adt_sort"] or date.min,                r["_aseq_sort"],            ),        )        for number, rec in enumerate(ordered, start=1):            rec["BORSEQ"] = number    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)    fieldnames = [        "STUDYID",        "USUBJID",        "ASEQ",        "ADT",        "RANDDY",        "AVALC",        "BORCAT",        "BORPRI",        "BORSEQ",    ]    with open(OUTPUT_DIR / "adrs.csv", "w", newline="", encoding="utf-8") as f:        writer = csv.DictWriter(f, fieldnames=fieldnames)        writer.writeheader()        for rec in records:            writer.writerow(                {                    "STUDYID": rec["STUDYID"],                    "USUBJID": rec["USUBJID"],                    "ASEQ": rec["ASEQ"],                    "ADT": rec["ADT"],                    "RANDDY": rec["RANDDY"],                    "AVALC": rec["AVALC"],                    "BORCAT": rec["BORCAT"],                    "BORPRI": "" if rec["BORPRI"] is None else rec["BORPRI"],                    "BORSEQ": "" if rec["BORSEQ"] is None else rec["BORSEQ"],                }            )if __name__ == "__main__":    main()

solutions/adam-adrs-response-prep/result.sas

Edit
38 lines
/* opensas reference solution for the yamaa benchmark adam-adrs-response-prep.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data adrs_raw;  length STUDYID USUBJID ADT RANDDY AVALC $1024;  infile "/app/input/adrs_raw.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. ASEQ ADT : $char1024. RANDDY : $char1024. AVALC : $char1024.;run;data adsl;  length STUDYID USUBJID NTXSTDT $1024;  infile "/app/input/adsl.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. NTXSTDT : $char1024.;run;proc sql; create table joined as select a.*,b.NTXSTDT from adrs_raw as a left join adsl as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID; quit;data categories;  length BORCAT $16;  set joined;  if AVALC in ('CR','PR','SD','NON-CR/NON-PD','PD','NE') and (missing(NTXSTDT) or ADT<NTXSTDT) then do;    BORCAT=AVALC;    if AVALC in ('SD','NON-CR/NON-PD') and (missing(RANDDY) or input(RANDDY,best32.)<42) then BORCAT='NE';  end;  select(BORCAT); when('CR') BORPRI=1; when('PR') BORPRI=2; when('SD') BORPRI=3; when('NON-CR/NON-PD') BORPRI=4; when('PD') BORPRI=5; when('NE') BORPRI=6; otherwise BORPRI=.; end;  unusable=missing(BORPRI);run;proc sort data=categories; by STUDYID USUBJID unusable BORPRI ADT ASEQ; run;data result;  set categories; by STUDYID USUBJID;  if first.USUBJID then sequence=0;  if not missing(BORPRI) then do; sequence+1; BORSEQ=sequence; end;run;proc sql;  create table final as select STUDYID, USUBJID, ASEQ, ADT, RANDDY, AVALC, BORCAT, BORPRI, BORSEQ from result;quit;proc export data=final outfile="/app/output/adrs.csv" dbms=csv replace; run;