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: derive LSTALVDT, DTHFL, DTHDT, and DTHDTF for each subject from the dates collected across the study's exposure, adverse event, disposition, visit, vital signs, and survival follow-up records.

Input: demographics (DM) carrying the collected death date (DTHDTC) and death flag (DTHFL), alongside Study Data Tabulation Model (SDTM) records whose collected dates can show a subject alive: exposure (EX), adverse events (AE), disposition (DS), subject visits (SV), vital signs (VS), and survival follow-up in subject status (SS).

Variables:

  • LSTALVDT is the latest date on which a record shows the subject alive: an exposure start or end (EXSTDTC, EXENDTC), an adverse event start or end (AESTDTC, AEENDTC), a disposition date (DSSTDTC) except on a DEATH or LOST TO FOLLOW-UP record, a visit start (SVSTDTC), a vital signs date (VSDTC), or a survival follow-up date (SSDTC) whose status (SSSTRESC) is ALIVE. It never falls after DTHDT: a record dated after death is a data error, and the date stops at the death date. Empty when no such record has a date.
  • DTHFL is Y when demographics records the subject as dead, a death without a collected date included; empty otherwise.
  • DTHDT is the collected death date completed to a day. A year and month take the first day of that month and a year alone 1 January, unless the latest of the dates counted for LSTALVDT falls on a later day inside the collected month or year: then the death date is that day. A complete death date stands as collected. Empty when no death date was collected.
  • DTHDTF is D when the death date's day was supplied and M when its month and day were; empty for a complete or missing death date.

Note: every collected date counts on the day it names. A date with a time keeps its date, a year and month count as the first of that month, and a year alone as 1 January, so completing a partial date never claims contact beyond what was collected and a complete date within that month or year wins. The domains are this study's: another study reads its own (concomitant medications or laboratory samples, for example) under the same rule.

Inputs

Under review
7 source files

input/ae.csv

Edit
8 rows
input/ae.csv
STUDYIDUSUBJIDAESEQAEDECODAESTDTCAEENDTCAEOUT
YAMAA-011NAUSEA2025-01-142025-01-16RECOVERED/RESOLVED
YAMAA-012FATIGUE2025-03-20—NOT RECOVERED/NOT RESOLVED
YAMAA-011RASH2025-03-052025-04RECOVERED/RESOLVED
YAMAA-011BACK PAIN2025-03-032025-04RECOVERED/RESOLVED
YAMAA-012INSOMNIA2025—NOT RECOVERED/NOT RESOLVED
YAMAA-011ANAEMIA2025-03-152025-04-30RECOVERED/RESOLVED
YAMAA-012SEPSIS2025-04-122025-04-18FATAL
YAMAA-011RESPIRATORY FAILURE2025-06-03—FATAL

input/dm.csv

Edit
11 rows
input/dm.csv
STUDYIDUSUBJIDDTHDTCDTHFL
YAMAA-01——
YAMAA-01——
YAMAA-01——
YAMAA-01——
YAMAA-01——
YAMAA-01——
YAMAA-012025-04-18Y
YAMAA-012025-05Y
YAMAA-012025Y
YAMAA-012025-07Y
YAMAA-01—Y

input/ds.csv

Edit
9 rows
input/ds.csv
STUDYIDUSUBJIDDSSEQDSCATDSDECODDSTERMDSSTDTC
YAMAA-011DISPOSITION EVENTCOMPLETEDCOMPLETED2025-03-31
YAMAA-011DISPOSITION EVENTLOST TO FOLLOW-UPLOST TO FOLLOW-UP2025-05-30
YAMAA-011DISPOSITION EVENTWITHDRAWAL BY SUBJECTWITHDREW CONSENT2025-03-17
YAMAA-011DISPOSITION EVENTSCREEN FAILURESCREEN FAILURE2025-01-21
YAMAA-011DISPOSITION EVENTDEATHDEATH2025-04-18
YAMAA-011DISPOSITION EVENTDEATHDEATH2025-05
YAMAA-011DISPOSITION EVENTWITHDRAWAL BY SUBJECTWITHDREW CONSENT2025-04-15
YAMAA-011DISPOSITION EVENTDEATHDEATH2025-07
YAMAA-011DISPOSITION EVENTDEATHDEATH—

input/ex.csv

Edit
10 rows
input/ex.csv
STUDYIDUSUBJIDEXSEQEXTRTEXSTDTCEXENDTC
YAMAA-011DRUG A2025-01-06T09:002025-03-30
YAMAA-011DRUG A2025-01-13T10:002025-03-09
YAMAA-011DRUG A2025-01-202025-03-12
YAMAA-011DRUG A2025-01-272025-03-23
YAMAA-011DRUG A2025-02-03T08:502025-03-16
YAMAA-011DRUG A2025-02-102025-04-06
YAMAA-011DRUG A2025-02-172025-04-13
YAMAA-011DRUG A2025-02-242025-03-23
YAMAA-011DRUG A2025-03-032025-05-25
YAMAA-011DRUG A2025-03-102025-04-06

input/ss.csv

Edit
7 rows
input/ss.csv
STUDYIDUSUBJIDSSSEQSSTESTCDSSTESTSSSTRESCSSDTC
YAMAA-011SURVSTATSurvival StatusALIVE2025-06-15T14:30
YAMAA-012SURVSTATSurvival StatusUNKNOWN2025-09-10
YAMAA-011SURVSTATSurvival StatusUNKNOWN2025-05-30
YAMAA-011SURVSTATSurvival StatusDEAD2025-06-20
YAMAA-011SURVSTATSurvival StatusDEAD2025-10-02
YAMAA-011SURVSTATSurvival StatusALIVE2025-05-05
YAMAA-012SURVSTATSurvival StatusDEAD2025-08-11

input/sv.csv

Edit
11 rows
input/sv.csv
STUDYIDUSUBJIDVISITNUMVISITSVSTDTC
YAMAA-014END OF TREATMENT2025-03-31
YAMAA-013WEEK 82025-03-10
YAMAA-012.01UNSCHEDULED 2.012025-03-04
YAMAA-013WEEK 82025-03-24
YAMAA-014END OF TREATMENT2025-03-17
YAMAA-011SCREENING2025-01-08
YAMAA-013WEEK 82025-04-07
YAMAA-014END OF TREATMENT2025-05-12
YAMAA-012WEEK 42025-03-24
YAMAA-013WEEK 122025-05-26
YAMAA-012WEEK 42025-04-07

input/vs.csv

Edit
3 rows
input/vs.csv
STUDYIDUSUBJIDVSSEQVSTESTCDVSDTC
YAMAA-017SYSBP2025-04-09
YAMAA-019SYSBP2025-03-17T10:15
YAMAA-011SYSBP2025-01-08

Expected output

Under review
11 expected rows

expected/adsl.csv

Edit
11 rows
expected/adsl.csv
STUDYIDUSUBJIDLSTALVDTDTHFLDTHDTDTHDTF
YAMAA-012025-06-15———
YAMAA-012025-03-20———
YAMAA-012025-03-12———
YAMAA-012025-04-09———
YAMAA-012025-04-01———
YAMAA-012025-01-21———
YAMAA-012025-04-18Y2025-04-18—
YAMAA-012025-05-12Y2025-05-12D
YAMAA-012025-04-15Y2025-04-15M
YAMAA-012025-06-03Y2025-07-01D
YAMAA-012025-05-05Y——
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
ADSLSTUDYIDStudy IdentifiercharacterCopy value from DM.STUDYID.Predecessor
ADSLUSUBJIDUnique Subject IdentifiercharacterCopy value from DM.USUBJID.Predecessor
ADSLDTHFLSubject Death FlagcharacterYCopy value from DM.DTHFL.Predecessor
ADSLDTHDTDate of DeathnumericISO 8601Impute incomplete date from DM.DTHDTC, defaulting month=1, day=first, never before ALIVEDT; a bound outside the collected period is ignored; invalid text is an error.Derived
ADSLDTHDTFDate of Death Imputation FlagcharacterM, DDate imputation flag for DTHDT: "Y" when the whole date was imputed, "M" when the month and day were, "D" when only the day was; blank when the date was collected in full.Derived
ADSLLSTALVDTDate Last Known AlivenumericISO 8601If ALIVEDT > DTHDT then "DTHDT"; otherwise "ALIVEDT".Derived
VariablePermitted values
DTHFLY
DTHDTFM, D
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-adsl-alive-date/full.md

Edit
33 lines

Following CDISC ADaM standards, use the provided AE, DM, DS, EX, SS, SV, and VS datasets to create an ADSL dataset with one record per subject in DM.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF

A date shows the subject alive when it is an exposure start or end (EXSTDTC, EXENDTC), an adverse event start or end (AESTDTC, AEENDTC), a disposition date (DSSTDTC) except on a record whose DSDECOD is "DEATH" or "LOST TO FOLLOW-UP", a visit start (SVSTDTC), a vital signs date (VSDTC), or a survival status date (SSDTC) whose SSSTRESC is "ALIVE". Only these dates count, and the death date is not one of them. Each date counts on the day it names: a date with a time counts on its date, a year and month on the first of that month, and a year alone on 1 January.

  • LSTALVDT is the latest date showing the subject alive, but never later than DTHDT: when that latest date falls after DTHDT, LSTALVDT is DTHDT.
  • DTHFL is "Y" for a subject DM records as dead, including one without a collected death date, and has no value otherwise.
  • DTHDT is the death date collected in DM (DTHDTC), completed to a day; no other domain supplies it, and it has no value when DM collected none. A complete date stands as collected. A year and month take the first day of that month and a year alone 1 January, unless the latest date showing the subject alive falls on a later day inside the collected month or year: then DTHDT is that day.
  • DTHDTF is "D" when the day of DTHDT was supplied (a year and month were collected) and "M" when its month and day were (a year alone was collected). The flag is the same when DTHDT takes the day of the latest date showing the subject alive. It has no value for a complete or missing death date.

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.csv.

prompts/adam-adsl-alive-date/conventions.md

Edit
11 lines

Following CDISC ADaM standards, use the provided AE, DM, DS, EX, SS, SV, and VS datasets to create an ADSL dataset with one record per subject in DM.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF

DTHFL is "Y" or has no value. DTHDTF is "D" when the day of DTHDT was supplied, "M" when its month and day were, or has no value.

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.csv.

prompts/adam-adsl-alive-date/brief.md

Edit
8 lines

Following CDISC ADaM standards, use the provided AE, DM, DS, EX, SS, SV, and VS datasets to create an ADSL dataset with one record per subject in DM.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF

Read the source datasets from /app/input and save the completed dataset as /app/output/adsl.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-adsl-alive-date/result.R

Edit
109 lines
# Reference solution for the yamaa benchmark adam-adsl-alive-date (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.suppressPackageStartupMessages({  library(dplyr)  library(readr)  library(stringr)  library(lubridate)})input_dir <- "/app/input"output_dir <- "/app/output"read_domain <- function(name) {  read_csv(    file.path(input_dir, paste0(name, ".csv")),    col_types = cols(.default = col_character()),    na = character(),    trim_ws = TRUE,    progress = FALSE  ) |>    mutate(across(everything(), \(x) if_else(is.na(x) | x == "", NA_character_, str_trim(x))))}# Split an ISO 8601 date or date-time into year, month and day parts.# Anything after the date (a time) is dropped.parse_parts <- function(x) {  m <- str_match(x, "^(\\d{4})(?:-(\\d{2})(?:-(\\d{2}))?)?(?:$|[T ])")  tibble(    year = as.integer(m[, 2]),    month = as.integer(m[, 3]),    day = as.integer(m[, 4])  )}# Date a value counts on: a date-time on its date, a year and month on the# first of that month, a year alone on 1 January.to_date <- function(x) {  p <- parse_parts(x)  make_date(p$year, coalesce(p$month, 1L), coalesce(p$day, 1L))}dm <- read_domain("dm")ae <- read_domain("ae")ds <- read_domain("ds")ex <- read_domain("ex")ss <- read_domain("ss")sv <- read_domain("sv")vs <- read_domain("vs")alive_dates <- bind_rows(  ex |> transmute(STUDYID, USUBJID, DT = to_date(EXSTDTC)),  ex |> transmute(STUDYID, USUBJID, DT = to_date(EXENDTC)),  ae |> transmute(STUDYID, USUBJID, DT = to_date(AESTDTC)),  ae |> transmute(STUDYID, USUBJID, DT = to_date(AEENDTC)),  ds |>    filter(!(str_to_upper(coalesce(DSDECOD, "")) %in% c("DEATH", "LOST TO FOLLOW-UP"))) |>    transmute(STUDYID, USUBJID, DT = to_date(DSSTDTC)),  sv |> transmute(STUDYID, USUBJID, DT = to_date(SVSTDTC)),  vs |> transmute(STUDYID, USUBJID, DT = to_date(VSDTC)),  ss |>    filter(str_to_upper(coalesce(SSSTRESC, "")) == "ALIVE") |>    transmute(STUDYID, USUBJID, DT = to_date(SSDTC))) |>  filter(!is.na(DT)) |>  group_by(STUDYID, USUBJID) |>  summarise(MAXALIVE = max(DT), .groups = "drop")fmt_date <- function(d) if_else(is.na(d), NA_character_, format(d, "%Y-%m-%d"))adsl <- dm |>  distinct(STUDYID, USUBJID, .keep_all = TRUE) |>  left_join(alive_dates, by = c("STUDYID", "USUBJID")) |>  mutate(    parts = parse_parts(DTHDTC),    dth_year = parts$year,    dth_month = parts$month,    dth_day = parts$day,    dth_base = make_date(dth_year, coalesce(dth_month, 1L), coalesce(dth_day, 1L)),    dth_partial = !is.na(dth_year) & (is.na(dth_month) | is.na(dth_day)),    # the latest alive date replaces the imputed day when it falls later    # inside the collected month (or year, when only the year was collected)    inside_collected = !is.na(MAXALIVE) & !is.na(dth_year) &      year(MAXALIVE) == dth_year &      (is.na(dth_month) | month(MAXALIVE) == dth_month),    take_alive = dth_partial & inside_collected & !is.na(MAXALIVE) &      !is.na(dth_base) & MAXALIVE > dth_base,    DTHDT_D = if_else(take_alive, MAXALIVE, dth_base),    DTHDTF = case_when(      dth_partial & is.na(dth_month) ~ "M",      dth_partial ~ "D",      TRUE ~ NA_character_    ),    DTHFL = if_else(      str_to_upper(coalesce(DTHFL, "")) == "Y" | !is.na(dth_year),      "Y",      NA_character_    ),    LSTALVDT_D = if_else(!is.na(DTHDT_D) & !is.na(MAXALIVE) & MAXALIVE > DTHDT_D, DTHDT_D, MAXALIVE),    LSTALVDT = fmt_date(LSTALVDT_D),    DTHDT = fmt_date(DTHDT_D)  ) |>  select(STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF)dir.create(output_dir, showWarnings = FALSE, recursive = TRUE)write_csv(adsl, file.path(output_dir, "adsl.csv"), na = "")

solutions/adam-adsl-alive-date/result.py

Edit
135 lines
# Reference solution for the yamaa benchmark adam-adsl-alive-date (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."""Derive ADSL (STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF) from SDTM.Reads the SDTM datasets from /app/input and writes /app/output/adsl.csv."""import csvimport refrom datetime import dateINPUT = "/app/input"OUTPUT = "/app/output"DATE_RE = re.compile(r"^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?(?:[T ].*)?$")def read(name):    with open(f"{INPUT}/{name}.csv", newline="", encoding="utf-8-sig") as handle:        return list(csv.DictReader(handle))def parse_iso(text):    """Return (year, month, day) with None for components not collected."""    match = DATE_RE.match((text or "").strip())    if not match:        return None    year, month, day = match.groups()    return int(year), (int(month) if month else None), (int(day) if day else None)def complete(text):    """Complete a date to a day: a year and month to the 1st, a year to 1 Jan."""    parts = parse_iso(text)    if parts is None:        return None    year, month, day = parts    try:        return date(year, month or 1, day or 1)    except ValueError:        return Nonedef norm(value):    return (value or "").strip().upper()def main():    dm = read("dm")    alive = {}    def add(usubjid, text):        day = complete(text)        if day is None:            return        current = alive.get(usubjid)        if current is None or day > current:            alive[usubjid] = day    for row in read("ex"):        add(row["USUBJID"], row.get("EXSTDTC"))        add(row["USUBJID"], row.get("EXENDTC"))    for row in read("ae"):        add(row["USUBJID"], row.get("AESTDTC"))        add(row["USUBJID"], row.get("AEENDTC"))    for row in read("ds"):        if norm(row.get("DSDECOD")) in {"DEATH", "LOST TO FOLLOW-UP"}:            continue        add(row["USUBJID"], row.get("DSSTDTC"))    for row in read("sv"):        add(row["USUBJID"], row.get("SVSTDTC"))    for row in read("vs"):        add(row["USUBJID"], row.get("VSDTC"))    for row in read("ss"):        if norm(row.get("SSSTRESC")) == "ALIVE":            add(row["USUBJID"], row.get("SSDTC"))    out = []    seen = set()    for row in dm:        usubjid = row["USUBJID"]        if usubjid in seen:            continue        seen.add(usubjid)        last_alive = alive.get(usubjid)        dthdtc = (row.get("DTHDTC") or "").strip()        parts = parse_iso(dthdtc) if dthdtc else None        dead = norm(row.get("DTHFL")) == "Y" or parts is not None        dthdt = None        dthdtf = ""        if parts is not None:            year, month, day = parts            base = complete(dthdtc)            if day is not None:                dthdt = base            else:                dthdt = base                dthdtf = "D" if month is not None else "M"                if last_alive is not None and last_alive > base:                    same_period = last_alive.year == year and (                        month is None or last_alive.month == month                    )                    if same_period:                        dthdt = last_alive        lstalvdt = last_alive        if lstalvdt is not None and dthdt is not None and lstalvdt > dthdt:            lstalvdt = dthdt        out.append(            {                "STUDYID": row["STUDYID"],                "USUBJID": usubjid,                "LSTALVDT": lstalvdt.isoformat() if lstalvdt else "",                "DTHFL": "Y" if dead else "",                "DTHDT": dthdt.isoformat() if dthdt else "",                "DTHDTF": dthdtf,            }        )    columns = ["STUDYID", "USUBJID", "LSTALVDT", "DTHFL", "DTHDT", "DTHDTF"]    with open(f"{OUTPUT}/adsl.csv", "w", newline="", encoding="utf-8") as handle:        writer = csv.DictWriter(handle, fieldnames=columns, lineterminator="\n")        writer.writeheader()        writer.writerows(out)if __name__ == "__main__":    main()

solutions/adam-adsl-alive-date/result.sas

Edit
112 lines
/* opensas reference solution for the yamaa benchmark adam-adsl-alive-date.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data dm;  length STUDYID USUBJID DTHDTC DTHFL $1024;  infile "/app/input/dm.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. DTHDTC : $char1024. DTHFL : $char1024.;run;data ex;  length STUDYID USUBJID EXSEQ EXTRT EXSTDTC EXENDTC $1024;  infile "/app/input/ex.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. EXSEQ : $char1024. EXTRT : $char1024. EXSTDTC : $char1024. EXENDTC : $char1024.;run;data ae;  length STUDYID USUBJID AESEQ AEDECOD AESTDTC AEENDTC AEOUT $1024;  infile "/app/input/ae.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. AESEQ : $char1024. AEDECOD : $char1024. AESTDTC : $char1024. AEENDTC : $char1024. AEOUT : $char1024.;run;data ds;  length STUDYID USUBJID DSSEQ DSCAT DSDECOD DSTERM DSSTDTC $1024;  infile "/app/input/ds.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. DSSEQ : $char1024. DSCAT : $char1024. DSDECOD : $char1024. DSTERM : $char1024. DSSTDTC : $char1024.;run;data sv;  length STUDYID USUBJID VISITNUM VISIT SVSTDTC $1024;  infile "/app/input/sv.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. VISITNUM : $char1024. VISIT : $char1024. SVSTDTC : $char1024.;run;data vs;  length STUDYID USUBJID VSSEQ VSTESTCD VSDTC $1024;  infile "/app/input/vs.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. VSSEQ : $char1024. VSTESTCD : $char1024. VSDTC : $char1024.;run;data ss;  length STUDYID USUBJID SSSEQ SSTESTCD SSTEST SSSTRESC SSDTC $1024;  infile "/app/input/ss.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. SSSEQ : $char1024. SSTESTCD : $char1024. SSTEST : $char1024. SSSTRESC : $char1024. SSDTC : $char1024.;run;/* Every collected date that shows the subject alive. */proc sql;  create table alive_text as  select STUDYID,USUBJID,EXSTDTC as DTC from ex  union all select STUDYID,USUBJID,EXENDTC from ex  union all select STUDYID,USUBJID,AESTDTC from ae  union all select STUDYID,USUBJID,AEENDTC from ae  union all select STUDYID,USUBJID,DSSTDTC from ds where DSDECOD not in ('DEATH','LOST TO FOLLOW-UP')  union all select STUDYID,USUBJID,SVSTDTC from sv  union all select STUDYID,USUBJID,VSDTC from vs  union all select STUDYID,USUBJID,SSDTC from ss where SSSTRESC='ALIVE';quit;/* A date counts on the day it names: a time is dropped, a year and month   take the first of the month, and a year alone takes 1 January. */data alive_days;  length day $10;  set alive_text;  txt=strip(DTC);  if prxmatch('/^[0-9]{4}-[0-9]{2}-[0-9]{2}/',txt) then day=substr(txt,1,10);  else if prxmatch('/^[0-9]{4}-[0-9]{2}$/',txt) then day=cats(txt,'-01');  else if prxmatch('/^[0-9]{4}$/',txt) then day=cats(txt,'-01-01');  alive=input(day,yymmdd10.);  if not missing(alive);  keep STUDYID USUBJID alive;run;proc sql;  create table latest as select STUDYID,USUBJID,max(alive) as alive from alive_days group by STUDYID,USUBJID;  create table joined as select a.*,b.alive from dm as a left join latest as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID;quit;data result;  length LSTALVDT DTHDT $10 DTHDTF $1;  set joined;  t=strip(DTHDTC);  death=.;  if prxmatch('/^[0-9]{4}-[0-9]{2}-[0-9]{2}/',t) then death=input(substr(t,1,10),yymmdd10.);  else if prxmatch('/^[0-9]{4}-[0-9]{2}$/',t) then do;    death=input(cats(t,'-01'),yymmdd10.);    DTHDTF='D';    if not missing(alive) then do;      if alive>death and substr(put(alive,yymmdd10.),1,7)=t then death=alive;    end;  end;  else if prxmatch('/^[0-9]{4}$/',t) then do;    death=input(cats(t,'-01-01'),yymmdd10.);    DTHDTF='M';    if not missing(alive) then do;      if alive>death and substr(put(alive,yymmdd10.),1,4)=t then death=alive;    end;  end;  last=alive;  if not missing(last) and not missing(death) then do;    if last>death then last=death;  end;  if not missing(last) then LSTALVDT=put(last,yymmdd10.);  if not missing(death) then DTHDT=put(death,yymmdd10.);  if strip(DTHFL)='Y' or not missing(death) then DTHFL='Y';  else DTHFL='';run;proc sql;  create table final as select STUDYID, USUBJID, LSTALVDT, DTHFL, DTHDT, DTHDTF from result;quit;proc export data=final outfile="/app/output/adsl.csv" dbms=csv replace; run;