use chrono::{DateTime, Utc};
use std::sync::Arc;
use crate::models::session::SessionMetadata;
pub mod anomalies;
pub mod forecasting;
pub mod insights;
pub mod patterns;
pub mod plugin_usage;
pub mod trends;
#[cfg(test)]
mod tests;
pub use anomalies::{detect_anomalies, Anomaly, AnomalyMetric, AnomalySeverity};
pub use forecasting::{forecast_usage, ForecastData, TrendDirection};
pub use insights::{generate_budget_alerts, generate_insights, Alert};
pub use patterns::{detect_patterns, UsagePatterns};
pub use plugin_usage::{aggregate_plugin_usage, PluginAnalytics, PluginType, PluginUsage};
pub use trends::{compute_trends, SessionDurationStats, TrendsData};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Period {
Days(usize),
Available,
}
impl Period {
pub fn last_7d() -> Self {
Self::Days(7)
}
pub fn last_30d() -> Self {
Self::Days(30)
}
pub fn last_90d() -> Self {
Self::Days(90)
}
pub fn available() -> Self {
Self::Available
}
pub fn days(&self) -> usize {
match self {
Period::Days(n) => *n,
Period::Available => 36500, }
}
pub fn display(&self, total_loaded: usize) -> String {
match self {
Period::Days(n) => format!("Last {} days", n),
Period::Available => format!("All loaded ({} sessions)", total_loaded),
}
}
}
#[derive(Debug, Clone)]
pub struct AnalyticsData {
pub trends: TrendsData,
pub forecast: ForecastData,
pub patterns: UsagePatterns,
pub insights: Vec<String>,
pub computed_at: DateTime<Utc>,
pub period: Period,
}
impl AnalyticsData {
pub fn compute(sessions: &[Arc<SessionMetadata>], period: Period) -> Self {
let trends = compute_trends(sessions, period.days());
let forecast = forecast_usage(&trends);
let patterns = detect_patterns(sessions, period.days());
let insights = generate_insights(&trends, &patterns, &forecast);
Self {
trends,
forecast,
patterns,
insights,
computed_at: Utc::now(),
period,
}
}
pub fn from_sessions_only(sessions: &[Arc<SessionMetadata>], period: Period) -> Self {
tracing::warn!("Stats cache missing, computing analytics from sessions only");
Self {
trends: compute_trends(sessions, period.days()),
forecast: ForecastData::unavailable("Stats cache required for cost forecasting"),
patterns: detect_patterns(sessions, period.days()),
insights: vec!["Limited insights: stats cache unavailable".to_string()],
computed_at: Utc::now(),
period,
}
}
}