Skip to main content

driven/scale/
analyzers.rs

1//! Scale Analyzers
2//!
3//! Individual analyzers that contribute to scale detection.
4
5use super::{ProjectContext, ProjectScale};
6
7/// Trait for scale analyzers
8pub trait ScaleAnalyzer: Send + Sync {
9    /// Analyze the project context and return a scale suggestion with confidence
10    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)>;
11
12    /// Get the name of this analyzer
13    fn name(&self) -> &'static str;
14}
15
16/// Analyzes project scale based on file size metrics
17#[derive(Debug, Default)]
18pub struct FileSizeAnalyzer;
19
20impl FileSizeAnalyzer {
21    /// Create a new file size analyzer
22    pub fn new() -> Self {
23        Self
24    }
25}
26
27impl ScaleAnalyzer for FileSizeAnalyzer {
28    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
29        let loc = context.lines_of_code?;
30        let file_count = context.file_count.unwrap_or(0);
31
32        // Scale thresholds based on lines of code
33        let (scale, confidence) = match loc {
34            0..=500 => (ProjectScale::BugFix, 0.9),
35            501..=5_000 => (ProjectScale::Feature, 0.8),
36            5_001..=50_000 => (ProjectScale::Product, 0.7),
37            _ => (ProjectScale::Enterprise, 0.8),
38        };
39
40        // Adjust confidence based on file count correlation
41        let adjusted_confidence = if file_count > 0 {
42            let expected_files = match scale {
43                ProjectScale::BugFix => 1..=10,
44                ProjectScale::Feature => 5..=50,
45                ProjectScale::Product => 20..=500,
46                ProjectScale::Enterprise => 100..=10000,
47            };
48
49            if expected_files.contains(&file_count) {
50                confidence
51            } else {
52                confidence * 0.8
53            }
54        } else {
55            confidence * 0.9
56        };
57
58        Some((scale, adjusted_confidence))
59    }
60
61    fn name(&self) -> &'static str {
62        "FileSizeAnalyzer"
63    }
64}
65
66/// Analyzes project scale based on dependency count
67#[derive(Debug, Default)]
68pub struct DependencyAnalyzer;
69
70impl DependencyAnalyzer {
71    /// Create a new dependency analyzer
72    pub fn new() -> Self {
73        Self
74    }
75}
76
77impl ScaleAnalyzer for DependencyAnalyzer {
78    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
79        let deps = context.dependency_count?;
80
81        let (scale, confidence) = match deps {
82            0..=5 => (ProjectScale::BugFix, 0.6),
83            6..=20 => (ProjectScale::Feature, 0.7),
84            21..=100 => (ProjectScale::Product, 0.7),
85            _ => (ProjectScale::Enterprise, 0.8),
86        };
87
88        Some((scale, confidence))
89    }
90
91    fn name(&self) -> &'static str {
92        "DependencyAnalyzer"
93    }
94}
95
96/// Analyzes project scale based on team size (contributors)
97#[derive(Debug, Default)]
98pub struct TeamSizeAnalyzer;
99
100impl TeamSizeAnalyzer {
101    /// Create a new team size analyzer
102    pub fn new() -> Self {
103        Self
104    }
105}
106
107impl ScaleAnalyzer for TeamSizeAnalyzer {
108    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
109        let contributors = context.contributor_count?;
110
111        let (scale, confidence) = match contributors {
112            0..=1 => (ProjectScale::BugFix, 0.5),
113            2..=3 => (ProjectScale::Feature, 0.7),
114            4..=10 => (ProjectScale::Product, 0.8),
115            _ => (ProjectScale::Enterprise, 0.9),
116        };
117
118        Some((scale, confidence))
119    }
120
121    fn name(&self) -> &'static str {
122        "TeamSizeAnalyzer"
123    }
124}
125
126/// Analyzes project scale based on git history
127#[derive(Debug, Default)]
128pub struct HistoryAnalyzer;
129
130impl HistoryAnalyzer {
131    /// Create a new history analyzer
132    pub fn new() -> Self {
133        Self
134    }
135}
136
137impl ScaleAnalyzer for HistoryAnalyzer {
138    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
139        let commits = context.commit_count?;
140
141        let (scale, confidence) = match commits {
142            0..=10 => (ProjectScale::BugFix, 0.5),
143            11..=100 => (ProjectScale::Feature, 0.6),
144            101..=1000 => (ProjectScale::Product, 0.7),
145            _ => (ProjectScale::Enterprise, 0.8),
146        };
147
148        Some((scale, confidence))
149    }
150
151    fn name(&self) -> &'static str {
152        "HistoryAnalyzer"
153    }
154}
155
156/// Analyzes project scale based on infrastructure complexity
157#[derive(Debug, Default)]
158pub struct ComplexityAnalyzer;
159
160impl ComplexityAnalyzer {
161    /// Create a new complexity analyzer
162    pub fn new() -> Self {
163        Self
164    }
165}
166
167impl ScaleAnalyzer for ComplexityAnalyzer {
168    fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
169        // Score based on infrastructure indicators
170        let mut score = 0u32;
171        let mut factors = 0u32;
172
173        if context.has_ci_cd {
174            score += 2;
175            factors += 1;
176        }
177
178        if context.has_tests {
179            score += 1;
180            factors += 1;
181        }
182
183        if context.has_docs {
184            score += 1;
185            factors += 1;
186        }
187
188        if factors == 0 {
189            return None;
190        }
191
192        let (scale, confidence) = match score {
193            0 => (ProjectScale::BugFix, 0.5),
194            1 => (ProjectScale::Feature, 0.6),
195            2..=3 => (ProjectScale::Product, 0.7),
196            _ => (ProjectScale::Enterprise, 0.8),
197        };
198
199        Some((scale, confidence))
200    }
201
202    fn name(&self) -> &'static str {
203        "ComplexityAnalyzer"
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_file_size_analyzer_bug_fix() {
213        let analyzer = FileSizeAnalyzer::new();
214        let context = ProjectContext::new().with_lines_of_code(100);
215
216        let result = analyzer.analyze(&context);
217        assert!(result.is_some());
218        let (scale, confidence) = result.unwrap();
219        assert_eq!(scale, ProjectScale::BugFix);
220        assert!(confidence > 0.5);
221    }
222
223    #[test]
224    fn test_file_size_analyzer_enterprise() {
225        let analyzer = FileSizeAnalyzer::new();
226        let context = ProjectContext::new().with_lines_of_code(100_000);
227
228        let result = analyzer.analyze(&context);
229        assert!(result.is_some());
230        let (scale, _) = result.unwrap();
231        assert_eq!(scale, ProjectScale::Enterprise);
232    }
233
234    #[test]
235    fn test_dependency_analyzer() {
236        let analyzer = DependencyAnalyzer::new();
237        let context = ProjectContext::new().with_dependency_count(50);
238
239        let result = analyzer.analyze(&context);
240        assert!(result.is_some());
241        let (scale, _) = result.unwrap();
242        assert_eq!(scale, ProjectScale::Product);
243    }
244
245    #[test]
246    fn test_team_size_analyzer() {
247        let analyzer = TeamSizeAnalyzer::new();
248        let context = ProjectContext::new().with_contributor_count(15);
249
250        let result = analyzer.analyze(&context);
251        assert!(result.is_some());
252        let (scale, _) = result.unwrap();
253        assert_eq!(scale, ProjectScale::Enterprise);
254    }
255
256    #[test]
257    fn test_history_analyzer() {
258        let analyzer = HistoryAnalyzer::new();
259        let context = ProjectContext::new().with_commit_count(500);
260
261        let result = analyzer.analyze(&context);
262        assert!(result.is_some());
263        let (scale, _) = result.unwrap();
264        assert_eq!(scale, ProjectScale::Product);
265    }
266
267    #[test]
268    fn test_complexity_analyzer() {
269        let analyzer = ComplexityAnalyzer::new();
270        let context = ProjectContext::new()
271            .with_ci_cd(true)
272            .with_tests(true)
273            .with_docs(true);
274
275        let result = analyzer.analyze(&context);
276        assert!(result.is_some());
277        let (scale, _) = result.unwrap();
278        assert!(scale == ProjectScale::Product || scale == ProjectScale::Enterprise);
279    }
280
281    #[test]
282    fn test_analyzer_returns_none_without_data() {
283        let analyzer = FileSizeAnalyzer::new();
284        let context = ProjectContext::new();
285
286        let result = analyzer.analyze(&context);
287        assert!(result.is_none());
288    }
289}