use std::collections::BTreeMap;
use std::path::Path;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::core::config::{assign_workspace, Config, WorkspaceOverlay};
use crate::core::error::{Error, Result};
use crate::core::severity::Severity;
pub const FLOOR_CCN: f64 = 25.0;
pub const FLOOR_COGNITIVE: f64 = 50.0;
pub const FLOOR_DUPLICATION_PCT: f64 = 30.0;
pub const FLOOR_OK_CCN: f64 = 11.0;
pub const FLOOR_OK_COGNITIVE: f64 = 8.0;
pub const FLOOR_OK_HOTSPOT: f64 = 22.0;
pub const FLOOR_OK_TEST_HOTSPOT: f64 = 25.0;
pub const FLOOR_OK_DOC_HOTSPOT: f64 = 5.0;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct MetricFloors {
pub critical: Option<f64>,
pub ok: Option<f64>,
}
pub const STRATEGY_PERCENTILE: &str = "percentile";
pub const MIN_SAMPLES_FOR_PERCENTILES: usize = 5;
const CALIBRATION_HEADER: &str = "\
# Generated by `heal calibrate` from this codebase's score distribution.
# Regenerate after the codebase shifts with `heal calibrate --force`
# (heal never recalibrates automatically).
# Hand edits are preserved on read but will be overwritten by --force;
# put `floor_critical` overrides in `config.toml` instead.
";
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Calibration {
pub meta: CalibrationMeta,
#[serde(default)]
pub calibration: MetricCalibrations,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub workspaces: BTreeMap<String, MetricCalibrations>,
}
impl Calibration {
#[must_use]
pub fn metrics_for_file(
&self,
file: &Path,
workspaces: &[WorkspaceOverlay],
) -> &MetricCalibrations {
self.metrics_for_workspace(assign_workspace(file, workspaces))
}
#[must_use]
pub fn metrics_for_workspace(&self, workspace: Option<&str>) -> &MetricCalibrations {
if let Some(ws) = workspace {
if let Some(table) = self.workspaces.get(ws) {
return table;
}
}
&self.calibration
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CalibrationMeta {
pub created_at: DateTime<Utc>,
pub codebase_files: u32,
pub strategy: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub calibrated_at_sha: Option<String>,
}
impl Default for CalibrationMeta {
fn default() -> Self {
Self {
created_at: DateTime::<Utc>::from_timestamp(0, 0).unwrap_or_default(),
codebase_files: 0,
strategy: STRATEGY_PERCENTILE.to_owned(),
calibrated_at_sha: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct MetricCalibrations {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ccn: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cognitive: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duplication: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub change_coupling: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hotspot: Option<HotspotCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lcom: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub coverage_pct: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skip_ratio: Option<MetricCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub test_hotspot: Option<HotspotCalibration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc_hotspot: Option<HotspotCalibration>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct MetricCalibration {
pub p50: f64,
pub p75: f64,
pub p90: f64,
pub p95: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub floor_critical: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub floor_ok: Option<f64>,
}
impl MetricCalibration {
#[must_use]
pub fn classify(&self, value: f64) -> Severity {
if let Some(floor) = self.floor_critical {
if value >= floor {
return Severity::Critical;
}
}
if let Some(floor) = self.floor_ok {
if value < floor {
return Severity::Ok;
}
}
if !self.has_meaningful_spread() {
return Severity::Ok;
}
if value >= self.p95 {
Severity::Critical
} else if value >= self.p90 {
Severity::High
} else if value >= self.p75 {
Severity::Medium
} else {
Severity::Ok
}
}
fn has_meaningful_spread(&self) -> bool {
let (Some(critical), Some(ok)) = (self.floor_critical, self.floor_ok) else {
return true;
};
if !self.p50.is_finite() || !self.p95.is_finite() {
return true;
}
let band = critical - ok;
if band <= 0.0 {
return true;
}
let spread_min = band / 2.0;
(self.p95 - self.p50) >= spread_min
}
#[must_use]
pub fn from_distribution(values: &[f64], floors: MetricFloors) -> Self {
let mut sorted: Vec<f64> = values.iter().copied().filter(|v| v.is_finite()).collect();
if sorted.len() < MIN_SAMPLES_FOR_PERCENTILES {
return Self {
p50: f64::NAN,
p75: f64::NAN,
p90: f64::NAN,
p95: f64::NAN,
floor_critical: floors.critical,
floor_ok: floors.ok,
};
}
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Self {
p50: percentile(&sorted, 50.0),
p75: percentile(&sorted, 75.0),
p90: percentile(&sorted, 90.0),
p95: percentile(&sorted, 95.0),
floor_critical: floors.critical,
floor_ok: floors.ok,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct HotspotCalibration {
pub p50: f64,
pub p75: f64,
pub p90: f64,
pub p95: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub floor_ok: Option<f64>,
}
impl HotspotCalibration {
#[must_use]
pub fn flag(&self, score: f64) -> bool {
if let Some(floor) = self.floor_ok {
if score < floor {
return false;
}
}
score >= self.p90
}
#[must_use]
pub fn from_distribution(scores: &[f64]) -> Self {
Self::from_distribution_with_floor(scores, Some(FLOOR_OK_HOTSPOT))
}
#[must_use]
pub fn from_distribution_with_floor(scores: &[f64], floor_ok: Option<f64>) -> Self {
if scores.len() < MIN_SAMPLES_FOR_PERCENTILES {
return Self {
p50: f64::NAN,
p75: f64::NAN,
p90: f64::NAN,
p95: f64::NAN,
floor_ok,
};
}
let mut sorted: Vec<f64> = scores.iter().copied().filter(|v| v.is_finite()).collect();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Self {
p50: percentile(&sorted, 50.0),
p75: percentile(&sorted, 75.0),
p90: percentile(&sorted, 90.0),
p95: percentile(&sorted, 95.0),
floor_ok,
}
}
}
impl Calibration {
pub fn load(path: &Path) -> Result<Self> {
let raw = std::fs::read_to_string(path).map_err(|e| Error::Io {
path: path.to_path_buf(),
source: e,
})?;
toml::from_str(&raw).map_err(|source| Error::ConfigParse {
path: path.to_path_buf(),
source,
})
}
pub fn save(&self, path: &Path) -> Result<()> {
let body = toml::to_string_pretty(self).expect("Calibration serialization is infallible");
let mut out = String::with_capacity(body.len() + CALIBRATION_HEADER.len());
out.push_str(CALIBRATION_HEADER);
out.push_str(&body);
crate::core::fs::atomic_write(path, out.as_bytes())
}
#[must_use]
pub fn with_overrides(mut self, config: &Config) -> Self {
apply_metric_overrides(&mut self.calibration, config);
for (ws_path, table) in &mut self.workspaces {
apply_metric_overrides(table, config);
if let Some(overlay) = config
.project
.workspaces
.iter()
.find(|w| w.path.trim_end_matches('/') == ws_path.as_str())
{
apply_workspace_metric_overrides(table, &overlay.metrics);
}
}
self
}
}
fn apply_workspace_metric_overrides(
table: &mut MetricCalibrations,
overrides: &crate::core::config::WorkspaceMetricsOverlay,
) {
if let Some(c) = table.ccn.as_mut() {
if let Some(f) = overrides.ccn.floor_critical {
c.floor_critical = Some(f);
}
if let Some(f) = overrides.ccn.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.cognitive.as_mut() {
if let Some(f) = overrides.cognitive.floor_critical {
c.floor_critical = Some(f);
}
if let Some(f) = overrides.cognitive.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.duplication.as_mut() {
if let Some(f) = overrides.duplication.floor_critical {
c.floor_critical = Some(f);
}
}
if let Some(c) = table.change_coupling.as_mut() {
if let Some(f) = overrides.change_coupling.floor_critical {
c.floor_critical = Some(f);
}
}
if let Some(c) = table.lcom.as_mut() {
if let Some(f) = overrides.lcom.floor_critical {
c.floor_critical = Some(f);
}
}
}
fn apply_metric_overrides(table: &mut MetricCalibrations, config: &Config) {
if let Some(c) = table.ccn.as_mut() {
if let Some(f) = config.metrics.ccn.floor_critical {
c.floor_critical = Some(f);
}
if let Some(f) = config.metrics.ccn.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.cognitive.as_mut() {
if let Some(f) = config.metrics.cognitive.floor_critical {
c.floor_critical = Some(f);
}
if let Some(f) = config.metrics.cognitive.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.duplication.as_mut() {
if let Some(f) = config.metrics.duplication.floor_critical {
c.floor_critical = Some(f);
}
}
if let Some(c) = table.change_coupling.as_mut() {
if let Some(f) = config.metrics.change_coupling.floor_critical {
c.floor_critical = Some(f);
}
}
if let Some(c) = table.lcom.as_mut() {
if let Some(f) = config.metrics.lcom.floor_critical {
c.floor_critical = Some(f);
}
}
if let Some(c) = table.hotspot.as_mut() {
if let Some(f) = config.metrics.hotspot.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.test_hotspot.as_mut() {
if let Some(f) = config.features.test.hotspot.floor_ok {
c.floor_ok = Some(f);
}
}
if let Some(c) = table.doc_hotspot.as_mut() {
if let Some(f) = config.features.docs.hotspot.floor_ok {
c.floor_ok = Some(f);
}
}
}
fn percentile(sorted: &[f64], p: f64) -> f64 {
let n = sorted.len();
if n == 0 {
return 0.0;
}
if n == 1 {
return sorted[0];
}
#[allow(clippy::cast_precision_loss)]
let rank = (p / 100.0) * (n as f64 - 1.0);
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let lo = rank.floor() as usize;
let frac = rank - rank.floor();
if lo + 1 >= n {
return sorted[n - 1];
}
sorted[lo] + frac * (sorted[lo + 1] - sorted[lo])
}
#[cfg(test)]
mod tests {
use super::*;
fn cal(p50: f64, p75: f64, p90: f64, p95: f64, floor: Option<f64>) -> MetricCalibration {
MetricCalibration {
p50,
p75,
p90,
p95,
floor_critical: floor,
floor_ok: None,
}
}
#[test]
fn classify_uses_floor_first() {
let c = cal(1.0, 2.0, 3.0, 4.0, Some(10.0));
assert_eq!(c.classify(11.0), Severity::Critical);
assert_eq!(c.classify(5.0), Severity::Critical);
}
#[test]
fn classify_floor_ok_forces_ok_below_threshold() {
let c = MetricCalibration {
p50: 1.0,
p75: 2.0,
p90: 3.0,
p95: 4.0,
floor_critical: None,
floor_ok: Some(11.0),
};
assert_eq!(c.classify(5.0), Severity::Ok);
assert_eq!(c.classify(10.99), Severity::Ok);
assert_eq!(c.classify(11.0), Severity::Critical);
}
#[test]
fn classify_floor_critical_overrides_floor_ok() {
let c = MetricCalibration {
p50: 1.0,
p75: 2.0,
p90: 3.0,
p95: 4.0,
floor_critical: Some(5.0),
floor_ok: Some(11.0),
};
assert_eq!(c.classify(6.0), Severity::Critical);
}
#[test]
fn with_overrides_applies_floor_ok_from_config() {
let cal = Calibration {
meta: CalibrationMeta::default(),
calibration: MetricCalibrations {
ccn: Some(MetricCalibration::from_distribution(
&[1.0, 2.0, 3.0, 4.0, 5.0],
MetricFloors {
critical: Some(FLOOR_CCN),
ok: Some(FLOOR_OK_CCN),
},
)),
..MetricCalibrations::default()
},
workspaces: BTreeMap::new(),
};
let mut config = Config::default();
config.metrics.ccn.floor_ok = Some(15.0);
config.metrics.ccn.floor_critical = Some(40.0);
let merged = cal.with_overrides(&config);
let ccn = merged.calibration.ccn.unwrap();
assert_eq!(ccn.floor_ok, Some(15.0));
assert_eq!(ccn.floor_critical, Some(40.0));
}
#[test]
fn with_overrides_applies_workspace_metric_overlay() {
let mut workspaces = BTreeMap::new();
let make_table = || MetricCalibrations {
ccn: Some(MetricCalibration::from_distribution(
&[1.0, 2.0, 3.0, 4.0, 5.0],
MetricFloors {
critical: Some(FLOOR_CCN),
ok: Some(FLOOR_OK_CCN),
},
)),
..MetricCalibrations::default()
};
workspaces.insert("packages/web".to_owned(), make_table());
workspaces.insert("packages/api".to_owned(), make_table());
let cal = Calibration {
meta: CalibrationMeta::default(),
calibration: make_table(),
workspaces,
};
let cfg = Config::from_toml_str(
r#"
[metrics.ccn]
floor_critical = 25
[[project.workspaces]]
path = "packages/web"
[project.workspaces.metrics.ccn]
floor_critical = 40
[[project.workspaces]]
path = "packages/api"
"#,
)
.unwrap();
let merged = cal.with_overrides(&cfg);
assert_eq!(
merged.calibration.ccn.as_ref().unwrap().floor_critical,
Some(25.0),
);
assert_eq!(
merged.workspaces["packages/api"]
.ccn
.as_ref()
.unwrap()
.floor_critical,
Some(25.0),
);
assert_eq!(
merged.workspaces["packages/web"]
.ccn
.as_ref()
.unwrap()
.floor_critical,
Some(40.0),
);
}
#[test]
fn classify_spread_gate_graduates_tight_distribution() {
let c = MetricCalibration {
p50: 12.0,
p75: 13.0,
p90: 13.5,
p95: 14.0,
floor_critical: Some(25.0),
floor_ok: Some(11.0),
};
assert_eq!(c.classify(14.0), Severity::Ok);
assert_eq!(c.classify(10.0), Severity::Ok);
assert_eq!(c.classify(30.0), Severity::Critical);
}
#[test]
fn classify_spread_gate_keeps_wide_distribution() {
let c = MetricCalibration {
p50: 4.0,
p75: 12.0,
p90: 18.0,
p95: 22.0,
floor_critical: Some(25.0),
floor_ok: Some(11.0),
};
assert_eq!(c.classify(22.0), Severity::Critical);
assert_eq!(c.classify(18.0), Severity::High);
assert_eq!(c.classify(12.0), Severity::Medium);
}
#[test]
fn classify_spread_gate_silent_without_both_floors() {
let c = MetricCalibration {
p50: 12.0,
p75: 13.0,
p90: 13.5,
p95: 14.0,
floor_critical: Some(25.0),
floor_ok: None,
};
assert_eq!(c.classify(14.0), Severity::Critical);
}
#[test]
fn classify_floor_ok_works_with_nan_percentiles() {
let c = MetricCalibration::from_distribution(
&[1.0, 2.0],
MetricFloors {
critical: Some(25.0),
ok: Some(11.0),
},
);
assert!(c.p95.is_nan());
assert_eq!(c.classify(5.0), Severity::Ok);
assert_eq!(c.classify(30.0), Severity::Critical);
}
#[test]
fn classify_breaks_match_todo_ladder() {
let c = cal(1.0, 2.0, 3.0, 4.0, None);
assert_eq!(c.classify(0.5), Severity::Ok);
assert_eq!(c.classify(2.0), Severity::Medium);
assert_eq!(c.classify(3.0), Severity::High);
assert_eq!(c.classify(4.0), Severity::Critical);
}
#[test]
fn classify_inclusive_at_breaks() {
let c = cal(1.0, 2.0, 3.0, 4.0, None);
assert_eq!(c.classify(2.0), Severity::Medium);
assert_eq!(c.classify(3.0), Severity::High);
assert_eq!(c.classify(4.0), Severity::Critical);
}
#[test]
fn percentile_linear_interpolation() {
let sorted = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((percentile(&sorted, 50.0) - 3.0).abs() < 1e-9);
assert!((percentile(&sorted, 75.0) - 4.0).abs() < 1e-9);
assert!((percentile(&sorted, 25.0) - 2.0).abs() < 1e-9);
}
#[test]
fn percentile_handles_edges() {
assert!((percentile(&[], 50.0) - 0.0).abs() < 1e-9);
assert!((percentile(&[7.0], 95.0) - 7.0).abs() < 1e-9);
}
#[test]
fn from_distribution_marks_breaks_nan_below_min_samples() {
let c = MetricCalibration::from_distribution(
&[1.0, 2.0],
MetricFloors {
critical: Some(25.0),
ok: None,
},
);
assert!(c.p50.is_nan());
assert!(c.p95.is_nan());
assert_eq!(c.floor_critical, Some(25.0));
assert_eq!(c.classify(30.0), Severity::Critical);
assert_eq!(c.classify(5.0), Severity::Ok);
}
#[test]
fn from_distribution_drops_non_finite() {
let values = vec![1.0, 2.0, f64::NAN, 3.0, f64::INFINITY, 4.0, 5.0];
let c = MetricCalibration::from_distribution(&values, MetricFloors::default());
assert!((c.p50 - 3.0).abs() < 1e-9);
}
#[test]
fn hotspot_floor_ok_blocks_top_decile_in_cold_codebase() {
let h = HotspotCalibration {
p50: 3.0,
p75: 8.0,
p90: 15.0,
p95: 18.0,
floor_ok: Some(FLOOR_OK_HOTSPOT),
};
assert!(!h.flag(15.0));
assert!(!h.flag(20.0));
assert!(h.flag(25.0));
}
#[test]
fn hotspot_floor_ok_disabled_falls_back_to_percentile() {
let h = HotspotCalibration {
p50: 3.0,
p75: 8.0,
p90: 15.0,
p95: 18.0,
floor_ok: None,
};
assert!(h.flag(15.0));
assert!(!h.flag(14.0));
}
#[test]
fn hotspot_flag_at_p90() {
let h = HotspotCalibration {
p50: 5.0,
p75: 18.0,
p90: 67.0,
p95: 145.0,
floor_ok: Some(FLOOR_OK_HOTSPOT),
};
assert!(!h.flag(50.0));
assert!(h.flag(67.0));
assert!(h.flag(200.0));
}
#[test]
fn toml_roundtrip_with_deny_unknown_fields() {
let cal = Calibration {
meta: CalibrationMeta {
created_at: DateTime::<Utc>::from_timestamp(1_700_000_000, 0).unwrap(),
codebase_files: 142,
strategy: STRATEGY_PERCENTILE.to_owned(),
calibrated_at_sha: Some("abcd1234".into()),
},
calibration: MetricCalibrations {
ccn: Some(MetricCalibration {
p50: 4.2,
p75: 8.1,
p90: 14.3,
p95: 21.7,
floor_critical: Some(FLOOR_CCN),
floor_ok: Some(FLOOR_OK_CCN),
}),
hotspot: Some(HotspotCalibration {
p50: 5.0,
p75: 18.0,
p90: 67.0,
p95: 145.0,
floor_ok: Some(FLOOR_OK_HOTSPOT),
}),
..MetricCalibrations::default()
},
workspaces: BTreeMap::new(),
};
let s = toml::to_string_pretty(&cal).unwrap();
assert!(s.contains("created_at"));
assert!(s.contains("[calibration.ccn]"));
assert!(s.contains("[calibration.hotspot]"));
let back: Calibration = toml::from_str(&s).unwrap();
assert_eq!(back, cal);
}
#[test]
fn save_prepends_provenance_header_and_round_trips() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("calibration.toml");
let cal = Calibration {
meta: CalibrationMeta {
created_at: DateTime::<Utc>::from_timestamp(1_700_000_000, 0).unwrap(),
codebase_files: 7,
strategy: STRATEGY_PERCENTILE.to_owned(),
calibrated_at_sha: None,
},
calibration: MetricCalibrations::default(),
workspaces: BTreeMap::new(),
};
cal.save(&path).unwrap();
let raw = std::fs::read_to_string(&path).unwrap();
assert!(raw.starts_with("# Generated by `heal calibrate`"));
assert!(raw.contains("heal calibrate --force"));
let back = Calibration::load(&path).unwrap();
assert_eq!(back, cal);
}
#[test]
fn nan_breaks_round_trip_through_toml() {
let cal = Calibration {
meta: CalibrationMeta::default(),
calibration: MetricCalibrations {
ccn: Some(MetricCalibration::from_distribution(
&[1.0, 2.0],
MetricFloors {
critical: Some(FLOOR_CCN),
ok: Some(FLOOR_OK_CCN),
},
)),
..MetricCalibrations::default()
},
workspaces: BTreeMap::new(),
};
let s = toml::to_string_pretty(&cal).unwrap();
let back: Calibration = toml::from_str(&s).unwrap();
let breaks = back.calibration.ccn.as_ref().unwrap();
assert!(breaks.p50.is_nan());
assert!(breaks.p95.is_nan());
assert_eq!(breaks.floor_critical, Some(FLOOR_CCN));
assert_eq!(breaks.classify(30.0), Severity::Critical);
assert_eq!(breaks.classify(5.0), Severity::Ok);
}
#[test]
fn unknown_fields_are_rejected() {
let bad = r#"
[meta]
created_at = "2026-04-28T09:00:00Z"
codebase_files = 1
strategy = "percentile"
[calibration.ccn]
p50 = 1.0
p75 = 2.0
p90 = 3.0
p95 = 4.0
unknown = 99
"#;
let err = toml::from_str::<Calibration>(bad).unwrap_err();
assert!(err.to_string().contains("unknown"));
}
}