///|
/// Severity categories for a quantitative safety case.
pub(all) enum SafetySeverity {
SafetyNegligible
SafetyMinor
SafetySerious
SafetyCritical
SafetyCatastrophic
} derive(Debug, Eq)
///|
pub(all) enum SafetyLikelihood {
SafetyFrequent
SafetyProbable
SafetyOccasional
SafetyRemote
SafetyImprobable
} derive(Debug, Eq)
///|
pub struct SafetyHazard {
hazard_id : Int
title : String
severity : SafetySeverity
likelihood : SafetyLikelihood
exposure : Double
detectability : Double
mitigated : Bool
owner : Int
}
///|
pub fn safety_hazard(
hazard_id : Int,
title : String,
severity : SafetySeverity,
likelihood : SafetyLikelihood,
exposure : Double,
detectability : Double,
mitigated : Bool,
owner : Int,
) -> SafetyHazard {
if hazard_id < 0 ||
exposure < 0.0 ||
detectability < 0.0 ||
detectability > 1.0 ||
owner < 0 {
abort("invalid safety hazard")
}
{
hazard_id,
title,
severity,
likelihood,
exposure,
detectability,
mitigated,
owner,
}
}
///|
pub fn safety_severity_score(severity : SafetySeverity) -> Int {
match severity {
SafetyNegligible => 1
SafetyMinor => 2
SafetySerious => 3
SafetyCritical => 4
SafetyCatastrophic => 5
}
}
///|
pub fn safety_likelihood_score(likelihood : SafetyLikelihood) -> Int {
match likelihood {
SafetyFrequent => 5
SafetyProbable => 4
SafetyOccasional => 3
SafetyRemote => 2
SafetyImprobable => 1
}
}
///|
pub fn safety_hazard_score(hazard : SafetyHazard) -> Double {
safety_severity_score(hazard.severity).to_double() *
safety_likelihood_score(hazard.likelihood).to_double() *
hazard.exposure *
(1.0 - hazard.detectability)
}
///|
pub fn safety_hazard_risk_class(hazard : SafetyHazard) -> String {
let score = safety_hazard_score(hazard)
if score >= 50.0 {
"unacceptable"
} else if score >= 20.0 {
"tolerable-with-controls"
} else {
"broadly-acceptable"
}
}
///|
pub fn safety_hazard_is_actionable(hazard : SafetyHazard) -> Bool {
safety_hazard_score(hazard) >= 20.0 || !hazard.mitigated
}
///|
pub fn safety_hazard_residual_score(
hazard : SafetyHazard,
mitigation_effectiveness : Double,
) -> Double {
if mitigation_effectiveness < 0.0 || mitigation_effectiveness > 1.0 {
abort("mitigation effectiveness must be in [0, 1]")
}
safety_hazard_score(hazard) * (1.0 - mitigation_effectiveness)
}
///|
pub fn safety_hazard_expected_loss(
hazard : SafetyHazard,
consequence_cost : Double,
) -> Double {
if consequence_cost < 0.0 {
abort("consequence cost must be non-negative")
}
hazard.exposure * hazard.detectability * consequence_cost
}
///|
pub struct SafetyBarrier {
barrier_id : Int
hazard_id : Int
name : String
probability_of_success : Double
independence : Double
test_interval : Double
last_test : Double
failed : Bool
}
///|
pub fn safety_barrier(
barrier_id : Int,
hazard_id : Int,
name : String,
probability_of_success : Double,
independence : Double,
test_interval : Double,
last_test : Double,
failed : Bool,
) -> SafetyBarrier {
if barrier_id < 0 ||
hazard_id < 0 ||
probability_of_success < 0.0 ||
probability_of_success > 1.0 ||
independence < 0.0 ||
independence > 1.0 ||
test_interval <= 0.0 ||
last_test < 0.0 {
abort("invalid safety barrier")
}
{
barrier_id,
hazard_id,
name,
probability_of_success,
independence,
test_interval,
last_test,
failed,
}
}
///|
pub fn safety_barrier_is_due(barrier : SafetyBarrier, as_of : Double) -> Bool {
as_of - barrier.last_test >= barrier.test_interval
}
///|
pub fn safety_barrier_effectiveness(barrier : SafetyBarrier) -> Double {
if barrier.failed {
0.0
} else {
barrier.probability_of_success
}
}
///|
pub fn safety_barrier_common_cause_factor(barrier : SafetyBarrier) -> Double {
1.0 - barrier.independence
}
///|
pub fn safety_barrier_retest(
barrier : SafetyBarrier,
as_of : Double,
passed : Bool,
) -> SafetyBarrier {
{ ..barrier, last_test: as_of, failed: !passed }
}
///|
pub fn safety_barrier_failure_probability(barrier : SafetyBarrier) -> Double {
1.0 - safety_barrier_effectiveness(barrier)
}
///|
pub fn safety_barrier_contribution(
barrier : SafetyBarrier,
hazard_exposure : Double,
) -> Double {
hazard_exposure * safety_barrier_failure_probability(barrier)
}
///|
pub struct SafetyBarrierChain {
hazard_id : Int
barriers : Array[SafetyBarrier]
architecture : String
residual_probability : Double
independence_penalty : Double
}
///|
pub fn safety_barrier_chain(
hazard_id : Int,
barriers : Array[SafetyBarrier],
architecture : String,
) -> SafetyBarrierChain {
if hazard_id < 0 ||
barriers.is_empty() ||
(architecture != "series" && architecture != "parallel") {
abort("invalid barrier chain")
}
let effectiveness = barriers.map(barrier => {
safety_barrier_effectiveness(barrier)
})
let residual = if architecture == "series" {
allocation_series_failure_rate(effectiveness, 1.0)
} else {
allocation_parallel_failure_rate(effectiveness, 1.0)
}
let penalty = barriers.fold(init=0.0, (total, barrier) => {
total + safety_barrier_common_cause_factor(barrier)
}) /
barriers.length().to_double()
{
hazard_id,
barriers,
architecture,
residual_probability: residual * (1.0 + penalty),
independence_penalty: penalty,
}
}
///|
pub fn safety_barrier_chain_effectiveness(chain : SafetyBarrierChain) -> Double {
(1.0 - chain.residual_probability).max(0.0).min(1.0)
}
///|
pub fn safety_barrier_chain_failure_probability(
chain : SafetyBarrierChain,
) -> Double {
chain.residual_probability.min(1.0)
}
///|
pub fn safety_barrier_chain_due_count(
chain : SafetyBarrierChain,
as_of : Double,
) -> Int {
chain.barriers.fold(init=0, (count, barrier) => {
if safety_barrier_is_due(barrier, as_of) {
count + 1
} else {
count
}
})
}
///|
pub fn safety_barrier_chain_failed_count(chain : SafetyBarrierChain) -> Int {
chain.barriers.fold(init=0, (count, barrier) => {
if barrier.failed {
count + 1
} else {
count
}
})
}
///|
pub fn safety_barrier_chain_score(chain : SafetyBarrierChain) -> Double {
safety_barrier_chain_effectiveness(chain) / (1.0 + chain.independence_penalty)
}
///|
pub struct SafetyRequirement {
requirement_id : Int
title : String
target_probability : Double
verified : Bool
evidence_strength : Double
hazard_ids : Array[Int]
}
///|
pub fn safety_requirement(
requirement_id : Int,
title : String,
target_probability : Double,
verified : Bool,
evidence_strength : Double,
hazard_ids : Array[Int],
) -> SafetyRequirement {
if requirement_id < 0 ||
target_probability < 0.0 ||
target_probability > 1.0 ||
evidence_strength < 0.0 ||
evidence_strength > 1.0 {
abort("invalid safety requirement")
}
{
requirement_id,
title,
target_probability,
verified,
evidence_strength,
hazard_ids,
}
}
///|
pub fn safety_requirement_passes(
requirement : SafetyRequirement,
measured_probability : Double,
) -> Bool {
requirement.verified &&
measured_probability <= requirement.target_probability &&
requirement.evidence_strength >= 0.8
}
///|
pub fn safety_requirement_gap(
requirement : SafetyRequirement,
measured_probability : Double,
) -> Double {
measured_probability - requirement.target_probability
}
///|
pub fn safety_requirement_evidence_gap(
requirement : SafetyRequirement,
) -> Double {
(0.8 - requirement.evidence_strength).max(0.0)
}
///|
pub fn safety_requirement_hazard_count(requirement : SafetyRequirement) -> Int {
requirement.hazard_ids.length()
}
///|
pub fn safety_requirement_checksum(requirement : SafetyRequirement) -> Double {
requirement.requirement_id.to_double() +
requirement.target_probability +
requirement.evidence_strength +
requirement.hazard_ids.length().to_double()
}
///|
pub struct SafetyVerification {
requirement_id : Int
test_id : Int
measured_probability : Double
confidence : Double
passed : Bool
evidence : String
}
///|
pub fn safety_verification(
requirement_id : Int,
test_id : Int,
measured_probability : Double,
confidence : Double,
passed : Bool,
evidence : String,
) -> SafetyVerification {
if requirement_id < 0 ||
test_id < 0 ||
measured_probability < 0.0 ||
measured_probability > 1.0 ||
confidence <= 0.0 ||
confidence > 1.0 {
abort("invalid safety verification")
}
{
requirement_id,
test_id,
measured_probability,
confidence,
passed,
evidence,
}
}
///|
pub fn safety_verification_strength(
verification : SafetyVerification,
) -> Double {
verification.confidence * (if verification.passed { 1.0 } else { 0.0 })
}
///|
pub fn safety_verification_is_credible(
verification : SafetyVerification,
minimum_confidence : Double,
) -> Bool {
verification.passed && verification.confidence >= minimum_confidence
}
///|
pub fn safety_verification_risk(verification : SafetyVerification) -> Double {
verification.measured_probability * (1.0 - verification.confidence)
}
///|
pub struct SafetyCaseSummary {
hazard_count : Int
actionable_hazards : Int
barrier_count : Int
failed_barriers : Int
requirement_count : Int
verified_requirements : Int
verification_count : Int
evidence_score : Double
residual_risk : Double
status : String
}
///|
pub fn safety_case_summary(
hazards : Array[SafetyHazard],
chains : Array[SafetyBarrierChain],
requirements : Array[SafetyRequirement],
verifications : Array[SafetyVerification],
) -> SafetyCaseSummary {
let actionable = hazards.fold(init=0, (count, hazard) => {
if safety_hazard_is_actionable(hazard) {
count + 1
} else {
count
}
})
let barriers = chains.fold(init=0, (count, chain) => {
count + chain.barriers.length()
})
let failed = chains.fold(init=0, (count, chain) => {
count + safety_barrier_chain_failed_count(chain)
})
let verified = requirements.fold(init=0, (count, requirement) => {
if requirement.verified {
count + 1
} else {
count
}
})
let evidence = if verifications.is_empty() {
0.0
} else {
mean(
verifications.map(verification => {
safety_verification_strength(verification)
}),
)
}
let residual = chains.fold(init=0.0, (total, chain) => {
total + safety_barrier_chain_failure_probability(chain)
})
{
hazard_count: hazards.length(),
actionable_hazards: actionable,
barrier_count: barriers,
failed_barriers: failed,
requirement_count: requirements.length(),
verified_requirements: verified,
verification_count: verifications.length(),
evidence_score: evidence,
residual_risk: residual,
status: if actionable == 0 &&
failed == 0 &&
verified == requirements.length() &&
evidence >= 0.8 {
"accepted"
} else {
"open"
},
}
}
///|
pub fn safety_case_is_accepted(summary : SafetyCaseSummary) -> Bool {
summary.status == "accepted"
}
///|
pub fn safety_case_gap(summary : SafetyCaseSummary) -> Int {
summary.actionable_hazards +
summary.failed_barriers +
summary.requirement_count -
summary.verified_requirements
}
///|
pub fn safety_case_evidence_score(summary : SafetyCaseSummary) -> Double {
summary.evidence_score
}
///|
pub fn safety_case_residual_risk(summary : SafetyCaseSummary) -> Double {
summary.residual_risk
}
///|
pub fn safety_case_checksum(summary : SafetyCaseSummary) -> Double {
summary.hazard_count.to_double() +
summary.actionable_hazards.to_double() +
summary.barrier_count.to_double() +
summary.failed_barriers.to_double() +
summary.requirement_count.to_double() +
summary.verified_requirements.to_double() +
summary.verification_count.to_double() +
summary.evidence_score +
summary.residual_risk
}
///|
pub fn safety_hazard_rank(hazards : Array[SafetyHazard]) -> Array[SafetyHazard] {
let result = hazards.copy()
result.sort_by((left, right) => {
let l = safety_hazard_score(left)
let r = safety_hazard_score(right)
if l > r {
-1
} else if l < r {
1
} else {
0
}
})
result
}
///|
pub fn safety_hazard_scores(hazards : Array[SafetyHazard]) -> Array[Double] {
hazards.map(hazard => safety_hazard_score(hazard))
}
///|
pub fn safety_hazard_total_exposure(hazards : Array[SafetyHazard]) -> Double {
hazards.fold(init=0.0, (total, hazard) => total + hazard.exposure)
}
///|
pub fn safety_hazard_total_loss(
hazards : Array[SafetyHazard],
consequence_cost : Double,
) -> Double {
hazards.fold(init=0.0, (total, hazard) => {
total + safety_hazard_expected_loss(hazard, consequence_cost)
})
}
///|
pub fn safety_hazard_residual_scores(
hazards : Array[SafetyHazard],
mitigation_effectiveness : Double,
) -> Array[Double] {
hazards.map(hazard => {
safety_hazard_residual_score(hazard, mitigation_effectiveness)
})
}
///|
pub fn safety_hazard_unmitigated_count(hazards : Array[SafetyHazard]) -> Int {
hazards.fold(init=0, (count, hazard) => {
if hazard.mitigated {
count
} else {
count + 1
}
})
}
///|
pub fn safety_barrier_effectiveness_curve(
barriers : Array[SafetyBarrier],
) -> Array[Double] {
barriers.map(barrier => safety_barrier_effectiveness(barrier))
}
///|
pub fn safety_barrier_due_fraction(
barriers : Array[SafetyBarrier],
as_of : Double,
) -> Double {
if barriers.is_empty() {
0.0
} else {
barriers
.fold(init=0, (count, barrier) => {
if safety_barrier_is_due(barrier, as_of) {
count + 1
} else {
count
}
})
.to_double() /
barriers.length().to_double()
}
}
///|
pub fn safety_barrier_failure_fraction(
barriers : Array[SafetyBarrier],
) -> Double {
if barriers.is_empty() {
0.0
} else {
barriers
.fold(init=0, (count, barrier) => {
if barrier.failed {
count + 1
} else {
count
}
})
.to_double() /
barriers.length().to_double()
}
}
///|
pub fn safety_requirement_completion(
requirements : Array[SafetyRequirement],
) -> Double {
if requirements.is_empty() {
1.0
} else {
requirements
.fold(init=0, (count, requirement) => {
if requirement.verified {
count + 1
} else {
count
}
})
.to_double() /
requirements.length().to_double()
}
}
///|
pub fn safety_verification_pass_rate(
verifications : Array[SafetyVerification],
) -> Double {
if verifications.is_empty() {
0.0
} else {
verifications
.fold(init=0, (count, verification) => {
if verification.passed {
count + 1
} else {
count
}
})
.to_double() /
verifications.length().to_double()
}
}
///|
pub fn safety_verification_average_confidence(
verifications : Array[SafetyVerification],
) -> Double {
if verifications.is_empty() {
0.0
} else {
mean(verifications.map(verification => verification.confidence))
}
}
///|
pub fn safety_owner_ids(hazards : Array[SafetyHazard]) -> Array[Int] {
let result = []
for hazard in hazards {
if !result.contains(hazard.owner) {
result.push(hazard.owner)
}
}
result.sort()
result
}
///|
pub fn safety_owner_risk(hazards : Array[SafetyHazard], owner : Int) -> Double {
hazards
.filter(hazard => hazard.owner == owner)
.fold(init=0.0, (total, hazard) => total + safety_hazard_score(hazard))
}
///|
pub fn safety_owner_risk_scores(hazards : Array[SafetyHazard]) -> Array[Double] {
safety_owner_ids(hazards).map(owner => safety_owner_risk(hazards, owner))
}
///|
pub fn safety_owner_rank(hazards : Array[SafetyHazard]) -> Array[Int] {
let ids = safety_owner_ids(hazards)
ids.sort_by((left, right) => {
let l = safety_owner_risk(hazards, left)
let r = safety_owner_risk(hazards, right)
if l > r {
-1
} else if l < r {
1
} else {
0
}
})
ids
}
///|
pub fn safety_case_priority(summary : SafetyCaseSummary) -> Double {
summary.residual_risk +
summary.actionable_hazards.to_double() +
summary.failed_barriers.to_double()
}
///|
pub fn safety_case_label(summary : SafetyCaseSummary) -> String {
if safety_case_is_accepted(summary) {
"accepted"
} else if safety_case_priority(summary) < 5.0 {
"review"
} else {
"escalate"
}
}
///|
pub fn safety_case_readiness(summary : SafetyCaseSummary) -> Double {
let hazard_score = if summary.hazard_count == 0 {
1.0
} else {
1.0 -
summary.actionable_hazards.to_double() / summary.hazard_count.to_double()
}
let barrier_score = if summary.barrier_count == 0 {
1.0
} else {
1.0 -
summary.failed_barriers.to_double() / summary.barrier_count.to_double()
}
let requirement_score = if summary.requirement_count == 0 {
1.0
} else {
summary.verified_requirements.to_double() /
summary.requirement_count.to_double()
}
(0.35 * hazard_score + 0.35 * barrier_score + 0.30 * requirement_score).min(
1.0,
)
}
///|
pub fn safety_case_readiness_label(summary : SafetyCaseSummary) -> String {
if safety_case_readiness(summary) >= 0.9 {
"ready"
} else if safety_case_readiness(summary) >= 0.7 {
"conditional"
} else {
"not-ready"
}
}