Skip to main content

nargo_changes/
stats.rs

1#![warn(missing_docs)]
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7use crate::change_set::ChangeSet;
8use crate::types::ChangeType;
9
10/// Change statistics.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChangeStats {
13    /// Total number of change sets.
14    pub total_change_sets: usize,
15    /// Change sets grouped by type.
16    pub change_sets_by_type: HashMap<ChangeType, usize>,
17    /// Change sets grouped by package.
18    pub change_sets_by_package: HashMap<String, usize>,
19    /// Total number of breaking changes.
20    pub breaking_changes: usize,
21    /// Total number of features.
22    pub features: usize,
23    /// Total number of bug fixes.
24    pub bug_fixes: usize,
25    /// Total number of other changes.
26    pub other_changes: usize,
27    /// Average changes per package.
28    pub avg_changes_per_package: f64,
29    /// Most common change type.
30    pub most_common_change_type: Option<ChangeType>,
31    /// Most affected package.
32    pub most_affected_package: Option<String>,
33}
34
35/// Change trend data point.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ChangeTrendPoint {
38    /// The timestamp for this data point.
39    pub timestamp: DateTime<Utc>,
40    /// Number of change sets in this period.
41    pub change_set_count: usize,
42    /// Change sets by type in this period.
43    pub change_sets_by_type: HashMap<ChangeType, usize>,
44    /// Number of packages affected in this period.
45    pub packages_affected: usize,
46}
47
48/// Change trend analysis.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ChangeTrend {
51    /// The time period for the trend analysis.
52    pub time_period: String,
53    /// The trend data points.
54    pub data_points: Vec<ChangeTrendPoint>,
55    /// Overall statistics for the trend period.
56    pub overall_stats: ChangeStats,
57    /// Change rate over time (changes per day).
58    pub change_rate: f64,
59    /// Trend direction (positive, negative, or stable).
60    pub trend_direction: String,
61    /// Most active period.
62    pub most_active_period: Option<DateTime<Utc>>,
63}
64
65impl ChangeStats {
66    /// Creates a new ChangeStats instance from a list of change sets.
67    pub fn from_change_sets(change_sets: &[ChangeSet]) -> Self {
68        let total_change_sets = change_sets.len();
69        let mut change_sets_by_type = HashMap::new();
70        let mut change_sets_by_package = HashMap::new();
71        let mut breaking_changes = 0;
72        let mut features = 0;
73        let mut bug_fixes = 0;
74        let mut other_changes = 0;
75
76        // Count changes by type and package
77        for change_set in change_sets {
78            // Count by type
79            *change_sets_by_type.entry(change_set.r#type.clone()).or_insert(0) += 1;
80
81            // Count by package
82            for package in &change_set.packages {
83                *change_sets_by_package.entry(package.clone()).or_insert(0) += 1;
84            }
85
86            // Count specific types
87            match change_set.r#type {
88                ChangeType::Breaking => breaking_changes += 1,
89                ChangeType::Feature => features += 1,
90                ChangeType::Fix => bug_fixes += 1,
91                _ => other_changes += 1,
92            }
93        }
94
95        // Calculate average changes per package
96        let avg_changes_per_package = if change_sets_by_package.is_empty() { 0.0 } else { change_sets.iter().map(|cs| cs.packages.len()).sum::<usize>() as f64 / change_sets_by_package.len() as f64 };
97
98        // Find most common change type
99        let most_common_change_type = change_sets_by_type.iter().max_by(|a, b| a.1.cmp(b.1)).map(|(ctype, _)| ctype.clone());
100
101        // Find most affected package
102        let most_affected_package = change_sets_by_package.iter().max_by(|a, b| a.1.cmp(b.1)).map(|(pkg, _)| pkg.clone());
103
104        Self { total_change_sets, change_sets_by_type, change_sets_by_package, breaking_changes, features, bug_fixes, other_changes, avg_changes_per_package, most_common_change_type, most_affected_package }
105    }
106
107    /// Generates a summary of the change statistics.
108    pub fn generate_summary(&self) -> String {
109        let mut summary = String::new();
110
111        summary.push_str(&format!("Change Statistics Summary:\n"));
112        summary.push_str(&format!("- Total Change Sets: {}\n", self.total_change_sets));
113        summary.push_str(&format!("- Breaking Changes: {}\n", self.breaking_changes));
114        summary.push_str(&format!("- Features: {}\n", self.features));
115        summary.push_str(&format!("- Bug Fixes: {}\n", self.bug_fixes));
116        summary.push_str(&format!("- Other Changes: {}\n", self.other_changes));
117        summary.push_str(&format!("- Average Changes per Package: {:.2}\n", self.avg_changes_per_package));
118
119        if let Some(ctype) = &self.most_common_change_type {
120            summary.push_str(&format!("- Most Common Change Type: {}\n", ctype.as_str()));
121        }
122
123        if let Some(pkg) = &self.most_affected_package {
124            summary.push_str(&format!("- Most Affected Package: {}\n", pkg));
125        }
126
127        summary.push_str("\nChanges by Type:\n");
128        for (ctype, count) in &self.change_sets_by_type {
129            summary.push_str(&format!("- {}: {}\n", ctype.as_str(), count));
130        }
131
132        summary.push_str("\nChanges by Package:\n");
133        for (pkg, count) in &self.change_sets_by_package {
134            summary.push_str(&format!("- {}: {}\n", pkg, count));
135        }
136
137        summary
138    }
139}
140
141impl ChangeTrend {
142    /// Creates a new ChangeTrend instance from a list of change sets and a time period.
143    pub fn from_change_sets(change_sets: &[ChangeSet], time_period: &str) -> Self {
144        // For simplicity, we'll create a basic trend analysis
145        // In a real implementation, you would group changes by time periods
146        let stats = ChangeStats::from_change_sets(change_sets);
147
148        // Create a single data point for now
149        let data_point = ChangeTrendPoint { timestamp: chrono::Utc::now(), change_set_count: change_sets.len(), change_sets_by_type: stats.change_sets_by_type.clone(), packages_affected: stats.change_sets_by_package.len() };
150
151        // Calculate change rate (changes per day)
152        // For simplicity, we'll assume a 30-day period
153        let change_rate = change_sets.len() as f64 / 30.0;
154
155        // Determine trend direction
156        let trend_direction = if change_rate > 1.0 {
157            "positive"
158        }
159        else if change_rate < 0.5 {
160            "negative"
161        }
162        else {
163            "stable"
164        };
165
166        Self { time_period: time_period.to_string(), data_points: vec![data_point], overall_stats: stats, change_rate, trend_direction: trend_direction.to_string(), most_active_period: Some(chrono::Utc::now()) }
167    }
168
169    /// Generates a summary of the change trend.
170    pub fn generate_summary(&self) -> String {
171        let mut summary = String::new();
172
173        summary.push_str(&format!("Change Trend Analysis ({}):\n", self.time_period));
174        summary.push_str(&format!("- Change Rate: {:.2} changes per day\n", self.change_rate));
175        summary.push_str(&format!("- Trend Direction: {}\n", self.trend_direction));
176
177        if let Some(period) = &self.most_active_period {
178            summary.push_str(&format!("- Most Active Period: {}\n", period.format("%Y-%m-%d %H:%M:%S")));
179        }
180
181        summary.push_str("\nOverall Statistics:\n");
182        summary.push_str(&self.overall_stats.generate_summary());
183
184        summary
185    }
186}