use serde::{Deserialize, Serialize};
use crate::analyses::hotspots::HotspotRow;
pub const MI_BAND_HIGH_PERCENTILE: f64 = 0.75;
pub const MI_BAND_MODERATE_PERCENTILE: f64 = 0.25;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MiBand {
Low,
Moderate,
High,
}
impl MiBand {
#[must_use]
pub fn from_rank(rank: f64) -> Self {
if rank >= MI_BAND_HIGH_PERCENTILE {
Self::High
} else if rank >= MI_BAND_MODERATE_PERCENTILE {
Self::Moderate
} else {
Self::Low
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Low => "low",
Self::Moderate => "moderate",
Self::High => "high",
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct MiRollup {
pub low: usize,
pub moderate: usize,
pub high: usize,
pub unknown: usize,
}
impl MiRollup {
#[must_use]
pub fn from_hotspots(rows: &[HotspotRow]) -> Self {
let mut r = Self::default();
for row in rows {
match (row.mi, row.mi_rank) {
(Some(_), Some(rank)) if rank.is_finite() => match MiBand::from_rank(rank) {
MiBand::Low => r.low += 1,
MiBand::Moderate => r.moderate += 1,
MiBand::High => r.high += 1,
},
_ => r.unknown += 1,
}
}
r
}
#[must_use]
pub fn known(self) -> usize {
self.low + self.moderate + self.high
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_rank_boundary_values() {
assert_eq!(MiBand::from_rank(0.0), MiBand::Low);
assert_eq!(MiBand::from_rank(0.24), MiBand::Low);
assert_eq!(MiBand::from_rank(0.25), MiBand::Moderate);
assert_eq!(MiBand::from_rank(0.50), MiBand::Moderate);
assert_eq!(MiBand::from_rank(0.74), MiBand::Moderate);
assert_eq!(MiBand::from_rank(0.75), MiBand::High);
assert_eq!(MiBand::from_rank(1.00), MiBand::High);
}
#[test]
fn rollup_counts_each_band_and_unknown() {
let make = |path: &str, mi: Option<f64>, rank: Option<f64>| HotspotRow {
path: path.to_owned(),
revisions: 1,
cognitive: 0.0,
cognitive_health: 100.0,
hotspot_score: 0.0,
mi,
mi_rank: rank,
ai_pct: None,
hotspot_score_anchored: None,
};
let rows = vec![
make("a.rs", Some(80.0), Some(0.90)), make("b.rs", Some(50.0), Some(0.50)), make("c.rs", Some(50.0), Some(0.30)), make("d.rs", Some(10.0), Some(0.10)), make("e.rs", None, None), make("f.rs", Some(0.0), None), ];
let r = MiRollup::from_hotspots(&rows);
assert_eq!(r.high, 1);
assert_eq!(r.moderate, 2);
assert_eq!(r.low, 1);
assert_eq!(r.unknown, 2);
assert_eq!(r.known(), 4);
}
#[test]
fn rollup_ignores_non_finite_ranks() {
let row = HotspotRow {
path: "x.rs".into(),
revisions: 1,
cognitive: 0.0,
cognitive_health: 100.0,
hotspot_score: 0.0,
mi: Some(50.0),
mi_rank: Some(f64::NAN),
ai_pct: None,
hotspot_score_anchored: None,
};
let r = MiRollup::from_hotspots(std::slice::from_ref(&row));
assert_eq!(r.unknown, 1);
assert_eq!(r.known(), 0);
}
}