agentic_evolve_core/collective/
success.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone, Default)]
7pub struct SuccessRecord {
8 pub total_attempts: u64,
9 pub successes: u64,
10 pub failures: u64,
11 pub streak: i32, pub best_streak: u32,
13 pub recent_results: Vec<bool>, }
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#[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}