agentic_evolve_core/metrics/
conservation.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::audit::AuditLog;
7use super::tokens::TokenMetrics;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum ConservationVerdict {
12 Excellent,
14 Good,
16 Fair,
18 Poor,
20 Wasteful,
22}
23
24impl ConservationVerdict {
25 pub fn from_score(score: f64) -> Self {
27 if score >= 0.9 {
28 Self::Excellent
29 } else if score >= 0.7 {
30 Self::Good
31 } else if score >= 0.5 {
32 Self::Fair
33 } else if score >= 0.3 {
34 Self::Poor
35 } else {
36 Self::Wasteful
37 }
38 }
39
40 pub fn meets_target(&self) -> bool {
42 matches!(self, Self::Excellent | Self::Good)
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ConservationReport {
49 pub generated_at: DateTime<Utc>,
51 pub conservation_score: f64,
53 pub verdict: ConservationVerdict,
55 pub total_tokens_used: u64,
57 pub total_tokens_saved: u64,
59 pub cache_hit_rate: f64,
61 pub query_count: usize,
63 pub target_met: bool,
65}
66
67pub fn generate_report(metrics: &TokenMetrics, audit: &AuditLog) -> ConservationReport {
69 let score = metrics.conservation_score();
70 let verdict = ConservationVerdict::from_score(score);
71
72 ConservationReport {
73 generated_at: Utc::now(),
74 conservation_score: score,
75 verdict,
76 total_tokens_used: metrics.total_used(),
77 total_tokens_saved: metrics.total_saved(),
78 cache_hit_rate: audit.cache_hit_rate(),
79 query_count: audit.len(),
80 target_met: verdict.meets_target(),
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::metrics::tokens::Layer;
88
89 #[test]
90 fn verdict_from_score_excellent() {
91 assert_eq!(
92 ConservationVerdict::from_score(0.95),
93 ConservationVerdict::Excellent
94 );
95 }
96
97 #[test]
98 fn verdict_from_score_good() {
99 assert_eq!(
100 ConservationVerdict::from_score(0.75),
101 ConservationVerdict::Good
102 );
103 }
104
105 #[test]
106 fn verdict_from_score_fair() {
107 assert_eq!(
108 ConservationVerdict::from_score(0.55),
109 ConservationVerdict::Fair
110 );
111 }
112
113 #[test]
114 fn verdict_from_score_poor() {
115 assert_eq!(
116 ConservationVerdict::from_score(0.35),
117 ConservationVerdict::Poor
118 );
119 }
120
121 #[test]
122 fn verdict_from_score_wasteful() {
123 assert_eq!(
124 ConservationVerdict::from_score(0.1),
125 ConservationVerdict::Wasteful
126 );
127 }
128
129 #[test]
130 fn meets_target_only_good_and_excellent() {
131 assert!(ConservationVerdict::Excellent.meets_target());
132 assert!(ConservationVerdict::Good.meets_target());
133 assert!(!ConservationVerdict::Fair.meets_target());
134 assert!(!ConservationVerdict::Poor.meets_target());
135 assert!(!ConservationVerdict::Wasteful.meets_target());
136 }
137
138 #[test]
139 fn generate_report_empty() {
140 let metrics = TokenMetrics::new();
141 let audit = AuditLog::new();
142 let report = generate_report(&metrics, &audit);
143 assert_eq!(report.conservation_score, 0.0);
144 assert_eq!(report.verdict, ConservationVerdict::Wasteful);
145 assert_eq!(report.query_count, 0);
146 assert!(!report.target_met);
147 }
148
149 #[test]
150 fn generate_report_with_savings() {
151 let metrics = TokenMetrics::new();
152 metrics.record(Layer::Cache, 0, 100);
154 metrics.record(Layer::Cache, 0, 100);
155 metrics.record(Layer::Cache, 0, 100);
156 metrics.record(Layer::Full, 100, 100);
157
158 let audit = AuditLog::new();
159 let report = generate_report(&metrics, &audit);
160 assert!((report.conservation_score - 0.75).abs() < f64::EPSILON);
162 assert_eq!(report.verdict, ConservationVerdict::Good);
163 assert!(report.target_met);
164 }
165}