Skip to main content

batuta/stack/diagnostics/
types.rs

1//! Core types for stack diagnostics
2//!
3//! This module contains the fundamental data types used throughout
4//! the diagnostics system, including health status, component nodes,
5//! metrics, and anomaly detection structures.
6
7use crate::stack::quality::{QualityGrade, StackLayer};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11// ============================================================================
12// Health Status (Andon System)
13// ============================================================================
14
15/// Health status for components (Andon-style visual control)
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum HealthStatus {
18    /// All systems healthy - normal operation
19    Green,
20    /// Attention needed - warnings present
21    Yellow,
22    /// Critical issues - stop-the-line
23    Red,
24    /// Not yet analyzed
25    Unknown,
26}
27
28impl HealthStatus {
29    /// Create from quality grade
30    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    /// Get display icon for status
39    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    /// Get ASCII symbol for terminal without emoji support
49    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// ============================================================================
66// Component Node (Stack Knowledge Graph)
67// ============================================================================
68
69/// A component in the stack knowledge graph
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ComponentNode {
72    /// Component name (e.g., "trueno", "aprender")
73    pub name: String,
74    /// Semantic version
75    pub version: String,
76    /// Stack layer classification
77    pub layer: StackLayer,
78    /// Current health status
79    pub health: HealthStatus,
80    /// Quality metrics
81    pub metrics: ComponentMetrics,
82}
83
84impl ComponentNode {
85    /// Create a new component node
86    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    /// Update health status from metrics
97    pub fn update_health(&mut self) {
98        self.health = HealthStatus::from_grade(self.metrics.grade);
99    }
100}
101
102// ============================================================================
103// Component Metrics
104// ============================================================================
105
106/// Quality and performance metrics for a component
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ComponentMetrics {
109    /// Demo score (0-100 normalized)
110    pub demo_score: f64,
111    /// Test coverage percentage
112    pub coverage: f64,
113    /// Mutation score percentage
114    pub mutation_score: f64,
115    /// Average cyclomatic complexity
116    pub complexity_avg: f64,
117    /// SATD (Self-Admitted Technical Debt) count
118    pub satd_count: u32,
119    /// Dead code percentage
120    pub dead_code_pct: f64,
121    /// Overall quality grade
122    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, // Lowest grade as default
135        }
136    }
137}
138
139impl ComponentMetrics {
140    /// Create metrics with demo score
141    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    /// Check if metrics meet A- threshold
147    pub fn meets_threshold(&self) -> bool {
148        self.demo_score >= 85.0
149    }
150}
151
152// ============================================================================
153// Graph Metrics
154// ============================================================================
155
156/// Computed graph-level metrics
157#[derive(Debug, Clone, Default, Serialize, Deserialize)]
158pub struct GraphMetrics {
159    /// PageRank scores by node
160    pub pagerank: HashMap<String, f64>,
161    /// Betweenness centrality by node
162    pub betweenness: HashMap<String, f64>,
163    /// Clustering coefficient by node
164    pub clustering: HashMap<String, f64>,
165    /// Community assignments (node -> community_id)
166    pub communities: HashMap<String, usize>,
167    /// Depth from root nodes
168    pub depth_map: HashMap<String, u32>,
169    /// Total nodes in graph
170    pub total_nodes: usize,
171    /// Total edges in graph
172    pub total_edges: usize,
173    /// Graph density (edges / possible edges)
174    pub density: f64,
175    /// Average degree
176    pub avg_degree: f64,
177    /// Maximum depth
178    pub max_depth: u32,
179}
180
181impl GraphMetrics {
182    /// Get the most critical components by PageRank
183    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    /// Get bottleneck components (high betweenness)
190    pub fn bottlenecks(&self, threshold: f64) -> Vec<&String> {
191        self.betweenness.iter().filter(|(_, &v)| v > threshold).map(|(k, _)| k).collect()
192    }
193}
194
195// ============================================================================
196// Health Summary
197// ============================================================================
198
199/// Summary of stack health for dashboard
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct HealthSummary {
202    /// Total components in stack
203    pub total_components: usize,
204    /// Components with green status
205    pub green_count: usize,
206    /// Components with yellow status
207    pub yellow_count: usize,
208    /// Components with red status
209    pub red_count: usize,
210    /// Components with unknown status
211    pub unknown_count: usize,
212    /// Average demo score
213    pub avg_demo_score: f64,
214    /// Average test coverage
215    pub avg_coverage: f64,
216    /// Overall Andon status
217    pub andon_status: AndonStatus,
218}
219
220impl HealthSummary {
221    /// Check if all components are healthy
222    pub fn all_healthy(&self) -> bool {
223        self.red_count == 0 && self.yellow_count == 0 && self.green_count == self.total_components
224    }
225
226    /// Get percentage of healthy components
227    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// ============================================================================
236// Andon Status (Overall Stack)
237// ============================================================================
238
239/// Andon board status for the entire stack
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241pub enum AndonStatus {
242    /// All systems green - normal operation
243    Green,
244    /// Warnings present - attention needed
245    Yellow,
246    /// Critical issues - stop-the-line
247    Red,
248    /// Not yet analyzed
249    Unknown,
250}
251
252impl AndonStatus {
253    /// Get display message
254    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// ============================================================================
277// Anomaly Detection
278// ============================================================================
279
280/// Detected anomaly in the stack
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct Anomaly {
283    /// Component where anomaly was detected
284    pub component: String,
285    /// Anomaly score (0-1, higher = more anomalous)
286    pub score: f64,
287    /// Category of anomaly
288    pub category: AnomalyCategory,
289    /// Human-readable description
290    pub description: String,
291    /// Evidence supporting the anomaly
292    pub evidence: Vec<String>,
293    /// Suggested remediation
294    pub recommendation: Option<String>,
295}
296
297impl Anomaly {
298    /// Create a new anomaly
299    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    /// Add evidence
316    pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
317        self.evidence.push(evidence.into());
318        self
319    }
320
321    /// Add recommendation
322    pub fn with_recommendation(mut self, rec: impl Into<String>) -> Self {
323        self.recommendation = Some(rec.into());
324        self
325    }
326
327    /// Check if anomaly is critical (score > 0.8)
328    pub fn is_critical(&self) -> bool {
329        self.score > 0.8
330    }
331}
332
333/// Categories of anomalies
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
335pub enum AnomalyCategory {
336    /// Quality score regression
337    QualityRegression,
338    /// Coverage drop
339    CoverageDrop,
340    /// Build time spike
341    BuildTimeSpike,
342    /// Dependency risk
343    DependencyRisk,
344    /// Complexity increase
345    ComplexityIncrease,
346    /// Other anomaly
347    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}