Skip to main content

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/// use kasl::libs::summary::{DailySummary, SummaryCalculator};
161/// use chrono::Duration;
162/// use std::collections::HashSet;
163///
164/// let summaries: Vec<DailySummary> = vec![];
165/// let company_holidays = HashSet::new();
166/// let default_hours = Duration::hours(8);
167///
168/// let result = summaries
169///     .add_rest_dates(company_holidays, default_hours)
170///     .calculate_totals();
171/// ```
172///
173/// This design enables:
174/// - **Composability**: Methods can be chained for complex transformations
175/// - **Immutability**: Each method returns a new collection
176/// - **Readability**: Clear sequence of data processing steps
177/// - **Testability**: Each transformation can be tested independently
178///
179/// ## Implementation Strategy
180///
181/// Implementations should handle:
182/// - **Data Completeness**: Ensure all calendar days are represented
183/// - **Duplicate Prevention**: Avoid duplicate entries for the same date
184/// - **Statistical Accuracy**: Provide meaningful aggregate calculations
185/// - **Performance**: Efficient processing of month-long datasets
186pub trait SummaryCalculator {
187    /// Integrates company rest days into the summary collection.
188    ///
189    /// This method ensures comprehensive monthly coverage by adding entries
190    /// for company holidays, weekends, and other non-working days that should
191    /// be included in monthly reports. It prevents gaps in monthly summaries
192    /// and provides complete calendar coverage.
193    ///
194    /// ## Integration Logic
195    ///
196    /// The method processes rest dates as follows:
197    /// 1. **Existence Check**: Verifies if a summary already exists for each rest date
198    /// 2. **Gap Filling**: Adds new summary entries for missing rest dates
199    /// 3. **Default Values**: Assigns standard work hours and zero productivity
200    /// 4. **Duplicate Prevention**: Skips rest dates that already have work data
201    ///
202    /// ## Default Duration Rationale
203    ///
204    /// Rest days are assigned default work hours for several reasons:
205    /// - **Payroll Integration**: Many payroll systems expect standard hours
206    /// - **Monthly Targets**: Helps meet monthly hour requirements
207    /// - **Benefit Allocation**: Paid holidays contribute to monthly totals
208    /// - **Reporting Consistency**: Provides predictable monthly hour calculations
209    ///
210    /// ## Productivity Handling
211    ///
212    /// Rest days are assigned 0.0% productivity because:
213    /// - **Accuracy**: No actual work was performed
214    /// - **Statistical Integrity**: Prevents artificial inflation of productivity metrics
215    /// - **Clear Distinction**: Differentiates between work days and rest days
216    /// - **Analysis**: Enables separate analysis of work vs. rest day patterns
217    ///
218    /// # Arguments
219    ///
220    /// * `rest_dates` - Set of dates that are company holidays or rest days
221    /// * `duration` - Default work duration to assign to rest days (typically 8 hours)
222    ///
223    /// # Returns
224    ///
225    /// Returns a new collection with rest days integrated, maintaining the original
226    /// work data while filling gaps with rest day entries.
227    ///
228    /// # Examples
229    ///
230    /// ```rust,no_run
231    /// use kasl::libs::summary::{DailySummary, SummaryCalculator};
232    /// use std::collections::HashSet;
233    /// use chrono::{Duration, NaiveDate};
234    ///
235    /// let work_summaries: Vec<DailySummary> = vec![];
236    /// let mut rest_dates = HashSet::new();
237    /// rest_dates.insert(NaiveDate::from_ymd_opt(2025, 8, 15).unwrap()); // Company holiday
238    ///
239    /// let enhanced_summaries = work_summaries
240    ///     .add_rest_dates(rest_dates, Duration::hours(8));
241    /// ```
242    fn add_rest_dates(self, rest_dates: HashSet<NaiveDate>, duration: Duration) -> Self;
243
244    /// Calculates comprehensive statistics for the summary collection.
245    ///
246    /// This method performs the final aggregation step to produce monthly
247    /// statistics including total work hours, average daily hours, and
248    /// other metrics needed for reporting and analysis.
249    ///
250    /// ## Statistical Calculations
251    ///
252    /// ### Total Duration
253    /// - **Sum**: Aggregates all daily durations in the collection
254    /// - **Includes**: Both work days and rest days with default hours
255    /// - **Purpose**: Monthly hour totals for payroll and reporting
256    ///
257    /// ### Average Duration
258    /// - **Formula**: Total duration divided by number of days
259    /// - **Significance**: Daily hour target and performance benchmarking
260    /// - **Accuracy**: Reflects realistic daily work expectations
261    ///
262    /// ## Data Preparation
263    ///
264    /// Before calculation, the method:
265    /// 1. **Sorts** summaries by date for consistent processing
266    /// 2. **Validates** data integrity and completeness
267    /// 3. **Handles** edge cases like empty collections
268    /// 4. **Optimizes** calculations for performance
269    ///
270    /// ## Return Value Structure
271    ///
272    /// Returns a tuple containing:
273    /// - **Enhanced Collection**: Sorted and processed summary data
274    /// - **Total Duration**: Sum of all daily durations
275    /// - **Average Duration**: Mean daily duration across all days
276    ///
277    /// # Returns
278    ///
279    /// A tuple of `(Self, Duration, Duration)` where:
280    /// - First element: Processed and sorted summary collection
281    /// - Second element: Total duration across all days
282    /// - Third element: Average duration per day
283    ///
284    /// # Examples
285    ///
286    /// ```rust,no_run
287    /// use kasl::libs::summary::{DailySummary, SummaryCalculator};
288    /// use kasl::libs::formatter::format_duration;
289    /// use std::collections::HashSet;
290    /// use chrono::Duration;
291    ///
292    /// let summaries: Vec<DailySummary> = vec![];
293    /// let rest_dates = HashSet::new();
294    ///
295    /// let (processed_summaries, total_hours, average_daily) = summaries
296    ///     .add_rest_dates(rest_dates, Duration::hours(8))
297    ///     .calculate_totals();
298    ///
299    /// println!("Total monthly hours: {}", format_duration(&total_hours));
300    /// println!("Average daily hours: {}", format_duration(&average_daily));
301    /// # let _ = processed_summaries;
302    /// ```
303    fn calculate_totals(self) -> (Self, Duration, Duration)
304    where
305        Self: Sized;
306}
307
308impl SummaryCalculator for Vec<DailySummary> {
309    /// Integrates company rest days into the daily summary collection.
310    ///
311    /// This implementation provides the standard logic for incorporating
312    /// organizational rest days into monthly work summaries. It ensures
313    /// complete calendar coverage while preserving existing work data.
314    ///
315    /// ## Implementation Details
316    ///
317    /// ### Duplicate Detection
318    /// Uses an efficient lookup strategy:
319    /// - Creates a temporary set of existing dates for O(1) lookups
320    /// - Checks each rest date against existing summaries
321    /// - Only adds entries for truly missing dates
322    ///
323    /// ### Memory Efficiency
324    /// - Pre-allocates space for new entries to minimize reallocations
325    /// - Uses iterator chains to avoid intermediate collections
326    /// - Processes rest dates in batch for optimal performance
327    ///
328    /// ### Data Consistency
329    /// - Maintains the same data structure and field meanings
330    /// - Uses consistent duration and productivity value assignment
331    /// - Preserves the ability to distinguish between work and rest days
332    ///
333    /// # Arguments
334    ///
335    /// * `rest_dates` - HashSet of dates to be added as rest days
336    /// * `duration` - Standard duration to assign (typically 8 hours)
337    ///
338    /// # Returns
339    ///
340    /// A new Vec<DailySummary> with rest days integrated
341    fn add_rest_dates(mut self, rest_dates: HashSet<NaiveDate>, duration: Duration) -> Self {
342        // Process each rest date for potential addition
343        for rest_date in rest_dates {
344            // Check if we already have a summary for this date
345            let date_exists = self.iter().any(|summary| summary.date == rest_date);
346
347            if !date_exists {
348                // Create a new summary entry for the rest day
349                self.push(DailySummary {
350                    date: rest_date,
351                    duration,
352                    productivity: 0.0, // Rest days have zero productivity
353                });
354            }
355        }
356
357        self
358    }
359
360    /// Calculates total and average durations for the summary collection.
361    ///
362    /// This implementation provides comprehensive statistical analysis of
363    /// the monthly work data, producing both raw totals and meaningful
364    /// averages for reporting purposes.
365    ///
366    /// ## Processing Steps
367    ///
368    /// ### 1. Data Sorting
369    /// - Sorts summaries chronologically by date
370    /// - Ensures consistent ordering for reports and analysis
371    /// - Facilitates pattern recognition in work data
372    ///
373    /// ### 2. Total Calculation
374    /// - Uses iterator fold for efficient aggregation
375    /// - Handles Duration arithmetic correctly
376    /// - Accumulates across all days in the collection
377    ///
378    /// ### 3. Average Calculation
379    /// - Divides total by actual number of days
380    /// - Handles edge case of empty collections gracefully
381    /// - Provides realistic daily work expectations
382    ///
383    /// ## Error Handling
384    ///
385    /// - **Empty Collection**: Returns zero values for both total and average
386    /// - **Invalid Durations**: Negative durations are treated as zero
387    /// - **Overflow Protection**: Uses checked arithmetic where appropriate
388    ///
389    /// ## Performance Characteristics
390    ///
391    /// - **Time Complexity**: O(n log n) due to sorting requirement
392    /// - **Space Complexity**: O(1) additional space for calculations
393    /// - **Memory Usage**: In-place sorting minimizes memory overhead
394    ///
395    /// # Returns
396    ///
397    /// Tuple containing:
398    /// - Sorted summary collection
399    /// - Total duration across all days
400    /// - Average duration per day
401    fn calculate_totals(mut self) -> (Self, Duration, Duration) {
402        // Sort summaries chronologically for consistent presentation
403        self.sort_by_key(|summary| summary.date);
404
405        // Calculate total duration across all days
406        let total_duration = self.iter().fold(Duration::zero(), |accumulator, summary| accumulator + summary.duration);
407
408        // Calculate average duration per day
409        let day_count = self.len() as i64;
410        let average_duration = if day_count > 0 {
411            // Calculate average by dividing total seconds by number of days
412            Duration::seconds(total_duration.num_seconds() / day_count)
413        } else {
414            // Handle empty collection case
415            Duration::zero()
416        };
417
418        (self, total_duration, average_duration)
419    }
420}
421
422/// Trait for formatting calculated summaries into human-readable output.
423///
424/// This trait provides the final transformation step in the summary processing
425/// pipeline, converting calculated statistics into formatted strings suitable
426/// for display, reporting, and export. It bridges the gap between business
427/// logic and presentation layer requirements.
428///
429/// ## Design Goals
430///
431/// - **User-Friendly Formatting**: Convert technical data into readable formats
432/// - **Consistent Presentation**: Standardized formatting across the application
433/// - **Flexible Output**: Support multiple display contexts and requirements
434/// - **Localization Ready**: Structure that can support future localization needs
435///
436/// ## Output Formats
437///
438/// The trait produces multiple output formats:
439/// - **Daily Breakdown**: Day-by-day duration and productivity information
440/// - **Summary Statistics**: Total and average values for the entire period
441/// - **Structured Data**: Hash maps for flexible data access and manipulation
442///
443/// ## Integration Points
444///
445/// Formatted output is used by:
446/// - **Console Display**: Terminal-based monthly summaries
447/// - **Report Generation**: PDF and HTML report creation
448/// - **Export Functions**: CSV and JSON data export
449/// - **API Responses**: Web service and integration endpoints
450pub trait SummaryFormatter {
451    /// Formats summary data into comprehensive display-ready output.
452    ///
453    /// This method performs the final transformation of calculated summary
454    /// data into formatted strings suitable for human consumption. It handles
455    /// duration formatting, percentage display, and summary statistics
456    /// presentation.
457    ///
458    /// ## Output Structure
459    ///
460    /// Returns a tuple containing three components:
461    ///
462    /// ### 1. Daily Summary Map
463    /// - **Key**: Date for each day in the summary period
464    /// - **Value**: Tuple of (formatted_duration, formatted_productivity)
465    /// - **Purpose**: Day-by-day breakdown for detailed analysis
466    /// - **Format**: Consistent "HH:MM" duration and "XX.X%" productivity
467    ///
468    /// ### 2. Total Duration String
469    /// - **Content**: Formatted total of all daily durations
470    /// - **Format**: "HH:MM" representation of total work hours
471    /// - **Use Case**: Monthly hour totals for payroll and reporting
472    ///
473    /// ### 3. Average Duration String
474    /// - **Content**: Formatted average daily duration
475    /// - **Format**: "HH:MM" representation of typical daily hours
476    /// - **Use Case**: Performance benchmarking and planning
477    ///
478    /// ## Formatting Standards
479    ///
480    /// ### Duration Format
481    /// - **Pattern**: "HH:MM" (hours:minutes)
482    /// - **Examples**: "08:30", "07:45", "09:15"
483    /// - **Zero Handling**: "00:00" for zero durations
484    /// - **Large Values**: Properly handles >24 hour totals
485    ///
486    /// ### Productivity Format
487    /// - **Pattern**: "XX.X%" (percentage with one decimal place)
488    /// - **Examples**: "85.5%", "92.1%", "78.0%"
489    /// - **Range**: 0.0% to 100.0% (theoretical maximum)
490    /// - **Precision**: One decimal place for meaningful granularity
491    ///
492    /// # Returns
493    ///
494    /// A tuple of `(HashMap<NaiveDate, (String, String)>, String, String)` containing:
495    /// - Daily breakdown map with formatted duration and productivity
496    /// - Formatted total duration string
497    /// - Formatted average duration string
498    ///
499    /// # Examples
500    ///
501    /// ```rust,no_run
502    /// use kasl::libs::summary::{DailySummary, SummaryFormatter};
503    /// use chrono::Duration;
504    ///
505    /// let calculated_summaries: (Vec<DailySummary>, Duration, Duration) =
506    ///     (vec![], Duration::zero(), Duration::zero());
507    ///
508    /// let (daily_map, total_str, avg_str) = calculated_summaries.format_summary();
509    ///
510    /// // Access daily data
511    /// for (date, (duration, productivity)) in daily_map {
512    ///     println!("{}: {} hours ({})", date, duration, productivity);
513    /// }
514    ///
515    /// // Display summary statistics
516    /// println!("Total: {}, Average: {}", total_str, avg_str);
517    /// ```
518    fn format_summary(&self) -> (HashMap<NaiveDate, (String, String)>, String, String);
519}
520
521impl SummaryFormatter for (Vec<DailySummary>, Duration, Duration) {
522    /// Formats the complete summary tuple into display-ready output.
523    ///
524    /// This implementation provides comprehensive formatting for monthly
525    /// summary data, handling both daily breakdowns and aggregate statistics
526    /// with consistent formatting standards throughout.
527    ///
528    /// ## Processing Logic
529    ///
530    /// ### Daily Summary Processing
531    /// - Iterates through each daily summary in the collection
532    /// - Applies consistent duration formatting using shared utilities
533    /// - Formats productivity percentages with appropriate precision
534    /// - Creates a lookup map for efficient access by date
535    ///
536    /// ### Aggregate Formatting
537    /// - Uses the same duration formatter for consistency
538    /// - Handles large total durations (>24 hours) correctly
539    /// - Provides meaningful average calculations
540    ///
541    /// ## Implementation Details
542    ///
543    /// ### Memory Efficiency
544    /// - Pre-allocates HashMap with known capacity
545    /// - Uses iterator chains to minimize intermediate allocations
546    /// - Reuses formatting functions for consistency
547    ///
548    /// ### Error Handling
549    /// - Gracefully handles edge cases like zero durations
550    /// - Ensures consistent output format regardless of input quality
551    /// - Provides sensible defaults for missing or invalid data
552    ///
553    /// ### Consistency Guarantees
554    /// - All duration formatting uses the shared formatter
555    /// - Productivity formatting follows application-wide standards
556    /// - Output structure is consistent across all use cases
557    ///
558    /// # Returns
559    ///
560    /// Formatted summary data ready for display or further processing
561    fn format_summary(&self) -> (HashMap<NaiveDate, (String, String)>, String, String) {
562        // Extract components from the tuple
563        let (daily_summaries, total_duration, average_duration) = self;
564
565        // Format daily summaries into a lookup map
566        let daily_durations = daily_summaries
567            .iter()
568            .map(|summary| {
569                // Format duration using shared utility for consistency
570                let formatted_duration = format_duration(&summary.duration);
571
572                // Format productivity percentage with one decimal place
573                let formatted_productivity = format!("{:.1}%", summary.productivity);
574
575                // Create map entry
576                (summary.date, (formatted_duration, formatted_productivity))
577            })
578            .collect();
579
580        // Format aggregate statistics using shared utilities
581        let total_duration_str = format_duration(total_duration);
582        let average_duration_str = format_duration(average_duration);
583
584        (daily_durations, total_duration_str, average_duration_str)
585    }
586}