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.
- 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.
- Inputs: Do they look like data collected in a real trial, and do they include the situations you would expect to meet in one?
- 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
README.mdGoal: 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 |
Comments and assessment materials
| 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.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
Edit44 linesFollowing CDISC ADaM standards, use the provided ADSL, QS, and DS datasets to create an ADTTE dataset for time to PRO deterioration, one record per subject.
The ADTTE dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ.
Use PARAMCD "TTDGHS" and PARAM "Time to Deterioration in Global Health Status". STARTDT is the randomization date, the origin from which the time to deterioration is counted. QSSEQ is the input sequence number, as given.
The baseline score is the score from the earliest assessment. An assessment is a deterioration when it is dated after the baseline assessment and its score is at least 10 points below the baseline score; the baseline assessment itself is never a deterioration.
The event is the first deterioration, or death when the subject dies without a prior deterioration; a deterioration and a death on the same date count as deterioration. Neither counts when it falls after the first censoring reason. ADT is the deterioration date for a deterioration event and the death date for a death event.
Disease progression, study discontinuation, and withdrawal of consent are censoring reasons, never events; only the earliest such record counts. A censored subject's ADT is the latest assessment dated on or before the censoring reason, or the randomization date when no assessment qualifies. A deterioration dated the same day as the censoring reason still counts as the event.
AVAL is the number of whole calendar months from STARTDT to ADT. CNSR is 0 for a deterioration or death and 1 otherwise. EVNTDESC is PRO DETERIORATION for a deterioration, DEATH for a death, and CENSORED otherwise. CNSDTDSC has no value for an event and holds the censoring reason for a censored record, or STUDY COMPLETION when no DS record names a reason.
Trace ADT with SRCDOM QS, SRCVAR ADT, and the assessment sequence for a deterioration; with SRCDOM ADSL, SRCVAR DTHDT, and no sequence for a death; with SRCDOM QS, SRCVAR ADT, and the assessment sequence when censoring at an assessment; and with SRCDOM ADSL, SRCVAR RANDDT, and no sequence when censoring at randomization.
Read the source datasets from /app/input and save the completed dataset as /app/output/adtte.csv.
prompts/adam-adtte-pro-deterioration/conventions.md
Edit20 linesFollowing CDISC ADaM standards, use the provided ADSL, QS, and DS datasets to create an ADTTE dataset for time to PRO deterioration, one record per subject.
The ADTTE dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, STARTDT, ADT, AVAL, CNSR, EVNTDESC, CNSDTDSC, SRCDOM, SRCVAR, SRCSEQ.
Use PARAMCD "TTDGHS" and PARAM "Time to Deterioration in Global Health Status". A deterioration is dated after the earliest assessment and scores at least 10 points below it. The event is the first deterioration, or death without a prior one; neither counts when it falls after the first censoring reason. Progression, discontinuation, and withdrawal are censoring reasons, never events. AVAL counts whole calendar months from STARTDT to ADT; CNSR is 0 for an event and 1 otherwise. EVNTDESC is PRO DETERIORATION, DEATH, or CENSORED; CNSDTDSC holds the censoring reason (STUDY COMPLETION when no reason is recorded). Trace ADT as QS/ADT/sequence, ADSL/DTHDT, or ADSL/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
Edit10 linesFollowing CDISC ADaM standards, use the provided ADSL, QS, and DS datasets to create an ADTTE dataset for time to PRO deterioration, one record per subject.
The ADTTE 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.
run.R
Edit4 lineslibrary(yamaa)adtte <- yamaa_domain("spec.yaml")$outputadtterun.py
Edit4 linesimport yamaaadtte = yamaa.yamaa_domain("spec.yaml").outputadttesolutions/adam-adtte-pro-deterioration/result.R
Edit147 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# assessment, and an assessment deteriorates when it is dated after the# baseline and its score is at least 10 points below the baseline score.qs_flagged <- qs |> arrange(STUDYID, USUBJID, ADT, QSSEQ) |> group_by(STUDYID, USUBJID) |> mutate( BASEDT = first(ADT), BASEVAL = first(AVAL), DETERFL = if_else( !is.na(ADT) & !is.na(BASEDT) & ADT > BASEDT & AVAL <= BASEVAL - 10L, "Y", NA_character_ ) ) |> ungroup() |> 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 dated on or before the censoring reason, with the# latest sequence breaking a tied date.censor_qs <- qs_flagged |> inner_join(reason, by = c("STUDYID", "USUBJID")) |> filter(!is.na(ADT), 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
Edit178 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.import calendarfrom pathlib import Pathimport polars as pldef whole_months(d1, d2): """Whole calendar months from d1 to d2.""" m = (d2.year - d1.year) * 12 + (d2.month - d1.month) last = calendar.monthrange(d2.year, d2.month)[1] if d2.day < d1.day and d2.day != last: m -= 1 return madsl = pl.read_csv( "/app/input/adsl.csv", infer_schema=False, null_values="").with_columns( pl.col("RANDDT").str.to_date(strict=False), pl.col("DTHDT").str.to_date(strict=False),)qs = pl.read_csv("/app/input/qs.csv", infer_schema=False, null_values="").with_columns( pl.col("QSSEQ").cast(pl.Int64, strict=False), pl.col("ADT").str.to_date(strict=False), pl.col("AVAL").cast(pl.Int64, strict=False),)ds = pl.read_csv("/app/input/ds.csv", infer_schema=False, null_values="").with_columns( pl.col("DSSEQ").cast(pl.Int64, strict=False), pl.col("DSDTC").str.to_date(strict=False),)# Flag deteriorations as an internal step: the baseline is the earliest# assessment, and an assessment deteriorates when it is dated after the# baseline and its score is at least 10 points below the baseline score.qs_flagged = ( qs.sort(["STUDYID", "USUBJID", "ADT", "QSSEQ"], nulls_last=True) .with_columns( BASEDT=pl.col("ADT").first().over(["STUDYID", "USUBJID"]), BASEVAL=pl.col("AVAL").first().over(["STUDYID", "USUBJID"]), ) .with_columns( DETERFL=pl.when( pl.col("ADT").is_not_null() & (pl.col("ADT") > pl.col("BASEDT")) & (pl.col("AVAL") <= pl.col("BASEVAL") - 10) ) .then(pl.lit("Y")) .otherwise(None) ) .select("STUDYID", "USUBJID", "QSSEQ", "AVISIT", "ADT", "AVAL", "DETERFL") .sort(["USUBJID", "QSSEQ"]))# The first deterioration per subject, earliest date then lowest sequence.deter = ( qs_flagged.filter(pl.col("DETERFL") == "Y") .sort(["STUDYID", "USUBJID", "ADT", "QSSEQ"]) .unique(["STUDYID", "USUBJID"], keep="first", maintain_order=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( pl.col("DSDECOD").is_in( ["DISEASE PROGRESSION", "STUDY DISCONTINUATION", "WITHDRAWAL OF CONSENT"] ) ) .sort(["STUDYID", "USUBJID", "DSDTC", "DSSEQ"], nulls_last=True) .unique(["STUDYID", "USUBJID"], keep="first", maintain_order=True) .select("STUDYID", "USUBJID", CENSORRSNDT="DSDTC", CENSORRSN="DSDECOD"))# The latest assessment dated on or before the censoring reason, with the# latest sequence breaking a tied date.censor_qs = ( qs_flagged.join(reason, on=["STUDYID", "USUBJID"], how="inner") .filter(pl.col("ADT").is_not_null() & (pl.col("ADT") <= pl.col("CENSORRSNDT"))) .group_by(["STUDYID", "USUBJID"]) .agg( LASTQSLE=pl.col("ADT").max(), LASTQSSEQ=pl.col("QSSEQ").filter(pl.col("ADT") == pl.col("ADT").max()).max(), ))adtte = ( adsl.join(deter, on=["STUDYID", "USUBJID"], how="left", maintain_order="left") .join(reason, on=["STUDYID", "USUBJID"], how="left", maintain_order="left") .join(censor_qs, on=["STUDYID", "USUBJID"], how="left", maintain_order="left") .with_columns(STARTDT=pl.col("RANDDT")) .with_columns( # 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=pl.col("DETERDT").is_not_null() & (pl.col("DTHDT").is_null() | (pl.col("DETERDT") <= pl.col("DTHDT"))) & ( pl.col("CENSORRSNDT").is_null() | (pl.col("DETERDT") <= pl.col("CENSORRSNDT")) ), DEATH_EVENT=pl.col("DTHDT").is_not_null() & ( pl.col("CENSORRSNDT").is_null() | (pl.col("DTHDT") <= pl.col("CENSORRSNDT")) ), ) .with_columns( EVENTDT=pl.when(pl.col("DETER_EVENT")) .then(pl.col("DETERDT")) .when(pl.col("DEATH_EVENT")) .then(pl.col("DTHDT")) .otherwise(None), CENSORDT=pl.col("LASTQSLE").fill_null(pl.col("STARTDT")), ) .with_columns(ADT=pl.col("EVENTDT").fill_null(pl.col("CENSORDT"))) .with_columns( AVAL=pl.struct(["STARTDT", "ADT"]).map_elements( lambda s: whole_months(s["STARTDT"], s["ADT"]), return_dtype=pl.Int64 ), CNSR=pl.when(pl.col("EVENTDT").is_not_null()).then(0).otherwise(1), EVNTDESC=pl.when(pl.col("DETER_EVENT")) .then(pl.lit("PRO DETERIORATION")) .when(pl.col("DEATH_EVENT")) .then(pl.lit("DEATH")) .otherwise(pl.lit("CENSORED")), CNSDTDSC=pl.when(pl.col("EVENTDT").is_not_null()) .then(None) .otherwise(pl.col("CENSORRSN").fill_null(pl.lit("STUDY COMPLETION"))), SRCDOM=pl.when(pl.col("DETER_EVENT")) .then(pl.lit("QS")) .when(pl.col("DEATH_EVENT")) .then(pl.lit("ADSL")) .when(pl.col("LASTQSLE").is_null()) .then(pl.lit("ADSL")) .otherwise(pl.lit("QS")), SRCVAR=pl.when(pl.col("DETER_EVENT")) .then(pl.lit("ADT")) .when(pl.col("DEATH_EVENT")) .then(pl.lit("DTHDT")) .when(pl.col("LASTQSLE").is_null()) .then(pl.lit("RANDDT")) .otherwise(pl.lit("ADT")), SRCSEQ=pl.when(pl.col("DETER_EVENT")) .then(pl.col("DETERSEQ")) .when(pl.col("EVENTDT").is_null() & pl.col("LASTQSLE").is_not_null()) .then(pl.col("LASTQSSEQ")) .otherwise(None), PARAMCD=pl.lit("TTDGHS"), PARAM=pl.lit("Time to Deterioration in Global Health Status"), ) .select( "STUDYID", "USUBJID", "PARAMCD", "PARAM", "STARTDT", "ADT", "AVAL", "CNSR", "EVNTDESC", "CNSDTDSC", "SRCDOM", "SRCVAR", "SRCSEQ", ) .sort("USUBJID"))Path("/app/output").mkdir(parents=True, exist_ok=True)adtte.write_csv("/app/output/adtte.csv", null_value="")solutions/adam-adtte-pro-deterioration/result.sas
Edit57 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; if first.USUBJID then do; BASEDT=ADT; BASEVAL=AVAL; end; if ADT>BASEDT and not missing(AVAL) and not missing(BASEVAL) 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 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
Comments
Signed in with GitHub; saved in repository Discussions