Skip to main content

agentic_evolve_core/collective/
success.rs

1//! SuccessTracker — tracks pattern success rates over time.
2
3use std::collections::HashMap;
4
5/// Success record with history.
6#[derive(Debug, Clone, Default)]
7pub struct SuccessRecord {
8    pub total_attempts: u64,
9    pub successes: u64,
10    pub failures: u64,
11    pub streak: i32, // positive = success streak, negative = failure streak
12    pub best_streak: u32,
13    pub recent_results: Vec<bool>, // Last N results
14}
15
16impl SuccessRecord {
17    pub fn success_rate(&self) -> f64 {
18        if self.total_attempts == 0 {
19            0.0
20        } else {
21            self.successes as f64 / self.total_attempts as f64
22        }
23    }
24
25    pub fn recent_success_rate(&self, window: usize) -> f64 {
26        let recent: Vec<_> = self.recent_results.iter().rev().take(window).collect();
27        if recent.is_empty() {
28            return 0.0;
29        }
30        let successes = recent.iter().filter(|&&r| *r).count();
31        successes as f64 / recent.len() as f64
32    }
33}
34
35/// Tracks success/failure of pattern applications.
36#[derive(Debug, Default)]
37pub struct SuccessTracker {
38    records: HashMap<String, SuccessRecord>,
39    max_recent_results: usize,
40}
41
42impl SuccessTracker {
43    pub fn new() -> Self {
44        Self {
45            records: HashMap::new(),
46            max_recent_results: 100,
47        }
48    }
49
50    pub fn record(&mut self, pattern_id: &str, success: bool) {
51        let record = self.records.entry(pattern_id.to_string()).or_default();
52        record.total_attempts += 1;
53        if success {
54            record.successes += 1;
55            record.streak = if record.streak >= 0 {
56                record.streak + 1
57            } else {
58                1
59            };
60        } else {
61            record.failures += 1;
62            record.streak = if record.streak <= 0 {
63                record.streak - 1
64            } else {
65                -1
66            };
67        }
68        if record.streak > 0 {
69            record.best_streak = record.best_streak.max(record.streak as u32);
70        }
71        record.recent_results.push(success);
72        if record.recent_results.len() > self.max_recent_results {
73            record.recent_results.remove(0);
74        }
75    }
76
77    pub fn get(&self, pattern_id: &str) -> Option<&SuccessRecord> {
78        self.records.get(pattern_id)
79    }
80
81    pub fn success_rate(&self, pattern_id: &str) -> f64 {
82        self.records
83            .get(pattern_id)
84            .map_or(0.0, |r| r.success_rate())
85    }
86
87    pub fn top_performers(&self, limit: usize) -> Vec<(&str, f64)> {
88        let mut entries: Vec<_> = self
89            .records
90            .iter()
91            .filter(|(_, r)| r.total_attempts >= 3)
92            .map(|(k, r)| (k.as_str(), r.success_rate()))
93            .collect();
94        entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
95        entries.truncate(limit);
96        entries
97    }
98
99    pub fn underperformers(&self, threshold: f64) -> Vec<(&str, f64)> {
100        self.records
101            .iter()
102            .filter(|(_, r)| r.total_attempts >= 3 && r.success_rate() < threshold)
103            .map(|(k, r)| (k.as_str(), r.success_rate()))
104            .collect()
105    }
106
107    pub fn total_tracked(&self) -> usize {
108        self.records.len()
109    }
110
111    pub fn clear(&mut self) {
112        self.records.clear();
113    }
114}