use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{ExportData, ExportResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReportType {
Cost,
Usage,
Forecast,
Audit,
Budget,
Summary,
}
impl std::fmt::Display for ReportType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReportType::Cost => write!(f, "cost"),
ReportType::Usage => write!(f, "usage"),
ReportType::Forecast => write!(f, "forecast"),
ReportType::Audit => write!(f, "audit"),
ReportType::Budget => write!(f, "budget"),
ReportType::Summary => write!(f, "summary"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportRequest {
pub report_type: ReportType,
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub organization_id: Option<String>,
pub filters: ReportFilters,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReportFilters {
pub provider: Option<String>,
pub model: Option<String>,
pub user_id: Option<String>,
pub resource_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportResponse {
pub id: String,
pub report_type: ReportType,
pub generated_at: DateTime<Utc>,
pub data: ExportData,
pub summary: ReportSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportSummary {
pub total_records: usize,
pub date_range: DateRange,
pub aggregates: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateRange {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostReport {
pub provider: String,
pub model: String,
pub organization_id: String,
pub total_cost: f64,
pub currency: String,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsageReport {
pub provider: String,
pub model: String,
pub organization_id: String,
pub total_tokens: u64,
pub total_requests: u64,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForecastReport {
pub forecast_date: DateTime<Utc>,
pub predicted_cost: f64,
pub confidence_interval: (f64, f64),
pub trend: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditReport {
pub event_type: String,
pub user_id: String,
pub resource_type: String,
pub action: String,
pub timestamp: DateTime<Utc>,
pub status: String,
}
pub struct ReportGenerator;
impl ReportGenerator {
pub fn new() -> Self {
Self
}
pub async fn generate(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
match request.report_type {
ReportType::Cost => self.generate_cost_report(request).await,
ReportType::Usage => self.generate_usage_report(request).await,
ReportType::Forecast => self.generate_forecast_report(request).await,
ReportType::Audit => self.generate_audit_report(request).await,
ReportType::Budget => self.generate_budget_report(request).await,
ReportType::Summary => self.generate_summary_report(request).await,
}
}
async fn generate_cost_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Date".to_string(),
"Provider".to_string(),
"Model".to_string(),
"Organization".to_string(),
"Cost (USD)".to_string(),
"Requests".to_string(),
]);
data.add_metadata("report_type", "cost");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Cost,
generated_at: Utc::now(),
data,
summary,
})
}
async fn generate_usage_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Date".to_string(),
"Provider".to_string(),
"Model".to_string(),
"Total Tokens".to_string(),
"Input Tokens".to_string(),
"Output Tokens".to_string(),
"Requests".to_string(),
]);
data.add_metadata("report_type", "usage");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Usage,
generated_at: Utc::now(),
data,
summary,
})
}
async fn generate_forecast_report(
&self,
request: ReportRequest,
) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Forecast Date".to_string(),
"Predicted Cost".to_string(),
"Lower Bound".to_string(),
"Upper Bound".to_string(),
"Confidence".to_string(),
"Trend".to_string(),
]);
data.add_metadata("report_type", "forecast");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Forecast,
generated_at: Utc::now(),
data,
summary,
})
}
async fn generate_audit_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Timestamp".to_string(),
"Event Type".to_string(),
"User ID".to_string(),
"Resource Type".to_string(),
"Action".to_string(),
"Status".to_string(),
"IP Address".to_string(),
]);
data.add_metadata("report_type", "audit");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Audit,
generated_at: Utc::now(),
data,
summary,
})
}
async fn generate_budget_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Organization".to_string(),
"Budget Limit".to_string(),
"Current Spend".to_string(),
"Remaining".to_string(),
"Utilization %".to_string(),
"Status".to_string(),
]);
data.add_metadata("report_type", "budget");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Budget,
generated_at: Utc::now(),
data,
summary,
})
}
async fn generate_summary_report(
&self,
request: ReportRequest,
) -> ExportResult<ReportResponse> {
let mut data = ExportData::new(vec![
"Metric".to_string(),
"Value".to_string(),
"Change".to_string(),
"Trend".to_string(),
]);
data.add_metadata("report_type", "summary");
data.add_metadata("generated_at", Utc::now().to_rfc3339());
let summary = ReportSummary {
total_records: data.row_count(),
date_range: DateRange {
start: request.start_date,
end: request.end_date,
},
aggregates: std::collections::HashMap::new(),
};
Ok(ReportResponse {
id: uuid::Uuid::new_v4().to_string(),
report_type: ReportType::Summary,
generated_at: Utc::now(),
data,
summary,
})
}
}
impl Default for ReportGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_generate_cost_report() {
let generator = ReportGenerator::new();
let request = ReportRequest {
report_type: ReportType::Cost,
start_date: Utc::now() - chrono::Duration::days(7),
end_date: Utc::now(),
organization_id: Some("test_org".to_string()),
filters: ReportFilters::default(),
};
let result = generator.generate(request).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.report_type, ReportType::Cost);
}
#[tokio::test]
async fn test_generate_usage_report() {
let generator = ReportGenerator::new();
let request = ReportRequest {
report_type: ReportType::Usage,
start_date: Utc::now() - chrono::Duration::days(30),
end_date: Utc::now(),
organization_id: None,
filters: ReportFilters::default(),
};
let result = generator.generate(request).await;
assert!(result.is_ok());
}
}