///|
/// Structured hazard identification and risk-ranking helpers.
pub enum RiskLikelihood {
Rare
Unlikely
Possible
Likely
Frequent
}
///|
pub fn rare() -> RiskLikelihood {
Rare
}
///|
pub fn unlikely() -> RiskLikelihood {
Unlikely
}
///|
pub fn possible() -> RiskLikelihood {
Possible
}
///|
pub fn likely() -> RiskLikelihood {
Likely
}
///|
pub fn frequent() -> RiskLikelihood {
Frequent
}
///|
pub enum RiskSeverity {
Negligible
Minor
Serious
Major
Catastrophic
}
///|
pub fn negligible() -> RiskSeverity {
Negligible
}
///|
pub fn minor() -> RiskSeverity {
Minor
}
///|
pub fn serious() -> RiskSeverity {
Serious
}
///|
pub fn major() -> RiskSeverity {
Major
}
///|
pub fn catastrophic() -> RiskSeverity {
Catastrophic
}
///|
fn likelihood_score(value : RiskLikelihood) -> Int {
match value {
Rare => 1
Unlikely => 2
Possible => 3
Likely => 4
Frequent => 5
}
}
///|
fn severity_score(value : RiskSeverity) -> Int {
match value {
Negligible => 1
Minor => 2
Serious => 3
Major => 4
Catastrophic => 5
}
}
///|
pub struct Hazard {
id : String
node : String
deviation : String
cause : String
consequence : String
likelihood : RiskLikelihood
severity : RiskSeverity
safeguards : Array[String]
}
///|
pub fn hazard(
id : String,
node : String,
deviation : String,
cause : String,
consequence : String,
likelihood : RiskLikelihood,
severity : RiskSeverity,
safeguards : Array[String],
) -> Hazard {
{ id, node, deviation, cause, consequence, likelihood, severity, safeguards }
}
///|
pub fn Hazard::score(self : Hazard) -> Int {
likelihood_score(self.likelihood) * severity_score(self.severity)
}
///|
pub fn Hazard::level(self : Hazard) -> String {
if self.score() >= 15 {
"high"
} else if self.score() >= 8 {
"medium"
} else {
"low"
}
}
///|
pub fn Hazard::adequately_safeguarded(self : Hazard) -> Bool {
self.safeguards.length() > 0
}
///|
pub struct RiskRegister {
hazards : Array[Hazard]
}
///|
pub fn risk_register(hazards : Array[Hazard]) -> RiskRegister {
{ hazards, }
}
///|
pub fn RiskRegister::high(self : RiskRegister) -> Array[Hazard] {
self.hazards.filter(fn(h) { h.level() == "high" })
}
///|
pub fn RiskRegister::score(self : RiskRegister) -> Int {
self.hazards.fold(init=0, fn(total, h) { total + h.score() })
}
///|
pub fn RiskRegister::uncontrolled(self : RiskRegister) -> Array[Hazard] {
self.hazards.filter(fn(h) { !h.adequately_safeguarded() })
}
///|
pub fn RiskRegister::acceptable(self : RiskRegister, max_high : Int) -> Bool {
self.high().length() <= max_high && self.uncontrolled().length() == 0
}
///|
pub struct Safeguard {
name : String
kind : String
independent : Bool
test_interval_hours : Float
enabled : Bool
}
///|
pub fn safeguard(
name : String,
kind : String,
independent : Bool,
test_interval_hours : Float,
enabled : Bool,
) -> Safeguard {
{ name, kind, independent, test_interval_hours, enabled }
}
///|
pub fn Safeguard::valid(self : Safeguard) -> Bool {
self.name != "" && self.test_interval_hours > 0.0
}
///|
pub fn Safeguard::reliable(self : Safeguard) -> Bool {
self.valid() && self.enabled && self.independent
}
///|
pub struct BowTie {
hazard : String
threats : Array[String]
preventive : Array[Safeguard]
mitigative : Array[Safeguard]
}
///|
pub fn bow_tie(
hazard : String,
threats : Array[String],
preventive : Array[Safeguard],
mitigative : Array[Safeguard],
) -> BowTie {
{ hazard, threats, preventive, mitigative }
}
///|
pub fn BowTie::prevention_count(self : BowTie) -> Int {
self.preventive.filter(fn(s) { s.reliable() }).length()
}
///|
pub fn BowTie::mitigation_count(self : BowTie) -> Int {
self.mitigative.filter(fn(s) { s.reliable() }).length()
}
///|
pub fn BowTie::defended(self : BowTie) -> Bool {
self.prevention_count() > 0 && self.mitigation_count() > 0
}
///|
pub fn risk_register_table(register : RiskRegister) -> ReportTable {
let rows : Array[Array[String]] = []
for h in register.hazards {
rows.push([
h.id,
h.node,
h.deviation,
h.cause,
h.consequence,
"\{h.score()}",
h.level(),
"\{h.safeguards.length()}",
])
}
table(
[
"id", "node", "deviation", "cause", "consequence", "score", "level", "safeguards",
],
rows,
)
}
///|
pub fn risk_priority_order(hazards : Array[Hazard]) -> Array[Hazard] {
let result = hazards.copy()
result.sort_by(fn(a, b) { b.score() - a.score() })
result
}
///|
pub fn risk_reduction(before : Hazard, after : Hazard) -> Int {
before.score() - after.score()
}
///|
pub fn risk_summary(register : RiskRegister) -> String {
"hazards=\{register.hazards.length()},high=\{register.high().length()},uncontrolled=\{register.uncontrolled().length()}"
}