1use std::sync::atomic::Ordering;
2
3use crate::core::context_kernel::proxy_bridge;
4use crate::core::telemetry::global_metrics;
5
6use super::calibration::{CalibratedCount, compare_calibration};
7use super::fidelity::assess_fidelity;
8
9#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
11pub struct InputCompressionMetrics {
12 pub modes_tested: usize,
13 pub best_mode: String,
14 pub best_savings_pct: f64,
15 pub avg_savings_pct: f64,
16 pub avg_preservation_score: f64,
17 pub fidelity_class: String,
18 pub quality_gate_passed: bool,
19}
20
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23pub struct CacheEffectivenessMetrics {
24 pub session_cache_hit_rate: f64,
25 pub content_cache_hit_rate: f64,
26 pub response_cache_hit_rate: f64,
27 pub aggregate_hit_rate: f64,
28 pub estimated_token_savings: u64,
29}
30
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct TokenizerCalibrationMetrics {
34 pub families_tested: usize,
35 pub max_cross_family_variance_pct: f64,
36 pub dominant_family: String,
37 pub dominant_accuracy: String,
38}
39
40#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
42pub struct EtpaoSummary {
43 pub current_etpao: Option<f64>,
44 pub savings_rate_pct: f64,
45 pub total_events: u64,
46 pub quality_gate: String,
47}
48
49#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51pub struct QualityLabReport {
52 pub schema_version: String,
53 pub input_compression: InputCompressionMetrics,
54 pub cache_effectiveness: CacheEffectivenessMetrics,
55 pub tokenizer_calibration: TokenizerCalibrationMetrics,
56 pub etpao: EtpaoSummary,
57 pub overall_quality_grade: QualityGrade,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
61pub enum QualityGrade {
62 Premium,
63 Good,
64 Acceptable,
65 BelowThreshold,
66}
67
68#[derive(Debug, Clone, Copy)]
69struct CacheCounts {
70 session_hits: u64,
71 session_misses: u64,
72 content_hits: u64,
73 content_misses: u64,
74 response_hits: u64,
75 response_misses: u64,
76 tokens_saved: u64,
77}
78
79pub fn assess_input_compression(
80 original: &str,
81 compressed: &str,
82 ext: &str,
83) -> InputCompressionMetrics {
84 let preservation = crate::core::preservation::measure(original, compressed, ext);
85 let fidelity = assess_fidelity(original, compressed, ext);
86 let input_tokens = crate::core::tokens::count_tokens(original) as u64;
87 let output_tokens = crate::core::tokens::count_tokens(compressed) as u64;
88 let savings_pct = token_savings_pct(input_tokens, output_tokens);
89
90 InputCompressionMetrics {
91 modes_tested: 1,
92 best_mode: "provided".to_string(),
93 best_savings_pct: savings_pct,
94 avg_savings_pct: savings_pct,
95 avg_preservation_score: preservation.overall(),
96 fidelity_class: format!("{:?}", fidelity.class),
97 quality_gate_passed: fidelity.passed_quality_gate,
98 }
99}
100
101pub fn assess_cache_effectiveness() -> CacheEffectivenessMetrics {
102 let counts = telemetry_counts();
103 let session_requests = counts.session_hits.saturating_add(counts.session_misses);
104 let content_requests = counts.content_hits.saturating_add(counts.content_misses);
105 let response_requests = counts.response_hits.saturating_add(counts.response_misses);
106 let total_hits = counts
107 .session_hits
108 .saturating_add(counts.content_hits)
109 .saturating_add(counts.response_hits);
110 let total_requests = session_requests
111 .saturating_add(content_requests)
112 .saturating_add(response_requests);
113
114 CacheEffectivenessMetrics {
115 session_cache_hit_rate: hit_rate(counts.session_hits, session_requests),
116 content_cache_hit_rate: hit_rate(counts.content_hits, content_requests),
117 response_cache_hit_rate: hit_rate(counts.response_hits, response_requests),
118 aggregate_hit_rate: hit_rate(total_hits, total_requests),
119 estimated_token_savings: if total_requests == 0 {
120 0
121 } else {
122 counts.tokens_saved
123 },
124 }
125}
126
127pub fn assess_tokenizer_calibration(sample_text: &str) -> TokenizerCalibrationMetrics {
128 let counts = compare_calibration(sample_text);
129 let mut minimum = u64::MAX;
130 let mut maximum = 0_u64;
131 let mut dominant: Option<CalibratedCount> = None;
132
133 for count in counts.iter().copied() {
134 minimum = minimum.min(count.tokens);
135 if dominant.is_none_or(|current| count.tokens > current.tokens) {
136 dominant = Some(count);
137 }
138 maximum = maximum.max(count.tokens);
139 }
140
141 let variance = if maximum == 0 {
142 0.0
143 } else {
144 maximum.saturating_sub(minimum) as f64 / maximum as f64 * 100.0
145 };
146
147 TokenizerCalibrationMetrics {
148 families_tested: counts.len(),
149 max_cross_family_variance_pct: variance,
150 dominant_family: dominant.map_or_else(
151 || "Unknown".to_string(),
152 |count| format!("{:?}", count.family),
153 ),
154 dominant_accuracy: dominant.map_or_else(
155 || "CharFallback".to_string(),
156 |count| format!("{:?}", count.accuracy),
157 ),
158 }
159}
160
161pub fn compute_quality_grade(report: &QualityLabReport) -> QualityGrade {
162 let input = &report.input_compression;
163 let structural = matches!(input.fidelity_class.as_str(), "Exact" | "Structural");
164 let savings = input.best_savings_pct;
165 let cache = report.cache_effectiveness.aggregate_hit_rate;
166 let etpao = report.etpao.savings_rate_pct;
167
168 if savings >= 80.0 && cache >= 50.0 && structural && etpao >= 50.0 {
169 QualityGrade::Premium
170 } else if savings >= 60.0 && cache >= 30.0 && structural {
171 QualityGrade::Good
172 } else if savings >= 40.0 && structural {
173 QualityGrade::Acceptable
174 } else {
175 QualityGrade::BelowThreshold
176 }
177}
178
179pub fn run_quality_lab(original: &str, compressed: &str, ext: &str) -> QualityLabReport {
180 let input_compression = assess_input_compression(original, compressed, ext);
181 let cache_effectiveness = assess_cache_effectiveness();
182 let tokenizer_calibration = assess_tokenizer_calibration(original);
183 let etpao = assess_etpao(input_compression.best_savings_pct);
184 let mut report = QualityLabReport {
185 schema_version: "lean-ctx.quality-lab/v1".to_string(),
186 input_compression,
187 cache_effectiveness,
188 tokenizer_calibration,
189 etpao,
190 overall_quality_grade: QualityGrade::BelowThreshold,
191 };
192 report.overall_quality_grade = compute_quality_grade(&report);
193 report
194}
195
196pub fn format_quality_report(report: &QualityLabReport) -> String {
197 format!(
198 concat!(
199 "Quality Lab ({})\n",
200 "Input Compression\n",
201 " savings={:.1}% preservation={:.3} fidelity={} gate={}\n",
202 "Cache Effectiveness\n",
203 " session={:.1}% content={:.1}% response={:.1}% ",
204 "aggregate={:.1}%\n",
205 "Tokenizer Calibration\n",
206 " families={} variance={:.1}% dominant={} ({})\n",
207 "ETPAO\n",
208 " current={} savings={:.1}% events={} gate={}\n",
209 "Overall Grade: {:?}"
210 ),
211 report.schema_version,
212 report.input_compression.best_savings_pct,
213 report.input_compression.avg_preservation_score,
214 report.input_compression.fidelity_class,
215 gate_label(report.input_compression.quality_gate_passed),
216 report.cache_effectiveness.session_cache_hit_rate,
217 report.cache_effectiveness.content_cache_hit_rate,
218 report.cache_effectiveness.response_cache_hit_rate,
219 report.cache_effectiveness.aggregate_hit_rate,
220 report.tokenizer_calibration.families_tested,
221 report.tokenizer_calibration.max_cross_family_variance_pct,
222 report.tokenizer_calibration.dominant_family,
223 report.tokenizer_calibration.dominant_accuracy,
224 format_etpao(report.etpao.current_etpao),
225 report.etpao.savings_rate_pct,
226 report.etpao.total_events,
227 report.etpao.quality_gate,
228 report.overall_quality_grade,
229 )
230}
231
232fn token_savings_pct(input_tokens: u64, output_tokens: u64) -> f64 {
233 if input_tokens == 0 {
234 return 0.0;
235 }
236 let retained = output_tokens as f64 / input_tokens as f64;
237 ((1.0 - retained) * 100.0).clamp(0.0, 100.0)
238}
239
240fn hit_rate(hits: u64, requests: u64) -> f64 {
241 if requests == 0 {
242 0.0
243 } else {
244 hits as f64 / requests as f64 * 100.0
245 }
246}
247
248fn telemetry_counts() -> CacheCounts {
249 let metrics = global_metrics();
250 let aggregate_hits = metrics.cache_hits.load(Ordering::Relaxed);
251 let aggregate_misses = metrics.cache_misses.load(Ordering::Relaxed);
252 let content = crate::core::content_cache::stats();
253 let response = crate::core::ocla::response_cache::global_response_cache().stats();
254 let classified_hits = content.hits.saturating_add(response.hits);
255 let classified_misses = content.misses.saturating_add(response.misses);
256
257 CacheCounts {
258 session_hits: aggregate_hits.saturating_sub(classified_hits),
259 session_misses: aggregate_misses.saturating_sub(classified_misses),
260 content_hits: content.hits,
261 content_misses: content.misses,
262 response_hits: response.hits,
263 response_misses: response.misses,
264 tokens_saved: metrics.tokens_saved.load(Ordering::Relaxed),
265 }
266}
267
268fn assess_etpao(input_savings_pct: f64) -> EtpaoSummary {
269 let summary = proxy_bridge::etpao_summary();
270 let has_data = summary.accepted_outcomes > 0;
271 EtpaoSummary {
272 current_etpao: has_data.then_some(summary.etpao),
273 savings_rate_pct: input_savings_pct,
274 total_events: summary.accepted_outcomes as u64,
275 quality_gate: if has_data && input_savings_pct >= 50.0 {
276 "PASS".to_string()
277 } else if has_data {
278 "BELOW_THRESHOLD".to_string()
279 } else {
280 "NO_DATA".to_string()
281 },
282 }
283}
284
285fn gate_label(passed: bool) -> &'static str {
286 if passed { "PASS" } else { "FAIL" }
287}
288
289fn format_etpao(value: Option<f64>) -> String {
290 value.map_or_else(|| "n/a".to_string(), |current| format!("{current:.1}"))
291}
292
293#[cfg(test)]
294mod tests {
295 use super::{
296 CacheEffectivenessMetrics, EtpaoSummary, InputCompressionMetrics, QualityGrade,
297 QualityLabReport, TokenizerCalibrationMetrics, assess_cache_effectiveness,
298 assess_input_compression, assess_tokenizer_calibration, compute_quality_grade,
299 format_quality_report, run_quality_lab,
300 };
301
302 const ORIGINAL: &str = r"pub fn process(items: &[Item]) -> Result<Vec<Output>, Error> {
303 let mut outputs = Vec::with_capacity(items.len());
304 for item in items {
305 let validated = validate(item)?;
306 outputs.push(transform(validated));
307 }
308 Ok(outputs)
309}";
310
311 const COMPRESSED: &str = r"pub fn process(items: &[Item]) -> Result<Vec<Output>, Error> {
312 let outputs = items.iter().map(validate).map(transform).collect();
313 Ok(outputs)
314}";
315
316 #[test]
317 fn test_input_compression_assessment() {
318 let metrics = assess_input_compression(ORIGINAL, COMPRESSED, "rs");
319 assert_eq!(metrics.modes_tested, 1);
320 assert!((0.0..=100.0).contains(&metrics.best_savings_pct));
321 assert!((0.0..=1.0).contains(&metrics.avg_preservation_score));
322 assert!(!metrics.fidelity_class.is_empty());
323 }
324
325 #[test]
326 fn test_cache_effectiveness_valid_ranges() {
327 let metrics = assess_cache_effectiveness();
328 assert!((0.0..=100.0).contains(&metrics.session_cache_hit_rate));
329 assert!((0.0..=100.0).contains(&metrics.content_cache_hit_rate));
330 assert!((0.0..=100.0).contains(&metrics.response_cache_hit_rate));
331 assert!((0.0..=100.0).contains(&metrics.aggregate_hit_rate));
332 }
333
334 #[test]
335 fn test_tokenizer_calibration_variance() {
336 let metrics = assess_tokenizer_calibration(ORIGINAL);
337 assert!(metrics.families_tested > 1);
338 assert!(metrics.max_cross_family_variance_pct >= 0.0);
339 assert!(!metrics.dominant_family.is_empty());
340 }
341
342 #[test]
343 fn test_premium_grade_thresholds() {
344 let report = report_with(85.0, 55.0, "Structural", 70.0);
345 assert_eq!(compute_quality_grade(&report), QualityGrade::Premium);
346 }
347
348 #[test]
349 fn test_below_threshold_grade() {
350 let report = report_with(25.0, 90.0, "Lossy", 80.0);
351 assert_eq!(compute_quality_grade(&report), QualityGrade::BelowThreshold);
352 }
353
354 #[test]
355 fn test_quality_lab_report_serialization() {
356 let report = report_with(65.0, 35.0, "Exact", 40.0);
357 let json = serde_json::to_string(&report).expect("serialize report");
358 let decoded: QualityLabReport = serde_json::from_str(&json).expect("deserialize report");
359 assert_eq!(decoded.schema_version, report.schema_version);
360 assert_eq!(decoded.overall_quality_grade, report.overall_quality_grade);
361 }
362
363 #[test]
364 fn test_format_report_output() {
365 let output = format_quality_report(&report_with(65.0, 35.0, "Exact", 40.0));
366 assert!(output.contains("Input Compression"));
367 assert!(output.contains("Cache Effectiveness"));
368 assert!(output.contains("Tokenizer Calibration"));
369 assert!(output.contains("ETPAO"));
370 assert!(output.contains("Overall Grade"));
371 }
372
373 #[test]
374 fn test_run_quality_lab_integration() {
375 let report = run_quality_lab(ORIGINAL, COMPRESSED, "rs");
376 assert_eq!(report.schema_version, "lean-ctx.quality-lab/v1");
377 assert!(report.tokenizer_calibration.families_tested > 1);
378 assert!((0.0..=100.0).contains(&report.input_compression.best_savings_pct));
379 }
380
381 fn report_with(savings: f64, cache: f64, fidelity: &str, etpao: f64) -> QualityLabReport {
382 QualityLabReport {
383 schema_version: "lean-ctx.quality-lab/v1".to_string(),
384 input_compression: InputCompressionMetrics {
385 modes_tested: 1,
386 best_mode: "provided".to_string(),
387 best_savings_pct: savings,
388 avg_savings_pct: savings,
389 avg_preservation_score: 1.0,
390 fidelity_class: fidelity.to_string(),
391 quality_gate_passed: true,
392 },
393 cache_effectiveness: CacheEffectivenessMetrics {
394 session_cache_hit_rate: cache,
395 content_cache_hit_rate: cache,
396 response_cache_hit_rate: cache,
397 aggregate_hit_rate: cache,
398 estimated_token_savings: 1_024,
399 },
400 tokenizer_calibration: TokenizerCalibrationMetrics {
401 families_tested: 4,
402 max_cross_family_variance_pct: 8.0,
403 dominant_family: "Llama".to_string(),
404 dominant_accuracy: "ProxyTokenizer".to_string(),
405 },
406 etpao: EtpaoSummary {
407 current_etpao: Some(750.0),
408 savings_rate_pct: etpao,
409 total_events: 12,
410 quality_gate: "PASS".to_string(),
411 },
412 overall_quality_grade: QualityGrade::BelowThreshold,
413 }
414 }
415}