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.
| 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.
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
#> [1] "standard_reflowR"
vapply(p$readiness$workflow$stages, function(x) paste(x$stage, x$capability, x$status), character(1))
#> [1] "project std.project.reflowr_layout approved"
#> [2] "import std.import.delimited approved"
#> [3] "check std.check.mapped approved"
#> [4] "tidy std.tidy.dplyr approved"
#> [5] "describe std.describe.descrtab2 approved"
#> [6] "figures std.figures.accessible approved"
#> [7] "demo std.demo.synthetic approved"
#> [8] "report std.report.render approved"
p$readiness$blockers
#> [1] "environment_pending" "capability_gaps_recorded"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.
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.
sort(grep("^(code|config|analysis|demo)/", p$plan$path, value = TRUE))
#> [1] "analysis/_site.yml" "analysis/01_read_data.Rmd"
#> [3] "analysis/02_eda.Rmd" "analysis/03_report.Rmd"
#> [5] "analysis/index.Rmd" "analysis/README.md"
#> [7] "analysis/synthetic_demo.Rmd" "code/R/cttir_figures.R"
#> [9] "code/R/cttir_workflow.R" "code/render_report.R"
#> [11] "code/run_demo.R" "code/run_workflow.R"
#> [13] "code/validate_project.R" "config/analysis.yml"
#> [15] "config/workflow.yml" "demo/README.md"config/workflow.yml lists the enabled stages, their
capability IDs, approval status and the package versions pinned from the
project’s catalog snapshot:
config <- yaml::read_yaml(file.path(p$path, "config", "workflow.yml"))
config[c("profile", "table_backend", "engine")]
#> $profile
#> [1] "standard_reflowR"
#>
#> $table_backend
#> [1] "DescrTab2"
#>
#> $engine
#> NULL
vapply(config$dependencies, function(x) x$package, character(1))
#> [1] "DescrTab2" "RColorBrewer" "broom" "broom.mixed" "colorspace"
#> [6] "dplyr" "generics" "ggplot2" "grDevices" "jsonlite"
#> [11] "knitr" "nlme" "patchwork" "readr" "rmarkdown"
#> [16] "stats" "survival" "tools" "utils" "viridisLite"
#> [21] "yaml"Variables, estimand, missing-data intent and model settings are
explicit configuration. sync() previews the change
first.
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
#> [1] "planned"
preview$analysis$candidate_engine
#> [1] "stats::lm"
unlist(preview$analysis$missing_fields)
#> [1] "/analysis/approved"
applied <- cttiR::sync(p$path, options = options, dry_run = FALSE)
applied$state
#> [1] "applied"The remaining missing field is the analysis approval, which only a person can record. Recording it does not run anything.
check_analysis_data() checks an explicitly supplied data
frame against the mapping without changing it or fitting a model.
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")]
#> $state
#> [1] "passed"
#>
#> $rows_total
#> [1] 6
#>
#> $rows_complete
#> [1] 5Rscript 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:
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.
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:
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)
#> Setting r-universe rmarkdown theme...
#> Error: The study-data workflow is not ready. Missing:
#> - analysis.approved
#> - dataset.local_binding
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> - [email protected]
#> Execution haltedDiagnostics 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.