lean_ctx/core/stats/
model.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Serialize, Deserialize, Default, Clone)]
6pub struct StatsStore {
7 pub total_commands: u64,
8 pub total_input_tokens: u64,
9 pub total_output_tokens: u64,
10 pub first_use: Option<String>,
11 pub last_use: Option<String>,
12 pub commands: HashMap<String, CommandStats>,
13 pub daily: Vec<DayStats>,
14 #[serde(default)]
15 pub cep: CepStats,
16}
17
18#[derive(Serialize, Deserialize, Clone, Default)]
20pub struct CepStats {
21 pub sessions: u64,
22 pub total_cache_hits: u64,
23 pub total_cache_reads: u64,
24 pub total_tokens_original: u64,
25 pub total_tokens_compressed: u64,
26 pub modes: HashMap<String, u64>,
27 pub scores: Vec<CepSessionSnapshot>,
28 #[serde(default)]
29 pub last_session_pid: Option<u32>,
30 #[serde(default)]
31 pub last_session_original: Option<u64>,
32 #[serde(default)]
33 pub last_session_compressed: Option<u64>,
34 #[serde(default)]
39 pub last_session_cache_hits: Option<u64>,
40 #[serde(default)]
41 pub last_session_cache_reads: Option<u64>,
42}
43
44#[derive(Serialize, Deserialize, Clone)]
46pub struct CepSessionSnapshot {
47 pub timestamp: String,
48 pub score: u32,
49 pub cache_hit_rate: u32,
50 pub mode_diversity: u32,
51 pub compression_rate: u32,
52 pub tool_calls: u64,
53 pub tokens_saved: u64,
54 pub complexity: String,
55}
56
57#[derive(Serialize, Deserialize, Clone, Default, Debug)]
59pub struct CommandStats {
60 pub count: u64,
61 pub input_tokens: u64,
62 pub output_tokens: u64,
63}
64
65#[derive(Serialize, Deserialize, Clone, Default)]
67pub struct DayStats {
68 pub date: String,
69 pub commands: u64,
70 pub input_tokens: u64,
71 pub output_tokens: u64,
72 #[serde(default)]
76 pub version: String,
77}
78
79pub struct GainSummary {
81 pub total_saved: u64,
82 pub total_calls: u64,
83}
84
85pub const DEFAULT_INPUT_PRICE_PER_M: f64 = 2.50;
87pub const DEFAULT_OUTPUT_PRICE_PER_M: f64 = 10.0;
88
89pub struct CostModel {
91 pub input_price_per_m: f64,
92 pub output_price_per_m: f64,
93 pub avg_verbose_output_per_call: u64,
94 pub avg_concise_output_per_call: u64,
95}
96
97impl Default for CostModel {
98 fn default() -> Self {
99 let env_model = std::env::var("LEAN_CTX_MODEL")
100 .or_else(|_| std::env::var("LCTX_MODEL"))
101 .ok();
102 let pricing = crate::core::gain::model_pricing::ModelPricing::load();
103 let quote = pricing.quote(env_model.as_deref());
104 Self {
105 input_price_per_m: quote.cost.input_per_m,
106 output_price_per_m: quote.cost.output_per_m,
107 avg_verbose_output_per_call: 180,
108 avg_concise_output_per_call: 120,
109 }
110 }
111}
112
113pub struct CostBreakdown {
115 pub input_cost_without: f64,
116 pub input_cost_with: f64,
117 pub output_cost_without: f64,
118 pub output_cost_with: f64,
119 pub total_cost_without: f64,
120 pub total_cost_with: f64,
121 pub total_saved: f64,
122 pub estimated_output_tokens_without: u64,
123 pub estimated_output_tokens_with: u64,
124 pub output_tokens_saved: u64,
125}
126
127impl CostModel {
128 pub fn calculate(&self, store: &StatsStore) -> CostBreakdown {
130 let input_cost_without =
131 store.total_input_tokens as f64 / 1_000_000.0 * self.input_price_per_m;
132 let input_cost_with =
133 store.total_output_tokens as f64 / 1_000_000.0 * self.input_price_per_m;
134
135 let input_saved = store
136 .total_input_tokens
137 .saturating_sub(store.total_output_tokens);
138 let compression_rate = if store.total_input_tokens > 0 {
139 input_saved as f64 / store.total_input_tokens as f64
140 } else {
141 0.0
142 };
143 let est_output_without = store.total_commands * self.avg_verbose_output_per_call;
144 let est_output_with = if compression_rate > 0.01 {
145 store.total_commands * self.avg_concise_output_per_call
146 } else {
147 est_output_without
148 };
149 let output_saved = est_output_without.saturating_sub(est_output_with);
150
151 let output_cost_without = est_output_without as f64 / 1_000_000.0 * self.output_price_per_m;
152 let output_cost_with = est_output_with as f64 / 1_000_000.0 * self.output_price_per_m;
153
154 let total_without = input_cost_without + output_cost_without;
155 let total_with = input_cost_with + output_cost_with;
156
157 CostBreakdown {
158 input_cost_without,
159 input_cost_with,
160 output_cost_without,
161 output_cost_with,
162 total_cost_without: total_without,
163 total_cost_with: total_with,
164 total_saved: total_without - total_with,
165 estimated_output_tokens_without: est_output_without,
166 estimated_output_tokens_with: est_output_with,
167 output_tokens_saved: output_saved,
168 }
169 }
170}