--- title: "Validity-Date Versioning Design" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Validity-Date Versioning Design} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(cdiscdata) ``` ## The problem: storing many CT versions compactly CDISC Controlled Terminology is released quarterly. Each release contains roughly 45,000 rows. Storing every version as a separate file would mean 45,000 × 40+ releases ≈ 1.8 million rows and significant duplication, since most terms do not change between releases. ## The solution: validity-date design `cdiscdata` uses a _validity-date_ design. Each row represents a unique **term-state**: the set of values for a term at a given point in time. | Column | Meaning | |--------|---------| | `valid_from` | Date of the first CT release in which this term-state appeared | | `valid_to` | Date of the last CT release in which this term-state appeared; `NA` if still current | When a term's content changes between releases: 1. The old row is _closed_ by setting `valid_to = new_release_date - 1`. 2. A new row is added with `valid_from = new_release_date` and `valid_to = NA`. When a term is retired: - Its current row is closed with `valid_to = retirement_date - 1`. - No new row is added. ## Querying a specific version `get_ct()` and `get_dataset()` filter the table to rows valid at the requested date: ```r # Rows where valid_from <= query_date AND (valid_to >= query_date OR valid_to is NA) tbl[tbl$valid_from <= query_date & (is.na(tbl$valid_to) | tbl$valid_to >= query_date), ] ``` The query date must exactly match one of the release dates returned by `available_ct_versions()`. This ensures reproducibility: the same version string always returns the same rows. ## Growth rate Because most terms are stable across releases, only a small fraction of rows change per quarter (~1,000–1,500 new rows per release vs. 45,000+ total terms). The full historical archive therefore grows slowly, keeping the package size manageable for CRAN submission. ## Example ```{r} # How many rows represent "closed" (historical) term states? n_closed <- sum(!is.na(ct_sdtm$valid_to)) n_current <- sum(is.na(ct_sdtm$valid_to)) cat("Current rows:", n_current, "\nHistorical rows:", n_closed, "\n") ```