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}