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: split each adverse event (AE) into continuous analysis periods
and mark treatment-emergent periods (TRTEMFL).
Input: AE holds one record per event, with its sequence (AESEQ),
term (AEDECOD), overall start/end (AESTDTC, AEENDTC) and maximum
severity/grade (AESEV, AETOXGR). Findings About (FA) holds severity
(SEV) and toxicity-grade (TOXGR) findings at onset and each collected
change time (FADTC). FALNKID matches the event's AELNKID. The
subject-level analysis dataset (ADSL) supplies first exposure (TRTSDTM),
empty for an untreated subject.
Variables:
ASEQnumbers analysis periods within a subject; repeatedAESEQretains the source event identity and its original values.ASTDTMstarts a period at a collected change time.AENDTMis the next period's start, or the original end for the last period; an ongoing event's last end stays empty.ASEVandATOXGRNhold the period's severity and numeric toxicity grade (1 through 5). Each persists until that dimension changes; without an earlier collected value it stays empty.TRTEMFLisYfor events starting at or after first exposure. For an ongoing pre-treatment event, it starts at the first post-exposure increase over the last pre-treatment severity or grade and remainsYon subsequent periods. Earlier periods, improvement alone and untreated subjects' periods stay empty.
Note: findings are collected effective change times, rather than visit assessments from which a change time must be inferred. Simultaneous severity and grade changes create one period. Missing comparison values show no worsening for that dimension. Separate events sharing a term remain separate. Periods are continuous with end equal to next start.
Inputs
Under review3 source filesinput/adsl.csv
Edit5 rows| STUDYID | USUBJID | TRTSDTM |
|---|---|---|
| PILOT7 | 2025-03-04T09:00:00 | |
| PILOT7 | 2025-03-04T09:00:00 | |
| PILOT7 | 2025-03-04T09:00:00 | |
| PILOT7 | — | |
| PILOT7 | 2025-03-04T09:00:00 |
input/ae.csv
Edit6 rows| STUDYID | USUBJID | AESEQ | AELNKID | AEDECOD | AESTDTC | AEENDTC | AESEV | AETOXGR |
|---|---|---|---|---|---|---|---|---|
| PILOT7 | 1 | AE-1 | HEADACHE | 2025-03-04T07:00:00 | 2025-03-05T10:00:00 | SEVERE | 1 | |
| PILOT7 | 2 | AE-2 | HEADACHE | 2025-03-06T10:00:00 | 2025-03-06T11:00:00 | MILD | 1 | |
| PILOT7 | 1 | AE-1 | RASH | 2025-03-03T09:00:00 | 2025-03-07T09:00:00 | SEVERE | 2 | |
| PILOT7 | 1 | AE-1 | VOMITING | 2025-03-03T10:00:00 | 2025-03-06T10:00:00 | MODERATE | 2 | |
| PILOT7 | 1 | AE-1 | FATIGUE | 2025-03-03T09:00:00 | 2025-03-06T09:00:00 | SEVERE | 1 | |
| PILOT7 | 1 | AE-1 | COUGH | 2025-03-03T09:00:00 | — | SEVERE | 3 |
input/fa.csv
Edit20 rows| STUDYID | USUBJID | FASEQ | FALNKID | FATESTCD | FAOBJ | FASTRESC | FADTC |
|---|---|---|---|---|---|---|---|
| PILOT7 | 1 | AE-1 | SEV | HEADACHE | MILD | 2025-03-04T07:00:00 | |
| PILOT7 | 2 | AE-1 | TOXGR | HEADACHE | 1 | 2025-03-04T07:00:00 | |
| PILOT7 | 3 | AE-2 | SEV | HEADACHE | MILD | 2025-03-06T10:00:00 | |
| PILOT7 | 4 | AE-2 | TOXGR | HEADACHE | 1 | 2025-03-06T10:00:00 | |
| PILOT7 | 1 | AE-1 | SEV | RASH | MODERATE | 2025-03-03T09:00:00 | |
| PILOT7 | 2 | AE-1 | TOXGR | RASH | 2 | 2025-03-03T09:00:00 | |
| PILOT7 | 1 | AE-1 | SEV | VOMITING | MODERATE | 2025-03-03T10:00:00 | |
| PILOT7 | 2 | AE-1 | TOXGR | VOMITING | 1 | 2025-03-03T10:00:00 | |
| PILOT7 | 1 | AE-1 | SEV | FATIGUE | MILD | 2025-03-03T09:00:00 | |
| PILOT7 | 2 | AE-1 | TOXGR | FATIGUE | 1 | 2025-03-03T09:00:00 | |
| PILOT7 | 1 | AE-1 | SEV | COUGH | SEVERE | 2025-03-03T09:00:00 | |
| PILOT7 | 2 | AE-1 | TOXGR | COUGH | 3 | 2025-03-03T09:00:00 | |
| PILOT7 | 5 | AE-1 | SEV | HEADACHE | MODERATE | 2025-03-04T11:00:00 | |
| PILOT7 | 6 | AE-1 | SEV | HEADACHE | SEVERE | 2025-03-04T13:00:00 | |
| PILOT7 | 7 | AE-1 | SEV | HEADACHE | MILD | 2025-03-05T06:00:00 | |
| PILOT7 | 3 | AE-1 | SEV | RASH | MILD | 2025-03-05T09:00:00 | |
| PILOT7 | 4 | AE-1 | SEV | RASH | SEVERE | 2025-03-06T09:00:00 | |
| PILOT7 | 3 | AE-1 | TOXGR | VOMITING | 2 | 2025-03-05T10:00:00 | |
| PILOT7 | 3 | AE-1 | SEV | FATIGUE | SEVERE | 2025-03-05T09:00:00 | |
| PILOT7 | 3 | AE-1 | SEV | COUGH | MODERATE | 2025-03-05T09:00:00 |
Expected output
Under review14 expected rowsexpected/adae.csv
Edit14 rows| STUDYID | USUBJID | ASEQ | AESEQ | AEDECOD | AESTDTC | AEENDTC | ASTDTM | AENDTM | TRTSDTM | AESEV | AETOXGR | ASEV | ATOXGRN | TRTEMFL |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PILOT7 | 1 | 1 | HEADACHE | 2025-03-04T07:00:00 | 2025-03-05T10:00:00 | 2025-03-04T07:00:00 | 2025-03-04T11:00:00 | 2025-03-04T09:00:00 | SEVERE | 1 | MILD | 1 | — | |
| PILOT7 | 2 | 1 | HEADACHE | 2025-03-04T07:00:00 | 2025-03-05T10:00:00 | 2025-03-04T11:00:00 | 2025-03-04T13:00:00 | 2025-03-04T09:00:00 | SEVERE | 1 | MODERATE | 1 | Y | |
| PILOT7 | 3 | 1 | HEADACHE | 2025-03-04T07:00:00 | 2025-03-05T10:00:00 | 2025-03-04T13:00:00 | 2025-03-05T06:00:00 | 2025-03-04T09:00:00 | SEVERE | 1 | SEVERE | 1 | Y | |
| PILOT7 | 4 | 1 | HEADACHE | 2025-03-04T07:00:00 | 2025-03-05T10:00:00 | 2025-03-05T06:00:00 | 2025-03-05T10:00:00 | 2025-03-04T09:00:00 | SEVERE | 1 | MILD | 1 | Y | |
| PILOT7 | 5 | 2 | HEADACHE | 2025-03-06T10:00:00 | 2025-03-06T11:00:00 | 2025-03-06T10:00:00 | 2025-03-06T11:00:00 | 2025-03-04T09:00:00 | MILD | 1 | MILD | 1 | Y | |
| PILOT7 | 1 | 1 | RASH | 2025-03-03T09:00:00 | 2025-03-07T09:00:00 | 2025-03-03T09:00:00 | 2025-03-05T09:00:00 | 2025-03-04T09:00:00 | SEVERE | 2 | MODERATE | 2 | — | |
| PILOT7 | 2 | 1 | RASH | 2025-03-03T09:00:00 | 2025-03-07T09:00:00 | 2025-03-05T09:00:00 | 2025-03-06T09:00:00 | 2025-03-04T09:00:00 | SEVERE | 2 | MILD | 2 | — | |
| PILOT7 | 3 | 1 | RASH | 2025-03-03T09:00:00 | 2025-03-07T09:00:00 | 2025-03-06T09:00:00 | 2025-03-07T09:00:00 | 2025-03-04T09:00:00 | SEVERE | 2 | SEVERE | 2 | Y | |
| PILOT7 | 1 | 1 | VOMITING | 2025-03-03T10:00:00 | 2025-03-06T10:00:00 | 2025-03-03T10:00:00 | 2025-03-05T10:00:00 | 2025-03-04T09:00:00 | MODERATE | 2 | MODERATE | 1 | — | |
| PILOT7 | 2 | 1 | VOMITING | 2025-03-03T10:00:00 | 2025-03-06T10:00:00 | 2025-03-05T10:00:00 | 2025-03-06T10:00:00 | 2025-03-04T09:00:00 | MODERATE | 2 | MODERATE | 2 | Y | |
| PILOT7 | 1 | 1 | FATIGUE | 2025-03-03T09:00:00 | 2025-03-06T09:00:00 | 2025-03-03T09:00:00 | 2025-03-05T09:00:00 | — | SEVERE | 1 | MILD | 1 | — | |
| PILOT7 | 2 | 1 | FATIGUE | 2025-03-03T09:00:00 | 2025-03-06T09:00:00 | 2025-03-05T09:00:00 | 2025-03-06T09:00:00 | — | SEVERE | 1 | SEVERE | 1 | — | |
| PILOT7 | 1 | 1 | COUGH | 2025-03-03T09:00:00 | — | 2025-03-03T09:00:00 | 2025-03-05T09:00:00 | 2025-03-04T09:00:00 | SEVERE | 3 | SEVERE | 3 | — | |
| PILOT7 | 2 | 1 | COUGH | 2025-03-03T09:00:00 | — | 2025-03-05T09:00:00 | — | 2025-03-04T09:00:00 | SEVERE | 3 | MODERATE | 3 | — |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADAE | STUDYID | Study Identifier | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADAE | USUBJID | Unique Subject Identifier | character | Set per constructed row; see the Row construction sheet. | Derived | ||
| ADAE | AESEQ | Source Adverse Event Sequence Number | numeric | Take AESEQ from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | ||
| ADAE | AEDECOD | Dictionary-Derived Term | character | Take AEDECOD from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | ||
| ADAE | AESTDTC | Source Event Start Date/Time | numeric | ISO 8601 | Take AESTDTC from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | |
| ADAE | AEENDTC | Source Event End Date/Time | numeric | ISO 8601 | Take AEENDTC from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | |
| ADAE | ASTDTM | Analysis Segment Start Date/Time | numeric | ISO 8601 | Set per constructed row; see the Row construction sheet. | Derived | |
| ADAE | AENDTM | Analysis Segment End Date/Time | numeric | ISO 8601 | First non-missing of NEXTSTART and AEENDTC. | Derived | |
| ADAE | TRTSDTM | Datetime of First Exposure to Treatment | numeric | ISO 8601 | Copy value from ADSL.TRTSDTM. | Predecessor | |
| ADAE | AESEV | Source Event Severity/Intensity | character | Take AESEV from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | ||
| ADAE | AETOXGR | Source Event Standard Toxicity Grade | character | Take AETOXGR from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error. | Derived | ||
| ADAE | ASEV | Analysis Severity/Intensity | character | MILD, MODERATE, SEVERE | Last observation carried forward from SEVCOL using window EVENT_ORDER. | Derived | |
| ADAE | ATOXGRN | Analysis Toxicity Grade (N) | numeric | 1, 2, 3, 4, 5 | Last observation carried forward from GRDCOL using window EVENT_ORDER. | Derived | |
| ADAE | TRTEMFL | Treatment Emergent Analysis Flag | character | Y | "Y" when AESTDTC IS NOT NULL AND TRTSDTM IS NOT NULL AND (AESTDTC >= TRTSDTM OR ASTDTM >= FIRSTWRS); blank otherwise. | Derived | |
| ADAE | ASEQ | Analysis Sequence Number | numeric | Row number within each group of STUDYID, USUBJID, ordered by AESEQ then ASTDTM. | Derived |
| Template | Include when | STUDYID | USUBJID | AELNKID | ASTDTM | SEVCOL | GRDCOL |
|---|---|---|---|---|---|---|---|
| severity_periods | Aggregate MAX(FA.STUDYID) over records matching on the shared output keys | Aggregate MAX(FA.USUBJID) over records matching on the shared output keys | Aggregate MAX(FA.FALNKID) over records matching on the shared output keys | Aggregate MAX(FA.FADTC) over records matching on the shared output keys | Aggregate ONLY(FA.FASTRESC) over records matching on the shared output keys, where FA.FATESTCD = 'SEV' | Aggregate ONLY(FA.FASTRESC) over records matching on the shared output keys, where FA.FATESTCD = 'TOXGR' |
| Variable | Permitted values |
|---|---|
| ASEV | MILD, MODERATE, SEVERE |
| ATOXGRN | 1, 2, 3, 4, 5 |
| TRTEMFL | Y |
| 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-adae-worsening-emergence/full.md
Edit33 linesFollowing CDISC ADaM standards, use the provided ADSL, AE, and FA datasets to create an ADAE dataset with one record per subject per adverse event per analysis period.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, AESEQ, AEDECOD, AESTDTC, AEENDTC, ASTDTM, AENDTM, TRTSDTM, AESEV, AETOXGR, ASEV, ATOXGRN, TRTEMFL
A period starts at the event's onset and at each change time collected in FA, where FALNKID matches the event's AELNKID, and the FA times are collected effective change times used as they stand. Severity and grade findings collected at the same time form a single period. Separate events that share a term remain separate events. Every period of an event holds the event's AESEQ, AEDECOD, AESTDTC, AEENDTC, AESEV, and AETOXGR. ASEQ numbers the periods within a subject in order of AESEQ and then ASTDTM, starting at 1. ASTDTM is the period's start. AENDTM is the next period's start, or AEENDTC for the last period, and the last period of an ongoing event has no AENDTM. ASEV is the severity most recently collected at or before the period's start, and ATOXGRN is the numeric toxicity grade (1 through 5) most recently collected at or before the period's start; each has no value until its dimension is first collected. Severity ranks in the order MILD, then MODERATE, then SEVERE. TRTEMFL is Y on every period of an event that starts at or after the subject's first exposure. For an event that starts before first exposure, TRTEMFL is Y from the first period that starts at or after first exposure and whose severity or grade is higher than the last pre-treatment period's severity or grade, and on every later period of that event. A missing severity or grade on either side of that comparison shows no increase for that dimension. TRTEMFL has no value on any other period: improvement alone is not treatment-emergent, and no period of an untreated subject is Y.
Read the source datasets from /app/input and save the completed dataset as /app/output/adae.csv.
prompts/adam-adae-worsening-emergence/conventions.md
Edit13 linesFollowing CDISC ADaM standards, use the provided ADSL, AE, and FA datasets to create an ADAE dataset with one record per subject per adverse event per analysis period.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, AESEQ, AEDECOD, AESTDTC, AEENDTC, ASTDTM, AENDTM, TRTSDTM, AESEV, AETOXGR, ASEV, ATOXGRN, TRTEMFL
Severity ranks in the order MILD, then MODERATE, then SEVERE. TRTEMFL is Y or has no value.
Read the source datasets from /app/input and save the completed dataset as /app/output/adae.csv.
prompts/adam-adae-worsening-emergence/brief.md
Edit10 linesFollowing CDISC ADaM standards, use the provided ADSL, AE, and FA datasets to create an ADAE dataset with one record per subject per adverse event per analysis period.
The output dataset should contain the following columns in this order: STUDYID, USUBJID, ASEQ, AESEQ, AEDECOD, AESTDTC, AEENDTC, ASTDTM, AENDTM, TRTSDTM, AESEV, AETOXGR, ASEV, ATOXGRN, TRTEMFL
Read the source datasets from /app/input and save the completed dataset as /app/output/adae.csv.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adae-worsening-emergence/result.R
Edit86 lines# R reference for the yamaa benchmark adam-adae-worsening-emergence.# Uses the collected event records and dated findings to construct periods.library(readr)read_input <- function(name) { read_csv(paste0('/app/input/', name), col_types = cols(.default = col_character()), show_col_types = FALSE)}normalize <- function(x) { short <- !is.na(x) & nchar(x) == 16L x[short] <- paste0(x[short], ':00') x}ae <- read_input('ae.csv')fa <- read_input('fa.csv')adsl <- read_input('adsl.csv')fa$FADTC <- normalize(fa$FADTC)ae <- ae[order(ae$STUDYID, ae$USUBJID, as.integer(ae$AESEQ)), ]ranks <- c(MILD = 1L, MODERATE = 2L, SEVERE = 3L)output <- list()previous_study <- previous_subject <- NA_character_aseq <- 0Lfor (i in seq_len(nrow(ae))) { event <- ae[i, ] subject <- event$USUBJID study <- event$STUDYID if (!identical(study, previous_study) || !identical(subject, previous_subject)) aseq <- 0L previous_study <- study previous_subject <- subject trt <- normalize(adsl$TRTSDTM[ adsl$STUDYID == study & adsl$USUBJID == subject ])[1] onset <- normalize(event$AESTDTC) ending <- normalize(event$AEENDTC) findings <- fa[fa$STUDYID == study & fa$USUBJID == subject & fa$FALNKID == event$AELNKID, ] times <- sort(unique(findings$FADTC)) sevs <- rep(NA_character_, length(times)) grades <- rep(NA_integer_, length(times)) sev <- NA_character_ grade <- NA_integer_ for (j in seq_along(times)) { updates <- findings[findings$FADTC == times[j], ] if (anyDuplicated(updates$FATESTCD)) stop('Duplicate finding at event/time') new_sev <- updates$FASTRESC[updates$FATESTCD == 'SEV'] new_grade <- updates$FASTRESC[updates$FATESTCD == 'TOXGR'] if (length(new_sev) && !is.na(new_sev)) sev <- new_sev if (length(new_grade) && !is.na(new_grade)) grade <- as.integer(new_grade) sevs[j] <- sev grades[j] <- grade } before <- if (is.na(trt)) integer() else which(times < trt) baseline_sev <- baseline_grade <- NA_integer_ if (length(before)) { last <- tail(before, 1) baseline_sev <- unname(ranks[sevs[last]]) baseline_grade <- grades[last] } emergent <- FALSE for (j in seq_along(times)) { if (!is.na(onset) && !is.na(trt)) { if (onset >= trt) emergent <- TRUE else if (times[j] >= trt) { rank <- unname(ranks[sevs[j]]) sev_worse <- !is.na(rank) && !is.na(baseline_sev) && rank > baseline_sev grade_worse <- !is.na(grades[j]) && !is.na(baseline_grade) && grades[j] > baseline_grade emergent <- emergent || sev_worse || grade_worse } } aseq <- aseq + 1L output[[length(output) + 1L]] <- data.frame( STUDYID = study, USUBJID = subject, ASEQ = aseq, AESEQ = as.integer(event$AESEQ), AEDECOD = event$AEDECOD, AESTDTC = onset, AEENDTC = ending, ASTDTM = times[j], AENDTM = if (j < length(times)) times[j + 1L] else ending, TRTSDTM = trt, AESEV = event$AESEV, AETOXGR = event$AETOXGR, ASEV = sevs[j], ATOXGRN = grades[j], TRTEMFL = if (emergent) 'Y' else NA_character_ ) }}dir.create('/app/output', showWarnings = FALSE)write_csv(do.call(rbind, output), '/app/output/adae.csv', na = '')solutions/adam-adae-worsening-emergence/result.py
Edit179 lines# Reference solution for the yamaa benchmark adam-adae-worsening-emergence (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 ADAE with one record per subject per adverse event per analysisperiod, carrying forward the most recently collected severity and toxicitygrade, and flagging treatment-emergent worsening after first exposure."""import csvfrom datetime import datetimeINPUT = "/app/input"OUTPUT = "/app/output"SEV_RANK = {"MILD": 1, "MODERATE": 2, "SEVERE": 3}def read_csv(path): with open(path, newline="") as f: return list(csv.DictReader(f))def parse_dt(text): text = (text or "").strip() if not text: return None return datetime.fromisoformat(text)def fmt_dt(value): if value is None: return "" return value.strftime("%Y-%m-%dT%H:%M:%S")def main(): adsl = read_csv(f"{INPUT}/adsl.csv") ae = read_csv(f"{INPUT}/ae.csv") fa = read_csv(f"{INPUT}/fa.csv") # First exposure per subject (blank for untreated subjects). trtsdtm = {} for row in adsl: trtsdtm[row["USUBJID"]] = row["TRTSDTM"] # FA findings per (subject, link id): lists of (time, value) per test. findings = {} for row in fa: key = (row["USUBJID"], row["FALNKID"]) entry = findings.setdefault(key, {"SEV": [], "TOXGR": [], "times": set()}) when = parse_dt(row["FADTC"]) entry["times"].add(when) if row["FATESTCD"] == "SEV": entry["SEV"].append((when, row["FASTRESC"])) elif row["FATESTCD"] == "TOXGR": entry["TOXGR"].append((when, row["FASTRESC"])) for entry in findings.values(): entry["SEV"].sort(key=lambda item: item[0]) entry["TOXGR"].sort(key=lambda item: item[0]) def most_recent(items, start): """Value most recently collected at or before start, else None.""" value = None for when, val in items: if when <= start: value = val else: break return value out_rows = [] # Events in AESEQ order within each subject, subjects as collected. events = sorted( ae, key=lambda r: (r["USUBJID"], int(r["AESEQ"])), ) subjects = [] for row in events: if row["USUBJID"] not in subjects: subjects.append(row["USUBJID"]) events.sort(key=lambda r: (subjects.index(r["USUBJID"]), int(r["AESEQ"]))) for subj in subjects: subj_events = [r for r in events if r["USUBJID"] == subj] exposure = parse_dt(trtsdtm.get(subj, "")) aseq = 0 for ev in subj_events: onset = parse_dt(ev["AESTDTC"]) entry = findings.get((subj, ev["AELNKID"]), {"SEV": [], "TOXGR": [], "times": set()}) starts = sorted({onset} | entry["times"]) periods = [] for i, start in enumerate(starts): if i + 1 < len(starts): end = fmt_dt(starts[i + 1]) else: # Last period ends at the event end; an ongoing event # (no AEENDTC) has no AENDTM. end = ev["AEENDTC"] if ev["AEENDTC"] else "" asev = most_recent(entry["SEV"], start) grade = most_recent(entry["TOXGR"], start) periods.append( { "start": start, "ASTDTM": fmt_dt(start), "AENDTM": end, "ASEV": asev if asev else "", "ATOXGRN": int(grade) if grade not in (None, "") else None, } ) # Treatment-emergent flag. flags = [""] * len(periods) if exposure is not None: if onset >= exposure: flags = ["Y"] * len(periods) else: # Reference: the last pre-treatment period's values. pre = [p for p in periods if p["start"] < exposure] ref_sev = SEV_RANK.get(pre[-1]["ASEV"]) if pre else None ref_grade = pre[-1]["ATOXGRN"] if pre else None emergent = False for i, p in enumerate(periods): if p["start"] < exposure: continue if not emergent: sev_rank = SEV_RANK.get(p["ASEV"]) sev_up = ( sev_rank is not None and ref_sev is not None and sev_rank > ref_sev ) grade_up = ( p["ATOXGRN"] is not None and ref_grade is not None and p["ATOXGRN"] > ref_grade ) if sev_up or grade_up: emergent = True if emergent: flags[i] = "Y" for p, flag in zip(periods, flags): aseq += 1 out_rows.append( { "STUDYID": ev["STUDYID"], "USUBJID": subj, "ASEQ": aseq, "AESEQ": ev["AESEQ"], "AEDECOD": ev["AEDECOD"], "AESTDTC": ev["AESTDTC"], "AEENDTC": ev["AEENDTC"], "ASTDTM": p["ASTDTM"], "AENDTM": p["AENDTM"], "TRTSDTM": trtsdtm.get(subj, ""), "AESEV": ev["AESEV"], "AETOXGR": ev["AETOXGR"], "ASEV": p["ASEV"], "ATOXGRN": "" if p["ATOXGRN"] is None else p["ATOXGRN"], "TRTEMFL": flag, } ) columns = [ "STUDYID", "USUBJID", "ASEQ", "AESEQ", "AEDECOD", "AESTDTC", "AEENDTC", "ASTDTM", "AENDTM", "TRTSDTM", "AESEV", "AETOXGR", "ASEV", "ATOXGRN", "TRTEMFL", ] with open(f"{OUTPUT}/adae.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=columns) writer.writeheader() writer.writerows(out_rows)if __name__ == "__main__": main()solutions/adam-adae-worsening-emergence/result.sas
Edit124 lines/* opensas reference for yamaa's adam-adae-worsening-emergence benchmark. Construct periods from one AE and its collected, dated FA findings. */data ae; length STUDYID USUBJID AELNKID AEDECOD AESTDTC AEENDTC AESEV AETOXGR $1024; infile '/app/input/ae.csv' dsd dlm=',' firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. AESEQ AELNKID : $char1024. AEDECOD : $char1024. AESTDTC : $char1024. AEENDTC : $char1024. AESEV : $char1024. AETOXGR : $char1024.; if lengthn(AESTDTC)=16 then AESTDTC=cats(AESTDTC,':00'); if lengthn(AEENDTC)=16 then AEENDTC=cats(AEENDTC,':00');run;data fa; length STUDYID USUBJID FALNKID FATESTCD FAOBJ FASTRESC FADTC $1024; infile '/app/input/fa.csv' dsd dlm=',' firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. FASEQ FALNKID : $char1024. FATESTCD : $char1024. FAOBJ : $char1024. FASTRESC : $char1024. FADTC : $char1024.; if lengthn(FADTC)=16 then FADTC=cats(FADTC,':00'); SEVRANK=.; if FATESTCD='SEV' then do; select(FASTRESC); when('MILD') SEVRANK=1; when('MODERATE') SEVRANK=2; when('SEVERE') SEVRANK=3; otherwise SEVRANK=.; end; end;run;data adsl; length STUDYID USUBJID TRTSDTM $1024; infile '/app/input/adsl.csv' dsd dlm=',' firstobs=2 truncover lrecl=32767; input STUDYID : $char1024. USUBJID : $char1024. TRTSDTM : $char1024.; if lengthn(TRTSDTM)=16 then TRTSDTM=cats(TRTSDTM,':00');run;proc sql; create table changes as select STUDYID,USUBJID,FALNKID,FADTC as ASTDTM, max(SEVRANK) as SEVCOLN, max(case when FATESTCD='TOXGR' then input(FASTRESC,best32.) else . end) as GRDCOL from fa group by STUDYID,USUBJID,FALNKID,FADTC; create table joined as select a.*,c.ASTDTM,c.SEVCOLN,c.GRDCOL,b.TRTSDTM from changes as c inner join ae as a on c.STUDYID=a.STUDYID and c.USUBJID=a.USUBJID and c.FALNKID=a.AELNKID left join adsl as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID;quit;proc sort data=joined; by STUDYID USUBJID AESEQ ASTDTM;run;data states; length ASEV $1024; set joined; by STUDYID USUBJID AESEQ; retain ASEVN ATOXGRN; if first.AESEQ then do; ASEVN=.; ATOXGRN=.; end; if not missing(SEVCOLN) then ASEVN=SEVCOLN; if not missing(GRDCOL) then ATOXGRN=GRDCOL; select(ASEVN); when(1) ASEV='MILD'; when(2) ASEV='MODERATE'; when(3) ASEV='SEVERE'; otherwise ASEV=''; end;run;/* Read the next start by scanning periods in reverse chronological order. */proc sort data=states; by STUDYID USUBJID AESEQ descending ASTDTM;run;data periods; length AENDTM NEXTSTART $1024; set states; by STUDYID USUBJID AESEQ; retain NEXTSTART; if first.AESEQ then NEXTSTART=''; if not missing(NEXTSTART) then AENDTM=NEXTSTART; else AENDTM=AEENDTC; NEXTSTART=ASTDTM;run;proc sql; create table pretreatment as select STUDYID,USUBJID,AESEQ,max(ASTDTM) as BASEDTM from periods where not missing(TRTSDTM) and ASTDTM<TRTSDTM group by STUDYID,USUBJID,AESEQ; create table baseline as select a.STUDYID,a.USUBJID,a.AESEQ, a.ASEVN as PRESEV,a.ATOXGRN as PREGRD from periods as a inner join pretreatment as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.AESEQ=b.AESEQ and a.ASTDTM=b.BASEDTM; create table compared as select a.*,b.PRESEV,b.PREGRD from periods as a left join baseline as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.AESEQ=b.AESEQ; create table worsening as select STUDYID,USUBJID,AESEQ,min(ASTDTM) as FIRSTWRS from compared where not missing(TRTSDTM) and ASTDTM>=TRTSDTM and ((not missing(ASEVN) and not missing(PRESEV) and ASEVN>PRESEV) or (not missing(ATOXGRN) and not missing(PREGRD) and ATOXGRN>PREGRD)) group by STUDYID,USUBJID,AESEQ; create table flagged as select a.*,b.FIRSTWRS from periods as a left join worsening as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.AESEQ=b.AESEQ;quit;proc sort data=flagged; by STUDYID USUBJID AESEQ ASTDTM;run;data result; length TRTEMFL $1; set flagged; by STUDYID USUBJID; retain ASEQ; if first.USUBJID then ASEQ=0; ASEQ=ASEQ+1; TRTEMFL=''; if not missing(AESTDTC) and not missing(TRTSDTM) then do; if AESTDTC>=TRTSDTM or (not missing(FIRSTWRS) and ASTDTM>=FIRSTWRS) then TRTEMFL='Y'; end;run;proc sql; create table final as select STUDYID,USUBJID,ASEQ,AESEQ,AEDECOD,AESTDTC,AEENDTC, ASTDTM,AENDTM,TRTSDTM,AESEV,AETOXGR,ASEV,ATOXGRN,TRTEMFL from result;quit;proc export data=final outfile='/app/output/adae.csv' dbms=csv replace; run;
yamaa