stc-methodology — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited stc-methodology (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Comprehensive methodological guidance for conducting rigorous Simulated Treatment Comparisons following NICE DSU TSD 18.
STC approach
MAIC approach
logit{P(Y = 1)} = beta_0 + beta_trt * Treatment
+ beta_X * X_centered
+ beta_trt_X * Treatment * X_centered
X_centered = X - X_externalWith centered covariates, beta_trt estimates the treatment effect in the external population, because X_centered = 0 corresponds to the aggregate target values.
STC additionally assumes that the outcome model is correctly specified:
Trade-off:
STC can be more precise than MAIC if the outcome model is correct.
STC can be biased if the model, functional form, or interactions are wrong.
MAIC avoids specifying an outcome model but can lose precision through low ESS.A covariate is an effect modifier if the relative treatment effect differs by covariate value. Selection should be based on:
Interaction p-values alone should not determine the final covariate set. Interaction tests are commonly underpowered, and selection after inspecting many tests can inflate false-positive findings.
candidate_covariates <- c("age", "sex_male", "biomarker_pos", "stage")
interaction_terms <- paste0("treatment:", candidate_covariates)
model_formula <- as.formula(
paste(
"response ~ treatment * (",
paste(candidate_covariates, collapse = " + "),
")"
)
)
fit_screen <- glm(model_formula, data = ipd_data, family = binomial())
summary(fit_screen)$coefficients[interaction_terms, , drop = FALSE]Use this as a structured screen, then document why each included covariate is or is not a credible effect modifier.
Without centering, the treatment coefficient is evaluated when every covariate equals zero, which is often clinically meaningless. With centering on the external aggregate population, the treatment coefficient is evaluated at the target population.
agd_targets <- c(age = 62, sex_male = 0.55, biomarker_pos = 0.40)
ipd_stc <- ipd_data |>
dplyr::mutate(
age_c = age - agd_targets["age"],
sex_male_c = sex_male - agd_targets["sex_male"],
biomarker_pos_c = biomarker_pos - agd_targets["biomarker_pos"]
)For centered binary covariates, subtract the external proportion. For categorical covariates with more than two levels, center compatible dummy variables or use another clearly documented parameterization.
When a treatment-covariate interaction is included, include the corresponding main effects unless there is a strong modeling reason not to. This keeps the model hierarchically well formed and the interaction interpretable.
Setup:
IPD trial: A vs Common
External aggregate trial: B vs Common
Target: A vs B
Steps:
1. Center effect modifiers on the external aggregate population.
2. Fit the IPD outcome model with interactions.
3. Extract A vs Common at the external population.
4. Obtain B vs Common and its uncertainty from aggregate data.
5. Calculate A vs B using Bucher logic on the correct effect scale.Setup:
No common comparator.
Target: direct comparison of absolute outcomes for A vs B.
Additional requirements:
1. Adjust for all prognostic factors, not only effect modifiers.
2. Assume absolute outcomes are transportable after adjustment.
3. Align outcome definitions, follow-up, and analysis populations.Unanchored STC is high risk and should be avoided when anchored evidence, NMA, or ML-NMR can answer the question.
library(dplyr)
ipd_stc <- ipd_data |>
mutate(
age_c = age - agd_targets["age"],
sex_male_c = sex_male - agd_targets["sex_male"],
treatment = relevel(factor(treatment), ref = "Placebo")
)
fit <- glm(
response ~ treatment * (age_c + sex_male_c),
data = ipd_stc,
family = binomial()
)
coef_name <- "treatmentDrug A"
log_or_ac <- unname(coef(fit)[coef_name])
se_log_or_ac <- sqrt(vcov(fit)[coef_name, coef_name])
log_or_bc <- log(external_or_bc)
se_log_or_bc <- (log(external_ci_upper) - log(external_ci_lower)) / (2 * qnorm(0.975))
log_or_ab <- log_or_ac - log_or_bc
se_log_or_ab <- sqrt(se_log_or_ac^2 + se_log_or_bc^2)
ci_log_or_ab <- log_or_ab + c(-1, 1) * qnorm(0.975) * se_log_or_ab
data.frame(
comparison = "Drug A vs Drug B",
effect_measure = "OR",
estimate = exp(log_or_ab),
ci_lower = exp(ci_log_or_ab[1]),
ci_upper = exp(ci_log_or_ab[2])
)Use the effect scale that matches the external estimate. Do not mix odds ratios, risk ratios, hazard ratios, mean differences, or risk differences without transforming to a common scale.
bayes_fit <- brms::brm(
response ~ treatment * (age_c + sex_male_c),
data = ipd_stc,
family = brms::bernoulli(),
prior = c(
brms::prior(normal(0, 2), class = "b"),
brms::prior(normal(0, 5), class = "Intercept")
),
chains = 4,
iter = 4000,
seed = 12345
)
brms::summary(bayes_fit)
brms::pp_check(bayes_fit)Priors must be justified on the model scale. Report convergence diagnostics and sensitivity to alternative prior scales.
| Aspect | STC | MAIC |
|---|---|---|
| Method | Outcome regression | Propensity weighting |
| Efficiency | Higher if model correct | Lower when weights reduce ESS |
| Model dependence | Higher | Lower |
| Continuous covariates | Natural | May be limited by reported summaries |
| Extrapolation | Possible, but risky | Limited by overlap |
| Main diagnostics | Model fit, residuals, interactions | ESS, weight distribution, balance |
Run both as sensitivity analyses when feasible. Similar results increase confidence; divergent results require investigation, not automatic averaging.
# 1. Choose effect modifiers from clinical rationale and supporting evidence.
effect_modifiers <- c("age", "sex_male")
agd_targets <- c(age = 62, sex_male = 0.55)
# 2. Center IPD on the target population.
ipd_stc <- ipd |>
dplyr::mutate(
age_c = age - agd_targets["age"],
sex_male_c = sex_male - agd_targets["sex_male"],
treatment = stats::relevel(factor(treatment), ref = "Placebo")
)
# 3. Fit outcome regression with interactions.
fit <- stats::glm(
response ~ treatment * (age_c + sex_male_c),
data = ipd_stc,
family = stats::binomial()
)
# 4. Extract adjusted A vs common-comparator effect.
coef_name <- "treatmentDrug A"
log_or_ac <- unname(stats::coef(fit)[coef_name])
se_log_or_ac <- sqrt(stats::vcov(fit)[coef_name, coef_name])
# 5. Combine with external B vs common-comparator estimate.
log_or_ab <- log_or_ac - log_or_bc
se_log_or_ab <- sqrt(se_log_or_ac^2 + se_log_or_bc^2)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.