ccboard_core/analytics/
mod.rs1use chrono::{DateTime, Utc};
7use std::sync::Arc;
8
9use crate::models::session::SessionMetadata;
10
11pub mod anomalies;
12pub mod forecasting;
13pub mod insights;
14pub mod patterns;
15pub mod plugin_usage;
16pub mod trends;
17
18#[cfg(test)]
19mod tests;
20
21pub use anomalies::{detect_anomalies, Anomaly, AnomalyMetric, AnomalySeverity};
22pub use forecasting::{forecast_usage, ForecastData, TrendDirection};
23pub use insights::{generate_budget_alerts, generate_insights, Alert};
24pub use patterns::{detect_patterns, UsagePatterns};
25pub use plugin_usage::{aggregate_plugin_usage, PluginAnalytics, PluginType, PluginUsage};
26pub use trends::{compute_trends, SessionDurationStats, TrendsData};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Period {
31 Days(usize),
33 Available,
35}
36
37impl Period {
38 pub fn last_7d() -> Self {
40 Self::Days(7)
41 }
42
43 pub fn last_30d() -> Self {
45 Self::Days(30)
46 }
47
48 pub fn last_90d() -> Self {
50 Self::Days(90)
51 }
52
53 pub fn available() -> Self {
55 Self::Available
56 }
57
58 pub fn days(&self) -> usize {
60 match self {
61 Period::Days(n) => *n,
62 Period::Available => 36500, }
64 }
65
66 pub fn display(&self, total_loaded: usize) -> String {
68 match self {
69 Period::Days(n) => format!("Last {} days", n),
70 Period::Available => format!("All loaded ({} sessions)", total_loaded),
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct AnalyticsData {
78 pub trends: TrendsData,
80 pub forecast: ForecastData,
82 pub patterns: UsagePatterns,
84 pub insights: Vec<String>,
86 pub computed_at: DateTime<Utc>,
88 pub period: Period,
90}
91
92impl AnalyticsData {
93 pub fn compute(sessions: &[Arc<SessionMetadata>], period: Period) -> Self {
101 let trends = compute_trends(sessions, period.days());
102 let forecast = forecast_usage(&trends);
103 let patterns = detect_patterns(sessions, period.days());
104 let insights = generate_insights(&trends, &patterns, &forecast);
105
106 Self {
107 trends,
108 forecast,
109 patterns,
110 insights,
111 computed_at: Utc::now(),
112 period,
113 }
114 }
115
116 pub fn from_sessions_only(sessions: &[Arc<SessionMetadata>], period: Period) -> Self {
121 tracing::warn!("Stats cache missing, computing analytics from sessions only");
122
123 Self {
124 trends: compute_trends(sessions, period.days()),
125 forecast: ForecastData::unavailable("Stats cache required for cost forecasting"),
126 patterns: detect_patterns(sessions, period.days()),
127 insights: vec!["Limited insights: stats cache unavailable".to_string()],
128 computed_at: Utc::now(),
129 period,
130 }
131 }
132}