Skip to main content

mur_core/
reports.rs

1//! Monthly value reports — show users what Commander saved them.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// A monthly value report.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ValueReport {
9    pub period: String,
10    pub total_executions: u64,
11    pub successful_executions: u64,
12    pub failed_executions: u64,
13    pub auto_fixes: u64,
14    pub scheduled_runs: u64,
15    pub manual_runs: u64,
16    pub total_api_cost: f64,
17    pub estimated_time_saved_hours: f64,
18    pub hourly_rate_assumption: f64,
19    pub estimated_value_saved: f64,
20    pub roi_ratio: f64,
21    pub workflow_success_rate: f64,
22    pub generated_at: DateTime<Utc>,
23}
24
25impl ValueReport {
26    /// Generate a report from execution data.
27    pub fn generate(
28        period: &str,
29        executions: u64,
30        successful: u64,
31        auto_fixes: u64,
32        scheduled: u64,
33        api_cost: f64,
34    ) -> Self {
35        let failed = executions.saturating_sub(successful);
36        let manual = executions.saturating_sub(scheduled);
37
38        // Estimate time saved:
39        // - Auto-fix: ~15 min each
40        // - Scheduled: ~5 min each (avoided manual trigger)
41        // - Manual one-click: ~10 min each
42        let time_saved = (auto_fixes as f64 * 0.25)
43            + (scheduled as f64 * 0.083)
44            + (manual as f64 * 0.167);
45
46        let hourly_rate = 50.0;
47        let value_saved = time_saved * hourly_rate;
48        let roi = if api_cost > 0.0 {
49            value_saved / api_cost
50        } else {
51            0.0
52        };
53        let success_rate = if executions > 0 {
54            successful as f64 / executions as f64 * 100.0
55        } else {
56            0.0
57        };
58
59        Self {
60            period: period.to_string(),
61            total_executions: executions,
62            successful_executions: successful,
63            failed_executions: failed,
64            auto_fixes,
65            scheduled_runs: scheduled,
66            manual_runs: manual,
67            total_api_cost: api_cost,
68            estimated_time_saved_hours: time_saved,
69            hourly_rate_assumption: hourly_rate,
70            estimated_value_saved: value_saved,
71            roi_ratio: roi,
72            workflow_success_rate: success_rate,
73            generated_at: Utc::now(),
74        }
75    }
76
77    /// Format as a human-readable report.
78    pub fn format(&self) -> String {
79        format!(
80            r#"📊 MUR Commander — {} Report
81
82🕐 Time Saved: {:.1} hours
83   ├── Auto-fixes: {} → {:.1}h saved
84   ├── Scheduled:  {} → {:.1}h saved
85   └── Manual:     {} → {:.1}h saved
86
87💰 API Cost: ${:.2}
88📈 ROI: {:.1}x (each $1 → ${:.0} value @ ${}/hr)
89🏥 Success Rate: {:.0}%
90
91📋 Executions: {} total ({} ✅ / {} ❌)"#,
92            self.period,
93            self.estimated_time_saved_hours,
94            self.auto_fixes,
95            self.auto_fixes as f64 * 0.25,
96            self.scheduled_runs,
97            self.scheduled_runs as f64 * 0.083,
98            self.manual_runs,
99            self.manual_runs as f64 * 0.167,
100            self.total_api_cost,
101            self.roi_ratio,
102            self.roi_ratio * 1.0,
103            self.hourly_rate_assumption,
104            self.workflow_success_rate,
105            self.total_executions,
106            self.successful_executions,
107            self.failed_executions,
108        )
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_generate_report() {
118        let report = ValueReport::generate("February 2026", 50, 45, 8, 30, 8.72);
119        assert_eq!(report.total_executions, 50);
120        assert_eq!(report.successful_executions, 45);
121        assert_eq!(report.auto_fixes, 8);
122        assert!(report.estimated_time_saved_hours > 0.0);
123        assert!(report.roi_ratio > 0.0);
124        assert!((report.workflow_success_rate - 90.0).abs() < 0.1);
125    }
126
127    #[test]
128    fn test_format_report() {
129        let report = ValueReport::generate("Feb 2026", 50, 45, 8, 30, 8.72);
130        let text = report.format();
131        assert!(text.contains("Time Saved"));
132        assert!(text.contains("API Cost"));
133        assert!(text.contains("ROI"));
134    }
135
136    #[test]
137    fn test_zero_executions() {
138        let report = ValueReport::generate("Empty Month", 0, 0, 0, 0, 0.0);
139        assert_eq!(report.workflow_success_rate, 0.0);
140        assert_eq!(report.roi_ratio, 0.0);
141    }
142}