Skip to main content

batuta/stack/quality/
report.rs

1//! Stack quality report
2//!
3//! Contains `StackQualityReport` - complete quality report for the stack.
4
5use serde::{Deserialize, Serialize};
6
7use super::component::ComponentQuality;
8use super::summary::QualitySummary;
9use super::types::QualityGrade;
10
11// ============================================================================
12// Stack Quality Report
13// ============================================================================
14
15/// Complete quality report for the stack
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct StackQualityReport {
18    /// Timestamp of the report
19    pub timestamp: chrono::DateTime<chrono::Utc>,
20    /// Individual component assessments
21    pub components: Vec<ComponentQuality>,
22    /// Summary statistics
23    pub summary: QualitySummary,
24    /// Overall Stack Quality Index
25    pub stack_quality_index: f64,
26    /// Overall grade
27    pub overall_grade: QualityGrade,
28    /// Whether all components are release-ready
29    pub release_ready: bool,
30    /// Components that block release
31    pub blocked_components: Vec<String>,
32    /// Recommendations
33    pub recommendations: Vec<String>,
34}
35
36impl StackQualityReport {
37    /// Create report from component list
38    pub fn from_components(components: Vec<ComponentQuality>) -> Self {
39        let summary = QualitySummary::from_components(&components);
40
41        // Calculate overall SQI as average of component SQIs
42        let sqi = if components.is_empty() {
43            0.0
44        } else {
45            components.iter().map(|c| c.sqi).sum::<f64>() / components.len() as f64
46        };
47
48        let grade = QualityGrade::from_sqi(sqi);
49        let blocked: Vec<String> =
50            components.iter().filter(|c| !c.release_ready).map(|c| c.name.clone()).collect();
51
52        let release_ready = blocked.is_empty();
53        let recommendations = Self::generate_recommendations(&components, &summary);
54
55        Self {
56            timestamp: chrono::Utc::now(),
57            components,
58            summary,
59            stack_quality_index: sqi,
60            overall_grade: grade,
61            release_ready,
62            blocked_components: blocked,
63            recommendations,
64        }
65    }
66
67    /// Generate recommendations based on analysis
68    fn generate_recommendations(
69        components: &[ComponentQuality],
70        summary: &QualitySummary,
71    ) -> Vec<String> {
72        let mut recs = Vec::new();
73
74        if summary.missing_hero_count > 0 {
75            recs.push(format!("Add hero images to {} components", summary.missing_hero_count));
76        }
77
78        if summary.below_threshold_count > 0 {
79            recs.push(format!(
80                "Improve quality scores for {} components below A- threshold",
81                summary.below_threshold_count
82            ));
83        }
84
85        // Specific recommendations for blocked components
86        for comp in components.iter().filter(|c| !c.release_ready) {
87            if comp.rust_score.value < 85 {
88                recs.push(format!("{}: Improve test coverage and documentation", comp.name));
89            }
90            if !comp.hero_image.valid {
91                recs.push(format!("{}: Add hero.png to docs/ directory", comp.name));
92            }
93        }
94
95        recs
96    }
97
98    /// Check if all components meet strict A+ requirement
99    pub fn is_all_a_plus(&self) -> bool {
100        self.components.iter().all(|c| c.grade.is_a_plus())
101    }
102}