Skip to main content

cc_token_usage/analysis/
mod.rs

1pub mod overview;
2pub mod project;
3pub mod session;
4pub mod trend;
5pub mod validate;
6
7use crate::data::models::{GlobalDataQuality, TokenUsage};
8use chrono::{DateTime, NaiveDate, Utc};
9use std::collections::HashMap;
10
11// ─── Common Aggregation ──────────────────────────────────────────────────────
12
13#[derive(Debug, Default, Clone)]
14pub struct AggregatedTokens {
15    pub input_tokens: u64,
16    pub output_tokens: u64,
17    pub cache_creation_tokens: u64,     // 保留总量
18    pub cache_write_5m_tokens: u64,     // 5分钟TTL缓存写入
19    pub cache_write_1h_tokens: u64,     // 1小时TTL缓存写入
20    pub cache_read_tokens: u64,
21    pub turns: usize,
22}
23
24impl AggregatedTokens {
25    pub fn add_usage(&mut self, usage: &TokenUsage) {
26        self.input_tokens += usage.input_tokens.unwrap_or(0);
27        self.output_tokens += usage.output_tokens.unwrap_or(0);
28        self.cache_creation_tokens += usage.cache_creation_input_tokens.unwrap_or(0);
29        self.cache_read_tokens += usage.cache_read_input_tokens.unwrap_or(0);
30
31        // Extract 5m/1h TTL breakdown from cache_creation detail
32        if let Some(ref detail) = usage.cache_creation {
33            self.cache_write_5m_tokens += detail.ephemeral_5m_input_tokens.unwrap_or(0);
34            self.cache_write_1h_tokens += detail.ephemeral_1h_input_tokens.unwrap_or(0);
35        }
36
37        self.turns += 1;
38    }
39
40    pub fn context_tokens(&self) -> u64 {
41        self.input_tokens + self.cache_creation_tokens + self.cache_read_tokens
42    }
43}
44
45// ─── Cost Breakdown ─────────────────────────────────────────────────────────
46
47#[derive(Debug, Clone, Default)]
48pub struct TurnCostBreakdown {
49    pub input_cost: f64,
50    pub output_cost: f64,
51    pub cache_write_5m_cost: f64,
52    pub cache_write_1h_cost: f64,
53    pub cache_read_cost: f64,
54    pub total: f64,
55}
56
57#[derive(Debug, Default)]
58pub struct CostByCategory {
59    pub input_cost: f64,
60    pub output_cost: f64,
61    pub cache_write_5m_cost: f64,
62    pub cache_write_1h_cost: f64,
63    pub cache_read_cost: f64,
64}
65
66// ─── Overview ────────────────────────────────────────────────────────────────
67
68pub struct OverviewResult {
69    pub total_sessions: usize,
70    pub total_turns: usize,
71    pub total_agent_turns: usize,
72    pub tokens_by_model: HashMap<String, AggregatedTokens>,
73    pub cost_by_model: HashMap<String, f64>,
74    pub total_cost: f64,
75    pub hourly_distribution: [usize; 24],
76    pub quality: GlobalDataQuality,
77    pub subscription_value: Option<SubscriptionValue>,
78    // 新增
79    pub weekday_hour_matrix: [[usize; 24]; 7],  // [weekday][hour] -> turn count
80    pub tool_counts: Vec<(String, usize)>,       // 工具名 -> 使用次数,排序
81    pub cost_by_category: CostByCategory,        // 费用按类别分拆
82    pub session_summaries: Vec<SessionSummary>,   // 所有 session 的汇总
83    pub total_output_tokens: u64,
84    pub total_context_tokens: u64,
85    pub avg_cache_hit_rate: f64,
86    pub cache_savings: CacheSavings,
87}
88
89/// How much money was saved by cache hits vs paying full input price.
90#[derive(Debug, Default)]
91pub struct CacheSavings {
92    pub total_saved: f64,           // $ saved by cache reads
93    pub without_cache_cost: f64,    // hypothetical cost if all cache_read charged at base_input
94    pub with_cache_cost: f64,       // actual cache_read cost
95    pub savings_pct: f64,           // percentage saved
96    pub by_model: Vec<(String, f64)>, // model -> savings, sorted desc
97}
98
99pub struct SubscriptionValue {
100    pub monthly_price: f64,
101    pub api_equivalent: f64,
102    pub value_multiplier: f64,
103}
104
105// ─── Project ─────────────────────────────────────────────────────────────────
106
107pub struct ProjectResult {
108    pub projects: Vec<ProjectSummary>,
109}
110
111pub struct ProjectSummary {
112    pub name: String,
113    pub display_name: String,
114    pub session_count: usize,
115    pub total_turns: usize,
116    pub agent_turns: usize,
117    pub tokens: AggregatedTokens,
118    pub cost: f64,
119    pub primary_model: String,
120}
121
122// ─── Session ─────────────────────────────────────────────────────────────────
123
124pub struct SessionResult {
125    pub session_id: String,
126    pub project: String,
127    pub turn_details: Vec<TurnDetail>,
128    pub agent_summary: AgentSummary,
129    pub total_tokens: AggregatedTokens,
130    pub total_cost: f64,
131    pub stop_reason_counts: HashMap<String, usize>,
132    // 新增
133    pub duration_minutes: f64,
134    pub max_context: u64,
135    pub compaction_count: usize,
136    pub cache_write_5m_pct: f64,  // 5m TTL 占比
137    pub cache_write_1h_pct: f64,  // 1h TTL 占比
138    pub model: String,             // 主力模型
139}
140
141#[derive(Debug)]
142pub struct TurnDetail {
143    pub turn_number: usize,
144    pub timestamp: DateTime<Utc>,
145    pub model: String,
146    pub input_tokens: u64,
147    pub output_tokens: u64,
148    pub cache_write_5m_tokens: u64,   // 5分钟TTL缓存写入
149    pub cache_write_1h_tokens: u64,   // 1小时TTL缓存写入
150    pub cache_read_tokens: u64,
151    pub context_size: u64,
152    pub cache_hit_rate: f64,
153    pub cost: f64,
154    pub cost_breakdown: TurnCostBreakdown, // 费用分拆
155    pub stop_reason: Option<String>,
156    pub is_agent: bool,
157    pub is_compaction: bool,          // 是否是 compaction 事件
158    pub context_delta: i64,           // 与上一 turn 的 context 变化
159    pub user_text: Option<String>,    // 用户消息文本
160    pub assistant_text: Option<String>, // 模型回复文本
161    pub tool_names: Vec<String>,      // 使用的工具名
162}
163
164#[derive(Debug, Default)]
165pub struct AgentSummary {
166    pub total_agent_turns: usize,
167    pub agent_output_tokens: u64,
168    pub agent_cost: f64,
169    pub agents: Vec<AgentDetail>,
170}
171
172#[derive(Debug)]
173pub struct AgentDetail {
174    pub agent_id: String,
175    pub agent_type: String,
176    pub description: String,
177    pub turns: usize,
178    pub output_tokens: u64,
179    pub cost: f64,
180}
181
182// ─── Session Summary ────────────────────────────────────────────────────────
183
184/// Session-level summary for overview reports and session ranking tables.
185#[derive(Debug)]
186pub struct SessionSummary {
187    pub session_id: String,
188    pub project_display_name: String,
189    pub first_timestamp: Option<DateTime<Utc>>,
190    pub duration_minutes: f64,
191    pub model: String,              // 主要使用的模型
192    pub turn_count: usize,
193    pub agent_turn_count: usize,
194    pub output_tokens: u64,
195    pub context_tokens: u64,
196    pub max_context: u64,
197    pub cache_hit_rate: f64,        // 平均
198    pub cache_write_5m_pct: f64,    // 5m TTL 占比
199    pub compaction_count: usize,
200    pub cost: f64,
201    pub tool_use_count: usize,      // tool_use stop_reason 的次数
202    pub top_tools: Vec<(String, usize)>, // 工具名 -> 使用次数,前5
203    pub turn_details: Option<Vec<TurnDetail>>, // 仅 top sessions 有详情
204}
205
206// ─── Trend ───────────────────────────────────────────────────────────────────
207
208pub struct TrendResult {
209    pub entries: Vec<TrendEntry>,
210    pub group_label: String, // "Day" or "Month"
211}
212
213pub struct TrendEntry {
214    pub label: String, // "2026-03-15" or "2026-03"
215    pub date: NaiveDate,
216    pub session_count: usize,
217    pub turn_count: usize,
218    pub tokens: AggregatedTokens,
219    pub cost: f64,
220    pub models: HashMap<String, u64>,
221    // 新增
222    pub cost_by_category: CostByCategory,
223}
224
225// Keep DailyStats as alias for internal use
226pub type DailyStats = TrendEntry;