1#![forbid(unsafe_code)]
17#![allow(clippy::significant_drop_tightening)]
18
19pub mod alert;
20pub mod confidence;
21pub mod forecast;
22pub mod metrics;
23
24pub use alert::{Alert, AlertEngine, AlertLevel, AlertRule, Comparison};
25pub use confidence::ConfidenceCalibrator;
26pub use forecast::{Forecast, ForecastEngine};
27pub use metrics::{MetricKind, MetricSample, MetricTracker};
28
29use chrono::{DateTime, Utc};
30use serde::{Deserialize, Serialize};
31use std::collections::VecDeque;
32use std::sync::RwLock;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct CognitiveMetrics {
37 pub imagination_quality: f32,
39 pub research_output: f32,
41 pub scenario_confidence: f32,
43 pub simulation_variance: f32,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct CognitiveForecast {
50 pub imagination_quality: Option<Forecast>,
52 pub research_output: Option<Forecast>,
54 pub scenario_confidence: Option<Forecast>,
56 pub simulation_variance: Option<Forecast>,
58}
59
60impl CognitiveForecast {
61 #[must_use]
63 pub const fn is_complete(&self) -> bool {
64 self.imagination_quality.is_some()
65 && self.research_output.is_some()
66 && self.scenario_confidence.is_some()
67 && self.simulation_variance.is_some()
68 }
69
70 #[must_use]
72 pub fn to_json(&self) -> serde_json::Value {
73 serde_json::json!({
74 "imagination_quality": self.imagination_quality.as_ref().map(|f| f.predicted_value),
75 "research_output": self.research_output.as_ref().map(|f| f.predicted_value),
76 "scenario_confidence": self.scenario_confidence.as_ref().map(|f| f.predicted_value),
77 "simulation_variance": self.simulation_variance.as_ref().map(|f| f.predicted_value),
78 })
79 }
80}
81
82const DEFAULT_HISTORY_CAPACITY: usize = 256;
84
85pub struct SelfModel {
88 metrics: RwLock<MetricTracker>,
89 forecast_engine: ForecastEngine,
90 alert_engine: RwLock<AlertEngine>,
91 calibrator: RwLock<ConfidenceCalibrator>,
92}
93
94impl SelfModel {
95 #[must_use]
97 pub fn new() -> Self {
98 Self::with_capacity(DEFAULT_HISTORY_CAPACITY)
99 }
100
101 #[must_use]
103 pub fn with_capacity(capacity: usize) -> Self {
104 Self {
105 metrics: RwLock::new(MetricTracker::new(capacity)),
106 forecast_engine: ForecastEngine::new(),
107 alert_engine: RwLock::new(AlertEngine::with_default_rules()),
108 calibrator: RwLock::new(ConfidenceCalibrator::new()),
109 }
110 }
111
112 pub fn record(&self, kind: MetricKind, value: f32) {
114 let sample = MetricSample {
115 kind,
116 value,
117 timestamp: Utc::now(),
118 };
119 if let Ok(mut metrics) = self.metrics.write() {
120 metrics.record(sample);
121 }
122 }
123
124 pub fn record_at(&self, kind: MetricKind, value: f32, timestamp: DateTime<Utc>) {
126 let sample = MetricSample {
127 kind,
128 value,
129 timestamp,
130 };
131 if let Ok(mut metrics) = self.metrics.write() {
132 metrics.record(sample);
133 }
134 }
135
136 #[must_use]
138 pub fn forecast(&self, kind: MetricKind, horizon: usize) -> Option<Forecast> {
139 let history = {
140 let metrics = self.metrics.read().ok()?;
141 let history = metrics.history(kind)?;
142 if history.len() < 2 {
143 return None;
144 }
145 history.clone()
146 };
147 Some(self.forecast_engine.forecast(&history, horizon))
148 }
149
150 #[must_use]
152 pub fn forecast_all(&self, horizon: usize) -> Vec<(MetricKind, Forecast)> {
153 let histories: Vec<(MetricKind, VecDeque<MetricSample>)> = {
154 let metrics = self.metrics.read();
155 let Ok(metrics) = metrics else {
156 return Vec::new();
157 };
158 metrics
159 .tracked_kinds()
160 .filter_map(|kind| {
161 let history = metrics.history(kind)?;
162 if history.len() < 2 {
163 return None;
164 }
165 Some((kind, history.clone()))
166 })
167 .collect()
168 };
169 histories
170 .into_iter()
171 .map(|(kind, history)| (kind, self.forecast_engine.forecast(&history, horizon)))
172 .collect()
173 }
174
175 #[must_use]
177 pub fn check_alerts(&self) -> Vec<Alert> {
178 let histories: Vec<(MetricKind, VecDeque<MetricSample>)> = {
180 let metrics = self.metrics.read();
181 let Ok(metrics) = metrics else {
182 return Vec::new();
183 };
184 metrics
185 .tracked_kinds()
186 .filter_map(|kind| {
187 let history = metrics.history(kind)?;
188 if history.len() < 2 {
189 return None;
190 }
191 Some((kind, history.clone()))
192 })
193 .collect()
194 };
195
196 let rules: Vec<AlertRule> = {
197 let alert_engine = self.alert_engine.read();
198 let Ok(alert_engine) = alert_engine else {
199 return Vec::new();
200 };
201 alert_engine.rules().to_vec()
202 };
203
204 let mut alerts = Vec::new();
205 for rule in &rules {
206 if let Some((_, history)) = histories.iter().find(|(k, _)| *k == rule.metric) {
207 let forecast = self.forecast_engine.forecast(history, rule.horizon);
208 if let Some(alert) = AlertEngine::evaluate_rule(rule, &forecast) {
209 alerts.push(alert);
210 }
211 }
212 }
213 alerts
214 }
215
216 #[must_use]
219 pub fn confidence(&self) -> f32 {
220 let (current, accuracy) = {
221 let metrics = self.metrics.read();
222 let Ok(metrics) = metrics else {
223 return 0.5;
224 };
225
226 let current: Vec<(MetricKind, f32)> = metrics
228 .tracked_kinds()
229 .filter_map(|kind| metrics.latest(kind).map(|s| (kind, s.value)))
230 .collect();
231
232 if current.is_empty() {
233 return 0.5;
234 }
235
236 let accuracy = self.compute_forecast_accuracy(&metrics);
237 (current, accuracy)
238 };
239
240 if let Ok(mut calibrator) = self.calibrator.write() {
241 calibrator.update(¤t, accuracy);
242 calibrator.confidence()
243 } else {
244 0.5
245 }
246 }
247
248 fn compute_forecast_accuracy(&self, metrics: &MetricTracker) -> f32 {
250 let mut total_error = 0.0_f32;
251 let mut count = 0_u32;
252
253 for kind in metrics.tracked_kinds() {
254 let history = match metrics.history(kind) {
255 Some(h) if h.len() >= 4 => h,
256 _ => continue,
257 };
258
259 let past: VecDeque<MetricSample> =
261 history.iter().take(history.len() - 1).cloned().collect();
262 let actual = history.back().unwrap().value;
263
264 let forecast = self.forecast_engine.forecast(&past, 1);
265 let error = (forecast.predicted_value - actual).abs() / actual.max(0.001);
266 total_error += error.min(1.0);
267 count += 1;
268 }
269
270 if count == 0 {
271 return 0.5; }
273
274 let avg_error = total_error / count as f32;
275 (1.0 - avg_error).clamp(0.0, 1.0)
276 }
277
278 #[must_use]
280 pub fn snapshot(&self) -> SelfModelSnapshot {
281 let (metric_snapshots, histories): (
283 Vec<MetricSnapshot>,
284 Vec<(MetricKind, VecDeque<MetricSample>)>,
285 ) = {
286 let metrics = self.metrics.read();
287 let Ok(metrics) = metrics else {
288 return SelfModelSnapshot {
289 timestamp: Utc::now(),
290 confidence: 0.5,
291 metrics: Vec::new(),
292 alerts: Vec::new(),
293 forecasts: Vec::new(),
294 };
295 };
296
297 let histories: Vec<(MetricKind, VecDeque<MetricSample>)> = metrics
298 .tracked_kinds()
299 .filter_map(|kind| {
300 let history = metrics.history(kind)?;
301 if history.len() < 2 {
302 return None;
303 }
304 Some((kind, history.clone()))
305 })
306 .collect();
307
308 let metric_snapshots: Vec<MetricSnapshot> = metrics
309 .tracked_kinds()
310 .filter_map(|kind| {
311 let latest = metrics.latest(kind)?;
312 let hist = metrics.history(kind)?;
313 let values: Vec<f32> = hist.iter().map(|s| s.value).collect();
314 Some(MetricSnapshot {
315 kind,
316 current: latest.value,
317 min: values.iter().copied().fold(f32::INFINITY, f32::min),
318 max: values.iter().copied().fold(f32::NEG_INFINITY, f32::max),
319 avg: values.iter().copied().sum::<f32>() / values.len() as f32,
320 sample_count: values.len(),
321 })
322 })
323 .collect();
324
325 (metric_snapshots, histories)
326 };
327
328 let forecasts: Vec<(MetricKind, Forecast)> = histories
330 .iter()
331 .map(|(kind, history)| (*kind, self.forecast_engine.forecast(history, 5)))
332 .collect();
333
334 let confidence = self.confidence();
335 let alerts = self.check_alerts();
336
337 SelfModelSnapshot {
338 timestamp: Utc::now(),
339 confidence,
340 metrics: metric_snapshots,
341 alerts,
342 forecasts,
343 }
344 }
345
346 pub fn add_alert_rule(&self, rule: AlertRule) {
348 if let Ok(mut engine) = self.alert_engine.write() {
349 engine.add_rule(rule);
350 }
351 }
352
353 #[must_use]
355 pub fn tracked_count(&self) -> usize {
356 self.metrics.read().map_or(0, |m| m.tracked_count())
357 }
358
359 #[must_use]
361 pub fn sample_count(&self, kind: MetricKind) -> usize {
362 self.metrics.read().map_or(0, |m| m.sample_count(kind))
363 }
364
365 pub fn record_cognitive(&self, metrics: &CognitiveMetrics) {
369 self.record(MetricKind::ImaginationQuality, metrics.imagination_quality);
370 self.record(MetricKind::ResearchOutput, metrics.research_output);
371 self.record(MetricKind::ScenarioConfidence, metrics.scenario_confidence);
372 self.record(MetricKind::SimulationVariance, metrics.simulation_variance);
373 }
374
375 pub fn record_cognitive_at(&self, metrics: &CognitiveMetrics, timestamp: DateTime<Utc>) {
377 self.record_at(
378 MetricKind::ImaginationQuality,
379 metrics.imagination_quality,
380 timestamp,
381 );
382 self.record_at(
383 MetricKind::ResearchOutput,
384 metrics.research_output,
385 timestamp,
386 );
387 self.record_at(
388 MetricKind::ScenarioConfidence,
389 metrics.scenario_confidence,
390 timestamp,
391 );
392 self.record_at(
393 MetricKind::SimulationVariance,
394 metrics.simulation_variance,
395 timestamp,
396 );
397 }
398
399 #[must_use]
403 pub fn forecast_cognitive(&self, horizon: usize) -> CognitiveForecast {
404 CognitiveForecast {
405 imagination_quality: self.forecast(MetricKind::ImaginationQuality, horizon),
406 research_output: self.forecast(MetricKind::ResearchOutput, horizon),
407 scenario_confidence: self.forecast(MetricKind::ScenarioConfidence, horizon),
408 simulation_variance: self.forecast(MetricKind::SimulationVariance, horizon),
409 }
410 }
411
412 #[must_use]
417 pub fn check_cognitive_alerts(&self) -> Vec<Alert> {
418 self.check_alerts()
419 .into_iter()
420 .filter(|a| {
421 matches!(
422 a.metric,
423 MetricKind::ImaginationQuality
424 | MetricKind::ResearchOutput
425 | MetricKind::ScenarioConfidence
426 | MetricKind::SimulationVariance
427 )
428 })
429 .collect()
430 }
431
432 #[must_use]
438 pub fn to_json(&self) -> serde_json::Value {
439 let (samples, rules, last_confidence, smoothing) = {
440 let samples = {
441 let metrics = self.metrics.read();
442 let Ok(metrics) = metrics else {
443 return serde_json::json!({});
444 };
445 metrics
446 .tracked_kinds()
447 .filter_map(|kind| {
448 metrics
449 .history(kind)
450 .cloned()
451 .map(|h| h.into_iter().collect::<Vec<_>>())
452 })
453 .flatten()
454 .collect::<Vec<MetricSample>>()
455 };
456 let rules = {
457 let engine = self.alert_engine.read();
458 engine.map(|e| e.rules().to_vec()).unwrap_or_default()
459 };
460 let calibrator = {
461 let c = self.calibrator.read();
462 c.map_or((0.5, 0.2), |c| (c.state().0, c.state().1))
463 };
464 (samples, rules, calibrator.0, calibrator.1)
465 };
466 serde_json::json!({
467 "samples": samples,
468 "rules": rules,
469 "last_confidence": last_confidence,
470 "smoothing": smoothing,
471 })
472 }
473
474 pub fn from_json(&self, value: &serde_json::Value) -> Result<(), String> {
479 let samples = value
480 .get("samples")
481 .and_then(serde_json::Value::as_array)
482 .ok_or_else(|| "self-model state missing 'samples'".to_string())?;
483 let restored: Vec<MetricSample> = samples
484 .iter()
485 .filter_map(|s| serde_json::from_value(s.clone()).ok())
486 .collect();
487 {
488 let mut metrics = self
489 .metrics
490 .write()
491 .map_err(|e| format!("metrics lock: {e}"))?;
492 for sample in restored {
493 metrics.record(sample);
494 }
495 }
496 if let Some(rules) = value.get("rules").and_then(serde_json::Value::as_array) {
497 let rules: Vec<AlertRule> = rules
498 .iter()
499 .filter_map(|r| serde_json::from_value(r.clone()).ok())
500 .collect();
501 if let Ok(mut engine) = self.alert_engine.write() {
502 for rule in rules {
503 engine.add_rule(rule);
504 }
505 }
506 }
507 if let Some(confidence) = value
508 .get("last_confidence")
509 .and_then(serde_json::Value::as_f64)
510 {
511 let smoothing = value
512 .get("smoothing")
513 .and_then(serde_json::Value::as_f64)
514 .unwrap_or(0.2);
515 if let Ok(mut calibrator) = self.calibrator.write() {
516 calibrator.restore_state(confidence as f32, smoothing as f32);
517 }
518 }
519 Ok(())
520 }
521}
522
523impl Default for SelfModel {
524 fn default() -> Self {
525 Self::new()
526 }
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct SelfModelSnapshot {
532 pub timestamp: DateTime<Utc>,
534 pub confidence: f32,
536 pub metrics: Vec<MetricSnapshot>,
538 pub alerts: Vec<Alert>,
540 pub forecasts: Vec<(MetricKind, Forecast)>,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct MetricSnapshot {
547 pub kind: MetricKind,
549 pub current: f32,
551 pub min: f32,
553 pub max: f32,
555 pub avg: f32,
557 pub sample_count: usize,
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564
565 #[test]
566 fn self_model_record_and_forecast() {
567 let model = SelfModel::new();
568 model.record(MetricKind::CpuLoad, 0.3);
569 model.record(MetricKind::CpuLoad, 0.4);
570 model.record(MetricKind::CpuLoad, 0.5);
571
572 let forecast = model.forecast(MetricKind::CpuLoad, 3);
573 assert!(forecast.is_some());
574 let f = forecast.unwrap();
575 assert!(f.predicted_value > 0.4);
576 assert!(f.confidence > 0.0);
577 }
578
579 #[test]
580 fn self_model_insufficient_data_returns_none() {
581 let model = SelfModel::new();
582 model.record(MetricKind::CpuLoad, 0.3);
583
584 assert!(model.forecast(MetricKind::CpuLoad, 3).is_none());
585 }
586
587 #[test]
588 fn self_model_confidence_no_data() {
589 let model = SelfModel::new();
590 let conf = model.confidence();
591 assert_eq!(conf, 0.5);
592 }
593
594 #[test]
595 fn self_model_confidence_with_data() {
596 let model = SelfModel::new();
597 for v in [0.1, 0.15, 0.12, 0.13, 0.11, 0.14] {
598 model.record(MetricKind::CpuLoad, v);
599 }
600 let conf = model.confidence();
601 assert!(conf > 0.0 && conf <= 1.0);
602 }
603
604 #[test]
605 fn self_model_snapshot_empty() {
606 let model = SelfModel::new();
607 let snap = model.snapshot();
608 assert_eq!(snap.metrics.len(), 0);
609 assert_eq!(snap.alerts.len(), 0);
610 assert_eq!(snap.forecasts.len(), 0);
611 }
612
613 #[test]
614 fn self_model_json_roundtrip_preserves_histories() {
615 let model = SelfModel::new();
616 model.record(MetricKind::ConformalCoverage, 0.9);
617 model.record(MetricKind::ConformalCoverage, 0.82);
618 model.record(MetricKind::BrierScore, 0.2);
619 model.record_at(
620 MetricKind::Coherence,
621 0.7,
622 chrono::DateTime::parse_from_rfc3339("2026-08-09T00:00:00Z")
623 .unwrap()
624 .with_timezone(&chrono::Utc),
625 );
626
627 let json = model.to_json();
628 assert!(json["samples"].as_array().unwrap().len() >= 3);
629
630 let restored = SelfModel::new();
631 restored.from_json(&json).unwrap();
632 assert_eq!(restored.sample_count(MetricKind::ConformalCoverage), 2);
633 assert_eq!(restored.sample_count(MetricKind::BrierScore), 1);
634 assert_eq!(restored.sample_count(MetricKind::Coherence), 1);
635
636 assert!(
638 restored
639 .forecast(MetricKind::ConformalCoverage, 3)
640 .is_some()
641 );
642 restored.record(MetricKind::ConformalCoverage, 0.75);
644 restored.record(MetricKind::ConformalCoverage, 0.72);
645 let drift = restored
646 .check_alerts()
647 .into_iter()
648 .any(|a| a.metric == MetricKind::ConformalCoverage);
649 assert!(drift);
650 }
651
652 #[test]
653 fn self_model_json_roundtrip_preserves_calibrator() {
654 let model = SelfModel::new();
655 model.record(MetricKind::CpuLoad, 0.3);
656 let json = model.to_json();
657 let restored = SelfModel::new();
658 restored.from_json(&json).unwrap();
659 let fresh = SelfModel::new();
662 fresh.record(MetricKind::CpuLoad, 0.3);
663 let (a, b) = (restored.confidence(), fresh.confidence());
664 assert!((a - b).abs() < 1e-6, "restored {a} != fresh {b}");
665 }
666
667 #[test]
668 fn self_model_from_json_malformed_errors() {
669 let model = SelfModel::new();
670 let err = model.from_json(&serde_json::json!({"nope": true}));
671 assert!(err.is_err());
672 }
673
674 #[test]
675 fn self_model_snapshot_with_data() {
676 let model = SelfModel::new();
677 model.record(MetricKind::CpuLoad, 0.3);
678 model.record(MetricKind::CpuLoad, 0.5);
679 model.record(MetricKind::CpuLoad, 0.7);
680 model.record(MetricKind::MemoryPressure, 0.2);
681 model.record(MetricKind::MemoryPressure, 0.3);
682
683 let snap = model.snapshot();
684 assert_eq!(snap.metrics.len(), 2);
685 assert!(snap.metrics.iter().any(|m| m.kind == MetricKind::CpuLoad));
686 assert!(
687 snap.metrics
688 .iter()
689 .any(|m| m.kind == MetricKind::MemoryPressure)
690 );
691 assert_eq!(snap.forecasts.len(), 2);
692 }
693
694 #[test]
695 fn self_model_check_alerts_clear() {
696 let model = SelfModel::new();
697 for v in [0.1, 0.12, 0.11, 0.13, 0.12] {
698 model.record(MetricKind::CpuLoad, v);
699 }
700 let alerts = model.check_alerts();
701 assert!(!alerts.iter().any(|a| a.level == AlertLevel::Critical));
703 }
704
705 #[test]
706 fn self_model_check_alerts_triggered() {
707 let model = SelfModel::new();
708 for v in [0.5, 0.6, 0.7, 0.8, 0.9, 0.95] {
710 model.record(MetricKind::CpuLoad, v);
711 }
712 let alerts = model.check_alerts();
713 assert!(!alerts.is_empty());
714 assert!(alerts.iter().any(|a| a.metric == MetricKind::CpuLoad));
715 }
716
717 #[test]
718 fn self_model_tracked_count() {
719 let model = SelfModel::new();
720 model.record(MetricKind::CpuLoad, 0.3);
721 model.record(MetricKind::MemoryPressure, 0.2);
722 assert_eq!(model.tracked_count(), 2);
723 }
724
725 #[test]
726 fn self_model_sample_count() {
727 let model = SelfModel::new();
728 model.record(MetricKind::CpuLoad, 0.3);
729 model.record(MetricKind::CpuLoad, 0.4);
730 model.record(MetricKind::CpuLoad, 0.5);
731 assert_eq!(model.sample_count(MetricKind::CpuLoad), 3);
732 assert_eq!(model.sample_count(MetricKind::MemoryPressure), 0);
733 }
734
735 #[test]
736 fn self_model_add_custom_alert_rule() {
737 let model = SelfModel::new();
738 model.add_alert_rule(AlertRule {
739 metric: MetricKind::ErrorRate,
740 threshold: 0.1,
741 comparison: Comparison::GreaterThan,
742 horizon: 3,
743 level: AlertLevel::Critical,
744 });
745 for v in [0.01, 0.02, 0.01, 0.02] {
746 model.record(MetricKind::ErrorRate, v);
747 }
748 let alerts = model.check_alerts();
750 assert!(!alerts.iter().any(|a| a.metric == MetricKind::ErrorRate));
751 }
752
753 #[test]
754 fn self_model_forecast_all() {
755 let model = SelfModel::new();
756 model.record(MetricKind::CpuLoad, 0.3);
757 model.record(MetricKind::CpuLoad, 0.4);
758 model.record(MetricKind::MemoryPressure, 0.2);
759 model.record(MetricKind::MemoryPressure, 0.25);
760
761 let forecasts = model.forecast_all(5);
762 assert_eq!(forecasts.len(), 2);
763 }
764
765 #[test]
766 fn self_model_default_impl() {
767 let model = SelfModel::default();
768 assert_eq!(model.tracked_count(), 0);
769 }
770
771 #[test]
772 fn self_model_snapshot_serialization() {
773 let model = SelfModel::new();
774 model.record(MetricKind::CpuLoad, 0.3);
775 model.record(MetricKind::CpuLoad, 0.5);
776 let snap = model.snapshot();
777 let json = serde_json::to_string(&snap).unwrap();
778 let back: SelfModelSnapshot = serde_json::from_str(&json).unwrap();
779 assert!((back.confidence - snap.confidence).abs() < 0.01);
780 }
781
782 #[test]
785 fn record_cognitive_records_all_four_metrics() {
786 let model = SelfModel::new();
787 let cm = CognitiveMetrics {
788 imagination_quality: 0.7,
789 research_output: 0.5,
790 scenario_confidence: 0.8,
791 simulation_variance: 0.1,
792 };
793 model.record_cognitive(&cm);
794 assert_eq!(model.sample_count(MetricKind::ImaginationQuality), 1);
795 assert_eq!(model.sample_count(MetricKind::ResearchOutput), 1);
796 assert_eq!(model.sample_count(MetricKind::ScenarioConfidence), 1);
797 assert_eq!(model.sample_count(MetricKind::SimulationVariance), 1);
798 }
799
800 #[test]
801 fn forecast_cognitive_returns_forecasts() {
802 let model = SelfModel::new();
803 for i in 0..5 {
804 let cm = CognitiveMetrics {
805 imagination_quality: 0.5_f32.mul_add(i as f32 * 0.05, 0.0),
806 research_output: 0.3_f32.mul_add(i as f32 * 0.02, 0.0),
807 scenario_confidence: 0.6,
808 simulation_variance: 0.15,
809 };
810 model.record_cognitive(&cm);
811 }
812 let forecast = model.forecast_cognitive(3);
813 assert!(forecast.imagination_quality.is_some());
814 assert!(forecast.research_output.is_some());
815 assert!(forecast.scenario_confidence.is_some());
816 assert!(forecast.simulation_variance.is_some());
817 assert!(forecast.is_complete());
818 }
819
820 #[test]
821 fn forecast_cognitive_insufficient_data() {
822 let model = SelfModel::new();
823 let cm = CognitiveMetrics {
824 imagination_quality: 0.5,
825 research_output: 0.3,
826 scenario_confidence: 0.6,
827 simulation_variance: 0.15,
828 };
829 model.record_cognitive(&cm);
830 let forecast = model.forecast_cognitive(3);
831 assert!(!forecast.is_complete());
833 }
834
835 #[test]
836 fn check_cognitive_alerts_filters_cognitive_only() {
837 let model = SelfModel::new();
838 for v in [0.7, 0.6, 0.5, 0.4, 0.3, 0.2] {
840 model.record(MetricKind::ImaginationQuality, v);
841 }
842 for v in [0.1, 0.12, 0.11, 0.13, 0.12] {
844 model.record(MetricKind::CpuLoad, v);
845 }
846 let cognitive_alerts = model.check_cognitive_alerts();
847 assert!(!cognitive_alerts.is_empty());
848 assert!(cognitive_alerts.iter().all(|a| {
850 matches!(
851 a.metric,
852 MetricKind::ImaginationQuality
853 | MetricKind::ResearchOutput
854 | MetricKind::ScenarioConfidence
855 | MetricKind::SimulationVariance
856 )
857 }));
858 }
859
860 #[test]
861 fn cognitive_metrics_serialization() {
862 let cm = CognitiveMetrics {
863 imagination_quality: 0.7,
864 research_output: 0.5,
865 scenario_confidence: 0.8,
866 simulation_variance: 0.1,
867 };
868 let json = serde_json::to_string(&cm).unwrap();
869 let back: CognitiveMetrics = serde_json::from_str(&json).unwrap();
870 assert!((back.imagination_quality - 0.7).abs() < 0.001);
871 assert!((back.research_output - 0.5).abs() < 0.001);
872 }
873
874 #[test]
875 fn cognitive_forecast_to_json() {
876 let model = SelfModel::new();
877 for i in 0..3 {
878 let cm = CognitiveMetrics {
879 imagination_quality: 0.5_f32.mul_add(i as f32 * 0.1, 0.0),
880 research_output: 0.3,
881 scenario_confidence: 0.6,
882 simulation_variance: 0.15,
883 };
884 model.record_cognitive(&cm);
885 }
886 let forecast = model.forecast_cognitive(2);
887 let json = forecast.to_json();
888 assert!(json["imagination_quality"].as_f64().is_some());
889 }
890
891 #[test]
892 fn simulation_variance_higher_is_better_false() {
893 assert!(!MetricKind::SimulationVariance.higher_is_better());
894 }
895
896 #[test]
897 fn imagination_quality_higher_is_better_true() {
898 assert!(MetricKind::ImaginationQuality.higher_is_better());
899 assert!(MetricKind::ResearchOutput.higher_is_better());
900 assert!(MetricKind::ScenarioConfidence.higher_is_better());
901 }
902
903 #[test]
904 fn cognitive_metric_thresholds() {
905 assert_eq!(MetricKind::ImaginationQuality.default_warning(), 0.4);
906 assert_eq!(MetricKind::ImaginationQuality.default_critical(), 0.2);
907 assert_eq!(MetricKind::SimulationVariance.default_warning(), 0.3);
908 assert_eq!(MetricKind::SimulationVariance.default_critical(), 0.5);
909 }
910
911 #[test]
912 fn record_cognitive_at_with_timestamp() {
913 let model = SelfModel::new();
914 let ts = Utc::now();
915 let cm = CognitiveMetrics {
916 imagination_quality: 0.7,
917 research_output: 0.5,
918 scenario_confidence: 0.8,
919 simulation_variance: 0.1,
920 };
921 model.record_cognitive_at(&cm, ts);
922 assert_eq!(model.sample_count(MetricKind::ImaginationQuality), 1);
923 }
924}