use crate::config::{DecisionMode, DecisionTierConfig};
use crate::db::repository::DecisionStats;
use std::collections::BTreeMap;
fn mode_label(mode: DecisionMode) -> &'static str {
match mode {
DecisionMode::Shadow => "shadow",
DecisionMode::Live => "live",
DecisionMode::Off => "off",
}
}
fn pct(part: i64, whole: i64) -> String {
if whole > 0 {
format!("{:.0}%", (part as f64 / whole as f64) * 100.0)
} else {
"0%".to_string()
}
}
pub fn render_usage_lines(
stats: &[DecisionStats],
cached_rows: &[(String, i64)],
tiers: &BTreeMap<String, DecisionTierConfig>,
) -> Option<Vec<String>> {
if stats.is_empty() {
return None;
}
let mut lines = Vec::with_capacity(stats.len() + 2);
lines.push("Decision cache (#1648), per tier:".to_string());
for s in stats {
let tier_cfg = tiers.get(&s.tier_id);
let mode = tier_cfg.map_or("(unconfigured)", |c| mode_label(c.mode));
let policy = tier_cfg.map_or("?", |c| c.policy_version.as_str());
let rows = cached_rows
.iter()
.find(|(t, _)| t == &s.tier_id)
.map_or(0, |(_, n)| *n);
lines.push(format!(
"- `{tier}` [{mode} v{policy}]: {calls} calls, would-hit {would} ({would_pct}), live-hit {live} ({live_pct}), {rows} rows cached",
tier = s.tier_id,
calls = s.calls,
would = s.would_hit,
would_pct = pct(s.would_hit, s.calls),
live = s.live_hit,
live_pct = pct(s.live_hit, s.calls),
));
}
let total_live: i64 = stats.iter().map(|s| s.live_hit).sum();
lines.push(format!(
"- est. model calls avoided (live hits): {total_live}"
));
Some(lines)
}
pub fn render_usage_block(
stats: &[DecisionStats],
cached_rows: &[(String, i64)],
tiers: &BTreeMap<String, DecisionTierConfig>,
) -> Option<String> {
render_usage_lines(stats, cached_rows, tiers).map(|lines| lines.join("\n"))
}