1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18pub enum QualityGrade {
19 APlus,
21 A,
23 AMinus,
25 BPlus,
27 B,
29 C,
31 D,
33 F,
35}
36
37impl QualityGrade {
38 pub fn from_rust_project_score(score: u32) -> Self {
40 match score {
41 105..=114 => Self::APlus,
42 95..=104 => Self::A,
43 85..=94 => Self::AMinus,
44 80..=84 => Self::BPlus,
45 70..=79 => Self::B,
46 60..=69 => Self::C,
47 50..=59 => Self::D,
48 _ => Self::F,
49 }
50 }
51
52 pub fn from_repo_score(score: u32) -> Self {
54 match score {
55 95..=110 => Self::APlus,
56 90..=94 => Self::A,
57 85..=89 => Self::AMinus,
58 80..=84 => Self::BPlus,
59 70..=79 => Self::B,
60 60..=69 => Self::C,
61 50..=59 => Self::D,
62 _ => Self::F,
63 }
64 }
65
66 pub fn from_readme_score(score: u32) -> Self {
68 match score {
69 18..=20 => Self::APlus,
70 16..=17 => Self::A,
71 14..=15 => Self::AMinus,
72 12..=13 => Self::BPlus,
73 10..=11 => Self::B,
74 8..=9 => Self::C,
75 6..=7 => Self::D,
76 _ => Self::F,
77 }
78 }
79
80 pub fn from_sqi(sqi: f64) -> Self {
82 match sqi as u32 {
83 95..=100 => Self::APlus,
84 90..=94 => Self::A,
85 85..=89 => Self::AMinus,
86 80..=84 => Self::BPlus,
87 70..=79 => Self::B,
88 60..=69 => Self::C,
89 50..=59 => Self::D,
90 _ => Self::F,
91 }
92 }
93
94 pub fn is_release_ready(&self) -> bool {
96 matches!(self, Self::APlus | Self::A | Self::AMinus)
97 }
98
99 pub fn is_a_plus(&self) -> bool {
101 matches!(self, Self::APlus)
102 }
103
104 pub fn symbol(&self) -> &'static str {
106 match self {
107 Self::APlus => "A+",
108 Self::A => "A",
109 Self::AMinus => "A-",
110 Self::BPlus => "B+",
111 Self::B => "B",
112 Self::C => "C",
113 Self::D => "D",
114 Self::F => "F",
115 }
116 }
117
118 pub fn icon(&self) -> &'static str {
120 match self {
121 Self::APlus => "✅",
122 Self::A | Self::AMinus => "⚠️",
123 _ => "❌",
124 }
125 }
126}
127
128impl std::fmt::Display for QualityGrade {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 write!(f, "{}", self.symbol())
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct Score {
141 pub value: u32,
143 pub max: u32,
145 pub grade: QualityGrade,
147}
148
149impl Score {
150 pub fn new(value: u32, max: u32, grade: QualityGrade) -> Self {
152 Self { value, max, grade }
153 }
154
155 pub fn percentage(&self) -> f64 {
157 if self.max == 0 {
158 0.0
159 } else {
160 (self.value as f64 / self.max as f64) * 100.0
161 }
162 }
163
164 pub fn normalized(&self) -> f64 {
166 self.percentage()
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176pub enum IssueSeverity {
177 Error,
179 Warning,
181 Info,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct QualityIssue {
188 pub issue_type: String,
190 pub message: String,
192 pub severity: IssueSeverity,
194 pub recommendation: Option<String>,
196}
197
198impl QualityIssue {
199 pub fn new(
201 issue_type: impl Into<String>,
202 message: impl Into<String>,
203 severity: IssueSeverity,
204 ) -> Self {
205 Self {
206 issue_type: issue_type.into(),
207 message: message.into(),
208 severity,
209 recommendation: None,
210 }
211 }
212
213 pub fn with_recommendation(mut self, rec: impl Into<String>) -> Self {
215 self.recommendation = Some(rec.into());
216 self
217 }
218
219 pub fn score_below_threshold(metric: &str, score: u32, threshold: u32) -> Self {
221 Self::new(
222 format!("{}_below_threshold", metric),
223 format!("{} score {} below A- threshold ({})", metric, score, threshold),
224 IssueSeverity::Error,
225 )
226 .with_recommendation(format!("Improve {} to at least {}", metric, threshold))
227 }
228
229 pub fn missing_hero_image() -> Self {
231 Self::new("missing_hero_image", "No hero image found", IssueSeverity::Error)
232 .with_recommendation("Add hero.png to docs/ or include image at top of README.md")
233 }
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
242pub enum StackLayer {
243 Compute,
245 Ml,
247 Training,
249 Transpilers,
251 Orchestration,
253 Quality,
255 DataMlops,
257 Presentation,
259}
260
261impl StackLayer {
262 pub fn from_component(name: &str) -> Self {
264 match name {
265 "trueno" | "trueno-viz" | "trueno-db" | "trueno-graph" | "trueno-rag"
266 | "trueno-zram" => Self::Compute,
267 "aprender" | "aprender-shell" | "aprender-tsp" => Self::Ml,
268 "entrenar" | "realizar" | "whisper-apr" => Self::Training,
269 "depyler" | "decy" | "ruchy" => Self::Transpilers,
270 "batuta" | "repartir" | "pepita" | "pforge" | "forjar" => Self::Orchestration,
271 "certeza" | "renacer" | "pmat" | "provable-contracts" | "tiny-model-ground-truth" => {
272 Self::Quality
273 }
274 "alimentar" | "pacha" => Self::DataMlops,
275 "presentar"
276 | "sovereign-ai-stack-book"
277 | "apr-cookbook"
278 | "alm-cookbook"
279 | "pres-cookbook" => Self::Presentation,
280 _ => Self::Orchestration, }
282 }
283
284 pub fn display_name(&self) -> &'static str {
286 match self {
287 Self::Compute => "COMPUTE PRIMITIVES",
288 Self::Ml => "ML ALGORITHMS",
289 Self::Training => "TRAINING & INFERENCE",
290 Self::Transpilers => "TRANSPILERS",
291 Self::Orchestration => "ORCHESTRATION",
292 Self::Quality => "QUALITY",
293 Self::DataMlops => "DATA & MLOPS",
294 Self::Presentation => "PRESENTATION",
295 }
296 }
297}