use crate::stack::quality::{QualityGrade, StackLayer};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HealthStatus {
Green,
Yellow,
Red,
Unknown,
}
impl HealthStatus {
pub fn from_grade(grade: QualityGrade) -> Self {
match grade {
QualityGrade::APlus | QualityGrade::A => Self::Green,
QualityGrade::AMinus | QualityGrade::BPlus => Self::Yellow,
_ => Self::Red,
}
}
pub fn icon(&self) -> &'static str {
match self {
Self::Green => "🟢",
Self::Yellow => "🟡",
Self::Red => "🔴",
Self::Unknown => "⚪",
}
}
pub fn symbol(&self) -> &'static str {
match self {
Self::Green => "●",
Self::Yellow => "◐",
Self::Red => "○",
Self::Unknown => "◌",
}
}
}
impl std::fmt::Display for HealthStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.icon())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentNode {
pub name: String,
pub version: String,
pub layer: StackLayer,
pub health: HealthStatus,
pub metrics: ComponentMetrics,
}
impl ComponentNode {
pub fn new(name: impl Into<String>, version: impl Into<String>, layer: StackLayer) -> Self {
Self {
name: name.into(),
version: version.into(),
layer,
health: HealthStatus::Unknown,
metrics: ComponentMetrics::default(),
}
}
pub fn update_health(&mut self) {
self.health = HealthStatus::from_grade(self.metrics.grade);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentMetrics {
pub demo_score: f64,
pub coverage: f64,
pub mutation_score: f64,
pub complexity_avg: f64,
pub satd_count: u32,
pub dead_code_pct: f64,
pub grade: QualityGrade,
}
impl Default for ComponentMetrics {
fn default() -> Self {
Self {
demo_score: 0.0,
coverage: 0.0,
mutation_score: 0.0,
complexity_avg: 0.0,
satd_count: 0,
dead_code_pct: 0.0,
grade: QualityGrade::F, }
}
}
impl ComponentMetrics {
pub fn with_demo_score(demo_score: f64) -> Self {
let grade = QualityGrade::from_sqi(demo_score);
Self { demo_score, grade, ..Default::default() }
}
pub fn meets_threshold(&self) -> bool {
self.demo_score >= 85.0
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GraphMetrics {
pub pagerank: HashMap<String, f64>,
pub betweenness: HashMap<String, f64>,
pub clustering: HashMap<String, f64>,
pub communities: HashMap<String, usize>,
pub depth_map: HashMap<String, u32>,
pub total_nodes: usize,
pub total_edges: usize,
pub density: f64,
pub avg_degree: f64,
pub max_depth: u32,
}
impl GraphMetrics {
pub fn top_by_pagerank(&self, n: usize) -> Vec<(&String, f64)> {
let mut scores: Vec<_> = self.pagerank.iter().map(|(k, v)| (k, *v)).collect();
scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
scores.into_iter().take(n).collect()
}
pub fn bottlenecks(&self, threshold: f64) -> Vec<&String> {
self.betweenness.iter().filter(|(_, &v)| v > threshold).map(|(k, _)| k).collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthSummary {
pub total_components: usize,
pub green_count: usize,
pub yellow_count: usize,
pub red_count: usize,
pub unknown_count: usize,
pub avg_demo_score: f64,
pub avg_coverage: f64,
pub andon_status: AndonStatus,
}
impl HealthSummary {
pub fn all_healthy(&self) -> bool {
self.red_count == 0 && self.yellow_count == 0 && self.green_count == self.total_components
}
pub fn health_percentage(&self) -> f64 {
if self.total_components == 0 {
return 0.0;
}
(self.green_count as f64 / self.total_components as f64) * 100.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AndonStatus {
Green,
Yellow,
Red,
Unknown,
}
impl AndonStatus {
pub fn message(&self) -> &'static str {
match self {
Self::Green => "All systems healthy",
Self::Yellow => "Attention needed",
Self::Red => "Stop-the-line",
Self::Unknown => "Analysis pending",
}
}
}
impl std::fmt::Display for AndonStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let icon = match self {
Self::Green => "🟢",
Self::Yellow => "🟡",
Self::Red => "🔴",
Self::Unknown => "⚪",
};
write!(f, "{} {}", icon, self.message())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Anomaly {
pub component: String,
pub score: f64,
pub category: AnomalyCategory,
pub description: String,
pub evidence: Vec<String>,
pub recommendation: Option<String>,
}
impl Anomaly {
pub fn new(
component: impl Into<String>,
score: f64,
category: AnomalyCategory,
description: impl Into<String>,
) -> Self {
Self {
component: component.into(),
score,
category,
description: description.into(),
evidence: Vec::new(),
recommendation: None,
}
}
pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
self.evidence.push(evidence.into());
self
}
pub fn with_recommendation(mut self, rec: impl Into<String>) -> Self {
self.recommendation = Some(rec.into());
self
}
pub fn is_critical(&self) -> bool {
self.score > 0.8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AnomalyCategory {
QualityRegression,
CoverageDrop,
BuildTimeSpike,
DependencyRisk,
ComplexityIncrease,
Other,
}
impl std::fmt::Display for AnomalyCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::QualityRegression => write!(f, "Quality Regression"),
Self::CoverageDrop => write!(f, "Coverage Drop"),
Self::BuildTimeSpike => write!(f, "Build Time Spike"),
Self::DependencyRisk => write!(f, "Dependency Risk"),
Self::ComplexityIncrease => write!(f, "Complexity Increase"),
Self::Other => write!(f, "Other"),
}
}
}