Skip to main content

cc_token_usage/analysis/
mod.rs

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