pub const W_CRITICAL: u64 = 4;
pub const W_HIGH: u64 = 3;
pub const W_MEDIUM: u64 = 2;
pub const W_LOW: u64 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CriticalityBand {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Copy)]
pub struct ValidatedThresholdSet {
high_end: f64,
medium_end: f64,
}
impl ValidatedThresholdSet {
#[must_use]
pub fn new(high_end: f64, medium_end: f64) -> Self {
assert!(
high_end > 0.0 && high_end < 1.0,
"high_end must be in (0, 1), got {high_end}"
);
assert!(
medium_end > 0.0 && medium_end < 1.0,
"medium_end must be in (0, 1), got {medium_end}"
);
assert!(
high_end < medium_end,
"high_end ({high_end}) must be strictly less than medium_end ({medium_end})"
);
Self {
high_end,
medium_end,
}
}
#[must_use]
pub fn default_thirds() -> Self {
Self {
high_end: 1.0 / 3.0,
medium_end: 2.0 / 3.0,
}
}
}
#[must_use]
pub fn classify(float: u64, max_float: u64) -> CriticalityBand {
let thresholds = ValidatedThresholdSet::default_thirds();
classify_with(float, max_float, &thresholds)
}
#[must_use]
pub fn classify_with(
float: u64,
max_float: u64,
thresholds: &ValidatedThresholdSet,
) -> CriticalityBand {
if float == 0 || max_float == 0 {
return CriticalityBand::Critical;
}
let ratio = float as f64 / max_float as f64;
if ratio <= thresholds.high_end {
CriticalityBand::High
} else if ratio <= thresholds.medium_end {
CriticalityBand::Medium
} else {
CriticalityBand::Low
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActivityRiskResult {
Risk(f64),
AllCritical,
}
#[must_use]
pub fn activity_risk(floats: &[u64], max_float: u64) -> ActivityRiskResult {
if max_float == 0 || floats.is_empty() {
return ActivityRiskResult::AllCritical;
}
let sum: u64 = floats.iter().sum();
let denominator = max_float * floats.len() as u64;
ActivityRiskResult::Risk(1.0 - (sum as f64) / (denominator as f64))
}
#[must_use]
pub fn criticality_risk(bands: &[CriticalityBand]) -> f64 {
if bands.is_empty() {
return 1.0;
}
let sum: u64 = bands
.iter()
.map(|b| match b {
CriticalityBand::Critical => W_CRITICAL,
CriticalityBand::High => W_HIGH,
CriticalityBand::Medium => W_MEDIUM,
CriticalityBand::Low => W_LOW,
})
.sum();
let max_possible = W_CRITICAL * bands.len() as u64;
sum as f64 / max_possible as f64
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiskBandFlag {
InTarget,
HighRisk,
OverDecompressed,
}
#[must_use]
pub fn band_flag(risk: f64) -> RiskBandFlag {
if risk < 0.4 {
RiskBandFlag::OverDecompressed
} else if risk > 0.75 {
RiskBandFlag::HighRisk
} else {
RiskBandFlag::InTarget
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weights_are_exactly_4_3_2_1() {
assert_eq!(W_CRITICAL, 4, "W_CRITICAL must be exactly 4");
assert_eq!(W_HIGH, 3, "W_HIGH must be exactly 3");
assert_eq!(W_MEDIUM, 2, "W_MEDIUM must be exactly 2");
assert_eq!(W_LOW, 1, "W_LOW must be exactly 1");
}
#[test]
fn test_classify_zero_float_is_critical() {
assert_eq!(classify(0, 12), CriticalityBand::Critical);
}
#[test]
fn test_classify_high_band() {
assert_eq!(classify(3, 12), CriticalityBand::High);
assert_eq!(classify(4, 12), CriticalityBand::High);
}
#[test]
fn test_classify_medium_band() {
assert_eq!(classify(5, 12), CriticalityBand::Medium);
assert_eq!(classify(8, 12), CriticalityBand::Medium);
}
#[test]
fn test_classify_low_band() {
assert_eq!(classify(9, 12), CriticalityBand::Low);
assert_eq!(classify(12, 12), CriticalityBand::Low);
}
#[test]
fn test_classify_max_float_zero_returns_critical() {
assert_eq!(classify(5, 0), CriticalityBand::Critical);
}
#[test]
#[should_panic]
fn test_threshold_inverted_panics() {
let _ = ValidatedThresholdSet::new(0.7, 0.3);
}
#[test]
#[should_panic]
fn test_threshold_equal_panics() {
let _ = ValidatedThresholdSet::new(0.5, 0.5);
}
#[test]
fn test_activity_risk_all_critical_when_max_float_zero() {
let floats = [0u64, 0, 0];
assert_eq!(activity_risk(&floats, 0), ActivityRiskResult::AllCritical);
}
#[test]
fn test_activity_risk_all_zero_floats_with_positive_max() {
let floats = [0u64, 0, 0];
assert_eq!(activity_risk(&floats, 12), ActivityRiskResult::Risk(1.0));
}
#[test]
fn test_activity_risk_known_distribution() {
let floats = [0u64, 4, 8];
let max_float = 12u64;
let expected = 1.0 - 12.0_f64 / 36.0_f64;
match activity_risk(&floats, max_float) {
ActivityRiskResult::Risk(r) => {
assert!((r - expected).abs() < 1e-10, "expected {expected}, got {r}");
}
ActivityRiskResult::AllCritical => panic!("expected Risk, got AllCritical"),
}
}
#[test]
fn test_activity_risk_empty_returns_all_critical() {
assert_eq!(activity_risk(&[], 10), ActivityRiskResult::AllCritical);
}
#[test]
fn test_criticality_risk_all_critical_is_one() {
let bands = [
CriticalityBand::Critical,
CriticalityBand::Critical,
CriticalityBand::Critical,
];
let r = criticality_risk(&bands);
assert!(
(r - 1.0).abs() < 1e-10,
"all-critical criticality_risk must be 1.0, got {r}"
);
}
#[test]
fn test_criticality_risk_empty_is_one() {
let r = criticality_risk(&[]);
assert!(
(r - 1.0).abs() < 1e-10,
"empty criticality_risk must be 1.0, got {r}"
);
}
#[test]
fn test_criticality_risk_known_distribution() {
let bands = [
CriticalityBand::Critical,
CriticalityBand::High,
CriticalityBand::Medium,
CriticalityBand::Low,
];
let expected = 10.0_f64 / 16.0_f64;
let r = criticality_risk(&bands);
assert!((r - expected).abs() < 1e-10, "expected {expected}, got {r}");
}
#[test]
fn test_criticality_risk_all_low_is_one_quarter() {
let bands = [CriticalityBand::Low; 4];
let r = criticality_risk(&bands);
assert!(
(r - 0.25).abs() < 1e-10,
"all-low criticality_risk must be 0.25, got {r}"
);
}
#[test]
fn test_band_flag_below_0_4_is_over_decompressed() {
assert_eq!(band_flag(0.0), RiskBandFlag::OverDecompressed);
assert_eq!(band_flag(0.39), RiskBandFlag::OverDecompressed);
assert_eq!(band_flag(0.4), RiskBandFlag::InTarget);
}
#[test]
fn test_band_flag_in_target_range() {
assert_eq!(band_flag(0.4), RiskBandFlag::InTarget);
assert_eq!(band_flag(0.57), RiskBandFlag::InTarget);
assert_eq!(band_flag(0.75), RiskBandFlag::InTarget);
}
#[test]
fn test_band_flag_above_0_75_is_high_risk() {
assert_eq!(band_flag(0.751), RiskBandFlag::HighRisk);
assert_eq!(band_flag(1.0), RiskBandFlag::HighRisk);
assert_eq!(band_flag(0.75), RiskBandFlag::InTarget);
}
#[test]
fn test_all_critical_schedule_end_to_end() {
let floats = [0u64, 0, 0, 0];
let max_float = 0u64;
assert_eq!(
activity_risk(&floats, max_float),
ActivityRiskResult::AllCritical
);
for &f in &floats {
assert_eq!(classify(f, max_float), CriticalityBand::Critical);
}
let bands: Vec<_> = floats.iter().map(|&f| classify(f, max_float)).collect();
let cr = criticality_risk(&bands);
assert!(
(cr - 1.0).abs() < 1e-10,
"all-critical criticality_risk must be 1.0, got {cr}"
);
}
}