Skip to main content

oris_evolution/
confidence.rs

1//! Confidence Lifecycle Scheduler
2//!
3//! This module implements automatic confidence decay and lifecycle management
4//! for genes and capsules within the evolution crate.
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9use crate::core::{
10    AssetState, Capsule, GeneId, MIN_REPLAY_CONFIDENCE, REPLAY_CONFIDENCE_DECAY_RATE_PER_HOUR,
11};
12
13/// Confidence scheduler configuration
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct ConfidenceSchedulerConfig {
16    /// How often to run the decay check (in seconds)
17    pub check_interval_secs: u64,
18    /// Maximum confidence boost per reuse success
19    pub confidence_boost_per_success: f32,
20    /// Maximum confidence (capped at 1.0)
21    pub max_confidence: f32,
22    /// Enable/disable the scheduler
23    pub enabled: bool,
24}
25
26impl Default for ConfidenceSchedulerConfig {
27    fn default() -> Self {
28        Self {
29            check_interval_secs: 3600, // 1 hour
30            confidence_boost_per_success: 0.1,
31            max_confidence: 1.0,
32            enabled: true,
33        }
34    }
35}
36
37/// Confidence update action
38#[derive(Clone, Debug)]
39pub enum ConfidenceAction {
40    /// Apply decay to a capsule
41    DecayCapsule {
42        capsule_id: String,
43        gene_id: GeneId,
44        old_confidence: f32,
45        new_confidence: f32,
46    },
47    /// Demote asset to quarantined due to low confidence
48    DemoteToQuarantined { asset_id: String, confidence: f32 },
49    /// Boost confidence on successful reuse
50    BoostConfidence {
51        asset_id: String,
52        old_confidence: f32,
53        new_confidence: f32,
54    },
55}
56
57/// Scheduler errors
58#[derive(Error, Debug)]
59pub enum SchedulerError {
60    #[error("Scheduler not running")]
61    NotRunning,
62
63    #[error("IO error: {0}")]
64    IoError(String),
65
66    #[error("Store error: {0}")]
67    StoreError(String),
68}
69
70/// Trait for confidence lifecycle management
71pub trait ConfidenceScheduler: Send + Sync {
72    /// Apply confidence decay to a single capsule
73    fn apply_decay_to_capsule(&self, capsule_confidence: f32, age_hours: f32) -> f32;
74
75    /// Boost confidence on successful reuse
76    fn boost_confidence(&self, current: f32) -> f32;
77
78    /// Check if confidence is below minimum threshold
79    fn should_quarantine(&self, confidence: f32) -> bool;
80}
81
82/// Standard implementation of confidence scheduler
83pub struct StandardConfidenceScheduler {
84    config: ConfidenceSchedulerConfig,
85}
86
87impl StandardConfidenceScheduler {
88    pub fn new(config: ConfidenceSchedulerConfig) -> Self {
89        Self { config }
90    }
91
92    pub fn with_default_config() -> Self {
93        Self::new(ConfidenceSchedulerConfig::default())
94    }
95
96    /// Calculate decayed confidence
97    pub fn calculate_decay(confidence: f32, hours: f32) -> f32 {
98        if confidence <= 0.0 {
99            return 0.0;
100        }
101        let decay = (-REPLAY_CONFIDENCE_DECAY_RATE_PER_HOUR * hours).exp();
102        (confidence * decay).clamp(0.0, 1.0)
103    }
104
105    /// Calculate age in hours from a timestamp
106    pub fn calculate_age_hours(created_at_ms: i64, now_ms: i64) -> f32 {
107        let diff_ms = now_ms - created_at_ms;
108        let diff_secs = diff_ms / 1000;
109        diff_secs as f32 / 3600.0
110    }
111}
112
113impl ConfidenceScheduler for StandardConfidenceScheduler {
114    fn apply_decay_to_capsule(&self, capsule_confidence: f32, age_hours: f32) -> f32 {
115        Self::calculate_decay(capsule_confidence, age_hours)
116    }
117
118    fn boost_confidence(&self, current: f32) -> f32 {
119        let new_confidence = current + self.config.confidence_boost_per_success;
120        new_confidence.min(self.config.max_confidence)
121    }
122
123    fn should_quarantine(&self, confidence: f32) -> bool {
124        confidence < MIN_REPLAY_CONFIDENCE
125    }
126}
127
128/// Confidence metrics
129#[derive(Clone, Debug, Default, Serialize, Deserialize)]
130pub struct ConfidenceMetrics {
131    pub decay_checks_total: u64,
132    pub capsules_decayed_total: u64,
133    pub capsules_quarantined_total: u64,
134    pub confidence_boosts_total: u64,
135}
136
137/// Apply decay to a capsule and return actions
138pub fn process_capsule_confidence(
139    scheduler: &dyn ConfidenceScheduler,
140    capsule_id: &str,
141    gene_id: &GeneId,
142    confidence: f32,
143    created_at_ms: i64,
144    current_time_ms: i64,
145    state: AssetState,
146) -> Vec<ConfidenceAction> {
147    let mut actions = Vec::new();
148
149    // Only process promoted capsules
150    if state != AssetState::Promoted {
151        return actions;
152    }
153
154    let age_hours =
155        StandardConfidenceScheduler::calculate_age_hours(created_at_ms, current_time_ms);
156
157    if age_hours > 0.0 {
158        let old_conf = confidence;
159        let new_conf = scheduler.apply_decay_to_capsule(old_conf, age_hours);
160
161        if (new_conf - old_conf).abs() > 0.001 {
162            actions.push(ConfidenceAction::DecayCapsule {
163                capsule_id: capsule_id.to_string(),
164                gene_id: gene_id.clone(),
165                old_confidence: old_conf,
166                new_confidence: new_conf,
167            });
168        }
169
170        // Check quarantine threshold
171        if scheduler.should_quarantine(new_conf) {
172            actions.push(ConfidenceAction::DemoteToQuarantined {
173                asset_id: capsule_id.to_string(),
174                confidence: new_conf,
175            });
176        }
177    }
178
179    actions
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_calculate_decay() {
188        // Initial confidence 1.0, after 0 hours should be 1.0
189        let conf = StandardConfidenceScheduler::calculate_decay(1.0, 0.0);
190        assert!((conf - 1.0).abs() < 0.001);
191
192        // After ~13.86 hours (ln(2)/0.05), confidence should be ~0.5
193        let conf = StandardConfidenceScheduler::calculate_decay(1.0, 13.86);
194        assert!((conf - 0.5).abs() < 0.01);
195
196        // After 24 hours: e^(-0.05*24) ≈ 0.30
197        let conf = StandardConfidenceScheduler::calculate_decay(1.0, 24.0);
198        assert!((conf - 0.30).abs() < 0.02);
199
200        // Zero confidence stays zero
201        let conf = StandardConfidenceScheduler::calculate_decay(0.0, 100.0);
202        assert!(conf.abs() < 0.001);
203    }
204
205    #[test]
206    fn test_should_quarantine() {
207        let scheduler = StandardConfidenceScheduler::with_default_config();
208
209        // Above threshold - should not quarantine
210        assert!(!scheduler.should_quarantine(0.5));
211        assert!(!scheduler.should_quarantine(0.35));
212        assert!(!scheduler.should_quarantine(0.36));
213
214        // Below threshold - should quarantine
215        assert!(scheduler.should_quarantine(0.34));
216        assert!(scheduler.should_quarantine(0.0));
217    }
218
219    #[test]
220    fn test_boost_confidence() {
221        let scheduler = StandardConfidenceScheduler::with_default_config();
222
223        // Boost from 0.5 should be 0.6
224        let conf = scheduler.boost_confidence(0.5);
225        assert!((conf - 0.6).abs() < 0.001);
226
227        // Boost should cap at 1.0
228        let conf = scheduler.boost_confidence(1.0);
229        assert!((conf - 1.0).abs() < 0.001);
230
231        // Boost from 0.95 should cap at 1.0
232        let conf = scheduler.boost_confidence(0.95);
233        assert!((conf - 1.0).abs() < 0.001);
234    }
235
236    #[test]
237    fn test_default_config() {
238        let config = ConfidenceSchedulerConfig::default();
239        assert!(config.enabled);
240        assert_eq!(config.check_interval_secs, 3600);
241        assert!((config.confidence_boost_per_success - 0.1).abs() < 0.001);
242    }
243
244    #[test]
245    fn test_calculate_age_hours() {
246        // 1 hour = 3600 seconds = 3600000 ms
247        let age = StandardConfidenceScheduler::calculate_age_hours(0, 3600000);
248        assert!((age - 1.0).abs() < 0.001);
249
250        // 24 hours
251        let age = StandardConfidenceScheduler::calculate_age_hours(0, 86400000);
252        assert!((age - 24.0).abs() < 0.001);
253
254        // Less than an hour
255        let age = StandardConfidenceScheduler::calculate_age_hours(0, 1800000);
256        assert!((age - 0.5).abs() < 0.001);
257    }
258}