Review ask

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

Lifecycle: reviewedEditUnder review
What this benchmark means
README.md

Public 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:

  • ASEQ numbers analysis periods within a subject; repeated AESEQ retains the source event identity and its original values.
  • ASTDTM starts a period at a collected change time. AENDTM is the next period's start, or the original end for the last period; an ongoing event's last end stays empty.
  • ASEV and ATOXGRN hold 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.
  • TRTEMFL is Y for 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 remains Y on 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 review
3 source files

input/adsl.csv

Edit
5 rows
input/adsl.csv
STUDYIDUSUBJIDTRTSDTM
PILOT72025-03-04T09:00:00
PILOT72025-03-04T09:00:00
PILOT72025-03-04T09:00:00
PILOT7—
PILOT72025-03-04T09:00:00

input/ae.csv

Edit
6 rows
input/ae.csv
STUDYIDUSUBJIDAESEQAELNKIDAEDECODAESTDTCAEENDTCAESEVAETOXGR
PILOT71AE-1HEADACHE2025-03-04T07:00:002025-03-05T10:00:00SEVERE1
PILOT72AE-2HEADACHE2025-03-06T10:00:002025-03-06T11:00:00MILD1
PILOT71AE-1RASH2025-03-03T09:00:002025-03-07T09:00:00SEVERE2
PILOT71AE-1VOMITING2025-03-03T10:00:002025-03-06T10:00:00MODERATE2
PILOT71AE-1FATIGUE2025-03-03T09:00:002025-03-06T09:00:00SEVERE1
PILOT71AE-1COUGH2025-03-03T09:00:00—SEVERE3

input/fa.csv

Edit
20 rows
input/fa.csv
STUDYIDUSUBJIDFASEQFALNKIDFATESTCDFAOBJFASTRESCFADTC
PILOT71AE-1SEVHEADACHEMILD2025-03-04T07:00:00
PILOT72AE-1TOXGRHEADACHE12025-03-04T07:00:00
PILOT73AE-2SEVHEADACHEMILD2025-03-06T10:00:00
PILOT74AE-2TOXGRHEADACHE12025-03-06T10:00:00
PILOT71AE-1SEVRASHMODERATE2025-03-03T09:00:00
PILOT72AE-1TOXGRRASH22025-03-03T09:00:00
PILOT71AE-1SEVVOMITINGMODERATE2025-03-03T10:00:00
PILOT72AE-1TOXGRVOMITING12025-03-03T10:00:00
PILOT71AE-1SEVFATIGUEMILD2025-03-03T09:00:00
PILOT72AE-1TOXGRFATIGUE12025-03-03T09:00:00
PILOT71AE-1SEVCOUGHSEVERE2025-03-03T09:00:00
PILOT72AE-1TOXGRCOUGH32025-03-03T09:00:00
PILOT75AE-1SEVHEADACHEMODERATE2025-03-04T11:00:00
PILOT76AE-1SEVHEADACHESEVERE2025-03-04T13:00:00
PILOT77AE-1SEVHEADACHEMILD2025-03-05T06:00:00
PILOT73AE-1SEVRASHMILD2025-03-05T09:00:00
PILOT74AE-1SEVRASHSEVERE2025-03-06T09:00:00
PILOT73AE-1TOXGRVOMITING22025-03-05T10:00:00
PILOT73AE-1SEVFATIGUESEVERE2025-03-05T09:00:00
PILOT73AE-1SEVCOUGHMODERATE2025-03-05T09:00:00

Expected output

Under review
14 expected rows

expected/adae.csv

Edit
14 rows
expected/adae.csv
STUDYIDUSUBJIDASEQAESEQAEDECODAESTDTCAEENDTCASTDTMAENDTMTRTSDTMAESEVAETOXGRASEVATOXGRNTRTEMFL
PILOT711HEADACHE2025-03-04T07:00:002025-03-05T10:00:002025-03-04T07:00:002025-03-04T11:00:002025-03-04T09:00:00SEVERE1MILD1—
PILOT721HEADACHE2025-03-04T07:00:002025-03-05T10:00:002025-03-04T11:00:002025-03-04T13:00:002025-03-04T09:00:00SEVERE1MODERATE1Y
PILOT731HEADACHE2025-03-04T07:00:002025-03-05T10:00:002025-03-04T13:00:002025-03-05T06:00:002025-03-04T09:00:00SEVERE1SEVERE1Y
PILOT741HEADACHE2025-03-04T07:00:002025-03-05T10:00:002025-03-05T06:00:002025-03-05T10:00:002025-03-04T09:00:00SEVERE1MILD1Y
PILOT752HEADACHE2025-03-06T10:00:002025-03-06T11:00:002025-03-06T10:00:002025-03-06T11:00:002025-03-04T09:00:00MILD1MILD1Y
PILOT711RASH2025-03-03T09:00:002025-03-07T09:00:002025-03-03T09:00:002025-03-05T09:00:002025-03-04T09:00:00SEVERE2MODERATE2—
PILOT721RASH2025-03-03T09:00:002025-03-07T09:00:002025-03-05T09:00:002025-03-06T09:00:002025-03-04T09:00:00SEVERE2MILD2—
PILOT731RASH2025-03-03T09:00:002025-03-07T09:00:002025-03-06T09:00:002025-03-07T09:00:002025-03-04T09:00:00SEVERE2SEVERE2Y
PILOT711VOMITING2025-03-03T10:00:002025-03-06T10:00:002025-03-03T10:00:002025-03-05T10:00:002025-03-04T09:00:00MODERATE2MODERATE1—
PILOT721VOMITING2025-03-03T10:00:002025-03-06T10:00:002025-03-05T10:00:002025-03-06T10:00:002025-03-04T09:00:00MODERATE2MODERATE2Y
PILOT711FATIGUE2025-03-03T09:00:002025-03-06T09:00:002025-03-03T09:00:002025-03-05T09:00:00—SEVERE1MILD1—
PILOT721FATIGUE2025-03-03T09:00:002025-03-06T09:00:002025-03-05T09:00:002025-03-06T09:00:00—SEVERE1SEVERE1—
PILOT711COUGH2025-03-03T09:00:00—2025-03-03T09:00:002025-03-05T09:00:002025-03-04T09:00:00SEVERE3SEVERE3—
PILOT721COUGH2025-03-03T09:00:00—2025-03-05T09:00:00—2025-03-04T09:00:00SEVERE3MODERATE3—
Shaded columns are derived— = missing value

Human review and assessment

Your review

3 questions and your comments · View benchmark discussions

Mapping spec

Generated from the benchmark YAML; do not edit by hand

Dataset NameVariable NameVariable LabelTypeCodelist/Controlled TermsCoreComputational MethodOrigin
ADAESTUDYIDStudy IdentifiercharacterSet per constructed row; see the Row construction sheet.Derived
ADAEUSUBJIDUnique Subject IdentifiercharacterSet per constructed row; see the Row construction sheet.Derived
ADAEAESEQSource Adverse Event Sequence NumbernumericTake AESEQ from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEAEDECODDictionary-Derived TermcharacterTake AEDECOD from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEAESTDTCSource Event Start Date/TimenumericISO 8601Take AESTDTC from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEAEENDTCSource Event End Date/TimenumericISO 8601Take AEENDTC from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEASTDTMAnalysis Segment Start Date/TimenumericISO 8601Set per constructed row; see the Row construction sheet.Derived
ADAEAENDTMAnalysis Segment End Date/TimenumericISO 8601First non-missing of NEXTSTART and AEENDTC.Derived
ADAETRTSDTMDatetime of First Exposure to TreatmentnumericISO 8601Copy value from ADSL.TRTSDTM.Predecessor
ADAEAESEVSource Event Severity/IntensitycharacterTake AESEV from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEAETOXGRSource Event Standard Toxicity GradecharacterTake AETOXGR from the AE record matching on STUDYID, USUBJID and AELNKID, and a row with no match is an error.Derived
ADAEASEVAnalysis Severity/IntensitycharacterMILD, MODERATE, SEVERELast observation carried forward from SEVCOL using window EVENT_ORDER.Derived
ADAEATOXGRNAnalysis Toxicity Grade (N)numeric1, 2, 3, 4, 5Last observation carried forward from GRDCOL using window EVENT_ORDER.Derived
ADAETRTEMFLTreatment Emergent Analysis FlagcharacterY"Y" when AESTDTC IS NOT NULL AND TRTSDTM IS NOT NULL AND (AESTDTC >= TRTSDTM OR ASTDTM >= FIRSTWRS); blank otherwise.Derived
ADAEASEQAnalysis Sequence NumbernumericRow number within each group of STUDYID, USUBJID, ordered by AESEQ then ASTDTM.Derived
TemplateInclude whenSTUDYIDUSUBJIDAELNKIDASTDTMSEVCOLGRDCOL
severity_periodsAggregate MAX(FA.STUDYID) over records matching on the shared output keysAggregate MAX(FA.USUBJID) over records matching on the shared output keysAggregate MAX(FA.FALNKID) over records matching on the shared output keysAggregate MAX(FA.FADTC) over records matching on the shared output keysAggregate 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'
VariablePermitted values
ASEVMILD, MODERATE, SEVERE
ATOXGRN1, 2, 3, 4, 5
TRTEMFLY
VersionDateAuthorDescriptionReviewerSign-off
1.72026-10-20yamaa docs buildRead 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.62026-10-17yamaa docs buildDescribe collected date parts written as ISO 8601 date text and times of day counted in seconds since midnight
1.52026-10-17yamaa docs buildName 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.42026-10-15yamaa docs buildState datetime precision codes through the hour and minute, and complete only the time parts a collected datetime lacks
1.32026-09-29yamaa docs buildShow study codelist names, external dictionaries, and ISO 8601 formats in variable rows; list the document's codelist values and metadata
1.22026-09-29yamaa docs buildClassify ADaM copies from a declared input as Predecessor and recodes as Derived, and SDTM recodes of locally derived columns as Derived
1.12026-09-29yamaa docs buildResolve lookups into the donor record they select, and give every derivation shape a plain-language rule instead of its raw YAML
1.0yamaa docs buildInitial generation from spec.yaml

Agent prompt

The request an agent receives with the input files; each tier states no more than the one above

prompts/adam-adae-worsening-emergence/full.md

Edit
33 lines

Following 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

Edit
13 lines

Following 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

Edit
10 lines

Following 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.

yamaa code

Public calls generated from benchmark.yaml; results must match the expected output

R

Edit
3 lines
library(yamaa)yamaa_spec("spec.yaml")$run()$output

Python

Edit
3 lines
import yamaayamaa.spec("spec.yaml").run().output

Solution without yamaa

Written from the full prompt and the inputs alone; CI checks it reproduces the expected output

solutions/adam-adae-worsening-emergence/result.R

Edit
86 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

Edit
179 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

Edit
124 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;