batuta/stack/diagnostics/
types.rs1use crate::stack::quality::{QualityGrade, StackLayer};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum HealthStatus {
18 Green,
20 Yellow,
22 Red,
24 Unknown,
26}
27
28impl HealthStatus {
29 pub fn from_grade(grade: QualityGrade) -> Self {
31 match grade {
32 QualityGrade::APlus | QualityGrade::A => Self::Green,
33 QualityGrade::AMinus | QualityGrade::BPlus => Self::Yellow,
34 _ => Self::Red,
35 }
36 }
37
38 pub fn icon(&self) -> &'static str {
40 match self {
41 Self::Green => "🟢",
42 Self::Yellow => "🟡",
43 Self::Red => "🔴",
44 Self::Unknown => "⚪",
45 }
46 }
47
48 pub fn symbol(&self) -> &'static str {
50 match self {
51 Self::Green => "●",
52 Self::Yellow => "◐",
53 Self::Red => "○",
54 Self::Unknown => "◌",
55 }
56 }
57}
58
59impl std::fmt::Display for HealthStatus {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "{}", self.icon())
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ComponentNode {
72 pub name: String,
74 pub version: String,
76 pub layer: StackLayer,
78 pub health: HealthStatus,
80 pub metrics: ComponentMetrics,
82}
83
84impl ComponentNode {
85 pub fn new(name: impl Into<String>, version: impl Into<String>, layer: StackLayer) -> Self {
87 Self {
88 name: name.into(),
89 version: version.into(),
90 layer,
91 health: HealthStatus::Unknown,
92 metrics: ComponentMetrics::default(),
93 }
94 }
95
96 pub fn update_health(&mut self) {
98 self.health = HealthStatus::from_grade(self.metrics.grade);
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ComponentMetrics {
109 pub demo_score: f64,
111 pub coverage: f64,
113 pub mutation_score: f64,
115 pub complexity_avg: f64,
117 pub satd_count: u32,
119 pub dead_code_pct: f64,
121 pub grade: QualityGrade,
123}
124
125impl Default for ComponentMetrics {
126 fn default() -> Self {
127 Self {
128 demo_score: 0.0,
129 coverage: 0.0,
130 mutation_score: 0.0,
131 complexity_avg: 0.0,
132 satd_count: 0,
133 dead_code_pct: 0.0,
134 grade: QualityGrade::F, }
136 }
137}
138
139impl ComponentMetrics {
140 pub fn with_demo_score(demo_score: f64) -> Self {
142 let grade = QualityGrade::from_sqi(demo_score);
143 Self { demo_score, grade, ..Default::default() }
144 }
145
146 pub fn meets_threshold(&self) -> bool {
148 self.demo_score >= 85.0
149 }
150}
151
152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
158pub struct GraphMetrics {
159 pub pagerank: HashMap<String, f64>,
161 pub betweenness: HashMap<String, f64>,
163 pub clustering: HashMap<String, f64>,
165 pub communities: HashMap<String, usize>,
167 pub depth_map: HashMap<String, u32>,
169 pub total_nodes: usize,
171 pub total_edges: usize,
173 pub density: f64,
175 pub avg_degree: f64,
177 pub max_depth: u32,
179}
180
181impl GraphMetrics {
182 pub fn top_by_pagerank(&self, n: usize) -> Vec<(&String, f64)> {
184 let mut scores: Vec<_> = self.pagerank.iter().map(|(k, v)| (k, *v)).collect();
185 scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
186 scores.into_iter().take(n).collect()
187 }
188
189 pub fn bottlenecks(&self, threshold: f64) -> Vec<&String> {
191 self.betweenness.iter().filter(|(_, &v)| v > threshold).map(|(k, _)| k).collect()
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct HealthSummary {
202 pub total_components: usize,
204 pub green_count: usize,
206 pub yellow_count: usize,
208 pub red_count: usize,
210 pub unknown_count: usize,
212 pub avg_demo_score: f64,
214 pub avg_coverage: f64,
216 pub andon_status: AndonStatus,
218}
219
220impl HealthSummary {
221 pub fn all_healthy(&self) -> bool {
223 self.red_count == 0 && self.yellow_count == 0 && self.green_count == self.total_components
224 }
225
226 pub fn health_percentage(&self) -> f64 {
228 if self.total_components == 0 {
229 return 0.0;
230 }
231 (self.green_count as f64 / self.total_components as f64) * 100.0
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241pub enum AndonStatus {
242 Green,
244 Yellow,
246 Red,
248 Unknown,
250}
251
252impl AndonStatus {
253 pub fn message(&self) -> &'static str {
255 match self {
256 Self::Green => "All systems healthy",
257 Self::Yellow => "Attention needed",
258 Self::Red => "Stop-the-line",
259 Self::Unknown => "Analysis pending",
260 }
261 }
262}
263
264impl std::fmt::Display for AndonStatus {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 let icon = match self {
267 Self::Green => "🟢",
268 Self::Yellow => "🟡",
269 Self::Red => "🔴",
270 Self::Unknown => "⚪",
271 };
272 write!(f, "{} {}", icon, self.message())
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct Anomaly {
283 pub component: String,
285 pub score: f64,
287 pub category: AnomalyCategory,
289 pub description: String,
291 pub evidence: Vec<String>,
293 pub recommendation: Option<String>,
295}
296
297impl Anomaly {
298 pub fn new(
300 component: impl Into<String>,
301 score: f64,
302 category: AnomalyCategory,
303 description: impl Into<String>,
304 ) -> Self {
305 Self {
306 component: component.into(),
307 score,
308 category,
309 description: description.into(),
310 evidence: Vec::new(),
311 recommendation: None,
312 }
313 }
314
315 pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
317 self.evidence.push(evidence.into());
318 self
319 }
320
321 pub fn with_recommendation(mut self, rec: impl Into<String>) -> Self {
323 self.recommendation = Some(rec.into());
324 self
325 }
326
327 pub fn is_critical(&self) -> bool {
329 self.score > 0.8
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
335pub enum AnomalyCategory {
336 QualityRegression,
338 CoverageDrop,
340 BuildTimeSpike,
342 DependencyRisk,
344 ComplexityIncrease,
346 Other,
348}
349
350impl std::fmt::Display for AnomalyCategory {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 match self {
353 Self::QualityRegression => write!(f, "Quality Regression"),
354 Self::CoverageDrop => write!(f, "Coverage Drop"),
355 Self::BuildTimeSpike => write!(f, "Build Time Spike"),
356 Self::DependencyRisk => write!(f, "Dependency Risk"),
357 Self::ComplexityIncrease => write!(f, "Complexity Increase"),
358 Self::Other => write!(f, "Other"),
359 }
360 }
361}