kasl/libs/summary.rs
1//! Monthly work summary calculation and formatting system.
2//!
3//! Provides comprehensive functionality for calculating, processing, and formatting
4//! monthly work summaries. Handles the complex business logic of combining actual
5//! work data with company rest days to produce complete monthly reports.
6//!
7//! ## Features
8//!
9//! - **Daily Summary Aggregation**: Combines work duration and productivity metrics
10//! - **Rest Day Integration**: Incorporates company holidays and weekends
11//! - **Statistical Calculations**: Computes totals, averages, and productivity metrics
12//! - **Flexible Formatting**: Provides multiple output formats for different use cases
13//! - **Report Generation**: Powers monthly reports and export functionality
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! use kasl::libs::summary::{DailySummary, SummaryCalculator, SummaryFormatter};
19//! use chrono::{Duration, NaiveDate};
20//! use std::collections::HashSet;
21//!
22//! let summaries = vec![
23//! DailySummary {
24//! date: NaiveDate::from_ymd_opt(2025, 8, 11).unwrap(),
25//! duration: Duration::hours(8),
26//! productivity: 85.5,
27//! },
28//! ];
29//!
30//! let rest_dates = HashSet::new();
31//! let (processed, total, average) = summaries
32//! .add_rest_dates(rest_dates, Duration::hours(8))
33//! .calculate_totals();
34//! ```
35
36use crate::libs::formatter::format_duration;
37use chrono::{Duration, NaiveDate};
38use std::collections::{HashMap, HashSet};
39
40/// Represents a complete work summary for a single calendar day.
41///
42/// This structure encapsulates all the key metrics needed to understand
43/// work performance on a given day, including both time-based and
44/// productivity-based measurements. It serves as the fundamental unit
45/// for monthly reporting and analysis.
46///
47/// ## Data Components
48///
49/// ### Duration Tracking
50/// The duration represents net productive work time:
51/// - **Gross Time**: Total presence time (workday start to end)
52/// - **Pause Time**: All break periods during the day
53/// - **Net Time**: Gross time minus pause time (stored in duration field)
54///
55/// ### Productivity Metrics
56/// Productivity is calculated as a percentage representing work efficiency:
57/// - **Formula**: (Net Work Time / Gross Presence Time) × 100
58/// - **Range**: 0.0 to 100.0 (theoretical maximum)
59/// - **Typical Values**: 70-90% for most office work patterns
60/// - **Factors**: Affected by meeting frequency, break patterns, and work type
61///
62/// ## Use Cases
63///
64/// ### Individual Analysis
65/// - Daily productivity tracking and improvement
66/// - Work pattern analysis and optimization
67/// - Break frequency and duration analysis
68///
69/// ### Organizational Reporting
70/// - Monthly time sheets and hour summaries
71/// - Productivity benchmarking across teams
72/// - Resource allocation and planning data
73/// - Compliance with work hour regulations
74///
75/// ### External Integration
76/// - Payroll system integration
77/// - Project time allocation
78/// - Client billing and invoicing
79/// - Performance review documentation
80#[derive(Debug, Clone)]
81pub struct DailySummary {
82 /// The specific calendar date for this work summary.
83 ///
84 /// Uses `NaiveDate` to avoid timezone complications in reporting.
85 /// All daily summaries are associated with local calendar dates
86 /// for consistency with user expectations and business requirements.
87 ///
88 /// ## Date Handling
89 /// - **Local Time**: Uses system local time for date determination
90 /// - **Calendar Days**: Aligned with user's local calendar
91 /// - **Reporting Periods**: Consistent with organizational calendars
92 /// - **Time Zones**: Avoids complexity by using naive dates
93 pub date: NaiveDate,
94
95 /// The total net productive work duration for this day.
96 ///
97 /// This represents the actual working time after subtracting
98 /// all pause periods from the total presence time. It provides
99 /// the most accurate measure of productive work hours.
100 ///
101 /// ## Calculation Method
102 /// ```text
103 /// Net Duration = (Workday End - Workday Start) - Total Pause Time
104 ///
105 /// Example:
106 /// Workday: 09:00 - 17:30 (8h 30m total presence)
107 /// Pauses: 30m lunch + 15m coffee breaks = 45m
108 /// Net Duration: 8h 30m - 45m = 7h 45m
109 /// ```
110 ///
111 /// ## Quality Considerations
112 /// - **Accuracy**: Reflects actual productive work time
113 /// - **Billing**: Suitable for client billing and time tracking
114 /// - **Analysis**: Enables meaningful productivity analysis
115 /// - **Reporting**: Meets organizational reporting standards
116 pub duration: Duration,
117
118 /// Work productivity percentage for this day (0.0 to 100.0).
119 ///
120 /// This metric provides insight into work efficiency by comparing
121 /// net productive time against total presence time. It helps identify
122 /// patterns in work effectiveness and opportunities for improvement.
123 ///
124 /// ## Calculation Formula
125 /// ```text
126 /// Productivity = (Net Work Time / Gross Presence Time) × 100
127 ///
128 /// Example:
129 /// Net Work: 7h 45m = 465 minutes
130 /// Gross Presence: 8h 30m = 510 minutes
131 /// Productivity = (465 / 510) × 100 = 91.2%
132 /// ```
133 ///
134 /// ## Interpretation Guidelines
135 /// - **90-100%**: Highly focused work with minimal interruptions
136 /// - **80-90%**: Good productivity with normal break patterns
137 /// - **70-80%**: Moderate productivity, may indicate heavy meeting load
138 /// - **60-70%**: Lower productivity, worth investigating causes
139 /// - **<60%**: Potential issues with work patterns or data accuracy
140 ///
141 /// ## Factors Affecting Productivity
142 /// - **Meeting Density**: High meeting days typically show lower productivity
143 /// - **Work Type**: Creative work may have different patterns than administrative
144 /// - **Break Habits**: More frequent short breaks vs. fewer long breaks
145 /// - **External Factors**: Interruptions, system issues, training sessions
146 pub productivity: f64,
147}
148
149/// Trait for processing and enhancing collections of daily summaries.
150///
151/// This trait provides the core business logic for preparing monthly
152/// reports by integrating actual work data with organizational calendar
153/// information. It ensures comprehensive coverage of all calendar days
154/// and provides statistical analysis capabilities.
155///
156/// ## Design Philosophy
157///
158/// The trait follows a functional programming approach with method chaining:
159/// ```rust,no_run
160/// let result = summaries
161/// .add_rest_dates(company_holidays, default_hours)
162/// .calculate_totals();
163/// ```
164///
165/// This design enables:
166/// - **Composability**: Methods can be chained for complex transformations
167/// - **Immutability**: Each method returns a new collection
168/// - **Readability**: Clear sequence of data processing steps
169/// - **Testability**: Each transformation can be tested independently
170///
171/// ## Implementation Strategy
172///
173/// Implementations should handle:
174/// - **Data Completeness**: Ensure all calendar days are represented
175/// - **Duplicate Prevention**: Avoid duplicate entries for the same date
176/// - **Statistical Accuracy**: Provide meaningful aggregate calculations
177/// - **Performance**: Efficient processing of month-long datasets
178pub trait SummaryCalculator {
179 /// Integrates company rest days into the summary collection.
180 ///
181 /// This method ensures comprehensive monthly coverage by adding entries
182 /// for company holidays, weekends, and other non-working days that should
183 /// be included in monthly reports. It prevents gaps in monthly summaries
184 /// and provides complete calendar coverage.
185 ///
186 /// ## Integration Logic
187 ///
188 /// The method processes rest dates as follows:
189 /// 1. **Existence Check**: Verifies if a summary already exists for each rest date
190 /// 2. **Gap Filling**: Adds new summary entries for missing rest dates
191 /// 3. **Default Values**: Assigns standard work hours and zero productivity
192 /// 4. **Duplicate Prevention**: Skips rest dates that already have work data
193 ///
194 /// ## Default Duration Rationale
195 ///
196 /// Rest days are assigned default work hours for several reasons:
197 /// - **Payroll Integration**: Many payroll systems expect standard hours
198 /// - **Monthly Targets**: Helps meet monthly hour requirements
199 /// - **Benefit Allocation**: Paid holidays contribute to monthly totals
200 /// - **Reporting Consistency**: Provides predictable monthly hour calculations
201 ///
202 /// ## Productivity Handling
203 ///
204 /// Rest days are assigned 0.0% productivity because:
205 /// - **Accuracy**: No actual work was performed
206 /// - **Statistical Integrity**: Prevents artificial inflation of productivity metrics
207 /// - **Clear Distinction**: Differentiates between work days and rest days
208 /// - **Analysis**: Enables separate analysis of work vs. rest day patterns
209 ///
210 /// # Arguments
211 ///
212 /// * `rest_dates` - Set of dates that are company holidays or rest days
213 /// * `duration` - Default work duration to assign to rest days (typically 8 hours)
214 ///
215 /// # Returns
216 ///
217 /// Returns a new collection with rest days integrated, maintaining the original
218 /// work data while filling gaps with rest day entries.
219 ///
220 /// # Examples
221 ///
222 /// ```rust,no_run
223 /// use std::collections::HashSet;
224 /// use chrono::{Duration, NaiveDate};
225 ///
226 /// let mut rest_dates = HashSet::new();
227 /// rest_dates.insert(NaiveDate::from_ymd_opt(2025, 8, 15).unwrap()); // Company holiday
228 ///
229 /// let enhanced_summaries = work_summaries
230 /// .add_rest_dates(rest_dates, Duration::hours(8));
231 /// ```
232 fn add_rest_dates(self, rest_dates: HashSet<NaiveDate>, duration: Duration) -> Self;
233
234 /// Calculates comprehensive statistics for the summary collection.
235 ///
236 /// This method performs the final aggregation step to produce monthly
237 /// statistics including total work hours, average daily hours, and
238 /// other metrics needed for reporting and analysis.
239 ///
240 /// ## Statistical Calculations
241 ///
242 /// ### Total Duration
243 /// - **Sum**: Aggregates all daily durations in the collection
244 /// - **Includes**: Both work days and rest days with default hours
245 /// - **Purpose**: Monthly hour totals for payroll and reporting
246 ///
247 /// ### Average Duration
248 /// - **Formula**: Total duration divided by number of days
249 /// - **Significance**: Daily hour target and performance benchmarking
250 /// - **Accuracy**: Reflects realistic daily work expectations
251 ///
252 /// ## Data Preparation
253 ///
254 /// Before calculation, the method:
255 /// 1. **Sorts** summaries by date for consistent processing
256 /// 2. **Validates** data integrity and completeness
257 /// 3. **Handles** edge cases like empty collections
258 /// 4. **Optimizes** calculations for performance
259 ///
260 /// ## Return Value Structure
261 ///
262 /// Returns a tuple containing:
263 /// - **Enhanced Collection**: Sorted and processed summary data
264 /// - **Total Duration**: Sum of all daily durations
265 /// - **Average Duration**: Mean daily duration across all days
266 ///
267 /// # Returns
268 ///
269 /// A tuple of `(Self, Duration, Duration)` where:
270 /// - First element: Processed and sorted summary collection
271 /// - Second element: Total duration across all days
272 /// - Third element: Average duration per day
273 ///
274 /// # Examples
275 ///
276 /// ```rust,no_run
277 /// let (processed_summaries, total_hours, average_daily) = summaries
278 /// .add_rest_dates(rest_dates, Duration::hours(8))
279 /// .calculate_totals();
280 ///
281 /// println!("Total monthly hours: {}", format_duration(&total_hours));
282 /// println!("Average daily hours: {}", format_duration(&average_daily));
283 /// ```
284 fn calculate_totals(self) -> (Self, Duration, Duration)
285 where
286 Self: Sized;
287}
288
289impl SummaryCalculator for Vec<DailySummary> {
290 /// Integrates company rest days into the daily summary collection.
291 ///
292 /// This implementation provides the standard logic for incorporating
293 /// organizational rest days into monthly work summaries. It ensures
294 /// complete calendar coverage while preserving existing work data.
295 ///
296 /// ## Implementation Details
297 ///
298 /// ### Duplicate Detection
299 /// Uses an efficient lookup strategy:
300 /// - Creates a temporary set of existing dates for O(1) lookups
301 /// - Checks each rest date against existing summaries
302 /// - Only adds entries for truly missing dates
303 ///
304 /// ### Memory Efficiency
305 /// - Pre-allocates space for new entries to minimize reallocations
306 /// - Uses iterator chains to avoid intermediate collections
307 /// - Processes rest dates in batch for optimal performance
308 ///
309 /// ### Data Consistency
310 /// - Maintains the same data structure and field meanings
311 /// - Uses consistent duration and productivity value assignment
312 /// - Preserves the ability to distinguish between work and rest days
313 ///
314 /// # Arguments
315 ///
316 /// * `rest_dates` - HashSet of dates to be added as rest days
317 /// * `duration` - Standard duration to assign (typically 8 hours)
318 ///
319 /// # Returns
320 ///
321 /// A new Vec<DailySummary> with rest days integrated
322 fn add_rest_dates(mut self, rest_dates: HashSet<NaiveDate>, duration: Duration) -> Self {
323 // Process each rest date for potential addition
324 for rest_date in rest_dates {
325 // Check if we already have a summary for this date
326 let date_exists = self.iter().any(|summary| summary.date == rest_date);
327
328 if !date_exists {
329 // Create a new summary entry for the rest day
330 self.push(DailySummary {
331 date: rest_date,
332 duration,
333 productivity: 0.0, // Rest days have zero productivity
334 });
335 }
336 }
337
338 self
339 }
340
341 /// Calculates total and average durations for the summary collection.
342 ///
343 /// This implementation provides comprehensive statistical analysis of
344 /// the monthly work data, producing both raw totals and meaningful
345 /// averages for reporting purposes.
346 ///
347 /// ## Processing Steps
348 ///
349 /// ### 1. Data Sorting
350 /// - Sorts summaries chronologically by date
351 /// - Ensures consistent ordering for reports and analysis
352 /// - Facilitates pattern recognition in work data
353 ///
354 /// ### 2. Total Calculation
355 /// - Uses iterator fold for efficient aggregation
356 /// - Handles Duration arithmetic correctly
357 /// - Accumulates across all days in the collection
358 ///
359 /// ### 3. Average Calculation
360 /// - Divides total by actual number of days
361 /// - Handles edge case of empty collections gracefully
362 /// - Provides realistic daily work expectations
363 ///
364 /// ## Error Handling
365 ///
366 /// - **Empty Collection**: Returns zero values for both total and average
367 /// - **Invalid Durations**: Negative durations are treated as zero
368 /// - **Overflow Protection**: Uses checked arithmetic where appropriate
369 ///
370 /// ## Performance Characteristics
371 ///
372 /// - **Time Complexity**: O(n log n) due to sorting requirement
373 /// - **Space Complexity**: O(1) additional space for calculations
374 /// - **Memory Usage**: In-place sorting minimizes memory overhead
375 ///
376 /// # Returns
377 ///
378 /// Tuple containing:
379 /// - Sorted summary collection
380 /// - Total duration across all days
381 /// - Average duration per day
382 fn calculate_totals(mut self) -> (Self, Duration, Duration) {
383 // Sort summaries chronologically for consistent presentation
384 self.sort_by_key(|summary| summary.date);
385
386 // Calculate total duration across all days
387 let total_duration = self.iter().fold(Duration::zero(), |accumulator, summary| accumulator + summary.duration);
388
389 // Calculate average duration per day
390 let day_count = self.len() as i64;
391 let average_duration = if day_count > 0 {
392 // Calculate average by dividing total seconds by number of days
393 Duration::seconds(total_duration.num_seconds() / day_count)
394 } else {
395 // Handle empty collection case
396 Duration::zero()
397 };
398
399 (self, total_duration, average_duration)
400 }
401}
402
403/// Trait for formatting calculated summaries into human-readable output.
404///
405/// This trait provides the final transformation step in the summary processing
406/// pipeline, converting calculated statistics into formatted strings suitable
407/// for display, reporting, and export. It bridges the gap between business
408/// logic and presentation layer requirements.
409///
410/// ## Design Goals
411///
412/// - **User-Friendly Formatting**: Convert technical data into readable formats
413/// - **Consistent Presentation**: Standardized formatting across the application
414/// - **Flexible Output**: Support multiple display contexts and requirements
415/// - **Localization Ready**: Structure that can support future localization needs
416///
417/// ## Output Formats
418///
419/// The trait produces multiple output formats:
420/// - **Daily Breakdown**: Day-by-day duration and productivity information
421/// - **Summary Statistics**: Total and average values for the entire period
422/// - **Structured Data**: Hash maps for flexible data access and manipulation
423///
424/// ## Integration Points
425///
426/// Formatted output is used by:
427/// - **Console Display**: Terminal-based monthly summaries
428/// - **Report Generation**: PDF and HTML report creation
429/// - **Export Functions**: CSV and JSON data export
430/// - **API Responses**: Web service and integration endpoints
431pub trait SummaryFormatter {
432 /// Formats summary data into comprehensive display-ready output.
433 ///
434 /// This method performs the final transformation of calculated summary
435 /// data into formatted strings suitable for human consumption. It handles
436 /// duration formatting, percentage display, and summary statistics
437 /// presentation.
438 ///
439 /// ## Output Structure
440 ///
441 /// Returns a tuple containing three components:
442 ///
443 /// ### 1. Daily Summary Map
444 /// - **Key**: Date for each day in the summary period
445 /// - **Value**: Tuple of (formatted_duration, formatted_productivity)
446 /// - **Purpose**: Day-by-day breakdown for detailed analysis
447 /// - **Format**: Consistent "HH:MM" duration and "XX.X%" productivity
448 ///
449 /// ### 2. Total Duration String
450 /// - **Content**: Formatted total of all daily durations
451 /// - **Format**: "HH:MM" representation of total work hours
452 /// - **Use Case**: Monthly hour totals for payroll and reporting
453 ///
454 /// ### 3. Average Duration String
455 /// - **Content**: Formatted average daily duration
456 /// - **Format**: "HH:MM" representation of typical daily hours
457 /// - **Use Case**: Performance benchmarking and planning
458 ///
459 /// ## Formatting Standards
460 ///
461 /// ### Duration Format
462 /// - **Pattern**: "HH:MM" (hours:minutes)
463 /// - **Examples**: "08:30", "07:45", "09:15"
464 /// - **Zero Handling**: "00:00" for zero durations
465 /// - **Large Values**: Properly handles >24 hour totals
466 ///
467 /// ### Productivity Format
468 /// - **Pattern**: "XX.X%" (percentage with one decimal place)
469 /// - **Examples**: "85.5%", "92.1%", "78.0%"
470 /// - **Range**: 0.0% to 100.0% (theoretical maximum)
471 /// - **Precision**: One decimal place for meaningful granularity
472 ///
473 /// # Returns
474 ///
475 /// A tuple of `(HashMap<NaiveDate, (String, String)>, String, String)` containing:
476 /// - Daily breakdown map with formatted duration and productivity
477 /// - Formatted total duration string
478 /// - Formatted average duration string
479 ///
480 /// # Examples
481 ///
482 /// ```rust,no_run
483 /// let (daily_map, total_str, avg_str) = calculated_summaries.format_summary();
484 ///
485 /// // Access daily data
486 /// for (date, (duration, productivity)) in daily_map {
487 /// println!("{}: {} hours ({})", date, duration, productivity);
488 /// }
489 ///
490 /// // Display summary statistics
491 /// println!("Total: {}, Average: {}", total_str, avg_str);
492 /// ```
493 fn format_summary(&self) -> (HashMap<NaiveDate, (String, String)>, String, String);
494}
495
496impl SummaryFormatter for (Vec<DailySummary>, Duration, Duration) {
497 /// Formats the complete summary tuple into display-ready output.
498 ///
499 /// This implementation provides comprehensive formatting for monthly
500 /// summary data, handling both daily breakdowns and aggregate statistics
501 /// with consistent formatting standards throughout.
502 ///
503 /// ## Processing Logic
504 ///
505 /// ### Daily Summary Processing
506 /// - Iterates through each daily summary in the collection
507 /// - Applies consistent duration formatting using shared utilities
508 /// - Formats productivity percentages with appropriate precision
509 /// - Creates a lookup map for efficient access by date
510 ///
511 /// ### Aggregate Formatting
512 /// - Uses the same duration formatter for consistency
513 /// - Handles large total durations (>24 hours) correctly
514 /// - Provides meaningful average calculations
515 ///
516 /// ## Implementation Details
517 ///
518 /// ### Memory Efficiency
519 /// - Pre-allocates HashMap with known capacity
520 /// - Uses iterator chains to minimize intermediate allocations
521 /// - Reuses formatting functions for consistency
522 ///
523 /// ### Error Handling
524 /// - Gracefully handles edge cases like zero durations
525 /// - Ensures consistent output format regardless of input quality
526 /// - Provides sensible defaults for missing or invalid data
527 ///
528 /// ### Consistency Guarantees
529 /// - All duration formatting uses the shared formatter
530 /// - Productivity formatting follows application-wide standards
531 /// - Output structure is consistent across all use cases
532 ///
533 /// # Returns
534 ///
535 /// Formatted summary data ready for display or further processing
536 fn format_summary(&self) -> (HashMap<NaiveDate, (String, String)>, String, String) {
537 // Extract components from the tuple
538 let (daily_summaries, total_duration, average_duration) = self;
539
540 // Format daily summaries into a lookup map
541 let daily_durations = daily_summaries
542 .iter()
543 .map(|summary| {
544 // Format duration using shared utility for consistency
545 let formatted_duration = format_duration(&summary.duration);
546
547 // Format productivity percentage with one decimal place
548 let formatted_productivity = format!("{:.1}%", summary.productivity);
549
550 // Create map entry
551 (summary.date, (formatted_duration, formatted_productivity))
552 })
553 .collect();
554
555 // Format aggregate statistics using shared utilities
556 let total_duration_str = format_duration(total_duration);
557 let average_duration_str = format_duration(average_duration);
558
559 (daily_durations, total_duration_str, average_duration_str)
560 }
561}