use crate::metrics::MetricKind;
pub struct ConfidenceCalibrator {
last_confidence: f32,
smoothing: f32,
}
impl ConfidenceCalibrator {
#[must_use]
pub const fn new() -> Self {
Self {
last_confidence: 0.5,
smoothing: 0.2,
}
}
#[must_use]
pub const fn with_smoothing(smoothing: f32) -> Self {
Self {
last_confidence: 0.5,
smoothing: smoothing.clamp(0.0, 1.0),
}
}
pub fn update(&mut self, metrics: &[(MetricKind, f32)], forecast_accuracy: f32) {
let raw = Self::compute_raw(metrics, forecast_accuracy);
self.last_confidence = self
.smoothing
.mul_add(raw, (1.0 - self.smoothing) * self.last_confidence);
}
#[must_use]
pub const fn confidence(&self) -> f32 {
self.last_confidence.clamp(0.0, 1.0)
}
fn compute_raw(metrics: &[(MetricKind, f32)], forecast_accuracy: f32) -> f32 {
let mut weighted_sum = 0.0_f32;
let mut total_weight = 0.0_f32;
for &(kind, value) in metrics {
let (score, weight) = Self::metric_score(kind, value);
weighted_sum = score.mul_add(weight, weighted_sum);
total_weight += weight;
}
weighted_sum = forecast_accuracy.mul_add(0.2, weighted_sum);
total_weight += 0.2;
if total_weight < f32::EPSILON {
return 0.5;
}
(weighted_sum / total_weight).clamp(0.0, 1.0)
}
fn metric_score(kind: MetricKind, value: f32) -> (f32, f32) {
match kind {
MetricKind::ErrorRate => {
let score = (1.0 - value / 0.3).clamp(0.0, 1.0);
(score, 0.30)
}
MetricKind::Coherence => {
(value.clamp(0.0, 1.0), 0.20)
}
MetricKind::CpuLoad => {
(1.0 - value.clamp(0.0, 1.0), 0.15)
}
MetricKind::MemoryPressure => (1.0 - value.clamp(0.0, 1.0), 0.15),
MetricKind::Latency => {
let score = (1.0 - value / 50.0).clamp(0.0, 1.0);
(score, 0.10)
}
MetricKind::SwapUsage => (1.0 - value.clamp(0.0, 1.0), 0.05),
MetricKind::DiskIo => (1.0 - value.clamp(0.0, 1.0), 0.05),
MetricKind::ImaginationQuality => (value.clamp(0.0, 1.0), 0.10),
MetricKind::ResearchOutput => (value.clamp(0.0, 1.0), 0.08),
MetricKind::ScenarioConfidence => (value.clamp(0.0, 1.0), 0.10),
MetricKind::SimulationVariance => (1.0 - value.clamp(0.0, 1.0), 0.05),
MetricKind::ConformalCoverage => (value.clamp(0.0, 1.0), 0.05),
MetricKind::BrierScore => (1.0 - value.clamp(0.0, 1.0), 0.05),
}
}
#[must_use]
pub fn is_conservative(&self) -> bool {
self.last_confidence < 0.5
}
#[must_use]
pub const fn state(&self) -> (f32, f32) {
(self.last_confidence, self.smoothing)
}
pub const fn restore_state(&mut self, last_confidence: f32, smoothing: f32) {
self.last_confidence = last_confidence.clamp(0.0, 1.0);
self.smoothing = smoothing.clamp(0.0, 1.0);
}
}
impl Default for ConfidenceCalibrator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calibrator_default_confidence() {
let cal = ConfidenceCalibrator::new();
assert_eq!(cal.confidence(), 0.5);
assert!(!cal.is_conservative());
}
#[test]
fn calibrator_perfect_metrics() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
let metrics = vec![
(MetricKind::ErrorRate, 0.0),
(MetricKind::Coherence, 1.0),
(MetricKind::CpuLoad, 0.0),
(MetricKind::MemoryPressure, 0.0),
(MetricKind::Latency, 0.0),
];
cal.update(&metrics, 1.0);
assert!(cal.confidence() > 0.7);
assert!(!cal.is_conservative());
}
#[test]
fn calibrator_terrible_metrics() {
let mut cal = ConfidenceCalibrator::new();
let metrics = vec![
(MetricKind::ErrorRate, 0.5),
(MetricKind::Coherence, 0.1),
(MetricKind::CpuLoad, 0.95),
(MetricKind::MemoryPressure, 0.9),
(MetricKind::Latency, 60.0),
];
cal.update(&metrics, 0.1);
assert!(cal.confidence() < 0.5);
assert!(cal.is_conservative());
}
#[test]
fn calibrator_smoothing_prevents_jumps() {
let mut cal = ConfidenceCalibrator::with_smoothing(0.2);
let good = vec![
(MetricKind::ErrorRate, 0.0),
(MetricKind::Coherence, 1.0),
(MetricKind::CpuLoad, 0.0),
];
cal.update(&good, 1.0);
let after_one = cal.confidence();
assert!(after_one > 0.5 && after_one < 0.95);
cal.update(&good, 1.0);
let after_two = cal.confidence();
assert!(after_two > after_one);
}
#[test]
fn calibrator_no_smoothing() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
let metrics = vec![
(MetricKind::ErrorRate, 0.0),
(MetricKind::Coherence, 1.0),
(MetricKind::CpuLoad, 0.0),
];
cal.update(&metrics, 1.0);
assert!(cal.confidence() > 0.8);
}
#[test]
fn calibrator_empty_metrics() {
let mut cal = ConfidenceCalibrator::new();
cal.update(&[], 0.5);
assert!((cal.confidence() - 0.5).abs() < 0.1);
}
#[test]
fn calibrator_error_rate_dominates() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
let metrics = vec![
(MetricKind::ErrorRate, 0.5),
(MetricKind::Coherence, 1.0),
(MetricKind::CpuLoad, 0.0),
(MetricKind::MemoryPressure, 0.0),
(MetricKind::Latency, 0.0),
];
cal.update(&metrics, 1.0);
assert!(cal.confidence() < 0.8);
}
#[test]
fn calibrator_is_conservative_threshold() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
let metrics = vec![
(MetricKind::ErrorRate, 0.3),
(MetricKind::Coherence, 0.2),
(MetricKind::CpuLoad, 0.8),
];
cal.update(&metrics, 0.3);
assert!(cal.is_conservative());
}
#[test]
fn calibrator_clamps_to_valid_range() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
let metrics = vec![
(MetricKind::ErrorRate, 100.0), (MetricKind::CpuLoad, 10.0), ];
cal.update(&metrics, 0.0);
assert!(cal.confidence() >= 0.0 && cal.confidence() <= 1.0);
}
#[test]
fn calibrator_with_smoothing_clamped() {
let cal = ConfidenceCalibrator::with_smoothing(5.0);
assert_eq!(cal.confidence(), 0.5); }
#[test]
fn calibrator_latency_scoring() {
let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
cal.update(&[(MetricKind::Latency, 0.0)], 1.0);
assert!(cal.confidence() > 0.9);
cal.update(&[(MetricKind::Latency, 50.0)], 1.0);
assert!(cal.confidence() < 0.9 && cal.confidence() > 0.5);
}
}