use crate::storage::database::Database;
use crate::utils::error::{GatewayError, Result};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::info;
pub struct AnalyticsEngine {
database: Arc<Database>,
metrics_collector: MetricsCollector,
cost_optimizer: CostOptimizer,
report_generator: ReportGenerator,
}
#[derive(Debug, Clone)]
pub struct MetricsCollector {
request_metrics: HashMap<String, RequestMetrics>,
provider_metrics: HashMap<String, ProviderMetrics>,
user_metrics: HashMap<String, UserMetrics>,
cost_metrics: CostMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestMetrics {
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub avg_response_time_ms: f64,
pub p95_response_time_ms: f64,
pub p99_response_time_ms: f64,
pub total_tokens: u64,
pub total_cost: f64,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderMetrics {
pub provider_name: String,
pub request_count: u64,
pub success_rate: f64,
pub avg_latency_ms: f64,
pub error_rate: f64,
pub cost_efficiency: f64,
pub uptime_percentage: f64,
pub rate_limit_hits: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMetrics {
pub user_id: String,
pub request_count: u64,
pub token_usage: TokenUsage,
pub cost_breakdown: CostBreakdown,
pub top_models: Vec<ModelUsage>,
pub usage_patterns: UsagePatterns,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub total_tokens: u64,
pub avg_tokens_per_request: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostBreakdown {
pub total_cost: f64,
pub by_provider: HashMap<String, f64>,
pub by_model: HashMap<String, f64>,
pub by_operation: HashMap<String, f64>,
pub daily_costs: Vec<DailyCost>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyCost {
pub date: DateTime<Utc>,
pub cost: f64,
pub requests: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelUsage {
pub model: String,
pub requests: u64,
pub tokens: u64,
pub cost: f64,
pub success_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsagePatterns {
pub peak_hours: Vec<u8>,
pub usage_by_weekday: HashMap<String, u64>,
pub request_size_distribution: RequestSizeDistribution,
pub seasonal_trends: Vec<SeasonalTrend>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestSizeDistribution {
pub small: u64,
pub medium: u64,
pub large: u64,
pub extra_large: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeasonalTrend {
pub period: String,
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub usage: u64,
pub growth_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostMetrics {
pub total_cost: f64,
pub cost_by_period: HashMap<String, f64>,
pub cost_trends: Vec<CostTrend>,
pub budget_utilization: HashMap<String, BudgetUtilization>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostTrend {
pub period: DateTime<Utc>,
pub cost: f64,
pub change_percentage: f64,
pub projected_cost: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BudgetUtilization {
pub budget_limit: f64,
pub current_usage: f64,
pub utilization_percentage: f64,
pub projected_usage: f64,
pub days_remaining: u32,
}
pub struct CostOptimizer {
optimization_rules: Vec<OptimizationRule>,
}
#[derive(Debug, Clone)]
pub struct OptimizationRule {
pub name: String,
pub description: String,
pub potential_savings: f64,
pub difficulty: OptimizationDifficulty,
pub rule_type: OptimizationType,
}
#[derive(Debug, Clone)]
pub enum OptimizationDifficulty {
Easy,
Medium,
Hard,
}
#[derive(Debug, Clone)]
pub enum OptimizationType {
ProviderSwitch,
ModelDowngrade,
Caching,
Batching,
PromptOptimization,
PricingTier,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationSuggestion {
pub title: String,
pub description: String,
pub potential_savings: f64,
pub effort: String,
pub priority: u8,
pub recommendations: Vec<String>,
}
pub struct ReportGenerator {
templates: HashMap<String, ReportTemplate>,
}
#[derive(Debug, Clone)]
pub struct ReportTemplate {
pub name: String,
pub description: String,
pub sections: Vec<ReportSection>,
pub format: ReportFormat,
}
#[derive(Debug, Clone)]
pub struct ReportSection {
pub title: String,
pub section_type: ReportSectionType,
pub queries: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum ReportSectionType {
Summary,
Chart,
Table,
Metrics,
Recommendations,
}
#[derive(Debug, Clone)]
pub enum ReportFormat {
PDF,
HTML,
JSON,
CSV,
Excel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneratedReport {
pub id: String,
pub title: String,
pub generated_at: DateTime<Utc>,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
pub sections: Vec<ReportSectionData>,
pub summary: ReportSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportSectionData {
pub title: String,
pub data: serde_json::Value,
pub charts: Vec<ChartData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChartData {
pub chart_type: String,
pub title: String,
pub data: Vec<DataPoint>,
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataPoint {
pub x: serde_json::Value,
pub y: serde_json::Value,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportSummary {
pub total_requests: u64,
pub total_cost: f64,
pub avg_response_time: f64,
pub success_rate: f64,
pub key_insights: Vec<String>,
pub recommendations: Vec<String>,
}
impl AnalyticsEngine {
pub fn new(database: Arc<Database>) -> Self {
Self {
database,
metrics_collector: MetricsCollector::new(),
cost_optimizer: CostOptimizer::new(),
report_generator: ReportGenerator::new(),
}
}
pub async fn generate_user_analytics(
&self,
user_id: &str,
start_date: DateTime<Utc>,
end_date: DateTime<Utc>,
) -> Result<UserMetrics> {
info!("Generating analytics for user: {}", user_id);
let usage_data = self
.database
.get_user_usage(user_id, start_date, end_date)
.await?;
let metrics = self
.metrics_collector
.process_user_data(user_id, &usage_data)
.await?;
Ok(metrics)
}
pub async fn generate_cost_suggestions(
&self,
user_id: &str,
period_days: u32,
) -> Result<Vec<OptimizationSuggestion>> {
info!(
"Generating cost optimization suggestions for user: {}",
user_id
);
let end_date = Utc::now();
let start_date = end_date - Duration::days(period_days as i64);
let metrics = self
.generate_user_analytics(user_id, start_date, end_date)
.await?;
let suggestions = self.cost_optimizer.analyze_and_suggest(&metrics).await?;
Ok(suggestions)
}
pub async fn generate_report(
&self,
template_name: &str,
user_id: Option<&str>,
start_date: DateTime<Utc>,
end_date: DateTime<Utc>,
) -> Result<GeneratedReport> {
info!(
"Generating report: {} for period {} to {}",
template_name, start_date, end_date
);
let report = self
.report_generator
.generate(template_name, user_id, start_date, end_date, &self.database)
.await?;
Ok(report)
}
}
impl MetricsCollector {
pub fn new() -> Self {
Self {
request_metrics: HashMap::new(),
provider_metrics: HashMap::new(),
user_metrics: HashMap::new(),
cost_metrics: CostMetrics {
total_cost: 0.0,
cost_by_period: HashMap::new(),
cost_trends: Vec::new(),
budget_utilization: HashMap::new(),
},
}
}
pub async fn process_user_data(
&self,
user_id: &str,
usage_data: &[serde_json::Value],
) -> Result<UserMetrics> {
let request_count = usage_data.len() as u64;
let total_tokens = usage_data
.iter()
.filter_map(|data| data.get("total_tokens")?.as_u64())
.sum();
let total_cost = usage_data
.iter()
.filter_map(|data| data.get("cost")?.as_f64())
.sum();
Ok(UserMetrics {
user_id: user_id.to_string(),
request_count,
token_usage: TokenUsage {
input_tokens: 0, output_tokens: 0, total_tokens,
avg_tokens_per_request: if request_count > 0 {
total_tokens as f64 / request_count as f64
} else {
0.0
},
},
cost_breakdown: CostBreakdown {
total_cost,
by_provider: HashMap::new(), by_model: HashMap::new(), by_operation: HashMap::new(), daily_costs: Vec::new(), },
top_models: Vec::new(), usage_patterns: UsagePatterns {
peak_hours: Vec::new(), usage_by_weekday: HashMap::new(), request_size_distribution: RequestSizeDistribution {
small: 0,
medium: 0,
large: 0,
extra_large: 0,
},
seasonal_trends: Vec::new(),
},
})
}
}
impl CostOptimizer {
pub fn new() -> Self {
Self {
optimization_rules: Self::default_rules(),
}
}
pub async fn analyze_and_suggest(
&self,
metrics: &UserMetrics,
) -> Result<Vec<OptimizationSuggestion>> {
let mut suggestions = Vec::new();
if metrics.cost_breakdown.total_cost > 100.0 {
suggestions.push(OptimizationSuggestion {
title: "Consider Model Optimization".to_string(),
description:
"Your usage patterns suggest potential savings through model optimization"
.to_string(),
potential_savings: metrics.cost_breakdown.total_cost * 0.2,
effort: "Medium".to_string(),
priority: 8,
recommendations: vec![
"Evaluate if smaller models can meet your needs".to_string(),
"Implement request caching for repeated queries".to_string(),
],
});
}
Ok(suggestions)
}
fn default_rules() -> Vec<OptimizationRule> {
vec![
OptimizationRule {
name: "Provider Cost Comparison".to_string(),
description: "Compare costs across different providers".to_string(),
potential_savings: 0.3,
difficulty: OptimizationDifficulty::Easy,
rule_type: OptimizationType::ProviderSwitch,
},
OptimizationRule {
name: "Model Right-sizing".to_string(),
description: "Use appropriately sized models for tasks".to_string(),
potential_savings: 0.4,
difficulty: OptimizationDifficulty::Medium,
rule_type: OptimizationType::ModelDowngrade,
},
]
}
}
impl ReportGenerator {
pub fn new() -> Self {
Self {
templates: Self::default_templates(),
}
}
pub async fn generate(
&self,
template_name: &str,
_user_id: Option<&str>,
start_date: DateTime<Utc>,
end_date: DateTime<Utc>,
_database: &Database,
) -> Result<GeneratedReport> {
let template = self
.templates
.get(template_name)
.ok_or_else(|| GatewayError::NotFound("Report template not found".to_string()))?;
let sections = Vec::new();
Ok(GeneratedReport {
id: uuid::Uuid::new_v4().to_string(),
title: template.name.clone(),
generated_at: Utc::now(),
period_start: start_date,
period_end: end_date,
sections,
summary: ReportSummary {
total_requests: 0,
total_cost: 0.0,
avg_response_time: 0.0,
success_rate: 0.0,
key_insights: Vec::new(),
recommendations: Vec::new(),
},
})
}
fn default_templates() -> HashMap<String, ReportTemplate> {
let mut templates = HashMap::new();
templates.insert(
"usage_summary".to_string(),
ReportTemplate {
name: "Usage Summary Report".to_string(),
description: "Comprehensive usage and cost summary".to_string(),
sections: vec![
ReportSection {
title: "Executive Summary".to_string(),
section_type: ReportSectionType::Summary,
queries: vec!["summary_stats".to_string()],
},
ReportSection {
title: "Cost Analysis".to_string(),
section_type: ReportSectionType::Chart,
queries: vec!["cost_trends".to_string()],
},
],
format: ReportFormat::PDF,
},
);
templates
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_metrics_collection() {
let collector = MetricsCollector::new();
let usage_data = vec![serde_json::json!({
"total_tokens": 100,
"cost": 0.01
})];
let metrics = collector
.process_user_data("user123", &usage_data)
.await
.unwrap();
assert_eq!(metrics.request_count, 1);
assert_eq!(metrics.token_usage.total_tokens, 100);
}
#[test]
fn test_cost_optimizer() {
let optimizer = CostOptimizer::new();
assert!(!optimizer.optimization_rules.is_empty());
}
}