Skip to main content

gitstack/stats/
mod.rs

1//! Statistics module
2//!
3//! Calculates per-author statistics and file change frequency for repositories
4
5mod analysis;
6mod health;
7mod quality;
8
9// =============================================================================
10// Re-exports from analysis
11// =============================================================================
12pub use analysis::{
13    calculate_activity_timeline, calculate_file_heatmap, calculate_ownership, calculate_stats,
14    ActivityTimeline, AuthorStats, CodeOwnership, CodeOwnershipEntry, FileHeatmap,
15    FileHeatmapEntry, RepoStats,
16};
17
18// =============================================================================
19// Re-exports from quality
20// =============================================================================
21pub use quality::{
22    calculate_change_coupling, calculate_impact_scores, calculate_quality_scores,
23    ChangeCouplingAnalysis, CommitImpactAnalysis, CommitImpactScore, CommitQualityAnalysis,
24    CommitQualityScore, FileCoupling,
25};
26
27// =============================================================================
28// Re-exports from health
29// =============================================================================
30pub use health::{
31    calculate_bus_factor, calculate_project_health, calculate_tech_debt, is_test_file,
32    AlertSeverity, ConfidenceLevel, HealthAlert, HealthAlertKind, HealthConfidence,
33    HealthScoreComponent, ProjectHealth,
34};
35
36// =============================================================================
37// Shared types (used across sub-modules)
38// =============================================================================
39
40/// Aggregation level (for heatmap display)
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub enum AggregationLevel {
43    /// File level (default)
44    #[default]
45    Files,
46    /// Shallow aggregation (up to 2 levels, e.g., src/auth/)
47    Shallow,
48    /// Deep aggregation (top level only, e.g., src/)
49    Deep,
50}
51
52impl AggregationLevel {
53    /// Switch to the next aggregation level
54    pub fn next(&self) -> Self {
55        match self {
56            AggregationLevel::Files => AggregationLevel::Shallow,
57            AggregationLevel::Shallow => AggregationLevel::Deep,
58            AggregationLevel::Deep => AggregationLevel::Files,
59        }
60    }
61
62    /// Switch to the previous aggregation level
63    pub fn prev(&self) -> Self {
64        match self {
65            AggregationLevel::Files => AggregationLevel::Deep,
66            AggregationLevel::Shallow => AggregationLevel::Files,
67            AggregationLevel::Deep => AggregationLevel::Shallow,
68        }
69    }
70
71    /// Get display name
72    pub fn display_name(&self) -> &'static str {
73        match self {
74            AggregationLevel::Files => "Files",
75            AggregationLevel::Shallow => "Directories (2 levels)",
76            AggregationLevel::Deep => "Directories (top level)",
77        }
78    }
79}
80
81// =============================================================================
82// Bus Factor types (shared, used by health.rs)
83// =============================================================================
84
85/// Bus factor entry
86#[derive(Debug, Clone)]
87pub struct BusFactorEntry {
88    /// Path (directory or file)
89    pub path: String,
90    /// Bus factor value (number of people with knowledge)
91    pub bus_factor: usize,
92    /// List of major contributors (sorted by commit count)
93    pub contributors: Vec<ContributorInfo>,
94    /// Total commit count
95    pub total_commits: usize,
96    /// Risk level
97    pub risk_level: BusFactorRisk,
98    /// Whether this is a directory
99    pub is_directory: bool,
100}
101
102/// Contributor information
103#[derive(Debug, Clone)]
104pub struct ContributorInfo {
105    /// Author name
106    pub name: String,
107    /// Commit count
108    pub commit_count: usize,
109    /// Contribution percentage
110    pub contribution_percent: f64,
111}
112
113/// Bus factor risk level
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum BusFactorRisk {
116    /// High risk (1 person only)
117    High,
118    /// Medium risk (2 people)
119    Medium,
120    /// Low risk (3 or more people)
121    Low,
122}
123
124impl BusFactorRisk {
125    /// Display name for the risk level
126    pub fn display_name(&self) -> &'static str {
127        match self {
128            BusFactorRisk::High => "High Risk",
129            BusFactorRisk::Medium => "Medium Risk",
130            BusFactorRisk::Low => "Low Risk",
131        }
132    }
133
134    /// Color for the risk level
135    pub fn color(&self) -> &'static str {
136        match self {
137            BusFactorRisk::High => "red",
138            BusFactorRisk::Medium => "yellow",
139            BusFactorRisk::Low => "green",
140        }
141    }
142}
143
144/// Bus factor analysis result
145#[derive(Debug, Clone, Default)]
146pub struct BusFactorAnalysis {
147    /// Entry list (sorted by risk)
148    pub entries: Vec<BusFactorEntry>,
149    /// Number of high risk areas
150    pub high_risk_count: usize,
151    /// Number of medium risk areas
152    pub medium_risk_count: usize,
153    /// Number of paths analyzed
154    pub total_paths_analyzed: usize,
155}
156
157impl BusFactorAnalysis {
158    /// Get entry count
159    pub fn entry_count(&self) -> usize {
160        self.entries.len()
161    }
162}
163
164// =============================================================================
165// Technical Debt types (shared, used by health.rs)
166// =============================================================================
167
168/// Technical debt entry
169#[derive(Debug, Clone)]
170pub struct TechDebtEntry {
171    /// File path
172    pub path: String,
173    /// Technical debt score (0.0-1.0)
174    pub score: f64,
175    /// Churn score (0.0-1.0)
176    pub churn_score: f64,
177    /// Complexity score (0.0-1.0) - based on file size
178    pub complexity_score: f64,
179    /// Age score (0.0-1.0) - time since last change
180    pub age_score: f64,
181    /// Change count
182    pub change_count: usize,
183    /// Total lines changed
184    pub total_changes: usize,
185    /// Debt level
186    pub debt_level: TechDebtLevel,
187}
188
189/// Technical debt level
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum TechDebtLevel {
192    /// High debt (action required)
193    High,
194    /// Medium debt (caution)
195    Medium,
196    /// Low debt (good)
197    Low,
198}
199
200impl TechDebtLevel {
201    /// Display name for the debt level
202    pub fn display_name(&self) -> &'static str {
203        match self {
204            TechDebtLevel::High => "High Debt",
205            TechDebtLevel::Medium => "Medium Debt",
206            TechDebtLevel::Low => "Low Debt",
207        }
208    }
209
210    /// Color for the debt level
211    pub fn color(&self) -> &'static str {
212        match self {
213            TechDebtLevel::High => "red",
214            TechDebtLevel::Medium => "yellow",
215            TechDebtLevel::Low => "green",
216        }
217    }
218}
219
220/// Technical debt analysis result
221#[derive(Debug, Clone, Default)]
222pub struct TechDebtAnalysis {
223    /// Entry list (sorted by score descending)
224    pub entries: Vec<TechDebtEntry>,
225    /// Average score
226    pub avg_score: f64,
227    /// Number of high debt files
228    pub high_debt_count: usize,
229    /// Number of files analyzed
230    pub total_files_analyzed: usize,
231}
232
233impl TechDebtAnalysis {
234    /// Get entry count
235    pub fn entry_count(&self) -> usize {
236        self.entries.len()
237    }
238}