Clinical submissions require a
systematic approach to identifying, scoring, and mitigating risks. The
r4subrisk package implements an FMEA (Failure Mode and
Effects Analysis) based risk framework aligned with ICH Q9 quality risk
management principles.
risk_config_default() defines the RPN (Risk Priority
Number) band thresholds and scoring scales:
A risk register is built from a data frame with at minimum
risk_id, description,
probability, impact, and
detectability — each scored 1–5. The RPN is computed as
probability * impact * detectability.
risks <- data.frame(
risk_id = paste0("RISK-00", 1:5),
description = c(
"Missing required variables in ADSL",
"Incomplete derivation documentation for ADTTE",
"Independent QC not completed for ADRS",
"Define-XML missing codelist references",
"ADRG not included in submission package"
),
category = c("data_quality", "traceability", "process",
"documentation", "regulatory"),
probability = c(3L, 4L, 2L, 2L, 1L),
impact = c(4L, 5L, 5L, 3L, 5L),
detectability = c(2L, 3L, 4L, 2L, 1L),
owner = c("Data Mgmt", "Stats", "Stats", "Programming", "Regulatory"),
stringsAsFactors = FALSE
)
rr <- create_risk_register(risks)
print(rr)
#> ℹ Risk Register: 5 risk(s), 5 open
#> ℹ Critical: 0, High: 2, Mean RPN: 28.2
#> # A tibble: 5 × 11
#> risk_id description category probability impact detectability owner mitigation
#> * <chr> <chr> <chr> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 RISK-0… Missing re… data_qu… 3 4 2 Data… <NA>
#> 2 RISK-0… Incomplete… traceab… 4 5 3 Stats <NA>
#> 3 RISK-0… Independen… process 2 5 4 Stats <NA>
#> 4 RISK-0… Define-XML… documen… 2 3 2 Prog… <NA>
#> 5 RISK-0… ADRG not i… regulat… 1 5 1 Regu… <NA>
#> # ℹ 3 more variables: status <chr>, rpn <dbl>, risk_level <chr>compute_risk_scores() returns aggregate metrics
including overall risk score (normalized 0-1), mean RPN, and
per-category breakdown:
rs <- compute_risk_scores(rr)
rs$overall_risk_score
#> [1] 0.2256
rs$mean_rpn
#> [1] 28.2
rs$risk_distribution
#> # A tibble: 3 × 2
#> risk_level n
#> <chr> <int>
#> 1 high 2
#> 2 low 2
#> 3 medium 1
rs$category_summary
#> # A tibble: 5 × 3
#> category n mean_rpn
#> <chr> <int> <dbl>
#> 1 data_quality 1 24
#> 2 documentation 1 12
#> 3 process 1 40
#> 4 regulatory 1 5
#> 5 traceability 1 60apply_mitigations() updates the register with
post-control scores to show residual risk after mitigations are
applied:
controls <- data.frame(
risk_id = c("RISK-001", "RISK-002", "RISK-003"),
mitigation = c(
"Automated variable check implemented in CI pipeline",
"Derivation review added to QC checklist",
"QC sign-off required before dataset lock"
),
probability_residual = c(1L, 2L, 1L),
impact_residual = c(4L, 5L, 5L),
detectability_residual = c(1L, 1L, 1L),
stringsAsFactors = FALSE
)
rr_mitigated <- apply_mitigations(rr, controls)compare_risk_registers() shows the RPN change before and
after mitigations:
comparison <- compare_risk_registers(rr, rr_mitigated)
comparison
#> $rpn_changes
#> # A tibble: 5 × 4
#> risk_id rpn_before rpn_after rpn_delta
#> <chr> <dbl> <dbl> <dbl>
#> 1 RISK-001 24 24 0
#> 2 RISK-002 60 60 0
#> 3 RISK-003 40 40 0
#> 4 RISK-004 12 12 0
#> 5 RISK-005 5 5 0
#>
#> $new_risks
#> character(0)
#>
#> $resolved_risks
#> character(0)
#>
#> $level_transitions
#> # A tibble: 5 × 4
#> risk_id level_before level_after changed
#> <chr> <chr> <chr> <lgl>
#> 1 RISK-001 medium medium FALSE
#> 2 RISK-002 high high FALSE
#> 3 RISK-003 high high FALSE
#> 4 RISK-004 low low FALSE
#> 5 RISK-005 low low FALSE
#>
#> $delta_mean_rpn
#> [1] 0evidence_to_risks() converts an evidence table with
indicator_domain == "risk" into a risk register format,
allowing automation of risk identification from evidence outputs:
# Requires r4subcore
ctx <- r4subcore::r4sub_run_context(study_id = "STUDY01")
ev <- r4subcore::as_evidence(
data.frame(
asset_type = "program", asset_id = "prod_adrs.R",
source_name = "r4subrisk", source_version = "0.1.0",
indicator_id = "R-001", indicator_name = "Program Validation",
indicator_domain = "risk", severity = "high",
result = "fail", metric_value = 0, metric_unit = "score",
message = "DVP not submitted", location = "prod_adrs.R",
evidence_payload = "{}", stringsAsFactors = FALSE
), ctx = ctx
)
risk_df <- evidence_to_risks(ev)
rr_from_evidence <- create_risk_register(risk_df)