---
title: "Standard workflow: routing, mappings and reviewed adapters"
output:
rmarkdown::html_vignette:
mathjax: null
vignette: >
%\VignetteIndexEntry{Standard workflow: routing, mappings and reviewed adapters}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
resource_files:
- figures/logo.svg
- figures/logo-dark.svg
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
example_root <- tempfile("cttir-standard-")
dir.create(example_root)
old_catalog_option <- getOption("cttiR.catalog_dir")
options(cttiR.catalog_dir = file.path(example_root, "catalog"))
stage_packages <- c("dplyr", "nlme", "survival", "broom", "broom.mixed", "DescrTab2",
"ggplot2", "patchwork", "viridisLite", "RColorBrewer", "colorspace", "jsonlite", "yaml")
has_stage_packages <- all(vapply(stage_packages, requireNamespace, logical(1), quietly = TRUE))
```
Every project receives a useful workflow from the same three inputs, whether or
not a CTTIR specialist package fits. This vignette shows how cttiR chooses the
profile, what the generated standard workflow contains, and which explicit steps
stand between a scaffold and a study-data analysis.
The preferred DescrTab2 2.1.16 backend is archived on CRAN. Choose
`options = list(workflow = list(table_backend = "none"))` when creating a project
to use the supported base descriptive table instead. To use the reviewed
DescrTab2 adapter, provision the exact archive explicitly with
`remotes::install_version("DescrTab2", version = "2.1.16", upgrade = "never")`.
The generated dependency lock remains authoritative for the other package pins;
an available archive does not establish ongoing upstream maintenance.
## Profiles
| Evidence | Profile |
|---|---|
| Approved CTTIR specialist adapters cover the analysis | `cttir_specialist` |
| Approved specialist stages cover part of it | `hybrid` |
| No approved specialist capability fits | `standard_reflowR` |
Specialist evidence means an adapter-tested capability whose package revisions
carry approvals in the pinned catalog. Infrastructure such as reflowR, renv or
targets never counts as a specialist match. When a goal mentions a specialist
area without an approved adapter, the standard profile is used and the gap is
recorded.
```{r route}
p <- cttiR::project(
"Cohort outcomes", "primary_research",
"Describe outcomes and plan an adjusted regression for a tabular cohort",
path = example_root
)
p$readiness$workflow$profile
vapply(p$readiness$workflow$stages, function(x) paste(x$stage, x$capability, x$status), character(1))
p$readiness$blockers
```
Goal keywords only suggest unset analysis fields (here the explanatory aim); the
suggestion is stored as an `inferred` decision and never approves anything.
Outcome family, unit structure, variables and estimands remain unknown until
you record them.
```{r decisions}
Filter(function(d) d$origin == "inferred", p$spec$decisions)[[1]][c("field", "reason")]
```
## What the standard bundle contains
The layout adapts the pinned MIT-licensed reflowR minimal template. Reviewed,
static stage code lives in `code/R/`; user text and column names are read from
configuration files as data and never reach formulas or code.
```{r files}
sort(grep("^(code|config|analysis|demo)/", p$plan$path, value = TRUE))
```
`config/workflow.yml` lists the enabled stages, their capability IDs, approval
status and the package versions pinned from the project's catalog snapshot:
```{r workflow-config}
config <- yaml::read_yaml(file.path(p$path, "config", "workflow.yml"))
config[c("profile", "table_backend", "engine")]
vapply(config$dependencies, function(x) x$package, character(1))
```
## Recording mappings explicitly
Variables, estimand, missing-data intent and model settings are explicit
configuration. `sync()` previews the change first.
```{r mapping}
options <- list(
data_sources = list(list(id = "cohort", label = "Cohort extract", logical_uri = NULL,
format = "csv", access_class = "restricted", checksum = NULL,
checksum_status = "unknown", schema_ref = NULL)),
analysis = list(
outcome_family = "continuous", unit_structure = "independent",
mapping = list(data_source_id = "cohort", outcome = "Systolic BP (mmHg)",
predictors = list("Dose (mg)", "Arm"), estimand = "Adjusted mean difference per mg",
missing_data = "complete_case"),
model = list(intercept = TRUE, reviewed = TRUE)
)
)
preview <- cttiR::sync(p$path, options = options)
preview$state
preview$analysis$candidate_engine
unlist(preview$analysis$missing_fields)
applied <- cttiR::sync(p$path, options = options, dry_run = FALSE)
applied$state
```
The remaining missing field is the analysis approval, which only a person can
record. Recording it does not run anything.
## Structural data checks
`check_analysis_data()` checks an explicitly supplied data frame against the
mapping without changing it or fitting a model.
```{r check}
synthetic <- data.frame(
`Systolic BP (mmHg)` = c(121, 135, 128, NA, 140, 118),
`Dose (mg)` = c(1, 5, 3, 4, 8, 0.5),
Arm = c("low", "high", "low", "high", "high", "low"),
check.names = FALSE
)
spec <- cttiR::validate_spec(file.path(p$path, "cttir-project.yml"))
result <- cttiR::check_analysis_data(synthetic, spec)
result[c("state", "rows_total", "rows_complete")]
```
## Synthetic demonstration
`Rscript code/run_demo.R` runs every stage on constructed data and compares the
linear, logistic, mixed and Cox adapters with independent reference
computations (normal equations, a hand-written Newton-Raphson, a dense Gaussian
likelihood and direct partial-likelihood maximisation). The same code can run in
memory:
```{r demo, eval = has_stage_packages}
env <- new.env()
for (file in c("cttir_figures.R", "cttir_workflow.R")) {
sys.source(file.path(p$path, "code", "R", file), envir = env)
}
receipt <- env$cw_run_demo(root = p$path, figures_policy = spec$figures, write = FALSE)
receipt$status
vapply(receipt$cases, function(x) x$reference$name, character(1))
```
These values are adapter checks on synthetic data, not results.
## Running on study data
`Rscript code/run_workflow.R` reads registered data only when all of these are
present: complete mapping and reviewed model settings, recorded analysis
approval, a local binding in `.cttir/local.yml`, approved stage adapters for the
pinned package revisions and matching installed package versions. Otherwise it
stops and lists what is missing, without reading data:
```{r runner}
result <- processx::run(file.path(R.home("bin"), "Rscript"),
c("--vanilla", "code/run_workflow.R"), wd = p$path, error_on_status = FALSE)
cat(result$stderr)
```
Diagnostics are computational evidence for review; a blocked fit reports no
estimates. Prediction, causal, count and specialist analyses have no reviewed
adapter in this bundle and remain explicit gaps.
```{r cleanup, include=FALSE}
options(cttiR.catalog_dir = old_catalog_option)
unlink(example_root, recursive = TRUE)
```