1use crate::metrics::MetricKind;
4
5pub struct ConfidenceCalibrator {
20 last_confidence: f32,
22 smoothing: f32,
25}
26
27impl ConfidenceCalibrator {
28 #[must_use]
30 pub const fn new() -> Self {
31 Self {
32 last_confidence: 0.5,
33 smoothing: 0.2,
34 }
35 }
36
37 #[must_use]
39 pub const fn with_smoothing(smoothing: f32) -> Self {
40 Self {
41 last_confidence: 0.5,
42 smoothing: smoothing.clamp(0.0, 1.0),
43 }
44 }
45
46 pub fn update(&mut self, metrics: &[(MetricKind, f32)], forecast_accuracy: f32) {
48 let raw = Self::compute_raw(metrics, forecast_accuracy);
49 self.last_confidence = self
51 .smoothing
52 .mul_add(raw, (1.0 - self.smoothing) * self.last_confidence);
53 }
54
55 #[must_use]
57 pub const fn confidence(&self) -> f32 {
58 self.last_confidence.clamp(0.0, 1.0)
59 }
60
61 fn compute_raw(metrics: &[(MetricKind, f32)], forecast_accuracy: f32) -> f32 {
64 let mut weighted_sum = 0.0_f32;
65 let mut total_weight = 0.0_f32;
66
67 for &(kind, value) in metrics {
68 let (score, weight) = Self::metric_score(kind, value);
69 weighted_sum = score.mul_add(weight, weighted_sum);
70 total_weight += weight;
71 }
72
73 weighted_sum = forecast_accuracy.mul_add(0.2, weighted_sum);
75 total_weight += 0.2;
76
77 if total_weight < f32::EPSILON {
78 return 0.5;
79 }
80
81 (weighted_sum / total_weight).clamp(0.0, 1.0)
82 }
83
84 fn metric_score(kind: MetricKind, value: f32) -> (f32, f32) {
86 match kind {
87 MetricKind::ErrorRate => {
88 let score = (1.0 - value / 0.3).clamp(0.0, 1.0);
90 (score, 0.30)
91 }
92 MetricKind::Coherence => {
93 (value.clamp(0.0, 1.0), 0.20)
95 }
96 MetricKind::CpuLoad => {
97 (1.0 - value.clamp(0.0, 1.0), 0.15)
99 }
100 MetricKind::MemoryPressure => (1.0 - value.clamp(0.0, 1.0), 0.15),
101 MetricKind::Latency => {
102 let score = (1.0 - value / 50.0).clamp(0.0, 1.0);
104 (score, 0.10)
105 }
106 MetricKind::SwapUsage => (1.0 - value.clamp(0.0, 1.0), 0.05),
107 MetricKind::DiskIo => (1.0 - value.clamp(0.0, 1.0), 0.05),
108 MetricKind::ImaginationQuality => (value.clamp(0.0, 1.0), 0.10),
110 MetricKind::ResearchOutput => (value.clamp(0.0, 1.0), 0.08),
111 MetricKind::ScenarioConfidence => (value.clamp(0.0, 1.0), 0.10),
112 MetricKind::SimulationVariance => (1.0 - value.clamp(0.0, 1.0), 0.05),
113 MetricKind::ConformalCoverage => (value.clamp(0.0, 1.0), 0.05),
114 MetricKind::BrierScore => (1.0 - value.clamp(0.0, 1.0), 0.05),
115 }
116 }
117
118 #[must_use]
120 pub fn is_conservative(&self) -> bool {
121 self.last_confidence < 0.5
122 }
123
124 #[must_use]
126 pub const fn state(&self) -> (f32, f32) {
127 (self.last_confidence, self.smoothing)
128 }
129
130 pub const fn restore_state(&mut self, last_confidence: f32, smoothing: f32) {
132 self.last_confidence = last_confidence.clamp(0.0, 1.0);
133 self.smoothing = smoothing.clamp(0.0, 1.0);
134 }
135}
136
137impl Default for ConfidenceCalibrator {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn calibrator_default_confidence() {
149 let cal = ConfidenceCalibrator::new();
150 assert_eq!(cal.confidence(), 0.5);
151 assert!(!cal.is_conservative());
152 }
153
154 #[test]
155 fn calibrator_perfect_metrics() {
156 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
157 let metrics = vec![
158 (MetricKind::ErrorRate, 0.0),
159 (MetricKind::Coherence, 1.0),
160 (MetricKind::CpuLoad, 0.0),
161 (MetricKind::MemoryPressure, 0.0),
162 (MetricKind::Latency, 0.0),
163 ];
164 cal.update(&metrics, 1.0);
165 assert!(cal.confidence() > 0.7);
166 assert!(!cal.is_conservative());
167 }
168
169 #[test]
170 fn calibrator_terrible_metrics() {
171 let mut cal = ConfidenceCalibrator::new();
172 let metrics = vec![
173 (MetricKind::ErrorRate, 0.5),
174 (MetricKind::Coherence, 0.1),
175 (MetricKind::CpuLoad, 0.95),
176 (MetricKind::MemoryPressure, 0.9),
177 (MetricKind::Latency, 60.0),
178 ];
179 cal.update(&metrics, 0.1);
180 assert!(cal.confidence() < 0.5);
182 assert!(cal.is_conservative());
183 }
184
185 #[test]
186 fn calibrator_smoothing_prevents_jumps() {
187 let mut cal = ConfidenceCalibrator::with_smoothing(0.2);
188 let good = vec![
190 (MetricKind::ErrorRate, 0.0),
191 (MetricKind::Coherence, 1.0),
192 (MetricKind::CpuLoad, 0.0),
193 ];
194 cal.update(&good, 1.0);
195 let after_one = cal.confidence();
196 assert!(after_one > 0.5 && after_one < 0.95);
198
199 cal.update(&good, 1.0);
201 let after_two = cal.confidence();
202 assert!(after_two > after_one);
203 }
204
205 #[test]
206 fn calibrator_no_smoothing() {
207 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
208 let metrics = vec![
209 (MetricKind::ErrorRate, 0.0),
210 (MetricKind::Coherence, 1.0),
211 (MetricKind::CpuLoad, 0.0),
212 ];
213 cal.update(&metrics, 1.0);
214 assert!(cal.confidence() > 0.8);
216 }
217
218 #[test]
219 fn calibrator_empty_metrics() {
220 let mut cal = ConfidenceCalibrator::new();
221 cal.update(&[], 0.5);
222 assert!((cal.confidence() - 0.5).abs() < 0.1);
224 }
225
226 #[test]
227 fn calibrator_error_rate_dominates() {
228 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
229 let metrics = vec![
231 (MetricKind::ErrorRate, 0.5),
232 (MetricKind::Coherence, 1.0),
233 (MetricKind::CpuLoad, 0.0),
234 (MetricKind::MemoryPressure, 0.0),
235 (MetricKind::Latency, 0.0),
236 ];
237 cal.update(&metrics, 1.0);
238 assert!(cal.confidence() < 0.8);
241 }
242
243 #[test]
244 fn calibrator_is_conservative_threshold() {
245 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
246 let metrics = vec![
247 (MetricKind::ErrorRate, 0.3),
248 (MetricKind::Coherence, 0.2),
249 (MetricKind::CpuLoad, 0.8),
250 ];
251 cal.update(&metrics, 0.3);
252 assert!(cal.is_conservative());
253 }
254
255 #[test]
256 fn calibrator_clamps_to_valid_range() {
257 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
258 let metrics = vec![
260 (MetricKind::ErrorRate, 100.0), (MetricKind::CpuLoad, 10.0), ];
263 cal.update(&metrics, 0.0);
264 assert!(cal.confidence() >= 0.0 && cal.confidence() <= 1.0);
265 }
266
267 #[test]
268 fn calibrator_with_smoothing_clamped() {
269 let cal = ConfidenceCalibrator::with_smoothing(5.0);
270 assert_eq!(cal.confidence(), 0.5); }
274
275 #[test]
276 fn calibrator_latency_scoring() {
277 let mut cal = ConfidenceCalibrator::with_smoothing(1.0);
279 cal.update(&[(MetricKind::Latency, 0.0)], 1.0);
280 assert!(cal.confidence() > 0.9);
283
284 cal.update(&[(MetricKind::Latency, 50.0)], 1.0);
285 assert!(cal.confidence() < 0.9 && cal.confidence() > 0.5);
287 }
288}