Skip to main content

lean_ctx/core/
wrapped.rs

1use crate::core::session::SessionState;
2use crate::core::stats;
3
4#[allow(dead_code)]
5pub struct WrappedReport {
6    pub period: String,
7    pub tokens_saved: u64,
8    pub tokens_input: u64,
9    pub cost_avoided_usd: f64,
10    pub total_commands: u64,
11    pub sessions_count: usize,
12    pub top_commands: Vec<(String, u64, f64)>,
13    pub cache_hit_rate: f64,
14    pub files_touched: u64,
15}
16
17impl WrappedReport {
18    pub fn generate(period: &str) -> Self {
19        let store = stats::load();
20        let sessions = SessionState::list_sessions();
21
22        let (tokens_saved, tokens_input, total_commands) = match period {
23            "week" => aggregate_recent_stats(&store, 7),
24            "month" => aggregate_recent_stats(&store, 30),
25            _ => (
26                store
27                    .total_input_tokens
28                    .saturating_sub(store.total_output_tokens),
29                store.total_input_tokens,
30                store.total_commands,
31            ),
32        };
33
34        let cost_per_token = crate::core::stats::DEFAULT_INPUT_PRICE_PER_M / 1_000_000.0;
35        let cost_avoided_usd = tokens_saved as f64 * cost_per_token;
36
37        let sessions_count = match period {
38            "week" => count_recent_sessions(&sessions, 7),
39            "month" => count_recent_sessions(&sessions, 30),
40            _ => sessions.len(),
41        };
42
43        let mut top_commands: Vec<(String, u64, f64)> = store
44            .commands
45            .iter()
46            .map(|(cmd, stats)| {
47                let saved = stats.input_tokens.saturating_sub(stats.output_tokens);
48                let pct = if stats.input_tokens > 0 {
49                    saved as f64 / stats.input_tokens as f64 * 100.0
50                } else {
51                    0.0
52                };
53                (cmd.clone(), saved, pct)
54            })
55            .collect();
56        top_commands.sort_by(|a, b| b.1.cmp(&a.1));
57        top_commands.truncate(5);
58
59        let cache_hit_rate = if tokens_input > 0 {
60            tokens_saved as f64 / tokens_input as f64 * 100.0
61        } else {
62            0.0
63        };
64
65        let files_touched: u64 = sessions.iter().map(|s| s.tool_calls as u64).sum();
66
67        WrappedReport {
68            period: period.to_string(),
69            tokens_saved,
70            tokens_input,
71            cost_avoided_usd,
72            total_commands,
73            sessions_count,
74            top_commands,
75            cache_hit_rate,
76            files_touched,
77        }
78    }
79
80    pub fn format_ascii(&self) -> String {
81        let period_label = match self.period.as_str() {
82            "week" => format!("Week of {}", chrono::Utc::now().format("%B %d, %Y")),
83            "month" => format!("Month of {}", chrono::Utc::now().format("%B %Y")),
84            _ => "All Time".to_string(),
85        };
86
87        let saved_str = format_tokens(self.tokens_saved);
88        let cost_str = format!("${:.2}", self.cost_avoided_usd);
89
90        let top_str = if self.top_commands.is_empty() {
91            "No data yet".to_string()
92        } else {
93            self.top_commands
94                .iter()
95                .take(3)
96                .map(|(cmd, _, pct)| format!("{cmd} {pct:.0}%"))
97                .collect::<Vec<_>>()
98                .join(" | ")
99        };
100
101        let width = 48;
102        let border = "\u{2500}".repeat(width);
103
104        format!(
105            "\n LeanCTX Wrapped \u{2014} {period_label}\n \
106             {border}\n  \
107             {saved_str} tokens saved      {cost_str} avoided\n  \
108             {sessions} sessions            {cmds} commands\n  \
109             Top: {top_str}\n  \
110             Cache efficiency: {cache:.1}%\n \
111             {border}\n  \
112             \"Your AI saw only what mattered.\"\n  \
113             leanctx.com\n",
114            sessions = self.sessions_count,
115            cmds = self.total_commands,
116            cache = self.cache_hit_rate,
117        )
118    }
119
120    pub fn format_compact(&self) -> String {
121        let saved_str = format_tokens(self.tokens_saved);
122        let cost_str = format!("${:.2}", self.cost_avoided_usd);
123        let top_str = self
124            .top_commands
125            .iter()
126            .take(3)
127            .map(|(cmd, _, pct)| format!("{cmd} {pct:.0}%"))
128            .collect::<Vec<_>>()
129            .join(" | ");
130
131        format!(
132            "WRAPPED [{}]: {} tok saved, {} avoided, {} sessions, {} cmds | Top: {} | Cache: {:.1}%",
133            self.period, saved_str, cost_str, self.sessions_count,
134            self.total_commands, top_str, self.cache_hit_rate,
135        )
136    }
137}
138
139fn aggregate_recent_stats(store: &stats::StatsStore, days: usize) -> (u64, u64, u64) {
140    let recent_days: Vec<&stats::DayStats> = store.daily.iter().rev().take(days).collect();
141
142    let input: u64 = recent_days.iter().map(|d| d.input_tokens).sum();
143    let output: u64 = recent_days.iter().map(|d| d.output_tokens).sum();
144    let commands: u64 = recent_days.iter().map(|d| d.commands).sum();
145    let saved = input.saturating_sub(output);
146
147    (saved, input, commands)
148}
149
150fn count_recent_sessions(sessions: &[crate::core::session::SessionSummary], days: i64) -> usize {
151    let cutoff = chrono::Utc::now() - chrono::Duration::days(days);
152    sessions.iter().filter(|s| s.updated_at > cutoff).count()
153}
154
155fn format_tokens(tokens: u64) -> String {
156    if tokens >= 1_000_000 {
157        format!("{:.1}M", tokens as f64 / 1_000_000.0)
158    } else if tokens >= 1_000 {
159        format!("{:.1}K", tokens as f64 / 1_000.0)
160    } else {
161        format!("{tokens}")
162    }
163}