llm_cost_ops/export/
reports.rs

1// Report types and generation
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::{ExportData, ExportResult};
7
8/// Report types
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum ReportType {
12    Cost,
13    Usage,
14    Forecast,
15    Audit,
16    Budget,
17    Summary,
18}
19
20impl std::fmt::Display for ReportType {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            ReportType::Cost => write!(f, "cost"),
24            ReportType::Usage => write!(f, "usage"),
25            ReportType::Forecast => write!(f, "forecast"),
26            ReportType::Audit => write!(f, "audit"),
27            ReportType::Budget => write!(f, "budget"),
28            ReportType::Summary => write!(f, "summary"),
29        }
30    }
31}
32
33/// Report request
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ReportRequest {
36    pub report_type: ReportType,
37    pub start_date: DateTime<Utc>,
38    pub end_date: DateTime<Utc>,
39    pub organization_id: Option<String>,
40    pub filters: ReportFilters,
41}
42
43#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44pub struct ReportFilters {
45    pub provider: Option<String>,
46    pub model: Option<String>,
47    pub user_id: Option<String>,
48    pub resource_type: Option<String>,
49}
50
51/// Report response
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ReportResponse {
54    pub id: String,
55    pub report_type: ReportType,
56    pub generated_at: DateTime<Utc>,
57    pub data: ExportData,
58    pub summary: ReportSummary,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ReportSummary {
63    pub total_records: usize,
64    pub date_range: DateRange,
65    pub aggregates: std::collections::HashMap<String, serde_json::Value>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct DateRange {
70    pub start: DateTime<Utc>,
71    pub end: DateTime<Utc>,
72}
73
74/// Cost report
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct CostReport {
77    pub provider: String,
78    pub model: String,
79    pub organization_id: String,
80    pub total_cost: f64,
81    pub currency: String,
82    pub period_start: DateTime<Utc>,
83    pub period_end: DateTime<Utc>,
84}
85
86/// Usage report
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct UsageReport {
89    pub provider: String,
90    pub model: String,
91    pub organization_id: String,
92    pub total_tokens: u64,
93    pub total_requests: u64,
94    pub period_start: DateTime<Utc>,
95    pub period_end: DateTime<Utc>,
96}
97
98/// Forecast report
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ForecastReport {
101    pub forecast_date: DateTime<Utc>,
102    pub predicted_cost: f64,
103    pub confidence_interval: (f64, f64),
104    pub trend: String,
105}
106
107/// Audit report
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct AuditReport {
110    pub event_type: String,
111    pub user_id: String,
112    pub resource_type: String,
113    pub action: String,
114    pub timestamp: DateTime<Utc>,
115    pub status: String,
116}
117
118/// Report generator
119pub struct ReportGenerator;
120
121impl ReportGenerator {
122    pub fn new() -> Self {
123        Self
124    }
125
126    pub async fn generate(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
127        match request.report_type {
128            ReportType::Cost => self.generate_cost_report(request).await,
129            ReportType::Usage => self.generate_usage_report(request).await,
130            ReportType::Forecast => self.generate_forecast_report(request).await,
131            ReportType::Audit => self.generate_audit_report(request).await,
132            ReportType::Budget => self.generate_budget_report(request).await,
133            ReportType::Summary => self.generate_summary_report(request).await,
134        }
135    }
136
137    async fn generate_cost_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
138        let mut data = ExportData::new(vec![
139            "Date".to_string(),
140            "Provider".to_string(),
141            "Model".to_string(),
142            "Organization".to_string(),
143            "Cost (USD)".to_string(),
144            "Requests".to_string(),
145        ]);
146
147        // Add metadata
148        data.add_metadata("report_type", "cost");
149        data.add_metadata("generated_at", Utc::now().to_rfc3339());
150
151        let summary = ReportSummary {
152            total_records: data.row_count(),
153            date_range: DateRange {
154                start: request.start_date,
155                end: request.end_date,
156            },
157            aggregates: std::collections::HashMap::new(),
158        };
159
160        Ok(ReportResponse {
161            id: uuid::Uuid::new_v4().to_string(),
162            report_type: ReportType::Cost,
163            generated_at: Utc::now(),
164            data,
165            summary,
166        })
167    }
168
169    async fn generate_usage_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
170        let mut data = ExportData::new(vec![
171            "Date".to_string(),
172            "Provider".to_string(),
173            "Model".to_string(),
174            "Total Tokens".to_string(),
175            "Input Tokens".to_string(),
176            "Output Tokens".to_string(),
177            "Requests".to_string(),
178        ]);
179
180        data.add_metadata("report_type", "usage");
181        data.add_metadata("generated_at", Utc::now().to_rfc3339());
182
183        let summary = ReportSummary {
184            total_records: data.row_count(),
185            date_range: DateRange {
186                start: request.start_date,
187                end: request.end_date,
188            },
189            aggregates: std::collections::HashMap::new(),
190        };
191
192        Ok(ReportResponse {
193            id: uuid::Uuid::new_v4().to_string(),
194            report_type: ReportType::Usage,
195            generated_at: Utc::now(),
196            data,
197            summary,
198        })
199    }
200
201    async fn generate_forecast_report(
202        &self,
203        request: ReportRequest,
204    ) -> ExportResult<ReportResponse> {
205        let mut data = ExportData::new(vec![
206            "Forecast Date".to_string(),
207            "Predicted Cost".to_string(),
208            "Lower Bound".to_string(),
209            "Upper Bound".to_string(),
210            "Confidence".to_string(),
211            "Trend".to_string(),
212        ]);
213
214        data.add_metadata("report_type", "forecast");
215        data.add_metadata("generated_at", Utc::now().to_rfc3339());
216
217        let summary = ReportSummary {
218            total_records: data.row_count(),
219            date_range: DateRange {
220                start: request.start_date,
221                end: request.end_date,
222            },
223            aggregates: std::collections::HashMap::new(),
224        };
225
226        Ok(ReportResponse {
227            id: uuid::Uuid::new_v4().to_string(),
228            report_type: ReportType::Forecast,
229            generated_at: Utc::now(),
230            data,
231            summary,
232        })
233    }
234
235    async fn generate_audit_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
236        let mut data = ExportData::new(vec![
237            "Timestamp".to_string(),
238            "Event Type".to_string(),
239            "User ID".to_string(),
240            "Resource Type".to_string(),
241            "Action".to_string(),
242            "Status".to_string(),
243            "IP Address".to_string(),
244        ]);
245
246        data.add_metadata("report_type", "audit");
247        data.add_metadata("generated_at", Utc::now().to_rfc3339());
248
249        let summary = ReportSummary {
250            total_records: data.row_count(),
251            date_range: DateRange {
252                start: request.start_date,
253                end: request.end_date,
254            },
255            aggregates: std::collections::HashMap::new(),
256        };
257
258        Ok(ReportResponse {
259            id: uuid::Uuid::new_v4().to_string(),
260            report_type: ReportType::Audit,
261            generated_at: Utc::now(),
262            data,
263            summary,
264        })
265    }
266
267    async fn generate_budget_report(&self, request: ReportRequest) -> ExportResult<ReportResponse> {
268        let mut data = ExportData::new(vec![
269            "Organization".to_string(),
270            "Budget Limit".to_string(),
271            "Current Spend".to_string(),
272            "Remaining".to_string(),
273            "Utilization %".to_string(),
274            "Status".to_string(),
275        ]);
276
277        data.add_metadata("report_type", "budget");
278        data.add_metadata("generated_at", Utc::now().to_rfc3339());
279
280        let summary = ReportSummary {
281            total_records: data.row_count(),
282            date_range: DateRange {
283                start: request.start_date,
284                end: request.end_date,
285            },
286            aggregates: std::collections::HashMap::new(),
287        };
288
289        Ok(ReportResponse {
290            id: uuid::Uuid::new_v4().to_string(),
291            report_type: ReportType::Budget,
292            generated_at: Utc::now(),
293            data,
294            summary,
295        })
296    }
297
298    async fn generate_summary_report(
299        &self,
300        request: ReportRequest,
301    ) -> ExportResult<ReportResponse> {
302        let mut data = ExportData::new(vec![
303            "Metric".to_string(),
304            "Value".to_string(),
305            "Change".to_string(),
306            "Trend".to_string(),
307        ]);
308
309        data.add_metadata("report_type", "summary");
310        data.add_metadata("generated_at", Utc::now().to_rfc3339());
311
312        let summary = ReportSummary {
313            total_records: data.row_count(),
314            date_range: DateRange {
315                start: request.start_date,
316                end: request.end_date,
317            },
318            aggregates: std::collections::HashMap::new(),
319        };
320
321        Ok(ReportResponse {
322            id: uuid::Uuid::new_v4().to_string(),
323            report_type: ReportType::Summary,
324            generated_at: Utc::now(),
325            data,
326            summary,
327        })
328    }
329}
330
331impl Default for ReportGenerator {
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[tokio::test]
342    async fn test_generate_cost_report() {
343        let generator = ReportGenerator::new();
344        let request = ReportRequest {
345            report_type: ReportType::Cost,
346            start_date: Utc::now() - chrono::Duration::days(7),
347            end_date: Utc::now(),
348            organization_id: Some("test_org".to_string()),
349            filters: ReportFilters::default(),
350        };
351
352        let result = generator.generate(request).await;
353        assert!(result.is_ok());
354
355        let response = result.unwrap();
356        assert_eq!(response.report_type, ReportType::Cost);
357    }
358
359    #[tokio::test]
360    async fn test_generate_usage_report() {
361        let generator = ReportGenerator::new();
362        let request = ReportRequest {
363            report_type: ReportType::Usage,
364            start_date: Utc::now() - chrono::Duration::days(30),
365            end_date: Utc::now(),
366            organization_id: None,
367            filters: ReportFilters::default(),
368        };
369
370        let result = generator.generate(request).await;
371        assert!(result.is_ok());
372    }
373}