Stacks the visit-bearing collected domains and reduces them to one record per subject per visit, with SVSTDTC/SVENDTC set to the earliest and latest date observed for that subject at that visit across all the domains supplied. A visit that spans two days in the lab but one in vitals therefore gets the full span, which is the point of building SV from the collected data rather than from any single domain.

derive_sv(domains, dm)

Arguments

domains

A named list of domain data frames (or a single data frame). Each needs USUBJID, VISITNUM and a date variable; VISIT is used when present. Names are used only in error messages.

dm

Demographics (DM). Supplies the subject universe and STUDYID.

Value

An sdtmgap_domain tibble with STUDYID, DOMAIN, USUBJID, VISITNUM, VISIT, SVSTDTC, SVENDTC.

Details

Partial dates are ordered by their earliest possible instant for the start and their latest for the end, and the winning value is written through as collected — SV never invents a precision the CRF did not have.

Records with a missing VISITNUM (unscheduled or unvisited data) or with no usable date are excluded, as are subjects absent from dm. SV has no --SEQ: USUBJID + VISITNUM is the key.

Limit. The window is the range of collected dates only. Planned visit windows from Trial Visits (TV) are not read, so derive_sv() will not flag a visit that fell outside its protocol window, nor emit a row for a visit at which nothing was collected.

SAS idiom replaced. The PROC SQL union of every raw domain followed by PROC MEANS/PROC SUMMARY with MIN(--DTC)/MAX(--DTC) BY USUBJID VISITNUM — with the character-date min/max trap that comes with it.

Examples

sv <- derive_sv(
  domains = list(vs = sdtmgap_example("edge_vs"), lb = sdtmgap_example("edge_lb")),
  dm      = sdtmgap_example("edge_dm")
)
sv[sv$USUBJID == "SDTMGAP01-001", ]
#> 
#> ── SDTM SV (6 records) ─────────────────────────────────────────────────────────
#>  31 visit record(s) for 7 subject(s), from 38 input record(s) across 2 domain(s).
#>  1 input record(s) had no VISITNUM, no usable date, or no matching subject.
#> # A tibble: 6 × 7
#>   STUDYID   DOMAIN USUBJID       VISITNUM VISIT       SVSTDTC    SVENDTC   
#>   <chr>     <chr>  <chr>            <dbl> <chr>       <chr>      <chr>     
#> 1 SDTMGAP01 SV     SDTMGAP01-001        1 SCREENING   2026-01-04 2026-01-05
#> 2 SDTMGAP01 SV     SDTMGAP01-001        2 DAY 1       2026-01-19 2026-01-19
#> 3 SDTMGAP01 SV     SDTMGAP01-001        3 WEEK 2      2026-02-02 2026-02-03
#> 4 SDTMGAP01 SV     SDTMGAP01-001        4 WEEK 4      2026-02-15 2026-02-15
#> 5 SDTMGAP01 SV     SDTMGAP01-001        5 FOLLOW-UP   2026-03-01 2026-03-01
#> 6 SDTMGAP01 SV     SDTMGAP01-001       99 UNSCHEDULED 2025-12-20 2025-12-20