benchkit/
templates.rs

1//! Template system for consistent documentation formatting
2//!
3//! Provides standardized report templates for common benchmarking scenarios
4//! with customizable sections while maintaining professional output quality.
5
6use crate ::measurement ::BenchmarkResult;
7use std ::collections ::HashMap;
8use std ::time ::SystemTime;
9
10type Result< T > = std ::result ::Result< T, Box< dyn std ::error ::Error > >;
11
12/// Historical benchmark results for regression analysis
13#[ derive( Debug, Clone ) ]
14pub struct HistoricalResults
15{
16  baseline_data: HashMap< String, BenchmarkResult >,
17  historical_runs: Vec< TimestampedResults >,
18}
19
20/// Timestamped benchmark results
21#[ derive( Debug, Clone ) ]
22pub struct TimestampedResults
23{
24  timestamp: SystemTime,
25  results: HashMap< String, BenchmarkResult >,
26}
27
28impl TimestampedResults
29{
30  /// Create new timestamped results
31  #[ must_use ]
32  pub fn new( timestamp: SystemTime, results: HashMap< String, BenchmarkResult > ) -> Self
33  {
34  Self { timestamp, results }
35 }
36
37  /// Get timestamp
38  #[ must_use ]
39  pub fn timestamp( &self ) -> SystemTime
40  {
41  self.timestamp
42 }
43
44  /// Get results
45  #[ must_use ]
46  pub fn results( &self ) -> &HashMap< String, BenchmarkResult >
47  {
48  &self.results
49 }
50}
51
52impl HistoricalResults
53{
54  /// Create new empty historical results
55  #[ must_use ]
56  pub fn new() -> Self
57  {
58  Self
59  {
60   baseline_data: HashMap ::new(),
61   historical_runs: Vec ::new(),
62 }
63 }
64
65  /// Set baseline data for comparison
66  #[ must_use ]
67  pub fn with_baseline( mut self, baseline: HashMap< String, BenchmarkResult > ) -> Self
68  {
69  self.baseline_data = baseline;
70  self
71 }
72
73  /// Add historical run data
74  #[ must_use ]
75  pub fn with_historical_run( mut self, timestamp: SystemTime, results: HashMap< String, BenchmarkResult > ) -> Self
76  {
77  self.historical_runs.push( TimestampedResults ::new( timestamp, results ) );
78  self
79 }
80
81  /// Add multiple historical runs
82  #[ must_use ]
83  pub fn with_historical_runs( mut self, runs: Vec< TimestampedResults > ) -> Self
84  {
85  self.historical_runs = runs;
86  self
87 }
88
89  /// Set the previous run (most recent historical run)
90  #[ must_use ]
91  pub fn with_previous_run( mut self, run: TimestampedResults ) -> Self
92  {
93  self.historical_runs = vec![ run ];
94  self
95 }
96
97  /// Get baseline data
98  #[ must_use ]
99  pub fn baseline_data( &self ) -> &HashMap< String, BenchmarkResult >
100  {
101  &self.baseline_data
102 }
103
104  /// Get historical runs
105  #[ must_use ]
106  pub fn historical_runs( &self ) -> &Vec< TimestampedResults >
107  {
108  &self.historical_runs
109 }
110}
111
112impl Default for HistoricalResults
113{
114  fn default() -> Self
115  {
116  Self ::new()
117 }
118}
119
120/// Baseline strategy for regression analysis
121#[ derive( Debug, Clone, PartialEq ) ]
122pub enum BaselineStrategy
123{
124  /// Compare against fixed baseline
125  FixedBaseline,
126  /// Compare against rolling average of historical runs
127  RollingAverage,
128  /// Compare against previous run
129  PreviousRun,
130}
131
132/// Performance trend detected in regression analysis
133#[ derive( Debug, Clone, PartialEq ) ]
134pub enum PerformanceTrend
135{
136  /// Performance improving over time
137  Improving,
138  /// Performance degrading over time
139  Degrading,
140  /// Performance stable within normal variation
141  Stable,
142}
143
144/// Regression analysis configuration and engine
145#[ derive( Debug, Clone ) ]
146pub struct RegressionAnalyzer
147{
148  /// Statistical significance threshold (default: 0.05)
149  significance_threshold: f64,
150  /// Number of historical runs to consider for trends (default: 5)
151  trend_window: usize,
152  /// Strategy for baseline comparison
153  baseline_strategy: BaselineStrategy,
154}
155
156impl RegressionAnalyzer
157{
158  /// Create new regression analyzer with default settings
159  #[ must_use ]
160  pub fn new() -> Self
161  {
162  Self
163  {
164   significance_threshold: 0.05,
165   trend_window: 5,
166   baseline_strategy: BaselineStrategy ::FixedBaseline,
167 }
168 }
169
170  /// Set baseline strategy
171  #[ must_use ]
172  pub fn with_baseline_strategy( mut self, strategy: BaselineStrategy ) -> Self
173  {
174  self.baseline_strategy = strategy;
175  self
176 }
177
178  /// Set significance threshold
179  #[ must_use ]
180  pub fn with_significance_threshold( mut self, threshold: f64 ) -> Self
181  {
182  self.significance_threshold = threshold;
183  self
184 }
185
186  /// Set trend window size
187  #[ must_use ]
188  pub fn with_trend_window( mut self, window: usize ) -> Self
189  {
190  self.trend_window = window;
191  self
192 }
193
194  /// Analyze current results against historical data
195  #[ must_use ]
196  pub fn analyze( &self, results: &HashMap< String, BenchmarkResult >, historical: &HistoricalResults ) -> RegressionReport
197  {
198  let mut report = RegressionReport ::new();
199
200  for ( operation_name, current_result ) in results
201  {
202   let analysis = self.analyze_single_operation( operation_name, current_result, historical );
203   report.add_operation_analysis( operation_name.clone(), analysis );
204 }
205
206  report
207 }
208
209  /// Analyze single operation
210  fn analyze_single_operation( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
211  {
212  match self.baseline_strategy
213  {
214   BaselineStrategy ::FixedBaseline => self.analyze_against_fixed_baseline( operation_name, current_result, historical ),
215   BaselineStrategy ::RollingAverage => self.analyze_against_rolling_average( operation_name, current_result, historical ),
216   BaselineStrategy ::PreviousRun => self.analyze_against_previous_run( operation_name, current_result, historical ),
217 }
218 }
219
220  /// Analyze against fixed baseline
221  fn analyze_against_fixed_baseline( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
222  {
223  if let Some( baseline_result ) = historical.baseline_data().get( operation_name )
224  {
225   let current_time = current_result.mean_time().as_secs_f64();
226   let baseline_time = baseline_result.mean_time().as_secs_f64();
227   let improvement_ratio = baseline_time / current_time;
228   
229   let trend = if improvement_ratio > 1.0 + self.significance_threshold
230   {
231  PerformanceTrend ::Improving
232 }
233   else if improvement_ratio < 1.0 - self.significance_threshold
234   {
235  PerformanceTrend ::Degrading
236 }
237   else
238   {
239  PerformanceTrend ::Stable
240 };
241
242   let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
243
244   OperationAnalysis
245   {
246  trend,
247  improvement_ratio,
248  is_statistically_significant: is_significant,
249  baseline_time: Some( baseline_time ),
250  has_historical_data: true,
251 }
252 }
253  else
254  {
255   OperationAnalysis ::no_data()
256 }
257 }
258
259  /// Analyze against rolling average  
260  fn analyze_against_rolling_average( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
261  {
262  let historical_runs = historical.historical_runs();
263  if historical_runs.is_empty()
264  {
265   return OperationAnalysis ::no_data();
266 }
267
268  // Calculate rolling average from recent runs
269  let recent_runs: Vec< _ > = historical_runs
270   .iter()
271   .rev() // Most recent first
272   .take( self.trend_window )
273   .filter_map( | run | run.results().get( operation_name ) )
274   .collect();
275
276  if recent_runs.is_empty()
277  {
278   return OperationAnalysis ::no_data();
279 }
280
281  let avg_time = recent_runs.iter()
282   .map( | result | result.mean_time().as_secs_f64() )
283   .sum :: < f64 >() / recent_runs.len() as f64;
284
285  let current_time = current_result.mean_time().as_secs_f64();
286  let improvement_ratio = avg_time / current_time;
287
288  let trend = if improvement_ratio > 1.0 + self.significance_threshold
289  {
290   PerformanceTrend ::Improving
291 }
292  else if improvement_ratio < 1.0 - self.significance_threshold
293  {
294   PerformanceTrend ::Degrading
295 }
296  else
297  {
298   PerformanceTrend ::Stable
299 };
300
301  let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
302
303  OperationAnalysis
304  {
305   trend,
306   improvement_ratio,
307   is_statistically_significant: is_significant,
308   baseline_time: Some( avg_time ),
309   has_historical_data: true,
310 }
311 }
312
313  /// Analyze against previous run
314  fn analyze_against_previous_run( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
315  {
316  let historical_runs = historical.historical_runs();
317  if let Some( previous_run ) = historical_runs.last()
318  {
319   if let Some( previous_result ) = previous_run.results().get( operation_name )
320   {
321  let current_time = current_result.mean_time().as_secs_f64();
322  let previous_time = previous_result.mean_time().as_secs_f64();
323  let improvement_ratio = previous_time / current_time;
324
325  let trend = if improvement_ratio > 1.0 + self.significance_threshold
326  {
327   PerformanceTrend ::Improving
328 }
329  else if improvement_ratio < 1.0 - self.significance_threshold
330  {
331   PerformanceTrend ::Degrading
332 }
333  else
334  {
335   PerformanceTrend ::Stable
336 };
337
338  let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
339
340  OperationAnalysis
341  {
342   trend,
343   improvement_ratio,
344   is_statistically_significant: is_significant,
345   baseline_time: Some( previous_time ),
346   has_historical_data: true,
347 }
348 }
349   else
350   {
351  OperationAnalysis ::no_data()
352 }
353 }
354  else
355  {
356   OperationAnalysis ::no_data()
357 }
358 }
359}
360
361impl Default for RegressionAnalyzer
362{
363  fn default() -> Self
364  {
365  Self ::new()
366 }
367}
368
369/// Analysis results for a single operation
370#[ derive( Debug, Clone ) ]
371pub struct OperationAnalysis
372{
373  trend: PerformanceTrend,
374  improvement_ratio: f64,
375  is_statistically_significant: bool,
376  baseline_time: Option< f64 >,
377  has_historical_data: bool,
378}
379
380impl OperationAnalysis
381{
382  /// Create analysis indicating no historical data available
383  #[ must_use ]
384  fn no_data() -> Self
385  {
386  Self
387  {
388   trend: PerformanceTrend ::Stable,
389   improvement_ratio: 1.0,
390   is_statistically_significant: false,
391   baseline_time: None,
392   has_historical_data: false,
393 }
394 }
395}
396
397/// Complete regression analysis report
398#[ derive( Debug, Clone ) ]
399pub struct RegressionReport
400{
401  operations: HashMap< String, OperationAnalysis >,
402}
403
404impl RegressionReport
405{
406  /// Create new regression report
407  #[ must_use ]
408  fn new() -> Self
409  {
410  Self
411  {
412   operations: HashMap ::new(),
413 }
414 }
415
416  /// Add analysis for an operation
417  fn add_operation_analysis( &mut self, operation: String, analysis: OperationAnalysis )
418  {
419  self.operations.insert( operation, analysis );
420 }
421
422  /// Check if any operations have significant changes
423  #[ must_use ]
424  pub fn has_significant_changes( &self ) -> bool
425  {
426  self.operations.values().any( | analysis | analysis.is_statistically_significant )
427 }
428
429  /// Get trend for specific operation
430  #[ must_use ]
431  pub fn get_trend_for( &self, operation: &str ) -> Option< PerformanceTrend >
432  {
433  self.operations.get( operation ).map( | analysis | analysis.trend.clone() )
434 }
435
436  /// Check if operation has statistically significant changes
437  #[ must_use ]
438  pub fn is_statistically_significant( &self, operation: &str ) -> bool
439  {
440  self.operations.get( operation )
441   .is_some_and( | analysis | analysis.is_statistically_significant )
442 }
443
444  /// Check if operation has historical data
445  #[ must_use ]
446  pub fn has_historical_data( &self, operation: &str ) -> bool
447  {
448  self.operations.get( operation )
449   .is_some_and( | analysis | analysis.has_historical_data )
450 }
451
452  /// Check if report has previous run data (for PreviousRun strategy)
453  #[ must_use ]
454  pub fn has_previous_run_data( &self ) -> bool
455  {
456  self.operations.values().any( | analysis | analysis.has_historical_data )
457 }
458
459  /// Format report as markdown
460  #[ must_use ]
461  pub fn format_markdown( &self ) -> String
462  {
463  let mut output = String ::new();
464
465  output.push_str( "### Performance Comparison Against Baseline\n\n" );
466
467  for ( operation_name, analysis ) in &self.operations
468  {
469   if !analysis.has_historical_data
470   {
471  output.push_str( &format!( 
472   "**{}** : â„šī¸ **New operation** - no baseline data available for comparison\n\n",
473   operation_name
474 ) );
475  continue;
476 }
477
478   if let Some( _baseline_time ) = analysis.baseline_time
479   {
480  let improvement_percent = ( analysis.improvement_ratio - 1.0 ) * 100.0;
481  
482  match analysis.trend
483  {
484   PerformanceTrend ::Improving =>
485   {
486  output.push_str( &format!( 
487   "**{}** : 🎉 **Performance improvement detected** - {:.1}% faster than baseline\n\n",
488   operation_name,
489   improvement_percent
490 ) );
491 },
492   PerformanceTrend ::Degrading =>
493   {
494  output.push_str( &format!( 
495   "**{}** : âš ī¸ **Performance regression detected** - {:.1}% slower than baseline\n\n",
496   operation_name,
497   improvement_percent.abs()
498 ) );
499 },
500   PerformanceTrend ::Stable =>
501   {
502  output.push_str( &format!( 
503   "**{}** : ✅ **Performance stable** - within normal variation of baseline\n\n",
504   operation_name
505 ) );
506 },
507 }
508 }
509 }
510
511  output.push_str( "### Analysis Summary & Recommendations\n\n" );
512  output.push_str( "Regression analysis complete. See individual operation results above for detailed findings.\n\n" );
513
514  output
515 }
516}
517
518/// Trait for report template generation
519pub trait ReportTemplate
520{
521  /// Generate the report content from benchmark results
522  fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >;
523}
524
525/// Standard performance benchmark report template
526#[ derive( Debug, Clone ) ]
527pub struct PerformanceReport
528{
529  /// Report title
530  title: String,
531  /// Context description for the benchmarks
532  context: Option< String >,
533  /// Whether to include detailed statistical analysis
534  include_statistical_analysis: bool,
535  /// Whether to include regression analysis section
536  include_regression_analysis: bool,
537  /// Custom sections to include
538  custom_sections: Vec< CustomSection >,
539  /// Historical data for regression analysis
540  historical_data: Option< HistoricalResults >,
541}
542
543impl PerformanceReport
544{
545  /// Create new performance report template
546  #[ must_use ]
547  pub fn new() -> Self
548  {
549  Self
550  {
551   title: "Performance Analysis".to_string(),
552   context: None,
553   include_statistical_analysis: true,
554   include_regression_analysis: false,
555   custom_sections: Vec ::new(),
556   historical_data: None,
557 }
558 }
559
560  /// Set the report title
561  #[ must_use ]
562  pub fn title( mut self, title: impl Into< String > ) -> Self
563  {
564  self.title = title.into();
565  self
566 }
567
568  /// Add context description
569  #[ must_use ]
570  pub fn add_context( mut self, context: impl Into< String > ) -> Self
571  {
572  self.context = Some( context.into() );
573  self
574 }
575
576  /// Enable or disable statistical analysis section
577  #[ must_use ]
578  pub fn include_statistical_analysis( mut self, include: bool ) -> Self
579  {
580  self.include_statistical_analysis = include;
581  self
582 }
583
584  /// Enable or disable regression analysis section
585  #[ must_use ]
586  pub fn include_regression_analysis( mut self, include: bool ) -> Self
587  {
588  self.include_regression_analysis = include;
589  self
590 }
591
592  /// Add custom section to the report
593  #[ must_use ]
594  pub fn add_custom_section( mut self, section: CustomSection ) -> Self
595  {
596  self.custom_sections.push( section );
597  self
598 }
599
600  /// Set historical data for regression analysis
601  #[ must_use ]
602  pub fn with_historical_data( mut self, historical: HistoricalResults ) -> Self
603  {
604  self.historical_data = Some( historical );
605  self
606 }
607}
608
609impl Default for PerformanceReport
610{
611  fn default() -> Self
612  {
613  Self ::new()
614 }
615}
616
617impl ReportTemplate for PerformanceReport
618{
619  fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >
620  {
621  let mut output = String ::new();
622
623  // Title and context
624  output.push_str( &format!( "# {}\n\n", self.title ) );
625  
626  if let Some( ref context ) = self.context
627  {
628   output.push_str( &format!( "*{}*\n\n", context ) );
629 }
630
631  if results.is_empty()
632  {
633   output.push_str( "No benchmark results available.\n" );
634   return Ok( output );
635 }
636
637  // Executive Summary
638  output.push_str( "## Executive Summary\n\n" );
639  self.add_executive_summary( &mut output, results );
640
641  // Performance Results Table
642  output.push_str( "## Performance Results\n\n" );
643  self.add_performance_table( &mut output, results );
644
645  // Statistical Analysis (optional)
646  if self.include_statistical_analysis
647  {
648   output.push_str( "## Statistical Analysis\n\n" );
649   self.add_statistical_analysis( &mut output, results );
650 }
651
652  // Regression Analysis (optional)
653  if self.include_regression_analysis
654  {
655   output.push_str( "## Regression Analysis\n\n" );
656   self.add_regression_analysis( &mut output, results );
657 }
658
659  // Custom sections
660  for section in &self.custom_sections
661  {
662   output.push_str( &format!( "## {}\n\n", section.title ) );
663   output.push_str( &section.content );
664   output.push_str( "\n\n" );
665 }
666
667  // Methodology footer
668  output.push_str( "## Methodology\n\n" );
669  self.add_methodology_note( &mut output );
670
671  Ok( output )
672 }
673}
674
675impl PerformanceReport
676{
677  /// Add executive summary section
678  fn add_executive_summary( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
679  {
680  let total_tests = results.len();
681  let reliable_tests = results.values().filter( | r | r.is_reliable() ).count();
682  let reliability_rate = ( reliable_tests as f64 / total_tests as f64 ) * 100.0;
683
684  output.push_str( &format!( "- **Total operations benchmarked** : {}\n", total_tests ) );
685  output.push_str( &format!( "- **Statistically reliable results** : {}/{} ({:.1}%)\n", 
686   reliable_tests, total_tests, reliability_rate ) );
687
688  if let Some( ( fastest_name, fastest_result ) ) = self.find_fastest( results )
689  {
690   output.push_str( &format!( "- **Best performing operation** : {} ({:.2?})\n", 
691  fastest_name, fastest_result.mean_time() ) );
692 }
693
694  if results.len() > 1
695  {
696   if let Some( ( slowest_name, slowest_result ) ) = self.find_slowest( results )
697   {
698  if let Some( ( fastest_name_inner, fastest_result ) ) = self.find_fastest( results )
699  {
700   let ratio = slowest_result.mean_time().as_secs_f64() / fastest_result.mean_time().as_secs_f64();
701   output.push_str( &format!( "- **Performance range** : {:.1}x difference ({} vs {})\n", 
702  ratio, fastest_name_inner, slowest_name ) );
703 }
704 }
705 }
706
707  output.push_str( "\n" );
708 }
709
710  /// Add performance results table
711  fn add_performance_table( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
712  {
713  output.push_str( "| Operation | Mean Time | 95% CI | Ops/sec | CV | Reliability | Samples |\n" );
714  output.push_str( "|-----------|-----------|--------|---------|----|-----------|---------|\n" );
715
716  // Sort by performance
717  let mut sorted_results: Vec< _ > = results.iter().collect();
718  sorted_results.sort_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) );
719
720  for ( name, result ) in sorted_results
721  {
722   let ( ci_lower, ci_upper ) = result.confidence_interval_95();
723   let cv = result.coefficient_of_variation();
724   let reliability = if result.is_reliable() { "✅" } else { "âš ī¸" };
725
726   output.push_str( &format!(
727  "| {} | {:.2?} | [{:.2?} - {:.2?}] | {:.0} | {:.1}% | {} | {} |\n",
728  name,
729  result.mean_time(),
730  ci_lower,
731  ci_upper,
732  result.operations_per_second(),
733  cv * 100.0,
734  reliability,
735  result.times.len()
736 ) );
737 }
738
739  output.push_str( "\n" );
740 }
741
742  /// Add statistical analysis section
743  fn add_statistical_analysis( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
744  {
745  let mut high_quality = Vec ::new();
746  let mut needs_improvement = Vec ::new();
747
748  for ( name, result ) in results
749  {
750   if result.is_reliable()
751   {
752  high_quality.push( name );
753 }
754   else
755   {
756  let cv = result.coefficient_of_variation();
757  let sample_size = result.times.len();
758  let mut issues = Vec ::new();
759
760  if sample_size < 10
761  {
762   issues.push( "insufficient samples" );
763 }
764  if cv > 0.1
765  {
766   issues.push( "high variability" );
767 }
768
769  needs_improvement.push( ( name, issues ) );
770 }
771 }
772
773  if !high_quality.is_empty()
774  {
775   output.push_str( "### ✅ Reliable Results\n" );
776   output.push_str( "*These measurements meet research-grade statistical standards*\n\n" );
777   for name in high_quality
778   {
779  let result = &results[ name ];
780  output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
781   name,
782   result.times.len(),
783   result.coefficient_of_variation() * 100.0 ) );
784 }
785   output.push_str( "\n" );
786 }
787
788  if !needs_improvement.is_empty()
789  {
790   output.push_str( "### âš ī¸ Measurements Needing Attention\n" );
791   output.push_str( "*Consider additional measurements for more reliable conclusions*\n\n" );
792   for ( name, issues ) in needs_improvement
793   {
794  output.push_str( &format!( "- **{}** : {}\n", name, issues.join( ", " ) ) );
795 }
796   output.push_str( "\n" );
797 }
798 }
799
800  /// Add regression analysis section
801  fn add_regression_analysis( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
802  {
803  if let Some( ref historical ) = self.historical_data
804  {
805   // Use RegressionAnalyzer for enhanced analysis capabilities
806   let analyzer = RegressionAnalyzer ::new()
807  .with_baseline_strategy( BaselineStrategy ::FixedBaseline )
808  .with_significance_threshold( 0.05 );
809   
810   let regression_report = analyzer.analyze( results, historical );
811   let markdown_output = regression_report.format_markdown();
812   
813   output.push_str( &markdown_output );
814
815   // Add enhanced recommendations with more context
816   self.add_enhanced_recommendations( output, &regression_report, results );
817 }
818  else
819  {
820   // Fallback to placeholder when no historical data available
821   output.push_str( "**Regression Analysis** : Not yet implemented. Historical baseline data required.\n\n" );
822   output.push_str( "**📖 Setup Guide** : See [`usage.md`](usage.md) for mandatory standards and requirements on: \n" );
823   output.push_str( "- Historical data collection and baseline management\n" );
824   output.push_str( "- Statistical analysis requirements and validation criteria\n" );
825   output.push_str( "- Integration with CI/CD pipelines for automated regression detection\n" );
826   output.push_str( "- Documentation automation best practices\n\n" );
827 }
828 }
829
830
831  /// Add enhanced recommendations based on regression report
832  fn add_enhanced_recommendations( &self, output: &mut String, regression_report: &RegressionReport, results: &HashMap< String, BenchmarkResult > )
833  {
834  // Collect operations by trend for enhanced reporting
835  let mut improving_ops = Vec ::new();
836  let mut degrading_ops = Vec ::new();
837  let mut stable_ops = Vec ::new();
838  let mut new_ops = Vec ::new();
839
840  for operation_name in results.keys()
841  {
842   match regression_report.get_trend_for( operation_name )
843   {
844  Some( PerformanceTrend ::Improving ) =>
845  {
846   if regression_report.is_statistically_significant( operation_name )
847   {
848  improving_ops.push( operation_name );
849 }
850 },
851  Some( PerformanceTrend ::Degrading ) =>
852  {
853   if regression_report.is_statistically_significant( operation_name )
854   {
855  degrading_ops.push( operation_name );
856 }
857 },
858  Some( PerformanceTrend ::Stable ) =>
859  {
860   stable_ops.push( operation_name );
861 },
862  None =>
863  {
864   if !regression_report.has_historical_data( operation_name )
865   {
866  new_ops.push( operation_name );
867 }
868 },
869 }
870 }
871
872  if !improving_ops.is_empty() || !degrading_ops.is_empty() || regression_report.has_significant_changes()
873  {
874   output.push_str( "### 📊 **Statistical Analysis Summary**\n\n" );
875   
876   if regression_report.has_significant_changes()
877   {
878  output.push_str( "**Statistically Significant Changes Detected** : This analysis identified performance changes that exceed normal measurement variance.\n\n" );
879 }
880   else
881   {
882  output.push_str( "**No Statistically Significant Changes** : All performance variations are within expected measurement noise.\n\n" );
883 }
884 }
885
886  if !improving_ops.is_empty()
887  {
888   output.push_str( "### đŸŽ¯ **Performance Optimization Insights**\n\n" );
889   output.push_str( "The following operations show statistically significant improvements: \n" );
890   for op in &improving_ops
891   {
892  output.push_str( &format!( "- **{}** : Consider documenting optimization techniques for knowledge sharing\n", op ) );
893 }
894   output.push_str( "\n**Next Steps** : Update performance baselines and validate improvements under production conditions.\n\n" );
895 }
896
897  if !degrading_ops.is_empty()
898  {
899   output.push_str( "### âš ī¸ **Regression Investigation Required**\n\n" );
900   output.push_str( "**Critical** : The following operations show statistically significant performance degradation: \n" );
901   for op in &degrading_ops
902   {
903  output.push_str( &format!( "- **{}** : Requires immediate investigation\n", op ) );
904 }
905   output.push_str( "\n**Recommended Actions** : \n" );
906   output.push_str( "1. **Profile regressed operations** to identify bottlenecks\n" );
907   output.push_str( "2. **Review recent code changes** affecting these operations\n" );
908   output.push_str( "3. **Run additional validation** with increased sample sizes\n" );
909   output.push_str( "4. **Consider deployment hold** until regressions are resolved\n\n" );
910 }
911
912  // Add project-specific recommendations
913  output.push_str( "### 🔗 **Integration Resources**\n\n" );
914  output.push_str( "For enhanced regression analysis capabilities: \n" );
915  output.push_str( "- **Configure baseline strategies** : Use `RegressionAnalyzer ::with_baseline_strategy()` for rolling averages or previous-run comparisons\n" );
916  output.push_str( "- **Adjust significance thresholds** : Use `with_significance_threshold()` for domain-specific sensitivity\n" );
917  output.push_str( "- **Historical data management** : Implement `TimestampedResults` for comprehensive trend analysis\n" );
918  output.push_str( "- **Automated monitoring** : Integrate with CI/CD pipelines for continuous performance validation\n\n" );
919 }
920
921  /// Add methodology note
922  fn add_methodology_note( &self, output: &mut String )
923  {
924  output.push_str( "**Statistical Reliability Criteria** : \n" );
925  output.push_str( "- Sample size â‰Ĩ 10 measurements\n" );
926  output.push_str( "- Coefficient of variation ≤ 10%\n" );
927  output.push_str( "- Maximum/minimum time ratio < 3.0x\n\n" );
928
929  output.push_str( "**Confidence Intervals** : 95% CI calculated using t-distribution\n" );
930  output.push_str( "**CV** : Coefficient of Variation (relative standard deviation)\n\n" );
931
932  output.push_str( "---\n" );
933  output.push_str( "*Generated by benchkit - Professional benchmarking toolkit*\n" );
934 }
935
936  /// Find fastest result
937  fn find_fastest< 'a >( &self, results: &'a HashMap< String, BenchmarkResult > ) -> Option< ( &'a String, &'a BenchmarkResult ) >
938  {
939  results.iter().min_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) )
940 }
941
942  /// Find slowest result
943  fn find_slowest< 'a >( &self, results: &'a HashMap< String, BenchmarkResult > ) -> Option< ( &'a String, &'a BenchmarkResult ) >
944  {
945  results.iter().max_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) )
946 }
947}
948
949/// Comparison report template for A/B testing scenarios
950#[ derive( Debug, Clone ) ]
951pub struct ComparisonReport
952{
953  /// Report title
954  title: String,
955  /// Baseline algorithm name
956  baseline: String,
957  /// Candidate algorithm name
958  candidate: String,
959  /// Statistical significance threshold (default: 0.05)
960  significance_threshold: f64,
961  /// Practical significance threshold (default: 0.10)
962  practical_significance_threshold: f64,
963}
964
965impl ComparisonReport
966{
967  /// Create new comparison report template
968  #[ must_use ]
969  pub fn new() -> Self
970  {
971  Self
972  {
973   title: "Performance Comparison".to_string(),
974   baseline: "Baseline".to_string(),
975   candidate: "Candidate".to_string(),
976   significance_threshold: 0.05,
977   practical_significance_threshold: 0.10,
978 }
979 }
980
981  /// Set the report title
982  #[ must_use ]
983  pub fn title( mut self, title: impl Into< String > ) -> Self
984  {
985  self.title = title.into();
986  self
987 }
988
989  /// Set baseline algorithm name
990  #[ must_use ]
991  pub fn baseline( mut self, baseline: impl Into< String > ) -> Self
992  {
993  self.baseline = baseline.into();
994  self
995 }
996
997  /// Set candidate algorithm name
998  #[ must_use ]
999  pub fn candidate( mut self, candidate: impl Into< String > ) -> Self
1000  {
1001  self.candidate = candidate.into();
1002  self
1003 }
1004
1005  /// Set statistical significance threshold
1006  #[ must_use ]
1007  pub fn significance_threshold( mut self, threshold: f64 ) -> Self
1008  {
1009  self.significance_threshold = threshold;
1010  self
1011 }
1012
1013  /// Set practical significance threshold
1014  #[ must_use ]
1015  pub fn practical_significance_threshold( mut self, threshold: f64 ) -> Self
1016  {
1017  self.practical_significance_threshold = threshold;
1018  self
1019 }
1020}
1021
1022impl Default for ComparisonReport
1023{
1024  fn default() -> Self
1025  {
1026  Self ::new()
1027 }
1028}
1029
1030impl ComparisonReport
1031{
1032  /// Get baseline name (for testing)
1033  #[ must_use ]
1034  pub fn baseline_name( &self ) -> &str
1035  {
1036  &self.baseline
1037 }
1038
1039  /// Get candidate name (for testing)
1040  #[ must_use ]
1041  pub fn candidate_name( &self ) -> &str
1042  {
1043  &self.candidate
1044 }
1045
1046  /// Get significance threshold (for testing)
1047  #[ must_use ]
1048  pub fn significance_threshold_value( &self ) -> f64
1049  {
1050  self.significance_threshold
1051 }
1052
1053  /// Get practical significance threshold (for testing)
1054  #[ must_use ]
1055  pub fn practical_significance_threshold_value( &self ) -> f64
1056  {
1057  self.practical_significance_threshold
1058 }
1059}
1060
1061impl ReportTemplate for ComparisonReport
1062{
1063  fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >
1064  {
1065  let mut output = String ::new();
1066
1067  output.push_str( &format!( "# {}\n\n", self.title ) );
1068
1069  // Get baseline and candidate results
1070  let baseline_result = results.get( &self.baseline )
1071   .ok_or_else( || -> Box< dyn std ::error ::Error > { format!( "Baseline result '{}' not found", self.baseline ).into() } )?;
1072  let candidate_result = results.get( &self.candidate )
1073   .ok_or_else( || -> Box< dyn std ::error ::Error > { format!( "Candidate result '{}' not found", self.candidate ).into() } )?;
1074
1075  // Calculate comparison metrics
1076  let baseline_time = baseline_result.mean_time().as_secs_f64();
1077  let candidate_time = candidate_result.mean_time().as_secs_f64();
1078  let improvement_ratio = baseline_time / candidate_time;
1079  let improvement_percent = ( improvement_ratio - 1.0 ) * 100.0;
1080
1081  // Executive summary
1082  output.push_str( "## Comparison Summary\n\n" );
1083  
1084  if improvement_ratio > 1.0 + self.practical_significance_threshold
1085  {
1086   output.push_str( &format!( "✅ **{} is {:.1}% faster** than {}\n\n", 
1087  self.candidate, improvement_percent, self.baseline ) );
1088 }
1089  else if improvement_ratio < 1.0 - self.practical_significance_threshold
1090  {
1091   let regression_percent = ( 1.0 - improvement_ratio ) * 100.0;
1092   output.push_str( &format!( "🚨 **{} is {:.1}% slower** than {}\n\n", 
1093  self.candidate, regression_percent, self.baseline ) );
1094 }
1095  else
1096  {
1097   output.push_str( &format!( "âš–ī¸ **No significant difference** between {} and {}\n\n", 
1098  self.baseline, self.candidate ) );
1099 }
1100
1101  // Detailed comparison table
1102  output.push_str( "## Detailed Comparison\n\n" );
1103  output.push_str( "| Algorithm | Mean Time | 95% CI | Ops/sec | CV | Samples | Reliability |\n" );
1104  output.push_str( "|-----------|-----------|--------|---------|----|---------|-----------|\n" );
1105
1106  for ( name, result ) in [ ( &self.baseline, baseline_result ), ( &self.candidate, candidate_result ) ]
1107  {
1108   let ( ci_lower, ci_upper ) = result.confidence_interval_95();
1109   let cv = result.coefficient_of_variation();
1110   let reliability = if result.is_reliable() { "✅" } else { "âš ī¸" };
1111
1112   output.push_str( &format!(
1113  "| {} | {:.2?} | [{:.2?} - {:.2?}] | {:.0} | {:.1}% | {} | {} |\n",
1114  name,
1115  result.mean_time(),
1116  ci_lower,
1117  ci_upper,
1118  result.operations_per_second(),
1119  cv * 100.0,
1120  result.times.len(),
1121  reliability
1122 ) );
1123 }
1124
1125  output.push_str( "\n" );
1126
1127  // Statistical analysis
1128  output.push_str( "## Statistical Analysis\n\n" );
1129  output.push_str( &format!( "- **Performance ratio** : {:.3}x\n", improvement_ratio ) );
1130  output.push_str( &format!( "- **Improvement** : {:.1}%\n", improvement_percent ) );
1131  
1132  // Confidence interval overlap analysis
1133  let baseline_ci = baseline_result.confidence_interval_95();
1134  let candidate_ci = candidate_result.confidence_interval_95();
1135  let ci_overlap = baseline_ci.1 >= candidate_ci.0 && candidate_ci.1 >= baseline_ci.0;
1136  
1137  if ci_overlap
1138  {
1139   output.push_str( "- **Statistical significance** : âš ī¸ Confidence intervals overlap - difference may not be statistically significant\n" );
1140 }
1141  else
1142  {
1143   output.push_str( "- **Statistical significance** : ✅ No confidence interval overlap - difference is likely statistically significant\n" );
1144 }
1145
1146  // Practical significance
1147  if improvement_percent.abs() >= self.practical_significance_threshold * 100.0
1148  {
1149   output.push_str( &format!( "- **Practical significance** : ✅ Difference exceeds {:.1}% threshold\n", 
1150  self.practical_significance_threshold * 100.0 ) );
1151 }
1152  else
1153  {
1154   output.push_str( &format!( "- **Practical significance** : âš ī¸ Difference below {:.1}% threshold\n", 
1155  self.practical_significance_threshold * 100.0 ) );
1156 }
1157
1158  output.push_str( "\n" );
1159
1160  // Reliability assessment
1161  output.push_str( "## Reliability Assessment\n\n" );
1162  
1163  if baseline_result.is_reliable() && candidate_result.is_reliable()
1164  {
1165   output.push_str( "✅ **Both measurements are statistically reliable** - conclusions can be drawn with confidence.\n\n" );
1166 }
1167  else
1168  {
1169   output.push_str( "âš ī¸ **One or both measurements have reliability concerns** - consider additional sampling.\n\n" );
1170   
1171   if !baseline_result.is_reliable()
1172   {
1173  output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
1174   self.baseline,
1175   baseline_result.times.len(),
1176   baseline_result.coefficient_of_variation() * 100.0 ) );
1177 }
1178   
1179   if !candidate_result.is_reliable()
1180   {
1181  output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
1182   self.candidate,
1183   candidate_result.times.len(),
1184   candidate_result.coefficient_of_variation() * 100.0 ) );
1185 }
1186   
1187   output.push_str( "\n" );
1188 }
1189
1190  // Methodology
1191  output.push_str( "## Methodology\n\n" );
1192  output.push_str( &format!( "**Significance Thresholds** : Statistical p < {}, Practical > {:.1}%\n", 
1193   self.significance_threshold, 
1194   self.practical_significance_threshold * 100.0 ) );
1195  output.push_str( "**Confidence Intervals** : 95% CI using t-distribution\n" );
1196  output.push_str( "**Reliability Criteria** : â‰Ĩ10 samples, CV ≤10%, max/min ratio <3x\n\n" );
1197
1198  output.push_str( "---\n" );
1199  output.push_str( "*Generated by benchkit - Professional benchmarking toolkit*\n" );
1200
1201  Ok( output )
1202 }
1203}
1204
1205/// Custom section for reports
1206#[ derive( Debug, Clone ) ]
1207pub struct CustomSection
1208{
1209  /// Section title
1210  pub title: String,
1211  /// Section content
1212  pub content: String,
1213}
1214
1215impl CustomSection
1216{
1217  /// Create new custom section
1218  #[ must_use ]
1219  pub fn new( title: impl Into< String >, content: impl Into< String > ) -> Self
1220  {
1221  Self
1222  {
1223   title: title.into(),
1224   content: content.into(),
1225 }
1226 }
1227}