use crate::input::InputData; /// Derived metrics computed once from InputData, reused by all sections. #[derive(Debug, Clone, Default)] pub struct ComputedMetrics { pub total_tokens: u64, pub usage_pct: f64, pub cost_velocity: Option, pub token_velocity: Option, pub cache_efficiency_pct: Option, } impl ComputedMetrics { pub fn from_input(input: &InputData) -> Self { let mut m = Self::default(); if let Some(ref cw) = input.context_window { let input_tok = cw.total_input_tokens.unwrap_or(0); let output_tok = cw.total_output_tokens.unwrap_or(0); m.total_tokens = input_tok + output_tok; m.usage_pct = cw.used_percentage.unwrap_or(0.0); if let Some(ref usage) = cw.current_usage { let input = usage.input_tokens.unwrap_or(0); let cache_read = usage.cache_read_input_tokens.unwrap_or(0); let cache_create = usage.cache_creation_input_tokens.unwrap_or(0); let total_input = input + cache_create + cache_read; if total_input > 0 { m.cache_efficiency_pct = Some(cache_read as f64 / total_input as f64 * 100.0); } } } if let Some(ref cost) = input.cost { // Prefer API duration (active processing time) over wall-clock // duration, which includes all idle time between turns. let active_ms = cost.total_api_duration_ms.or(cost.total_duration_ms); if let (Some(cost_usd), Some(duration_ms)) = (cost.total_cost_usd, active_ms) { if duration_ms > 0 { let minutes = duration_ms as f64 / 60_000.0; m.cost_velocity = Some(cost_usd / minutes); m.token_velocity = Some(m.total_tokens as f64 / minutes); } } } m } }