driven/scale/
analyzers.rs1use super::{ProjectContext, ProjectScale};
6
7pub trait ScaleAnalyzer: Send + Sync {
9 fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)>;
11
12 fn name(&self) -> &'static str;
14}
15
16#[derive(Debug, Default)]
18pub struct FileSizeAnalyzer;
19
20impl FileSizeAnalyzer {
21 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 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 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#[derive(Debug, Default)]
68pub struct DependencyAnalyzer;
69
70impl DependencyAnalyzer {
71 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#[derive(Debug, Default)]
98pub struct TeamSizeAnalyzer;
99
100impl TeamSizeAnalyzer {
101 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#[derive(Debug, Default)]
128pub struct HistoryAnalyzer;
129
130impl HistoryAnalyzer {
131 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#[derive(Debug, Default)]
158pub struct ComplexityAnalyzer;
159
160impl ComplexityAnalyzer {
161 pub fn new() -> Self {
163 Self
164 }
165}
166
167impl ScaleAnalyzer for ComplexityAnalyzer {
168 fn analyze(&self, context: &ProjectContext) -> Option<(ProjectScale, f32)> {
169 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}