1use crate::metrics::RawMetrics;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone)]
12pub struct RiskComponents {
13 pub r_cc: f64,
14 pub r_nd: f64,
15 pub r_fo: f64,
16 pub r_ns: f64,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum RiskBand {
23 Low, Moderate, High, Critical, }
28
29impl RiskBand {
30 pub fn as_str(&self) -> &'static str {
31 match self {
32 RiskBand::Low => "low",
33 RiskBand::Moderate => "moderate",
34 RiskBand::High => "high",
35 RiskBand::Critical => "critical",
36 }
37 }
38
39 pub fn parse(s: &str) -> Option<Self> {
40 match s {
41 "low" => Some(RiskBand::Low),
42 "moderate" => Some(RiskBand::Moderate),
43 "high" => Some(RiskBand::High),
44 "critical" => Some(RiskBand::Critical),
45 _ => None,
46 }
47 }
48}
49
50impl std::fmt::Display for RiskBand {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.write_str(self.as_str())
53 }
54}
55
56pub fn calculate_risk_components(metrics: &RawMetrics) -> RiskComponents {
64 RiskComponents {
65 r_cc: (metrics.cc as f64 + 1.0).log2().min(6.0),
66 r_nd: (metrics.nd as f64).min(8.0),
67 r_fo: (metrics.fo as f64 + 1.0).log2().min(6.0),
68 r_ns: (metrics.ns as f64).min(6.0),
69 }
70}
71
72#[derive(Debug, Clone, Copy)]
74pub struct LrsWeights {
75 pub cc: f64,
76 pub nd: f64,
77 pub fo: f64,
78 pub ns: f64,
79}
80
81impl Default for LrsWeights {
82 fn default() -> Self {
83 LrsWeights {
84 cc: 1.0,
85 nd: 0.8,
86 fo: 0.6,
87 ns: 0.7,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy)]
94pub struct RiskThresholds {
95 pub moderate: f64,
96 pub high: f64,
97 pub critical: f64,
98}
99
100impl Default for RiskThresholds {
101 fn default() -> Self {
102 RiskThresholds {
103 moderate: 3.0,
104 high: 6.0,
105 critical: 9.0,
106 }
107 }
108}
109
110pub fn calculate_lrs(risk: &RiskComponents) -> f64 {
115 calculate_lrs_with_weights(risk, &LrsWeights::default())
116}
117
118pub fn calculate_lrs_with_weights(risk: &RiskComponents, weights: &LrsWeights) -> f64 {
120 weights.cc * risk.r_cc
121 + weights.nd * risk.r_nd
122 + weights.fo * risk.r_fo
123 + weights.ns * risk.r_ns
124}
125
126pub fn assign_risk_band(lrs: f64) -> RiskBand {
128 assign_risk_band_with_thresholds(lrs, &RiskThresholds::default())
129}
130
131pub fn assign_risk_band_with_thresholds(lrs: f64, thresholds: &RiskThresholds) -> RiskBand {
133 if lrs < thresholds.moderate {
134 RiskBand::Low
135 } else if lrs < thresholds.high {
136 RiskBand::Moderate
137 } else if lrs < thresholds.critical {
138 RiskBand::High
139 } else {
140 RiskBand::Critical
141 }
142}
143
144pub fn analyze_risk(metrics: &RawMetrics) -> (RiskComponents, f64, RiskBand) {
146 let risk = calculate_risk_components(metrics);
147 let lrs = calculate_lrs(&risk);
148 let band = assign_risk_band(lrs);
149 (risk, lrs, band)
150}
151
152pub fn analyze_risk_with_config(
154 metrics: &RawMetrics,
155 weights: &LrsWeights,
156 thresholds: &RiskThresholds,
157) -> (RiskComponents, f64, RiskBand) {
158 let risk = calculate_risk_components(metrics);
159 let lrs = calculate_lrs_with_weights(&risk, weights);
160 let band = assign_risk_band_with_thresholds(lrs, thresholds);
161 (risk, lrs, band)
162}