ralph/productivity/
reports.rs1use crate::contracts::Task;
12
13use super::calculations::{
14 calculate_estimation_metrics, calculate_velocity, next_milestone, recent_completed_tasks,
15};
16use super::types::{
17 ProductivityEstimationReport, ProductivityStats, ProductivityStreakReport,
18 ProductivitySummaryReport, ProductivityVelocityReport,
19};
20
21pub fn build_summary_report(stats: &ProductivityStats, recent: usize) -> ProductivitySummaryReport {
23 let recent_completions = recent_completed_tasks(stats, recent);
24 ProductivitySummaryReport {
25 total_completed: stats.total_completed,
26 current_streak: stats.streak.current_streak,
27 longest_streak: stats.streak.longest_streak,
28 last_completed_date: stats.streak.last_completed_date.clone(),
29 next_milestone: next_milestone(stats.total_completed),
30 milestones: stats.milestones.clone(),
31 recent_completions,
32 }
33}
34
35pub fn build_streak_report(stats: &ProductivityStats) -> ProductivityStreakReport {
37 ProductivityStreakReport {
38 current_streak: stats.streak.current_streak,
39 longest_streak: stats.streak.longest_streak,
40 last_completed_date: stats.streak.last_completed_date.clone(),
41 }
42}
43
44pub fn build_velocity_report(stats: &ProductivityStats, days: u32) -> ProductivityVelocityReport {
46 let metrics = calculate_velocity(stats, days);
47 ProductivityVelocityReport {
48 window_days: days.max(1),
49 total_completed: metrics.total_completed,
50 average_per_day: metrics.average_per_day,
51 best_day: metrics.best_day,
52 }
53}
54
55pub fn build_estimation_report(tasks: &[Task]) -> ProductivityEstimationReport {
57 let metrics = calculate_estimation_metrics(tasks);
58 ProductivityEstimationReport {
59 tasks_analyzed: metrics.tasks_analyzed,
60 average_accuracy_ratio: metrics.average_accuracy_ratio,
61 median_accuracy_ratio: metrics.median_accuracy_ratio,
62 within_25_percent: metrics.within_25_percent,
63 average_absolute_error_minutes: metrics.average_absolute_error_minutes,
64 }
65}
66
67pub fn print_summary_report_text(report: &ProductivitySummaryReport) {
69 println!("Productivity Summary");
70 println!("====================");
71 println!();
72 println!("Total completed: {}", report.total_completed);
73 println!(
74 "Streak: {} (longest: {})",
75 report.current_streak, report.longest_streak
76 );
77 if let Some(next) = report.next_milestone {
78 println!("Next milestone: {} tasks", next);
79 } else {
80 println!("Next milestone: (none)");
81 }
82 println!();
83
84 if !report.milestones.is_empty() {
85 println!("Milestones achieved:");
86 for m in &report.milestones {
87 let celebrated = if m.celebrated { "✓" } else { " " };
88 println!(
89 " [{}] {} tasks at {}",
90 celebrated, m.threshold, m.achieved_at
91 );
92 }
93 println!();
94 }
95
96 if !report.recent_completions.is_empty() {
97 println!("Recent completions:");
98 for t in &report.recent_completions {
99 println!(" {} - {} ({})", t.id, t.title, t.completed_at);
100 }
101 }
102}
103
104pub fn print_velocity_report_text(report: &ProductivityVelocityReport) {
106 println!("Productivity Velocity ({} days)", report.window_days);
107 println!("===============================");
108 println!();
109 println!("Total completed: {}", report.total_completed);
110 println!("Average/day: {:.2}", report.average_per_day);
111 if let Some((day, count)) = &report.best_day {
112 println!("Best day: {} ({} tasks)", day, count);
113 }
114}
115
116pub fn print_streak_report_text(report: &ProductivityStreakReport) {
118 println!("Productivity Streak");
119 println!("===================");
120 println!();
121 println!("Current streak: {}", report.current_streak);
122 println!("Longest streak: {}", report.longest_streak);
123 println!(
124 "Last completion: {}",
125 report.last_completed_date.as_deref().unwrap_or("(none)")
126 );
127}
128
129pub fn print_estimation_report_text(report: &ProductivityEstimationReport) {
131 println!("Estimation Accuracy");
132 println!("===================");
133 println!();
134
135 if report.tasks_analyzed == 0 {
136 println!("No tasks with both estimated and actual minutes found.");
137 println!("Complete tasks with estimation data to see accuracy metrics.");
138 return;
139 }
140
141 println!("Tasks analyzed: {}", report.tasks_analyzed);
142 println!();
143 println!(
144 "Average accuracy: {:.2}x (1.0 = perfect)",
145 report.average_accuracy_ratio
146 );
147 println!("Median accuracy: {:.2}x", report.median_accuracy_ratio);
148 println!();
149 println!("Within 25%: {:.1}% of estimates", report.within_25_percent);
150 println!();
151 println!(
152 "Average absolute error: {:.1} minutes",
153 report.average_absolute_error_minutes
154 );
155 println!();
156
157 if report.average_accuracy_ratio < 0.9 {
159 println!("Trend: Tend to overestimate (actual < estimated)");
160 } else if report.average_accuracy_ratio > 1.1 {
161 println!("Trend: Tend to underestimate (actual > estimated)");
162 } else {
163 println!("Trend: Good calibration");
164 }
165}
166
167pub fn format_duration(seconds: i64) -> String {
169 if seconds < 60 {
170 format!("{}s", seconds)
171 } else if seconds < 3600 {
172 format!("{}m", seconds / 60)
173 } else if seconds < 86400 {
174 format!("{}h {}m", seconds / 3600, (seconds % 3600) / 60)
175 } else {
176 let days = seconds / 86400;
177 let hours = (seconds % 86400) / 3600;
178 format!("{}d {}h", days, hours)
179 }
180}