use crate::metrics::RawMetrics;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct RiskComponents {
pub r_cc: f64,
pub r_nd: f64,
pub r_fo: f64,
pub r_ns: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RiskBand {
Low, Moderate, High, Critical, }
impl RiskBand {
pub fn as_str(&self) -> &'static str {
match self {
RiskBand::Low => "low",
RiskBand::Moderate => "moderate",
RiskBand::High => "high",
RiskBand::Critical => "critical",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"low" => Some(RiskBand::Low),
"moderate" => Some(RiskBand::Moderate),
"high" => Some(RiskBand::High),
"critical" => Some(RiskBand::Critical),
_ => None,
}
}
}
impl std::fmt::Display for RiskBand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub fn calculate_risk_components(metrics: &RawMetrics) -> RiskComponents {
RiskComponents {
r_cc: (metrics.cc as f64 + 1.0).log2().min(6.0),
r_nd: (metrics.nd as f64).min(8.0),
r_fo: (metrics.fo as f64 + 1.0).log2().min(6.0),
r_ns: (metrics.ns as f64).min(6.0),
}
}
#[derive(Debug, Clone, Copy)]
pub struct LrsWeights {
pub cc: f64,
pub nd: f64,
pub fo: f64,
pub ns: f64,
}
impl Default for LrsWeights {
fn default() -> Self {
LrsWeights {
cc: 1.0,
nd: 0.8,
fo: 0.6,
ns: 0.7,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RiskThresholds {
pub moderate: f64,
pub high: f64,
pub critical: f64,
}
impl Default for RiskThresholds {
fn default() -> Self {
RiskThresholds {
moderate: 3.0,
high: 6.0,
critical: 9.0,
}
}
}
pub fn calculate_lrs(risk: &RiskComponents) -> f64 {
calculate_lrs_with_weights(risk, &LrsWeights::default())
}
pub fn calculate_lrs_with_weights(risk: &RiskComponents, weights: &LrsWeights) -> f64 {
weights.cc * risk.r_cc
+ weights.nd * risk.r_nd
+ weights.fo * risk.r_fo
+ weights.ns * risk.r_ns
}
pub fn assign_risk_band(lrs: f64) -> RiskBand {
assign_risk_band_with_thresholds(lrs, &RiskThresholds::default())
}
pub fn assign_risk_band_with_thresholds(lrs: f64, thresholds: &RiskThresholds) -> RiskBand {
if lrs < thresholds.moderate {
RiskBand::Low
} else if lrs < thresholds.high {
RiskBand::Moderate
} else if lrs < thresholds.critical {
RiskBand::High
} else {
RiskBand::Critical
}
}
pub fn analyze_risk(metrics: &RawMetrics) -> (RiskComponents, f64, RiskBand) {
let risk = calculate_risk_components(metrics);
let lrs = calculate_lrs(&risk);
let band = assign_risk_band(lrs);
(risk, lrs, band)
}
pub fn analyze_risk_with_config(
metrics: &RawMetrics,
weights: &LrsWeights,
thresholds: &RiskThresholds,
) -> (RiskComponents, f64, RiskBand) {
let risk = calculate_risk_components(metrics);
let lrs = calculate_lrs_with_weights(&risk, weights);
let band = assign_risk_band_with_thresholds(lrs, thresholds);
(risk, lrs, band)
}