batuta/stack/quality/
report.rs1use serde::{Deserialize, Serialize};
6
7use super::component::ComponentQuality;
8use super::summary::QualitySummary;
9use super::types::QualityGrade;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct StackQualityReport {
18 pub timestamp: chrono::DateTime<chrono::Utc>,
20 pub components: Vec<ComponentQuality>,
22 pub summary: QualitySummary,
24 pub stack_quality_index: f64,
26 pub overall_grade: QualityGrade,
28 pub release_ready: bool,
30 pub blocked_components: Vec<String>,
32 pub recommendations: Vec<String>,
34}
35
36impl StackQualityReport {
37 pub fn from_components(components: Vec<ComponentQuality>) -> Self {
39 let summary = QualitySummary::from_components(&components);
40
41 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 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 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 pub fn is_all_a_plus(&self) -> bool {
100 self.components.iter().all(|c| c.grade.is_a_plus())
101 }
102}