Skip to main content

cc_token_usage/analysis/
mod.rs

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