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: 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:
BORCATis the response category the record can support in the best overall response decision: complete response asCR, partial response asPR, stable disease asSD, neither complete response nor progressive disease asNON-CR/NON-PD, progressive disease asPD, or not evaluable asNE. 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, soBORCATstays empty.BORPRIorders 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.BORSEQnumbers the usable records of each study and subject in category order, then by analysis date, then by assessment sequence. The record numbered1supplies 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 review2 source filesinput/adrs_raw.csv
Edit21 rows| STUDYID | USUBJID | ASEQ | ADT | RANDDY | AVALC |
|---|---|---|---|---|---|
| PILOT7 | 1 | 2025-02-17 | 42 | PR | |
| PILOT7 | 2 | 2025-04-07 | 91 | CR | |
| PILOT7 | 1 | 2025-02-10 | 28 | SD | |
| PILOT7 | 2 | 2025-04-14 | 91 | SD | |
| PILOT7 | 3 | 2025-05-12 | 119 | SD | |
| PILOT7 | 1 | 2025-02-10 | 21 | SD | |
| PILOT7 | 2 | 2025-03-03 | 42 | PD | |
| PILOT7 | 1 | 2025-02-24 | 28 | SD | |
| PILOT7 | 2 | 2025-04-07 | 70 | NE | |
| PILOT7 | 1 | 2025-03-31 | 49 | NON-CR/NON-PD | |
| PILOT7 | 1 | 2025-02-03 | 21 | ND | |
| PILOT7 | 2 | 2025-03-17 | 63 | — | |
| PILOT7 | 3 | 2025-04-28 | 105 | PR | |
| PILOT7 | 1 | 2025-03-01 | — | SD | |
| PILOT7 | 2 | 2025-04-14 | 70 | CR | |
| PILOT7 | 1 | 2025-04-01 | 50 | SD | |
| PILOT7 | 2 | 2025-04-01 | 55 | SD | |
| PILOT7 | 1 | 2025-02-10 | 28 | PR | |
| PILOT7 | 2 | 2025-04-07 | 91 | CR | |
| PILOT8 | 1 | 2025-03-10 | 56 | SD | |
| PILOT8 | 2 | 2025-04-21 | 98 | PD |
input/adsl.csv
Edit10 rows| STUDYID | USUBJID | NTXSTDT |
|---|---|---|
| PILOT7 | — | |
| PILOT7 | — | |
| PILOT7 | 2025-03-03 | |
| PILOT7 | — | |
| PILOT7 | 2025-05-01 | |
| PILOT7 | — | |
| PILOT7 | — | |
| PILOT7 | — | |
| PILOT7 | 2025-03-01 | |
| PILOT8 | — |
Expected output
Under review21 expected rowsexpected/adrs.csv
Edit21 rows| STUDYID | USUBJID | ASEQ | ADT | RANDDY | AVALC | BORCAT | BORPRI | BORSEQ |
|---|---|---|---|---|---|---|---|---|
| PILOT7 | 1 | 2025-02-17 | 42 | PR | PR | 2 | 2 | |
| PILOT7 | 2 | 2025-04-07 | 91 | CR | CR | 1 | 1 | |
| PILOT7 | 1 | 2025-02-10 | 28 | SD | NE | 6 | 3 | |
| PILOT7 | 2 | 2025-04-14 | 91 | SD | SD | 3 | 1 | |
| PILOT7 | 3 | 2025-05-12 | 119 | SD | SD | 3 | 2 | |
| PILOT7 | 1 | 2025-02-10 | 21 | SD | NE | 6 | 1 | |
| PILOT7 | 2 | 2025-03-03 | 42 | PD | — | — | — | |
| PILOT7 | 1 | 2025-02-24 | 28 | SD | NE | 6 | 1 | |
| PILOT7 | 2 | 2025-04-07 | 70 | NE | NE | 6 | 2 | |
| PILOT7 | 1 | 2025-03-31 | 49 | NON-CR/NON-PD | NON-CR/NON-PD | 4 | 1 | |
| PILOT7 | 1 | 2025-02-03 | 21 | ND | — | — | — | |
| PILOT7 | 2 | 2025-03-17 | 63 | — | — | — | — | |
| PILOT7 | 3 | 2025-04-28 | 105 | PR | PR | 2 | 1 | |
| PILOT7 | 1 | 2025-03-01 | — | SD | NE | 6 | 2 | |
| PILOT7 | 2 | 2025-04-14 | 70 | CR | CR | 1 | 1 | |
| PILOT7 | 1 | 2025-04-01 | 50 | SD | SD | 3 | 1 | |
| PILOT7 | 2 | 2025-04-01 | 55 | SD | SD | 3 | 2 | |
| PILOT7 | 1 | 2025-02-10 | 28 | PR | PR | 2 | 1 | |
| PILOT7 | 2 | 2025-04-07 | 91 | CR | — | — | — | |
| PILOT8 | 1 | 2025-03-10 | 56 | SD | SD | 3 | 1 | |
| PILOT8 | 2 | 2025-04-21 | 98 | PD | PD | 5 | 2 |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADRS | STUDYID | Study Identifier | character | Copy value from ADRS_RAW.STUDYID. | Predecessor | ||
| ADRS | USUBJID | Unique Subject Identifier | character | Copy value from ADRS_RAW.USUBJID. | Predecessor | ||
| ADRS | ASEQ | Sequence Number | numeric | Copy value from ADRS_RAW.ASEQ. | Predecessor | ||
| ADRS | ADT | Analysis Date | numeric | ISO 8601 | Copy value from ADRS_RAW.ADT. | Predecessor | |
| ADRS | RANDDY | Analysis Day Relative to Randomization | numeric | Copy value from ADRS_RAW.RANDDY. | Predecessor | ||
| ADRS | AVALC | Analysis Value (C) | character | Copy value from ADRS_RAW.AVALC. | Predecessor | ||
| ADRS | BORCAT | Best Overall Response Category | character | CR, PR, SD, NON-CR/NON-PD, PD, NE | If 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 | |
| ADRS | BORPRI | Best Overall Response Priority | numeric | Recode 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 | ||
| ADRS | BORSEQ | Best Overall Response Sequence | numeric | Row number within each group of STUDYID, USUBJID, ordered by BORPRI then ADT then ASEQ. | Derived |
| Variable | Permitted values |
|---|---|
| BORCAT | CR, PR, SD, NON-CR/NON-PD, PD, NE |
| 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-adrs-response-prep/full.md
Edit30 linesFollowing 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
Edit17 linesFollowing 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
Edit9 linesFollowing 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.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adrs-response-prep/result.R
Edit72 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
Edit154 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
Edit38 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;
yamaa