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: give each infusion its duration in minutes as AVAL, under a
parameter PARAM that names the unit, and carry beside it CALCDUR, the
duration computed from the start and end times.
Input: Study Data Tabulation Model (SDTM) exposure (EX) records, one
per infusion (EXSEQ), with the start (EXSTDTC) and end (EXENDTC) as
ISO 8601 date or date-time text and, where the case report form collected
it, the infusion's duration as ISO 8601 duration text (EXDUR, for example
PT1H30M for one hour 30 minutes).
Variables:
AVALis the collected duration in minutes whenEXDURholds a readable fixed-length ISO 8601 duration; otherwise it isCALCDUR. Text that is not such a duration, for example one without its leadingP, counts as not collected. Empty when neither gives a value.CALCDURis the elapsed time in minutes from the start to the end, with seconds kept as a fraction of a minute and nothing added or rounded. It is empty when the start or the end was collected as a date without a time of day; no time is assumed.
Note: a readable collected duration always wins, even when it disagrees
with the start and end times. CALCDUR keeps the computed value beside it
so the two can be compared. Every infusion keeps its record, with an empty
AVAL when its duration cannot be known.
Inputs
Under review1 source fileinput/ex.csv
Edit8 rows| STUDYID | USUBJID | EXSEQ | EXTRT | EXROUTE | EXSTDTC | EXENDTC | EXDUR |
|---|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | STUDY DRUG | INTRAVENOUS | 2025-03-03T09:00 | 2025-03-03T10:30 | PT1H30M | |
| YAMAA-01 | 2 | STUDY DRUG | INTRAVENOUS | 2025-03-24 | 2025-03-24 | PT45M | |
| YAMAA-01 | 3 | STUDY DRUG | INTRAVENOUS | 2025-04-14T09:15 | 2025-04-14T11:20 | PT2H | |
| YAMAA-01 | 4 | STUDY DRUG | INTRAVENOUS | 2025-05-05T08:50 | 2025-05-05T10:05 | — | |
| YAMAA-01 | 1 | STUDY DRUG | INTRAVENOUS | 2025-03-05T13:00 | 2025-03-05T14:40 | 1H30M | |
| YAMAA-01 | 2 | STUDY DRUG | INTRAVENOUS | 2025-03-26T13:10 | 2025-03-26 | — | |
| YAMAA-01 | 3 | STUDY DRUG | INTRAVENOUS | 2025-04-16T23:30:00 | 2025-04-17T00:15:30 | — | |
| YAMAA-01 | 4 | STUDY DRUG | INTRAVENOUS | 2025-05-07 | 2025-05-07T14:00 | — |
Expected output
Under review8 expected rowsexpected/adex.csv
Edit8 rows| STUDYID | USUBJID | EXSEQ | EXSTDTC | EXENDTC | EXDUR | PARAMCD | PARAM | AVAL | CALCDUR |
|---|---|---|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | 2025-03-03T09:00 | 2025-03-03T10:30 | PT1H30M | INFDUR | Infusion Duration (min) | 90 | 90 | |
| YAMAA-01 | 2 | 2025-03-24 | 2025-03-24 | PT45M | INFDUR | Infusion Duration (min) | 45 | — | |
| YAMAA-01 | 3 | 2025-04-14T09:15 | 2025-04-14T11:20 | PT2H | INFDUR | Infusion Duration (min) | 120 | 125 | |
| YAMAA-01 | 4 | 2025-05-05T08:50 | 2025-05-05T10:05 | — | INFDUR | Infusion Duration (min) | 75 | 75 | |
| YAMAA-01 | 1 | 2025-03-05T13:00 | 2025-03-05T14:40 | 1H30M | INFDUR | Infusion Duration (min) | 100 | 100 | |
| YAMAA-01 | 2 | 2025-03-26T13:10 | 2025-03-26 | — | INFDUR | Infusion Duration (min) | — | — | |
| YAMAA-01 | 3 | 2025-04-16T23:30:00 | 2025-04-17T00:15:30 | — | INFDUR | Infusion Duration (min) | 45.5 | 45.5 | |
| YAMAA-01 | 4 | 2025-05-07 | 2025-05-07T14:00 | — | INFDUR | Infusion Duration (min) | — | — |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADEX | STUDYID | Study Identifier | character | Copy value from EX.STUDYID. | Predecessor | ||
| ADEX | USUBJID | Unique Subject Identifier | character | Copy value from EX.USUBJID. | Predecessor | ||
| ADEX | EXSEQ | Sequence Number | numeric | Copy value from EX.EXSEQ. | Predecessor | ||
| ADEX | EXSTDTC | Start Date/Time of Treatment | character | Copy value from EX.EXSTDTC. | Predecessor | ||
| ADEX | EXENDTC | End Date/Time of Treatment | character | Copy value from EX.EXENDTC. | Predecessor | ||
| ADEX | EXDUR | Duration of Treatment | character | Copy value from EX.EXDUR. | Predecessor | ||
| ADEX | PARAMCD | Parameter Code | character | Set constant value "INFDUR". | Assigned | ||
| ADEX | PARAM | Parameter | character | Set constant value "Infusion Duration (min)". | Assigned | ||
| ADEX | CALCDUR | Duration from Start and End (min) | numeric | Elapsed minutes from ASTDTM to AENDTM, negative when AENDTM is earlier, not rounded. | Derived | ||
| ADEX | AVAL | Analysis Value | numeric | First non-missing of COLLDUR and CALCDUR. | Derived |
| 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-adex-infusion-duration/full.md
Edit20 linesFollowing CDISC ADaM standards, use the provided EX dataset to create an ADEX dataset with one record per infusion.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, EXSEQ, EXSTDTC, EXENDTC, EXDUR, PARAMCD, PARAM, AVAL, CALCDUR
PARAMCD is "INFDUR" and PARAM is "Infusion Duration (min)" on every record. CALCDUR is the elapsed time in minutes from EXSTDTC to EXENDTC, with seconds kept as a fraction of a minute and nothing added or rounded. CALCDUR has no value when EXSTDTC or EXENDTC was collected as a date without a time of day; no time is assumed. AVAL is the collected duration in minutes when EXDUR holds a readable fixed-length ISO 8601 duration; otherwise AVAL is CALCDUR. Text that is not such a duration, for example one without its leading P, counts as not collected. A readable collected duration always wins, even when it disagrees with the start and end times. AVAL has no value when neither gives a value, and every infusion keeps its record.
Read the source datasets from /app/input and save the completed dataset as /app/output/adex.csv.
prompts/adam-adex-infusion-duration/conventions.md
Edit13 linesFollowing CDISC ADaM standards, use the provided EX dataset to create an ADEX dataset with one record per infusion.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, EXSEQ, EXSTDTC, EXENDTC, EXDUR, PARAMCD, PARAM, AVAL, CALCDUR
PARAMCD is "INFDUR" and PARAM is "Infusion Duration (min)" on every record. CALCDUR is the elapsed time in minutes from EXSTDTC to EXENDTC, with seconds kept as a fraction of a minute and nothing added or rounded.
Read the source datasets from /app/input and save the completed dataset as /app/output/adex.csv.
prompts/adam-adex-infusion-duration/brief.md
Edit9 linesFollowing CDISC ADaM standards, use the provided EX dataset to create an ADEX dataset with one record per infusion.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, EXSEQ, EXSTDTC, EXENDTC, EXDUR, PARAMCD, PARAM, AVAL, CALCDUR
Read the source datasets from /app/input and save the completed dataset as /app/output/adex.csv.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adex-infusion-duration/result.R
Edit44 lines# Reference solution for the yamaa benchmark adam-adex-infusion-duration (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(lubridate, warn.conflicts = FALSE)library(readr)library(stringr)ex <- read_csv( "/app/input/ex.csv", col_types = cols(EXSEQ = col_integer(), .default = col_character()))# A date alone has no time of day, and none is assumed; a time without# seconds counts as second zero.moment <- function(x) { full <- if_else(nchar(x) == 16, paste0(x, ":00"), x) if_else(!is.na(x) & nchar(x) > 10, ymd_hms(full, tz = "UTC", quiet = TRUE), as.POSIXct(NA, tz = "UTC"))}# An ISO 8601 duration of days, hours, minutes and seconds, with at least one# part; anything else counts as not collected.collected_minutes <- function(x) { pattern <- "^P(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$" parts <- str_match(x, pattern) number <- function(i) coalesce(as.numeric(parts[, i]), 0) found <- !is.na(parts[, 1]) & rowSums(!is.na(parts[, 2:5, drop = FALSE])) > 0 & !str_ends(coalesce(x, ""), "T") if_else(found, number(2) * 1440 + number(3) * 60 + number(4) + number(5) / 60, NA_real_)}adex <- ex |> mutate( CALCDUR = as.numeric(difftime(moment(EXENDTC), moment(EXSTDTC), units = "mins")), AVAL = coalesce(collected_minutes(EXDUR), CALCDUR), PARAMCD = "INFDUR", PARAM = "Infusion Duration (min)" ) |> select(STUDYID, USUBJID, EXSEQ, EXSTDTC, EXENDTC, EXDUR, PARAMCD, PARAM, AVAL, CALCDUR)dir.create("/app/output", showWarnings = FALSE)write_csv(adex, "/app/output/adex.csv", na = "")solutions/adam-adex-infusion-duration/result.py
Edit140 lines# Reference solution for the yamaa benchmark adam-adex-infusion-duration (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."""Create ADEX (one record per infusion) from EX.PARAMCD is "INFDUR" and PARAM is "Infusion Duration (min)" on everyrecord.CALCDUR is the elapsed time in minutes from EXSTDTC to EXENDTC, withseconds kept as a fraction of a minute and nothing added or rounded.It has no value when EXSTDTC or EXENDTC was collected as a datewithout a time of day.AVAL is the collected duration in minutes when EXDUR holds a readablefixed-length ISO 8601 duration; otherwise AVAL is CALCDUR. Text thatis not such a duration (for example one without its leading P) countsas not collected, and a readable collected duration always wins evenwhen it disagrees with the start and end times. AVAL has no value whenneither gives a value; every infusion keeps its record."""import csvimport refrom datetime import datetimeINPUT_EX = "/app/input/ex.csv"OUTPUT_ADEX = "/app/output/adex.csv"COLUMNS = [ "STUDYID", "USUBJID", "EXSEQ", "EXSTDTC", "EXENDTC", "EXDUR", "PARAMCD", "PARAM", "AVAL", "CALCDUR",]# A fixed-length ISO 8601 duration: P[nD][T[nH][nM][nS]] with at least# one component. Years, months and weeks are not fixed length, so a# duration using them is not readable here.DURATION_RE = re.compile( r"^P(?:(?P<days>\d+(?:\.\d+)?)D)?" r"(?:T(?:(?P<hours>\d+(?:\.\d+)?)H)?" r"(?:(?P<minutes>\d+(?:\.\d+)?)M)?" r"(?:(?P<seconds>\d+(?:\.\d+)?)S)?)?$")def parse_duration_minutes(text): """Minutes in a fixed-length ISO 8601 duration, or None.""" if not text: return None match = DURATION_RE.match(text.strip()) if match is None: return None parts = match.groupdict() if all(value is None for value in parts.values()): return None total = 0.0 if parts["days"] is not None: total += float(parts["days"]) * 1440.0 if parts["hours"] is not None: total += float(parts["hours"]) * 60.0 if parts["minutes"] is not None: total += float(parts["minutes"]) if parts["seconds"] is not None: total += float(parts["seconds"]) / 60.0 return totaldef parse_datetime(text): """A datetime for an ISO 8601 value that carries a time of day, or None when the value is a date without a time (or missing).""" if not text: return None text = text.strip() if "T" not in text: return None return datetime.fromisoformat(text)def calc_duration_minutes(start_text, end_text): """Elapsed minutes from start to end, or None when either value was collected as a date without a time of day.""" start = parse_datetime(start_text) end = parse_datetime(end_text) if start is None or end is None: return None return (end - start).total_seconds() / 60.0def format_number(value): """A number as text: whole minutes without a decimal point, otherwise the exact value (no rounding beyond float repr).""" if value is None: return "" if float(value).is_integer(): return str(int(value)) return repr(float(value))def main(): with open(INPUT_EX, newline="", encoding="utf-8") as handle: ex_rows = list(csv.DictReader(handle)) out_rows = [] for row in ex_rows: calcdur = calc_duration_minutes(row["EXSTDTC"], row["EXENDTC"]) collected = parse_duration_minutes(row["EXDUR"]) aval = collected if collected is not None else calcdur out_rows.append( { "STUDYID": row["STUDYID"], "USUBJID": row["USUBJID"], "EXSEQ": row["EXSEQ"], "EXSTDTC": row["EXSTDTC"], "EXENDTC": row["EXENDTC"], "EXDUR": row["EXDUR"], "PARAMCD": "INFDUR", "PARAM": "Infusion Duration (min)", "AVAL": format_number(aval), "CALCDUR": format_number(calcdur), } ) with open(OUTPUT_ADEX, "w", newline="", encoding="utf-8") as handle: writer = csv.DictWriter(handle, fieldnames=COLUMNS) writer.writeheader() writer.writerows(out_rows)if __name__ == "__main__": main()solutions/adam-adex-infusion-duration/result.sas
Edit42 lines/* opensas reference solution for the yamaa benchmark adam-adex-infusion-duration. Reads the benchmark inputs and derives the requested output. Execute with the existing opensas runtime: opensas /app/output/result.sas. */data ex; length STUDYID USUBJID EXTRT EXROUTE EXSTDTC EXENDTC EXDUR $1024; infile "/app/input/ex.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. EXSEQ EXTRT : $char1024. EXROUTE : $char1024. EXSTDTC : $char1024. EXENDTC : $char1024. EXDUR : $char1024.;run;/* A date alone has no time of day, and none is assumed; a time without seconds counts as second zero. An ISO 8601 duration of days, hours, minutes and seconds wins; anything else counts as not collected. */data final; length PARAMCD $6 PARAM $23; set ex; PARAMCD='INFDUR'; PARAM='Infusion Duration (min)'; st=.; en=.; if lengthn(EXSTDTC)>10 then st=input(cats(EXSTDTC, ifc(lengthn(EXSTDTC)=16, ':00', '')), e8601dt.); if lengthn(EXENDTC)>10 then en=input(cats(EXENDTC, ifc(lengthn(EXENDTC)=16, ':00', '')), e8601dt.); CALCDUR=.; if not missing(st) and not missing(en) then CALCDUR=(en-st)/60; COLL=.; rx=prxparse('/^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/'); if not missing(EXDUR) and prxmatch(rx, strip(EXDUR)) and lengthn(EXDUR)>1 and substr(EXDUR, lengthn(EXDUR), 1) ne 'T' then do; d=input(prxposn(rx, 1, strip(EXDUR)), ?? best.); h=input(prxposn(rx, 2, strip(EXDUR)), ?? best.); m=input(prxposn(rx, 3, strip(EXDUR)), ?? best.); s=input(prxposn(rx, 4, strip(EXDUR)), ?? best.); COLL=sum(0, d*1440, h*60, m, s/60); end; AVAL=coalesce(COLL, CALCDUR); format AVAL CALCDUR best32.; keep STUDYID USUBJID EXSEQ EXSTDTC EXENDTC EXDUR PARAMCD PARAM AVAL CALCDUR;run;proc sql; create table out as select STUDYID, USUBJID, EXSEQ, EXSTDTC, EXENDTC, EXDUR, PARAMCD, PARAM, AVAL, CALCDUR from final;quit;proc export data=out outfile="/app/output/adex.csv" dbms=csv replace; run;
yamaa