Skip to main content

batuta/stack/quality/
component.rs

1//! Component quality assessment
2//!
3//! Contains `ComponentQuality` - quality assessment for a single component.
4
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8use super::types::{QualityGrade, QualityIssue, Score, StackLayer};
9use crate::stack::hero_image::HeroImageResult;
10
11// ============================================================================
12// Component Quality
13// ============================================================================
14
15/// Quality assessment for a single component
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ComponentQuality {
18    /// Component name
19    pub name: String,
20    /// Layer in the stack
21    pub layer: StackLayer,
22    /// Repository path
23    pub path: PathBuf,
24    /// Rust project score
25    pub rust_score: Score,
26    /// Repository score
27    pub repo_score: Score,
28    /// README score
29    pub readme_score: Score,
30    /// Hero image result
31    pub hero_image: HeroImageResult,
32    /// Stack Quality Index (0-100)
33    pub sqi: f64,
34    /// Overall grade
35    pub grade: QualityGrade,
36    /// Whether release is allowed
37    pub release_ready: bool,
38    /// Issues found
39    pub issues: Vec<QualityIssue>,
40}
41
42impl ComponentQuality {
43    /// Create a new component quality assessment
44    pub fn new(
45        name: impl Into<String>,
46        path: PathBuf,
47        rust_score: Score,
48        repo_score: Score,
49        readme_score: Score,
50        hero_image: HeroImageResult,
51    ) -> Self {
52        let name = name.into();
53        let layer = StackLayer::from_component(&name);
54        let sqi = Self::calculate_sqi(&rust_score, &repo_score, &readme_score, &hero_image);
55        let grade = QualityGrade::from_sqi(sqi);
56        let issues = Self::collect_issues(&rust_score, &repo_score, &readme_score, &hero_image);
57        let release_ready = grade.is_release_ready() && hero_image.valid;
58
59        Self {
60            name,
61            layer,
62            path,
63            rust_score,
64            repo_score,
65            readme_score,
66            hero_image,
67            sqi,
68            grade,
69            release_ready,
70            issues,
71        }
72    }
73
74    /// Calculate Stack Quality Index
75    /// SQI = (0.40 x Rust) + (0.30 x Repo) + (0.20 x README) + (0.10 x Hero)
76    pub fn calculate_sqi(
77        rust: &Score,
78        repo: &Score,
79        readme: &Score,
80        hero: &HeroImageResult,
81    ) -> f64 {
82        let rust_normalized = rust.normalized();
83        let repo_normalized = repo.normalized();
84        let readme_normalized = readme.normalized();
85        let hero_normalized = if hero.valid { 100.0 } else { 0.0 };
86
87        (0.40 * rust_normalized)
88            + (0.30 * repo_normalized)
89            + (0.20 * readme_normalized)
90            + (0.10 * hero_normalized)
91    }
92
93    /// Collect issues based on scores
94    fn collect_issues(
95        rust: &Score,
96        repo: &Score,
97        readme: &Score,
98        hero: &HeroImageResult,
99    ) -> Vec<QualityIssue> {
100        let mut issues = Vec::new();
101
102        // Check Rust score (A- threshold = 85)
103        if rust.value < 85 {
104            issues.push(QualityIssue::score_below_threshold("rust_project", rust.value, 85));
105        }
106
107        // Check Repo score (A- threshold = 85)
108        if repo.value < 85 {
109            issues.push(QualityIssue::score_below_threshold("repo", repo.value, 85));
110        }
111
112        // Check README score (A- threshold = 14)
113        if readme.value < 14 {
114            issues.push(QualityIssue::score_below_threshold("readme", readme.value, 14));
115        }
116
117        // Check hero image
118        if !hero.valid {
119            issues.push(QualityIssue::missing_hero_image());
120        }
121
122        issues
123    }
124}