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: 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:
LSTALVDTis 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 aDEATHorLOST TO FOLLOW-UPrecord, a visit start (SVSTDTC), a vital signs date (VSDTC), or a survival follow-up date (SSDTC) whose status (SSSTRESC) isALIVE. It never falls afterDTHDT: 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.DTHFLisYwhen demographics records the subject as dead, a death without a collected date included; empty otherwise.DTHDTis 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 forLSTALVDTfalls 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.DTHDTFisDwhen the death date's day was supplied andMwhen 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 review7 source filesinput/ae.csv
Edit8 rows| STUDYID | USUBJID | AESEQ | AEDECOD | AESTDTC | AEENDTC | AEOUT |
|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | NAUSEA | 2025-01-14 | 2025-01-16 | RECOVERED/RESOLVED | |
| YAMAA-01 | 2 | FATIGUE | 2025-03-20 | — | NOT RECOVERED/NOT RESOLVED | |
| YAMAA-01 | 1 | RASH | 2025-03-05 | 2025-04 | RECOVERED/RESOLVED | |
| YAMAA-01 | 1 | BACK PAIN | 2025-03-03 | 2025-04 | RECOVERED/RESOLVED | |
| YAMAA-01 | 2 | INSOMNIA | 2025 | — | NOT RECOVERED/NOT RESOLVED | |
| YAMAA-01 | 1 | ANAEMIA | 2025-03-15 | 2025-04-30 | RECOVERED/RESOLVED | |
| YAMAA-01 | 2 | SEPSIS | 2025-04-12 | 2025-04-18 | FATAL | |
| YAMAA-01 | 1 | RESPIRATORY FAILURE | 2025-06-03 | — | FATAL |
input/dm.csv
Edit11 rows| STUDYID | USUBJID | DTHDTC | DTHFL |
|---|---|---|---|
| YAMAA-01 | — | — | |
| YAMAA-01 | — | — | |
| YAMAA-01 | — | — | |
| YAMAA-01 | — | — | |
| YAMAA-01 | — | — | |
| YAMAA-01 | — | — | |
| YAMAA-01 | 2025-04-18 | Y | |
| YAMAA-01 | 2025-05 | Y | |
| YAMAA-01 | 2025 | Y | |
| YAMAA-01 | 2025-07 | Y | |
| YAMAA-01 | — | Y |
input/ds.csv
Edit9 rows| STUDYID | USUBJID | DSSEQ | DSCAT | DSDECOD | DSTERM | DSSTDTC |
|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | DISPOSITION EVENT | COMPLETED | COMPLETED | 2025-03-31 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | LOST TO FOLLOW-UP | LOST TO FOLLOW-UP | 2025-05-30 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | WITHDRAWAL BY SUBJECT | WITHDREW CONSENT | 2025-03-17 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | SCREEN FAILURE | SCREEN FAILURE | 2025-01-21 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | DEATH | DEATH | 2025-04-18 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | DEATH | DEATH | 2025-05 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | WITHDRAWAL BY SUBJECT | WITHDREW CONSENT | 2025-04-15 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | DEATH | DEATH | 2025-07 | |
| YAMAA-01 | 1 | DISPOSITION EVENT | DEATH | DEATH | — |
input/ex.csv
Edit10 rows| STUDYID | USUBJID | EXSEQ | EXTRT | EXSTDTC | EXENDTC |
|---|---|---|---|---|---|
| YAMAA-01 | 1 | DRUG A | 2025-01-06T09:00 | 2025-03-30 | |
| YAMAA-01 | 1 | DRUG A | 2025-01-13T10:00 | 2025-03-09 | |
| YAMAA-01 | 1 | DRUG A | 2025-01-20 | 2025-03-12 | |
| YAMAA-01 | 1 | DRUG A | 2025-01-27 | 2025-03-23 | |
| YAMAA-01 | 1 | DRUG A | 2025-02-03T08:50 | 2025-03-16 | |
| YAMAA-01 | 1 | DRUG A | 2025-02-10 | 2025-04-06 | |
| YAMAA-01 | 1 | DRUG A | 2025-02-17 | 2025-04-13 | |
| YAMAA-01 | 1 | DRUG A | 2025-02-24 | 2025-03-23 | |
| YAMAA-01 | 1 | DRUG A | 2025-03-03 | 2025-05-25 | |
| YAMAA-01 | 1 | DRUG A | 2025-03-10 | 2025-04-06 |
input/ss.csv
Edit7 rows| STUDYID | USUBJID | SSSEQ | SSTESTCD | SSTEST | SSSTRESC | SSDTC |
|---|---|---|---|---|---|---|
| YAMAA-01 | 1 | SURVSTAT | Survival Status | ALIVE | 2025-06-15T14:30 | |
| YAMAA-01 | 2 | SURVSTAT | Survival Status | UNKNOWN | 2025-09-10 | |
| YAMAA-01 | 1 | SURVSTAT | Survival Status | UNKNOWN | 2025-05-30 | |
| YAMAA-01 | 1 | SURVSTAT | Survival Status | DEAD | 2025-06-20 | |
| YAMAA-01 | 1 | SURVSTAT | Survival Status | DEAD | 2025-10-02 | |
| YAMAA-01 | 1 | SURVSTAT | Survival Status | ALIVE | 2025-05-05 | |
| YAMAA-01 | 2 | SURVSTAT | Survival Status | DEAD | 2025-08-11 |
input/sv.csv
Edit11 rows| STUDYID | USUBJID | VISITNUM | VISIT | SVSTDTC |
|---|---|---|---|---|
| YAMAA-01 | 4 | END OF TREATMENT | 2025-03-31 | |
| YAMAA-01 | 3 | WEEK 8 | 2025-03-10 | |
| YAMAA-01 | 2.01 | UNSCHEDULED 2.01 | 2025-03-04 | |
| YAMAA-01 | 3 | WEEK 8 | 2025-03-24 | |
| YAMAA-01 | 4 | END OF TREATMENT | 2025-03-17 | |
| YAMAA-01 | 1 | SCREENING | 2025-01-08 | |
| YAMAA-01 | 3 | WEEK 8 | 2025-04-07 | |
| YAMAA-01 | 4 | END OF TREATMENT | 2025-05-12 | |
| YAMAA-01 | 2 | WEEK 4 | 2025-03-24 | |
| YAMAA-01 | 3 | WEEK 12 | 2025-05-26 | |
| YAMAA-01 | 2 | WEEK 4 | 2025-04-07 |
input/vs.csv
Edit3 rows| STUDYID | USUBJID | VSSEQ | VSTESTCD | VSDTC |
|---|---|---|---|---|
| YAMAA-01 | 7 | SYSBP | 2025-04-09 | |
| YAMAA-01 | 9 | SYSBP | 2025-03-17T10:15 | |
| YAMAA-01 | 1 | SYSBP | 2025-01-08 |
Expected output
Under review11 expected rowsexpected/adsl.csv
Edit11 rows| STUDYID | USUBJID | LSTALVDT | DTHFL | DTHDT | DTHDTF |
|---|---|---|---|---|---|
| YAMAA-01 | 2025-06-15 | — | — | — | |
| YAMAA-01 | 2025-03-20 | — | — | — | |
| YAMAA-01 | 2025-03-12 | — | — | — | |
| YAMAA-01 | 2025-04-09 | — | — | — | |
| YAMAA-01 | 2025-04-01 | — | — | — | |
| YAMAA-01 | 2025-01-21 | — | — | — | |
| YAMAA-01 | 2025-04-18 | Y | 2025-04-18 | — | |
| YAMAA-01 | 2025-05-12 | Y | 2025-05-12 | D | |
| YAMAA-01 | 2025-04-15 | Y | 2025-04-15 | M | |
| YAMAA-01 | 2025-06-03 | Y | 2025-07-01 | D | |
| YAMAA-01 | 2025-05-05 | Y | — | — |
Human review and assessment
| Dataset Name | Variable Name | Variable Label | Type | Codelist/Controlled Terms | Core | Computational Method | Origin |
|---|---|---|---|---|---|---|---|
| ADSL | STUDYID | Study Identifier | character | Copy value from DM.STUDYID. | Predecessor | ||
| ADSL | USUBJID | Unique Subject Identifier | character | Copy value from DM.USUBJID. | Predecessor | ||
| ADSL | DTHFL | Subject Death Flag | character | Y | Copy value from DM.DTHFL. | Predecessor | |
| ADSL | DTHDT | Date of Death | numeric | ISO 8601 | Impute 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 | |
| ADSL | DTHDTF | Date of Death Imputation Flag | character | M, D | Date 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 | |
| ADSL | LSTALVDT | Date Last Known Alive | numeric | ISO 8601 | If ALIVEDT > DTHDT then "DTHDT"; otherwise "ALIVEDT". | Derived |
| Variable | Permitted values |
|---|---|
| DTHFL | Y |
| DTHDTF | M, D |
| 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-adsl-alive-date/full.md
Edit33 linesFollowing 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
Edit11 linesFollowing 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
Edit8 linesFollowing 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.
R
Edit3 lineslibrary(yamaa)yamaa_spec("spec.yaml")$run()$outputPython
Edit3 linesimport yamaayamaa.spec("spec.yaml").run().outputsolutions/adam-adsl-alive-date/result.R
Edit109 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
Edit135 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
Edit112 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;
yamaa