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: draftEditUnder review
What this benchmark means
README.md

Public entries: Python and R. Passing coverage requires an installed inventory.

Goal: derive ANRIND, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL, CRIT2, and CRIT2FL: mark where each analysis value sits against its normal range, how that mark moved since baseline, and whether the record met a high-result criterion or a low-result criterion.

Input: laboratory records carrying the analysis value AVAL, the normal range limits ANRLO and ANRHI, and the baseline flag ABLFL (Y on the record taken as the baseline for the subject and parameter).

Variables:

  • ANRIND is the record's own mark: LOW below ANRLO, HIGH above ANRHI, and NORMAL between them, limits included, so a result exactly at a limit reads NORMAL; empty when AVAL, ANRLO, or ANRHI is missing, since one missing limit makes the mark unclassifiable.

  • BASE repeats the baseline record's AVAL on every record of the subject and parameter; empty when no record carries the flag.

  • BNRIND repeats the baseline record's ANRIND the same way; empty with no flagged baseline, or when the baseline record itself could not be marked.

  • SHIFT1 joins the baseline mark and the record's own mark, baseline first, so a result that stayed normal reads NORMAL to NORMAL and one that moved out of range reads NORMAL to HIGH; empty when either mark is missing - the record's own or the baseline's.

  • R2BASE divides AVAL by BASE, so the baseline record itself reads 1 when the ratio can be computed; empty when AVAL is missing, when there is no baseline, and when the baseline is zero since the multiple is not defined there.

  • CRIT1 states the criterion the record was assessed against, a result greater than three times the upper limit of normal (ULN): Result greater than 3 x ULN; empty when AVAL or ANRHI is missing, since the comparison cannot be made.

  • CRIT1FL says whether the record met it, Y or N; empty where it could not be assessed, which differs from assessed and not met. A result exactly at three times the limit does not meet it - the comparison is strictly greater than.

  • CRIT2 states the second criterion the record was assessed against, a result less than the lower limit of normal (LLN): Result less than LLN; empty when AVAL or ANRLO is missing, since the comparison cannot be made. It is assessed independently of the first: a record whose upper limit is missing can still meet the low criterion.

  • CRIT2FL says whether the record met it, Y or N; empty where it could not be assessed. A result exactly at the lower limit does not meet it - the comparison is strictly less than.

Note: the shift joins the baseline mark with the record's own mark and the ratio rests on the flagged baseline value, while the mark and the criteria rest on the record's own value and limits; each criterion text and its flag always arrive together, as do the mark and the shift. A record that breaks these pairings stops the run, and no output is written.

Inputs

Under review
1 source file

input/adlb_raw.csv

Edit
17 rows
input/adlb_raw.csv
STUDYIDUSUBJIDPARAMCDPARAMASEQAVISITAVALANRLOANRHIABLFL
YAMAA-01ALTAlanine Aminotransferase (U/L)1BASELINE30040Y
YAMAA-01ALTAlanine Aminotransferase (U/L)2WEEK 260040—
YAMAA-01ALTAlanine Aminotransferase (U/L)3WEEK 4150040—
YAMAA-01ALTAlanine Aminotransferase (U/L)4WEEK 840040—
YAMAA-01ALTAlanine Aminotransferase (U/L)1BASELINE45040Y
YAMAA-01ALTAlanine Aminotransferase (U/L)2WEEK 218040—
YAMAA-01ALTAlanine Aminotransferase (U/L)3WEEK 4—040—
YAMAA-01ALTAlanine Aminotransferase (U/L)4WEEK 8120040—
YAMAA-01EOSEosinophils (%)1BASELINE016Y
YAMAA-01EOSEosinophils (%)2WEEK 2416—
YAMAA-01EOSEosinophils (%)3WEEK 4116—
YAMAA-01LDHLactate Dehydrogenase (U/L)3BASELINE200——Y
YAMAA-01LDHLactate Dehydrogenase (U/L)4WEEK 2260———
YAMAA-01GGTGamma Glutamyl Transferase (U/L)5BASELINE5510—Y
YAMAA-01GGTGamma Glutamyl Transferase (U/L)6WEEK 27010——
YAMAA-01EOSEosinophils (%)4WEEK 80.516—
YAMAA-01GGTGamma Glutamyl Transferase (U/L)7WEEK 4510——

Expected output

Under review
17 expected rows

expected/adlb.csv

Edit
17 rows
expected/adlb.csv
STUDYIDUSUBJIDPARAMCDPARAMASEQAVISITAVALANRLOANRHIANRINDABLFLBASEBNRINDSHIFT1R2BASECRIT1CRIT1FLCRIT2CRIT2FL
YAMAA-01ALTAlanine Aminotransferase (U/L)1BASELINE30040NORMALY30NORMALNORMAL to NORMAL1Result greater than 3 x ULNNResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)2WEEK 260040HIGH—30NORMALNORMAL to HIGH2Result greater than 3 x ULNNResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)3WEEK 4150040HIGH—30NORMALNORMAL to HIGH5Result greater than 3 x ULNYResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)4WEEK 840040NORMAL—30NORMALNORMAL to NORMAL1.3333333333333333Result greater than 3 x ULNNResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)1BASELINE45040HIGHY45HIGHHIGH to HIGH1Result greater than 3 x ULNNResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)2WEEK 218040NORMAL—45HIGHHIGH to NORMAL0.4Result greater than 3 x ULNNResult less than LLNN
YAMAA-01ALTAlanine Aminotransferase (U/L)3WEEK 4—040——45HIGH——————
YAMAA-01ALTAlanine Aminotransferase (U/L)4WEEK 8120040HIGH—45HIGHHIGH to HIGH2.6666666666666665Result greater than 3 x ULNNResult less than LLNN
YAMAA-01EOSEosinophils (%)1BASELINE016LOWY0LOWLOW to LOW—Result greater than 3 x ULNNResult less than LLNY
YAMAA-01EOSEosinophils (%)2WEEK 2416NORMAL—0LOWLOW to NORMAL—Result greater than 3 x ULNNResult less than LLNN
YAMAA-01EOSEosinophils (%)3WEEK 4116NORMAL—0LOWLOW to NORMAL—Result greater than 3 x ULNNResult less than LLNN
YAMAA-01LDHLactate Dehydrogenase (U/L)3BASELINE200———Y200——1————
YAMAA-01LDHLactate Dehydrogenase (U/L)4WEEK 2260————200——1.3————
YAMAA-01GGTGamma Glutamyl Transferase (U/L)5BASELINE5510——Y55——1——Result less than LLNN
YAMAA-01GGTGamma Glutamyl Transferase (U/L)6WEEK 27010———55——1.2727272727272727——Result less than LLNN
YAMAA-01EOSEosinophils (%)4WEEK 80.516LOW—0LOWLOW to LOW—Result greater than 3 x ULNNResult less than LLNY
YAMAA-01GGTGamma Glutamyl Transferase (U/L)7WEEK 4510———55——0.09090909090909091——Result less than LLNY
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
ADLBSTUDYIDStudy IdentifiercharacterCopy value from ADLB_RAW.STUDYID.Predecessor
ADLBUSUBJIDUnique Subject IdentifiercharacterCopy value from ADLB_RAW.USUBJID.Predecessor
ADLBPARAMCDParameter CodecharacterCopy value from ADLB_RAW.PARAMCD.Predecessor
ADLBPARAMParametercharacterCopy value from ADLB_RAW.PARAM.Predecessor
ADLBASEQAnalysis Sequence NumbernumericCopy value from ADLB_RAW.ASEQ.Predecessor
ADLBAVISITAnalysis VisitcharacterCopy value from ADLB_RAW.AVISIT.Predecessor
ADLBAVALAnalysis ValuenumericCopy value from ADLB_RAW.AVAL.Predecessor
ADLBANRLOAnalysis Normal Range Lower LimitnumericCopy value from ADLB_RAW.ANRLO.Predecessor
ADLBANRHIAnalysis Normal Range Upper LimitnumericCopy value from ADLB_RAW.ANRHI.Predecessor
ADLBANRINDAnalysis Reference Range IndicatorcharacterLOW, NORMAL, HIGHIf AVAL IS NULL OR ANRLO IS NULL OR ANRHI IS NULL then "None"; If AVAL < ANRLO then "LOW"; If AVAL > ANRHI then "HIGH"; otherwise "NORMAL".Derived
ADLBABLFLBaseline Record FlagcharacterYCopy value from ADLB_RAW.ABLFL.Predecessor
ADLBBASEBaseline ValuenumericAggregate ONLY(AVAL) over records grouped by STUDYID, USUBJID, PARAMCD, matching on the shared output keys, where ABLFL = 'Y'.Derived
ADLBBNRINDBaseline Reference Range IndicatorcharacterLOW, NORMAL, HIGHAggregate ONLY(ANRIND) over records grouped by STUDYID, USUBJID, PARAMCD, matching on the shared output keys, where ABLFL = 'Y'.Derived
ADLBSHIFT1Shift 1characterFill the template "{BNRIND} to {ANRIND}"; a missing field → blank.Derived
ADLBR2BASERatio to BaselinenumericCompute AVAL / NULLIF(BASE, 0).Derived
ADLBCRIT1Analysis Criterion 1characterIf AVAL IS NOT NULL AND CRITLIM IS NOT NULL then "Result greater than 3 x ULN"; otherwise blank.Derived
ADLBCRIT1FLCriterion 1 Evaluation Result FlagcharacterY, NIf AVAL IS NULL OR CRITLIM IS NULL then "None"; If AVAL > CRITLIM then "Y"; otherwise "N".Derived
ADLBCRIT2Analysis Criterion 2characterIf AVAL IS NOT NULL AND ANRLO IS NOT NULL then "Result less than LLN"; otherwise blank.Derived
ADLBCRIT2FLCriterion 2 Evaluation Result FlagcharacterY, NIf AVAL IS NULL OR ANRLO IS NULL then "None"; If AVAL < ANRLO then "Y"; otherwise "N".Derived
VariablePermitted values
ANRINDLOW, NORMAL, HIGH
ABLFLY
BNRINDLOW, NORMAL, HIGH
CRIT1FLY, N
CRIT2FLY, N
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-adlb-shift-criteria/full.md

Edit
44 lines

Following CDISC ADaM standards, use the provided ADLB_RAW dataset to create an ADLB dataset with one record per laboratory record.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, ASEQ, AVISIT, AVAL, ANRLO, ANRHI, ANRIND, ABLFL, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL, CRIT2, CRIT2FL

  • ANRIND is "LOW" when AVAL is below ANRLO, "HIGH" when AVAL is above ANRHI, and "NORMAL" when AVAL is between ANRLO and ANRHI, limits included, so a result exactly at a limit is "NORMAL".
  • ANRIND has no value when AVAL, ANRLO, or ANRHI has no value.
  • BASE repeats the baseline record's AVAL on every record of the subject and parameter, where the baseline record is the one with ABLFL "Y".
  • BASE has no value when no record of the subject and parameter has ABLFL "Y".
  • BNRIND repeats the baseline record's ANRIND on every record of the subject and parameter.
  • BNRIND has no value when there is no baseline record, or when the baseline record's own ANRIND has no value.
  • SHIFT1 is the baseline mark and the record's own mark joined by " to ", baseline first, so a record that stayed normal reads "NORMAL to NORMAL" and one that moved out of range reads "NORMAL to HIGH".
  • SHIFT1 has no value when either ANRIND or BNRIND has no value.
  • R2BASE is AVAL divided by BASE, so the baseline record itself reads 1 when the ratio can be computed.
  • R2BASE has no value when AVAL has no value, when there is no baseline, or when BASE is zero.
  • R2BASE is not rounded.
  • CRIT1 is "Result greater than 3 x ULN" when AVAL and ANRHI have values, and has no value when AVAL or ANRHI has no value.
  • CRIT1FL is "Y" when AVAL is greater than three times ANRHI and "N" when it is not, and a result exactly at three times ANRHI does not meet the criterion.
  • CRIT1FL has no value when AVAL or ANRHI has no value.
  • CRIT2 is "Result less than LLN" when AVAL and ANRLO have values, and has no value when AVAL or ANRLO has no value.
  • CRIT2 is assessed independently of CRIT1, so a record whose ANRHI has no value can still meet the low criterion.
  • CRIT2FL is "Y" when AVAL is less than ANRLO and "N" when it is not, and a result exactly at ANRLO does not meet the criterion.
  • CRIT2FL has no value when AVAL or ANRLO has no value.

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

prompts/adam-adlb-shift-criteria/conventions.md

Edit
16 lines

Following CDISC ADaM standards, use the provided ADLB_RAW dataset to create an ADLB dataset with one record per laboratory record.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, ASEQ, AVISIT, AVAL, ANRLO, ANRHI, ANRIND, ABLFL, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL, CRIT2, CRIT2FL

ANRIND is "LOW", "HIGH", or "NORMAL", or has no value. SHIFT1 is the baseline mark and the record's own mark joined by " to ", or has no value. R2BASE is not rounded. CRIT1 is "Result greater than 3 x ULN" or has no value, and CRIT1FL is "Y" or "N", or has no value. CRIT2 is "Result less than LLN" or has no value, and CRIT2FL is "Y" or "N", or has no value.

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

prompts/adam-adlb-shift-criteria/brief.md

Edit
9 lines

Following CDISC ADaM standards, use the provided ADLB_RAW dataset to create an ADLB dataset with one record per laboratory record.

The output dataset should contain the following columns in this order: STUDYID, USUBJID, PARAMCD, PARAM, ASEQ, AVISIT, AVAL, ANRLO, ANRHI, ANRIND, ABLFL, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL, CRIT2, CRIT2FL

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

Edit
88 lines
# Reference solution for the yamaa benchmark adam-adlb-shift-criteria (R track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.library(dplyr, warn.conflicts = FALSE)library(readr)raw <- read_csv(  "/app/input/adlb_raw.csv",  col_types = cols(    ASEQ = col_integer(),    AVAL = col_double(),    ANRLO = col_double(),    ANRHI = col_double(),    .default = col_character()  ))# The record's own mark: LOW below the lower limit, HIGH above the upper# limit, NORMAL between them limits included. No value when the result or# either limit is missing.based <- raw |>  mutate(    ANRIND = case_when(      is.na(AVAL) | is.na(ANRLO) | is.na(ANRHI) ~ NA_character_,      AVAL < ANRLO ~ "LOW",      AVAL > ANRHI ~ "HIGH",      .default = "NORMAL"    )  )# BASE repeats the flagged baseline record's value; BNRIND repeats its# mark. Both have no value when no record carries the flag.baselines <- based |>  filter(ABLFL %in% "Y") |>  select(STUDYID, USUBJID, PARAMCD, BASE = AVAL, BNRIND = ANRIND)with_base <- based |>  left_join(baselines, by = c("STUDYID", "USUBJID", "PARAMCD"))# SHIFT1 joins the baseline mark and the record's own mark; R2BASE is the# record as a multiple of the baseline; CRIT1 assesses greater than three# times the upper limit; CRIT2 assesses less than the lower limit.adlb <- with_base |>  mutate(    SHIFT1 = if_else(      is.na(BNRIND) | is.na(ANRIND),      NA_character_,      paste(BNRIND, "to", ANRIND)    ),    R2BASE = if_else(      is.na(AVAL) | is.na(BASE) | BASE == 0,      NA_real_,      AVAL / BASE    ),    CRITLIM = 3 * ANRHI,    CRIT1 = if_else(      !is.na(AVAL) & !is.na(CRITLIM),      "Result greater than 3 x ULN",      NA_character_    ),    CRIT1FL = case_when(      is.na(AVAL) | is.na(CRITLIM) ~ NA_character_,      AVAL > CRITLIM ~ "Y",      .default = "N"    ),    CRIT2 = if_else(      !is.na(AVAL) & !is.na(ANRLO),      "Result less than LLN",      NA_character_    ),    CRIT2FL = case_when(      is.na(AVAL) | is.na(ANRLO) ~ NA_character_,      AVAL < ANRLO ~ "Y",      .default = "N"    )  ) |>  arrange(USUBJID, ASEQ) |>  select(    STUDYID, USUBJID, PARAMCD, PARAM, ASEQ, AVISIT, AVAL, ANRLO, ANRHI,    ANRIND, ABLFL, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL,    CRIT2, CRIT2FL  )dir.create("/app/output", showWarnings = FALSE)write_csv(adlb, "/app/output/adlb.csv", na = "")

solutions/adam-adlb-shift-criteria/result.py

Edit
129 lines
# Reference solution for the yamaa benchmark adam-adlb-shift-criteria (Python track).## Written from the benchmark's full prompt and its inputs alone, as an agent# would write it. Harbor's oracle agent runs it to check that the prompt can# be solved and that the grader scores a correct answer 1.import csvimport osINPUT_DIR = "/app/input"OUTPUT_DIR = "/app/output"def to_float(value):    if value is None:        return None    value = value.strip()    if value == "":        return None    return float(value)def fmt_number(x):    if x == int(x):        return str(int(x))    return repr(x)def main():    with open(os.path.join(INPUT_DIR, "adlb_raw.csv"), newline="") as f:        rows = list(csv.DictReader(f))    # Per-record ANRIND    for row in rows:        aval = to_float(row["AVAL"])        anrlo = to_float(row["ANRLO"])        anrhi = to_float(row["ANRHI"])        if aval is None or anrlo is None or anrhi is None:            row["_ANRIND"] = ""        elif aval < anrlo:            row["_ANRIND"] = "LOW"        elif aval > anrhi:            row["_ANRIND"] = "HIGH"        else:            row["_ANRIND"] = "NORMAL"    # Baseline values per subject and parameter    baselines = {}    for row in rows:        if row["ABLFL"].strip() == "Y":            baselines[(row["USUBJID"], row["PARAMCD"])] = row    out_rows = []    for row in rows:        aval = to_float(row["AVAL"])        anrlo = to_float(row["ANRLO"])        anrhi = to_float(row["ANRHI"])        anrind = row["_ANRIND"]        base_row = baselines.get((row["USUBJID"], row["PARAMCD"]))        if base_row is None:            base_txt = ""            base_val = None            bnrind = ""        else:            base_txt = base_row["AVAL"]            base_val = to_float(base_row["AVAL"])            bnrind = base_row["_ANRIND"]        if anrind == "" or bnrind == "":            shift1 = ""        else:            shift1 = bnrind + " to " + anrind        if aval is None or base_val is None or base_val == 0:            r2base = ""        else:            r2base = fmt_number(aval / base_val)        if aval is not None and anrhi is not None:            crit1 = "Result greater than 3 x ULN"            crit1fl = "Y" if aval > 3 * anrhi else "N"        else:            crit1 = ""            crit1fl = ""        if aval is not None and anrlo is not None:            crit2 = "Result less than LLN"            crit2fl = "Y" if aval < anrlo else "N"        else:            crit2 = ""            crit2fl = ""        out_rows.append({            "STUDYID": row["STUDYID"],            "USUBJID": row["USUBJID"],            "PARAMCD": row["PARAMCD"],            "PARAM": row["PARAM"],            "ASEQ": row["ASEQ"],            "AVISIT": row["AVISIT"],            "AVAL": row["AVAL"],            "ANRLO": row["ANRLO"],            "ANRHI": row["ANRHI"],            "ANRIND": anrind,            "ABLFL": row["ABLFL"],            "BASE": base_txt,            "BNRIND": bnrind,            "SHIFT1": shift1,            "R2BASE": r2base,            "CRIT1": crit1,            "CRIT1FL": crit1fl,            "CRIT2": crit2,            "CRIT2FL": crit2fl,        })    fieldnames = [        "STUDYID", "USUBJID", "PARAMCD", "PARAM", "ASEQ", "AVISIT",        "AVAL", "ANRLO", "ANRHI", "ANRIND", "ABLFL", "BASE", "BNRIND",        "SHIFT1", "R2BASE", "CRIT1", "CRIT1FL", "CRIT2", "CRIT2FL",    ]    os.makedirs(OUTPUT_DIR, exist_ok=True)    with open(os.path.join(OUTPUT_DIR, "adlb.csv"), "w", newline="") as f:        writer = csv.DictWriter(f, fieldnames=fieldnames)        writer.writeheader()        writer.writerows(out_rows)if __name__ == "__main__":    main()

solutions/adam-adlb-shift-criteria/result.sas

Edit
35 lines
/* opensas reference solution for the yamaa benchmark adam-adlb-shift-criteria.   Reads the benchmark inputs and derives the requested output.   Execute with the existing opensas runtime: opensas /app/output/result.sas. */data adlb_raw;  length STUDYID USUBJID PARAMCD PARAM AVISIT ABLFL $1024;  infile "/app/input/adlb_raw.csv" dsd dlm="," firstobs=2 truncover lrecl=32767;  input STUDYID : $char1024. USUBJID : $char1024. PARAMCD : $char1024. PARAM : $char1024. ASEQ AVISIT : $char1024. AVAL ANRLO ANRHI ABLFL : $char1024.;run;data marked;  length ANRIND $6;  set adlb_raw;  if not missing(AVAL) and not missing(ANRLO) and not missing(ANRHI) then do;    if AVAL<ANRLO then ANRIND='LOW'; else if AVAL>ANRHI then ANRIND='HIGH'; else ANRIND='NORMAL';  end;run;proc sql; create table combined as select a.*,b.AVAL as BASE,b.ANRIND as BNRIND from marked as a left join marked as b on a.STUDYID=b.STUDYID and a.USUBJID=b.USUBJID and a.PARAMCD=b.PARAMCD and b.ABLFL='Y'; quit;data result;  length SHIFT1 $20 CRIT1 $30 CRIT2 $30 CRIT1FL CRIT2FL $1;  set combined;  if not missing(ANRIND) and not missing(BNRIND) then SHIFT1=catx(' to ',BNRIND,ANRIND);  if not missing(AVAL) and not missing(BASE) and BASE ne 0 then R2BASE=AVAL/BASE;  if not missing(AVAL) and not missing(ANRHI) then do;    CRIT1='Result greater than 3 x ULN'; CRIT1FL='N'; if AVAL>3*ANRHI then CRIT1FL='Y';  end;  if not missing(AVAL) and not missing(ANRLO) then do;    CRIT2='Result less than LLN'; CRIT2FL='N'; if AVAL<ANRLO then CRIT2FL='Y';  end;run;proc sql;  create table final as select STUDYID, USUBJID, PARAMCD, PARAM, ASEQ, AVISIT, AVAL, ANRLO, ANRHI, ANRIND, ABLFL, BASE, BNRIND, SHIFT1, R2BASE, CRIT1, CRIT1FL, CRIT2, CRIT2FL from result;quit;proc export data=final outfile="/app/output/adlb.csv" dbms=csv replace; run;