kaizen/metrics/
quality.rs1use crate::store::Store;
5use anyhow::Result;
6use serde::Serialize;
7
8#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
9pub struct CaptureQualityReport {
10 pub events_total: u64,
11 pub proxy_events: u64,
12 pub trace_spans_total: u64,
13 pub orphan_span_count: u64,
14 pub token_coverage_pct: u8,
15 pub latency_coverage_pct: u8,
16 pub context_coverage_pct: u8,
17 pub proxy_correlation_pct: u8,
18}
19
20pub fn build_quality_report(
21 store: &Store,
22 workspace: &str,
23 start_ms: u64,
24 end_ms: u64,
25) -> Result<CaptureQualityReport> {
26 let rows = store.capture_quality_rows(workspace, start_ms, end_ms)?;
27 let spans = store.trace_span_quality_rows(workspace, start_ms, end_ms)?;
28 let proxy_events = rows.iter().filter(|r| r.source == "Proxy").count() as u64;
29 let correlated = proxy_events.min(spans.iter().filter(|r| r.kind == "llm").count() as u64);
30 Ok(CaptureQualityReport {
31 events_total: rows.len() as u64,
32 proxy_events,
33 trace_spans_total: spans.len() as u64,
34 orphan_span_count: spans.iter().filter(|r| r.is_orphan).count() as u64,
35 token_coverage_pct: pct(rows.len(), rows.iter().filter(|r| r.has_tokens).count()),
36 latency_coverage_pct: pct(rows.len(), rows.iter().filter(|r| r.has_latency).count()),
37 context_coverage_pct: pct(rows.len(), rows.iter().filter(|r| r.has_context).count()),
38 proxy_correlation_pct: pct(proxy_events as usize, correlated as usize),
39 })
40}
41
42fn pct(total: usize, good: usize) -> u8 {
43 if total == 0 {
44 return 0;
45 }
46 (((good * 100) + (total / 2)) / total).min(100) as u8
47}