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: flag each visit-level patient-reported outcome (PRO) score as a
deterioration or not, then build one time-to-deterioration record per
subject with parameter code TTDGHS (Time to Deterioration in Global Health Status), carrying STARTDT, ADT, AVAL, CNSR,
EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, and SRCSEQ.
Input: subject-level randomization and death dates (RANDDT,
DTHDT), visit-level PRO scores on a 0 to 100 scale, each with a
sequence number (QSSEQ), an assessment date (ADT), and a score
(AVAL), and disposition records naming disease progression, study
discontinuation, or withdrawal of consent (DSDECOD, DSDTC).
Variables:
DETERFLisYon an assessment dated after the subject's earliest assessment whose score has fallen at least 10 points below that earliest (baseline) score; blank otherwise, including on the baseline assessment itself. It is computed only to find the earliest flagged assessment per subject and is not an output column.STARTDTis the randomization date, the origin from which the time to deterioration is counted.ADTis the analysis date: the deterioration date when a flagged assessment exists and is not dated after the first censoring reason, else the death date when the subject died and the death is not dated after the first censoring reason. A deterioration and a death on the same date count as deterioration. With neither,ADTis the last assessment dated on or before the first censoring reason, or randomization when no assessment qualifies.AVALis the number of whole calendar months fromSTARTDTtoADT.CNSRis0when the subject has a deterioration or death event and1otherwise.EVNTDESCisPRO DETERIORATIONfor a deterioration event,DEATHfor a death event, andCENSOREDotherwise.CNSDTDSCis blank for an event, holds the censoring reason (DISEASE PROGRESSION,STUDY DISCONTINUATION, orWITHDRAWAL OF CONSENT) when one is available, and isSTUDY COMPLETIONotherwise.SRCDOMisQSfor a deterioration or a censored record with a supporting assessment, andADSLfor a death or a record censored at randomization.SRCVARisADTfor a deterioration or a censored record with a supporting assessment,DTHDTfor a death, andRANDDTfor a record censored at randomization.SRCSEQis the sequence number from the deterioration or censoring assessment, and blank when the analysis date comes from a subject-level date.
Note: progression, discontinuation, and withdrawal are censoring reasons, never events: a subject who progresses before any deterioration is censored at the last assessment on or before the progression date, and a deterioration dated the same day as the progression still counts as the event.
Inputs
Under review3 source filesinput/adsl.csv
Edit6 rows| STUDYID | USUBJID | RANDDT | DTHDT |
|---|---|---|---|
| YAMAA-01 | 2026-01-15 | 2026-08-20 | |
| YAMAA-01 | 2026-01-15 | 2026-06-10 | |
| YAMAA-01 | 2026-01-15 | — | |
| YAMAA-01 | 2026-01-15 | — | |
| YAMAA-01 | 2026-01-15 | — | |
| YAMAA-01 | 2026-01-15 | — |
input/ds.csv
Edit4 rows| STUDYID | USUBJID | DSDECOD | DSDTC | DSSEQ |
|---|---|---|---|---|
| YAMAA-01 | DISEASE PROGRESSION | 2026-04-01 | 1 | |
| YAMAA-01 | WITHDRAWAL OF CONSENT | 2026-06-01 | 1 | |
| YAMAA-01 | STUDY DISCONTINUATION | 2026-01-20 | 1 | |
| YAMAA-01 | DISEASE PROGRESSION | 2026-03-12 | 1 |
input/qs.csv
Edit14 rows| STUDYID | USUBJID | QSSEQ | AVISIT | ADT | AVAL |
|---|---|---|---|---|---|
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 80 | |
| YAMAA-01 | 2 | WEEK 8 | 2026-03-12 | 74 | |
| YAMAA-01 | 3 | WEEK 16 | 2026-05-07 | 60 | |
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 70 | |
| YAMAA-01 | 2 | WEEK 8 | 2026-03-12 | 68 | |
| YAMAA-01 | 3 | WEEK 16 | 2026-05-07 | 65 | |
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 75 | |
| YAMAA-01 | 2 | WEEK 8 | 2026-03-12 | 72 | |
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 80 | |
| YAMAA-01 | 2 | WEEK 8 | 2026-03-12 | 78 | |
| YAMAA-01 | 3 | WEEK 16 | 2026-05-07 | 76 | |
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 70 | |
| YAMAA-01 | 1 | BASELINE | 2026-01-15 | 85 | |
| YAMAA-01 | 2 | WEEK 8 | 2026-03-12 | 70 |
Expected output
Under review6 expected rowsexpected/adtte.csv
Edit6 rows| STUDYID | USUBJID | PARAMCD | PARAM | STARTDT | ADT | AVAL | CNSR | EVNTDESC | CNSDTDSC | SRCDOM | SRCVAR | SRCSEQ |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-05-07 | 3 | 0 | PRO DETERIORATION | — | QS | ADT | 3 | |
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-06-10 | 4 | 0 | DEATH | — | ADSL | DTHDT | — | |
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-03-12 | 1 | 1 | CENSORED | DISEASE PROGRESSION | QS | ADT | 2 | |
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-05-07 | 3 | 1 | CENSORED | WITHDRAWAL OF CONSENT | QS | ADT | 3 | |
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-01-15 | 0 | 1 | CENSORED | STUDY DISCONTINUATION | QS | ADT | 1 | |
| YAMAA-01 | TTDGHS | Time to Deterioration in Global Health Status | 2026-01-15 | 2026-03-12 | 1 | 0 | PRO DETERIORATION | — | QS | ADT | 2 |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADTTE | STUDYID | Study Identifier | character | Copy value from ADSL.STUDYID. | Predecessor | ||
| ADTTE | USUBJID | Unique Subject Identifier | character | Copy value from ADSL.USUBJID. | Predecessor | ||
| ADTTE | PARAMCD | Parameter Code | character | Set constant value "TTDGHS". | Assigned | ||
| ADTTE | PARAM | Parameter | character | Set constant value "Time to Deterioration in Global Health Status". | Assigned | ||
| ADTTE | STARTDT | Time-to-Event Origin Date | numeric | ISO 8601 | Copy value from ADSL.RANDDT. | Predecessor | |
| ADTTE | ADT | Analysis Date | numeric | ISO 8601 | First non-missing of EVENTDT and CENSORDT. | Derived | |
| ADTTE | AVAL | Analysis Value | numeric | Whole months from STARTDT to ADT, counting the end date but not the start. | Derived | ||
| ADTTE | CNSR | Censor | numeric | If EVENTDT IS NOT NULL then "0"; otherwise "1". | Derived | ||
| ADTTE | EVNTDESC | Event or Censoring Description | character | If DETERDT IS NOT NULL AND (DEATHDT IS NULL OR DETERDT <= DEATHDT) AND (CENSORRSNDT IS NULL OR DETERDT <= CENSORRSNDT) then "PRO DETERIORATION"; If DEATHDT IS NOT NULL AND (CENSORRSNDT IS NULL OR DEATHDT <= CENSORRSNDT) then "DEATH"; otherwise "CENSORED". | Derived | ||
| ADTTE | CNSDTDSC | Censoring Date Description | character | If EVENTDT IS NOT NULL then "None"; If CENSORRSN IS NOT NULL then "CENSORRSN"; otherwise "STUDY COMPLETION". | Derived | ||
| ADTTE | SRCDOM | Source Data | character | If DETERDT IS NOT NULL AND (DEATHDT IS NULL OR DETERDT <= DEATHDT) AND (CENSORRSNDT IS NULL OR DETERDT <= CENSORRSNDT) then "QS"; If DEATHDT IS NOT NULL AND (CENSORRSNDT IS NULL OR DEATHDT <= CENSORRSNDT) then "ADSL"; If LASTQSLE IS NULL then "ADSL"; otherwise "QS". | Derived | ||
| ADTTE | SRCVAR | Source Variable | character | If DETERDT IS NOT NULL AND (DEATHDT IS NULL OR DETERDT <= DEATHDT) AND (CENSORRSNDT IS NULL OR DETERDT <= CENSORRSNDT) then "ADT"; If DEATHDT IS NOT NULL AND (CENSORRSNDT IS NULL OR DEATHDT <= CENSORRSNDT) then "DTHDT"; If LASTQSLE IS NULL then "RANDDT"; otherwise "ADT". | Derived | ||
| ADTTE | SRCSEQ | Source Sequence Number | numeric | If DETERDT IS NOT NULL AND (DEATHDT IS NULL OR DETERDT <= DEATHDT) AND (CENSORRSNDT IS NULL OR DETERDT <= CENSORRSNDT) then "FIRSTDETER.QSSEQ"; If EVNTDESC = 'CENSORED' AND LASTQSLE IS NOT NULL then "LASTQSSEQ"; otherwise blank. | 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-adtte-pro-deterioration/full.md
Edit37 linesFollowing CDISC ADaM standards, use the provided ADSL, DS, and QS datasets to create an ADTTE dataset with one record per subject.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ
PARAMCD is "TTDGHS" and PARAM is "Time to Deterioration in Global Health Status". STARTDT is the randomization date. A deterioration is an assessment dated after the subject's earliest assessment whose score has fallen at least 10 points below that earliest assessment's score. The deterioration date is the date of the subject's earliest deterioration. The first censoring reason is the subject's earliest disposition record naming DISEASE PROGRESSION, STUDY DISCONTINUATION, or WITHDRAWAL OF CONSENT, and progression, discontinuation, and withdrawal are censoring reasons, never events. ADT is the deterioration date when a deterioration exists and is not dated after the first censoring reason. A deterioration and a death on the same date count as deterioration. Otherwise ADT is the death date when the subject died and the death is not dated after the first censoring reason. With neither, ADT is the last assessment dated on or before the first censoring reason, or the randomization date when no assessment qualifies. AVAL is the number of whole calendar months from STARTDT to ADT. CNSR is 0 when the subject has a deterioration or death event and 1 otherwise. EVNTDESC is "PRO DETERIORATION" for a deterioration event, "DEATH" for a death event, and "CENSORED" otherwise. CNSDTDSC has no value for an event, holds the censoring reason when one is available, and is "STUDY COMPLETION" otherwise. SRCDOM is "QS" for a deterioration or a censored record with a supporting assessment, and "ADSL" for a death or a record censored at randomization. SRCVAR is "ADT" for a deterioration or a censored record with a supporting assessment, "DTHDT" for a death, and "RANDDT" for a record censored at randomization. SRCSEQ is the QSSEQ of the deterioration or censoring assessment, and has no value when the analysis date comes from a subject-level date.
Read the source datasets from /app/input and save the completed dataset as /app/output/adtte.csv.
prompts/adam-adtte-pro-deterioration/conventions.md
Edit16 linesFollowing CDISC ADaM standards, use the provided ADSL, DS, and QS datasets to create an ADTTE dataset with one record per subject.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ
PARAMCD is "TTDGHS" and PARAM is "Time to Deterioration in Global Health Status". AVAL is the number of whole calendar months from STARTDT to ADT. CNSR is 0 or 1. EVNTDESC is "PRO DETERIORATION", "DEATH", or "CENSORED". CNSDTDSC holds the censoring reason, or is "STUDY COMPLETION", or has no value. SRCDOM is "QS" or "ADSL". SRCVAR is "ADT", "DTHDT", or "RANDDT".
Read the source datasets from /app/input and save the completed dataset as /app/output/adtte.csv.
prompts/adam-adtte-pro-deterioration/brief.md
Edit9 linesFollowing CDISC ADaM standards, use the provided ADSL, DS, and QS datasets to create an ADTTE dataset with one record per subject.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ
Read the source datasets from /app/input and save the completed dataset as /app/output/adtte.csv.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adtte-pro-deterioration/result.R
Edit153 lines# Reference solution for the yamaa benchmark adam-adtte-pro-deterioration# (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)library(lubridate, warn.conflicts = FALSE)whole_months <- function(d1, d2) { m <- (year(d2) - year(d1)) * 12 + (month(d2) - month(d1)) last <- days_in_month(d2) m - as.integer(day(d2) < day(d1) & day(d2) != last)}adsl <- read_csv( "/app/input/adsl.csv", col_types = cols( RANDDT = col_date(), DTHDT = col_date(), .default = col_character() ))qs <- read_csv( "/app/input/qs.csv", col_types = cols( QSSEQ = col_integer(), ADT = col_date(), AVAL = col_integer(), .default = col_character() ))ds <- read_csv( "/app/input/ds.csv", col_types = cols( DSSEQ = col_integer(), DSDTC = col_date(), .default = col_character() ))# Flag deteriorations as an internal step: the baseline is the earliest# dated assessment with a score (a scoreless row cannot be the baseline:# its null score would leave no later row able to meet the cutoff), and# an assessment deteriorates when it is dated after the baseline and its# score is at least 10 points below the baseline score.baseline <- qs |> filter(!is.na(ADT), !is.na(AVAL)) |> arrange(STUDYID, USUBJID, ADT, QSSEQ) |> distinct(STUDYID, USUBJID, .keep_all = TRUE) |> select(STUDYID, USUBJID, BASEDT = ADT, BASEVAL = AVAL)qs_flagged <- qs |> left_join(baseline, by = c("STUDYID", "USUBJID")) |> mutate( DETERFL = if_else( !is.na(ADT) & !is.na(BASEDT) & ADT > BASEDT & !is.na(AVAL) & AVAL <= BASEVAL - 10L, "Y", NA_character_ ) ) |> select(STUDYID, USUBJID, QSSEQ, AVISIT, ADT, AVAL, DETERFL) |> arrange(USUBJID, QSSEQ)# The first deterioration per subject, earliest date then lowest sequence.deter <- qs_flagged |> filter(DETERFL == "Y") |> arrange(STUDYID, USUBJID, ADT, QSSEQ) |> distinct(STUDYID, USUBJID, .keep_all = TRUE) |> select(STUDYID, USUBJID, DETERDT = ADT, DETERSEQ = QSSEQ)# The first censoring reason per subject. Only progression,# discontinuation, and withdrawal count; any other DS record is ignored.reason <- ds |> filter( DSDECOD %in% c( "DISEASE PROGRESSION", "STUDY DISCONTINUATION", "WITHDRAWAL OF CONSENT" ) ) |> arrange(STUDYID, USUBJID, DSDTC, DSSEQ) |> distinct(STUDYID, USUBJID, .keep_all = TRUE) |> select(STUDYID, USUBJID, CENSORRSNDT = DSDTC, CENSORRSN = DSDECOD)# The latest assessment with a score dated on or before the censoring# reason, with the latest sequence breaking a tied date; a scoreless row# is not an evaluable assessment and cannot set the censoring date.censor_qs <- qs_flagged |> inner_join(reason, by = c("STUDYID", "USUBJID")) |> filter(!is.na(ADT), !is.na(AVAL), ADT <= CENSORRSNDT) |> group_by(STUDYID, USUBJID) |> summarize( LASTQSLE = max(ADT), LASTQSSEQ = max(QSSEQ[ADT == max(ADT)]), .groups = "drop" )adtte <- adsl |> left_join(deter, by = c("STUDYID", "USUBJID")) |> left_join(reason, by = c("STUDYID", "USUBJID")) |> left_join(censor_qs, by = c("STUDYID", "USUBJID")) |> mutate( STARTDT = RANDDT, # Deterioration wins over death on the same date. Neither counts when # it falls after the first censoring reason: progression, # discontinuation, and withdrawal are censoring reasons, never events, # though a same-day deterioration or death still counts. DETER_EVENT = !is.na(DETERDT) & (is.na(DTHDT) | DETERDT <= DTHDT) & (is.na(CENSORRSNDT) | DETERDT <= CENSORRSNDT), DEATH_EVENT = !is.na(DTHDT) & (is.na(CENSORRSNDT) | DTHDT <= CENSORRSNDT), EVENTDT = case_when( DETER_EVENT ~ DETERDT, DEATH_EVENT ~ DTHDT, .default = as.Date(NA) ), CENSORDT = coalesce(LASTQSLE, STARTDT), ADT = coalesce(EVENTDT, CENSORDT), AVAL = whole_months(STARTDT, ADT), CNSR = if_else(!is.na(EVENTDT), 0L, 1L), EVNTDESC = case_when( DETER_EVENT ~ "PRO DETERIORATION", DEATH_EVENT ~ "DEATH", .default = "CENSORED" ), CNSDTDSC = case_when( !is.na(EVENTDT) ~ NA_character_, .default = coalesce(CENSORRSN, "STUDY COMPLETION") ), SRCDOM = case_when( DETER_EVENT ~ "QS", DEATH_EVENT ~ "ADSL", is.na(LASTQSLE) ~ "ADSL", .default = "QS" ), SRCVAR = case_when( DETER_EVENT ~ "ADT", DEATH_EVENT ~ "DTHDT", is.na(LASTQSLE) ~ "RANDDT", .default = "ADT" ), SRCSEQ = case_when( DETER_EVENT ~ DETERSEQ, is.na(EVENTDT) & !is.na(LASTQSLE) ~ LASTQSSEQ, .default = NA_integer_ ), PARAMCD = "TTDGHS", PARAM = "Time to Deterioration in Global Health Status" ) |> select( STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ ) |> arrange(USUBJID)dir.create("/app/output", showWarnings = FALSE)write_csv(adtte, "/app/output/adtte.csv", na = "")solutions/adam-adtte-pro-deterioration/result.py
Edit218 lines# Reference solution for the yamaa benchmark adam-adtte-pro-deterioration (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 the ADTTE dataset (time to deterioration in global health status).Reads ADSL, DS and QS from /app/input and writes /app/output/adtte.csvwith one record per subject."""import csvimport osfrom datetime import dateINPUT_DIR = "/app/input"OUTPUT_DIR = "/app/output"CENSOR_REASONS = { "DISEASE PROGRESSION", "STUDY DISCONTINUATION", "WITHDRAWAL OF CONSENT",}def parse_date(text): text = (text or "").strip() if not text: return None return date.fromisoformat(text[:10])def fmt(d): return d.isoformat() if d is not None else ""def whole_months(start, end): """Whole calendar months from start to end.""" months = (end.year - start.year) * 12 + (end.month - start.month) if end.day < start.day: months -= 1 return monthsdef read_csv(name): path = os.path.join(INPUT_DIR, name) with open(path, newline="", encoding="utf-8") as handle: return list(csv.DictReader(handle))def to_int(text, default=0): try: return int(float(text)) except (TypeError, ValueError): return defaultdef main(): adsl = read_csv("adsl.csv") ds = read_csv("ds.csv") qs = read_csv("qs.csv") ds_by_subject = {} for row in ds: ds_by_subject.setdefault(row["USUBJID"], []).append(row) qs_by_subject = {} for row in qs: qs_by_subject.setdefault(row["USUBJID"], []).append(row) out_rows = [] for subj in adsl: usubjid = subj["USUBJID"] randdt = parse_date(subj.get("RANDDT")) dthdt = parse_date(subj.get("DTHDT")) # Assessments with both a date and a score, in date/sequence order. assessments = [] for row in qs_by_subject.get(usubjid, []): adt = parse_date(row.get("ADT")) score_text = (row.get("AVAL") or "").strip() if adt is None or not score_text: continue assessments.append( { "date": adt, "score": float(score_text), "seq": to_int(row.get("QSSEQ")), } ) assessments.sort(key=lambda a: (a["date"], a["seq"])) # Deterioration: an assessment dated after the earliest assessment # whose score has fallen at least 10 points below that earliest # assessment's score; the earliest such assessment gives the date. deterioration = None if assessments: baseline = assessments[0] for a in assessments[1:]: if a["date"] > baseline["date"] and ( baseline["score"] - a["score"] >= 10 ): deterioration = a break # First censoring reason: earliest disposition record naming one of # the censoring reasons (never an event). censor_date = None censor_reason = "" records = [] for row in ds_by_subject.get(usubjid, []): if (row.get("DSDECOD") or "").strip() in CENSOR_REASONS: records.append( { "date": parse_date(row.get("DSDTC")), "seq": to_int(row.get("DSSEQ")), "reason": (row.get("DSDECOD") or "").strip(), } ) records.sort( key=lambda r: (r["date"] is None, r["date"] or date.min, r["seq"]) ) if records: censor_date = records[0]["date"] censor_reason = records[0]["reason"] def not_after_censor(d): return d is not None and (censor_date is None or d <= censor_date) # Event selection: deterioration and death compete on date, with a # deterioration winning a same-date tie; each must not be dated # after the first censoring reason. event = None # (kind, date, seq) candidates = [] if deterioration is not None and not_after_censor(deterioration["date"]): candidates.append(("deterioration", deterioration["date"], 0)) if dthdt is not None and not_after_censor(dthdt): candidates.append(("death", dthdt, 1)) if candidates: candidates.sort(key=lambda c: (c[1], c[2])) kind, event_date, _ = candidates[0] if kind == "deterioration": event = ("deterioration", event_date, deterioration["seq"]) else: event = ("death", event_date, None) if event is not None: kind, adt, seq = event cnsr = 0 cnsdtdsc = "" if kind == "deterioration": evntdesc = "PRO DETERIORATION" srcdom, srcvar, srcseq = "QS", "ADT", str(seq) else: evntdesc = "DEATH" srcdom, srcvar, srcseq = "ADSL", "DTHDT", "" else: cnsr = 1 evntdesc = "CENSORED" cnsdtdsc = censor_reason if censor_reason else "STUDY COMPLETION" qualifying = [ a for a in assessments if censor_date is None or a["date"] <= censor_date ] if qualifying: last = max(qualifying, key=lambda a: (a["date"], a["seq"])) adt = last["date"] srcdom, srcvar, srcseq = "QS", "ADT", str(last["seq"]) else: adt = randdt srcdom, srcvar, srcseq = "ADSL", "RANDDT", "" out_rows.append( { "STUDYID": subj["STUDYID"], "USUBJID": usubjid, "PARAMCD": "TTDGHS", "PARAM": "Time to Deterioration in Global Health Status", "STARTDT": fmt(randdt), "ADT": fmt(adt), "AVAL": str(whole_months(randdt, adt)), "CNSR": str(cnsr), "EVNTDESC": evntdesc, "CNSDTDSC": cnsdtdsc, "SRCDOM": srcdom, "SRCVAR": srcvar, "SRCSEQ": srcseq, } ) fieldnames = [ "STUDYID", "USUBJID", "PARAMCD", "PARAM", "STARTDT", "ADT", "AVAL", "CNSR", "EVNTDESC", "CNSDTDSC", "SRCDOM", "SRCVAR", "SRCSEQ", ] os.makedirs(OUTPUT_DIR, exist_ok=True) with open( os.path.join(OUTPUT_DIR, "adtte.csv"), "w", newline="", encoding="utf-8" ) as handle: writer = csv.DictWriter(handle, fieldnames=fieldnames) writer.writeheader() writer.writerows(out_rows)if __name__ == "__main__": main()solutions/adam-adtte-pro-deterioration/result.sas
Edit60 lines/* opensas reference solution for the yamaa benchmark adam-adtte-pro-deterioration. 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 RANDDT DTHDT $1024; infile "/app/input/adsl.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. RANDDT : $char1024. DTHDT : $char1024.;run;data ds; length STUDYID USUBJID DSDECOD DSDTC $1024; infile "/app/input/ds.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. DSDECOD : $char1024. DSDTC : $char1024. DSSEQ;run;data qs; length STUDYID USUBJID AVISIT ADT $1024; infile "/app/input/qs.csv" dsd dlm="," firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. QSSEQ AVISIT : $char1024. ADT : $char1024. AVAL;run;proc sort data=qs(where=(ADT ne '')) out=ordered; by STUDYID USUBJID ADT QSSEQ; run;data deteriorations; length BASEDT $10; retain BASEDT BASEVAL; set ordered; by STUDYID USUBJID; /* The baseline is the earliest dated assessment with a score; a scoreless row cannot be the baseline. */ if first.USUBJID then do; BASEDT=''; BASEVAL=.; end; if missing(BASEDT) and not missing(AVAL) then do; BASEDT=ADT; BASEVAL=AVAL; end; if not missing(BASEDT) and ADT>BASEDT and not missing(AVAL) and AVAL<=BASEVAL-10;run;data deterioration; set deteriorations; by STUDYID USUBJID; if first.USUBJID; keep STUDYID USUBJID ADT QSSEQ; rename ADT=DETERDT QSSEQ=DETERSEQ; run;data ds_order; set ds; NODATE=missing(DSDTC); run;proc sort data=ds_order(where=(DSDECOD in ('DISEASE PROGRESSION','STUDY DISCONTINUATION','WITHDRAWAL OF CONSENT'))) out=reasons; by STUDYID USUBJID NODATE DSDTC DSSEQ; run;data reason; set reasons; by STUDYID USUBJID; if first.USUBJID; keep STUDYID USUBJID DSDTC DSDECOD; rename DSDTC=REASONDT DSDECOD=REASON; run;proc sql; create table assessment_reasons as select a.*,b.REASONDT from ordered as a left join reason as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID; quit;data candidates; set assessment_reasons; if not missing(AVAL) and (missing(REASONDT) or ADT<=REASONDT); run;proc sort data=candidates; by STUDYID USUBJID descending ADT descending QSSEQ; run;data lastassessment; set candidates; by STUDYID USUBJID; if first.USUBJID; keep STUDYID USUBJID ADT QSSEQ; rename ADT=LASTDT QSSEQ=LASTSEQ; run;proc sql; create table joined as select a.*,b.DETERDT,b.DETERSEQ,c.REASONDT,c.REASON,d.LASTDT,d.LASTSEQ from adsl as a left join deterioration as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID left join reason as c on a.STUDYID=c.STUDYID and a.USUBJID=c.USUBJID left join lastassessment as d on a.STUDYID=d.STUDYID and a.USUBJID=d.USUBJID; quit;data result; length PARAMCD $8 PARAM $60 STARTDT ADT $10 EVNTDESC CNSDTDSC $40 SRCDOM $4 SRCVAR $16; set joined; PARAMCD='TTDGHS'; PARAM='Time to Deterioration in Global Health Status'; STARTDT=RANDDT; if not missing(DETERDT) and (missing(DTHDT) or DETERDT<=DTHDT) and (missing(REASONDT) or DETERDT<=REASONDT) then do; ADT=DETERDT; CNSR=0; EVNTDESC='PRO DETERIORATION'; SRCDOM='QS'; SRCVAR='ADT'; SRCSEQ=DETERSEQ; end; else if not missing(DTHDT) and (missing(REASONDT) or DTHDT<=REASONDT) then do; ADT=DTHDT; CNSR=0; EVNTDESC='DEATH'; SRCDOM='ADSL'; SRCVAR='DTHDT'; end; else do; CNSR=1; EVNTDESC='CENSORED'; CNSDTDSC=coalescec(REASON,'STUDY COMPLETION'); if not missing(LASTDT) then do; ADT=LASTDT; SRCDOM='QS'; SRCVAR='ADT'; SRCSEQ=LASTSEQ; end; else do; ADT=RANDDT; SRCDOM='ADSL'; SRCVAR='RANDDT'; end; end; if not missing(STARTDT) and not missing(ADT) then AVAL=intck('month',input(STARTDT,yymmdd10.),input(ADT,yymmdd10.),'continuous');run;proc sql; create table final as select STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ from result;quit;proc export data=final outfile="/app/output/adtte.csv" dbms=csv replace; run;
yamaa