1use std::fmt;
4
5use super::widget::Color;
6use super::Brick;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct BrickScore {
23 pub performance: u8,
25 pub efficiency: u8,
27 pub correctness: u8,
29 pub stability: u8,
31}
32
33impl BrickScore {
34 pub const fn new(performance: u8, efficiency: u8, correctness: u8, stability: u8) -> Self {
36 Self {
37 performance: if performance > 40 { 40 } else { performance },
38 efficiency: if efficiency > 25 { 25 } else { efficiency },
39 correctness: if correctness > 20 { 20 } else { correctness },
40 stability: if stability > 15 { 15 } else { stability },
41 }
42 }
43
44 pub const fn perfect() -> Self {
46 Self::new(40, 25, 20, 15)
47 }
48
49 pub const fn zero() -> Self {
51 Self::new(0, 0, 0, 0)
52 }
53
54 pub const fn total(&self) -> u8 {
56 self.performance + self.efficiency + self.correctness + self.stability
57 }
58
59 pub fn grade(&self) -> BrickGrade {
61 match self.total() {
62 90..=100 => BrickGrade::A,
63 80..=89 => BrickGrade::B,
64 70..=79 => BrickGrade::C,
65 60..=69 => BrickGrade::D,
66 _ => BrickGrade::F,
67 }
68 }
69
70 pub fn performance_pct(&self) -> f64 {
72 self.performance as f64 / 40.0
73 }
74
75 pub fn efficiency_pct(&self) -> f64 {
77 self.efficiency as f64 / 25.0
78 }
79
80 pub fn correctness_pct(&self) -> f64 {
82 self.correctness as f64 / 20.0
83 }
84
85 pub fn stability_pct(&self) -> f64 {
87 self.stability as f64 / 15.0
88 }
89
90 pub fn score_performance(actual_gflops: f64, theoretical_gflops: f64) -> u8 {
93 if theoretical_gflops <= 0.0 {
94 return 0;
95 }
96 let ratio = actual_gflops / theoretical_gflops;
97 (ratio * 40.0).min(40.0) as u8
98 }
99
100 pub fn score_speedup(speedup: f64) -> u8 {
103 if speedup <= 1.0 {
104 return 0;
105 }
106 (speedup.log2() * 5.0).min(20.0) as u8
107 }
108
109 pub fn score_cv(cv_percent: f64) -> u8 {
112 if cv_percent < 5.0 {
113 8
114 } else if cv_percent < 10.0 {
115 4
116 } else {
117 0
118 }
119 }
120
121 pub fn render_bar(value: u8, max: u8, width: usize) -> String {
123 let ratio = value as f64 / max as f64;
124 let filled = (ratio * width as f64).round() as usize;
125 let empty = width.saturating_sub(filled);
126 format!("{}{}", "█".repeat(filled), "░".repeat(empty))
127 }
128}
129
130impl Default for BrickScore {
131 fn default() -> Self {
132 Self::zero()
133 }
134}
135
136impl fmt::Display for BrickScore {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "{}/100 ({})", self.total(), self.grade().description())
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum BrickGrade {
147 F,
149 D,
151 C,
153 B,
155 A,
157}
158
159impl PartialOrd for BrickGrade {
160 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
161 Some(self.cmp(other))
162 }
163}
164
165impl Ord for BrickGrade {
166 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
167 let self_val = match self {
169 Self::A => 4,
170 Self::B => 3,
171 Self::C => 2,
172 Self::D => 1,
173 Self::F => 0,
174 };
175 let other_val = match other {
176 Self::A => 4,
177 Self::B => 3,
178 Self::C => 2,
179 Self::D => 1,
180 Self::F => 0,
181 };
182 self_val.cmp(&other_val)
183 }
184}
185
186impl BrickGrade {
187 pub const fn description(&self) -> &'static str {
189 match self {
190 Self::A => "Excellent",
191 Self::B => "Good",
192 Self::C => "Acceptable",
193 Self::D => "Needs Improvement",
194 Self::F => "Failing",
195 }
196 }
197
198 pub const fn letter(&self) -> char {
200 match self {
201 Self::A => 'A',
202 Self::B => 'B',
203 Self::C => 'C',
204 Self::D => 'D',
205 Self::F => 'F',
206 }
207 }
208
209 pub const fn color(&self) -> Color {
211 match self {
212 Self::A | Self::B => Color::ANDON_GREEN,
213 Self::C => Color::ANDON_YELLOW,
214 Self::D | Self::F => Color::ANDON_RED,
215 }
216 }
217}
218
219impl fmt::Display for BrickGrade {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "{}", self.letter())
222 }
223}
224
225pub trait Scorable: Brick {
229 fn score(&self) -> BrickScore;
231
232 fn score_report(&self) -> String {
234 let score = self.score();
235 let perf_bar = BrickScore::render_bar(score.performance, 40, 20);
236 let eff_bar = BrickScore::render_bar(score.efficiency, 25, 20);
237 let corr_bar = BrickScore::render_bar(score.correctness, 20, 20);
238 let stab_bar = BrickScore::render_bar(score.stability, 15, 20);
239
240 format!(
241 "╭──────────────────────────────────────────────────────╮\n\
242 │ ComputeBrick Score: {:<24} │\n\
243 ├──────────────────────────────────────────────────────┤\n\
244 │ Performance: {:>5}/40 {} {:>3.0}% │\n\
245 │ Efficiency: {:>5}/25 {} {:>3.0}% │\n\
246 │ Correctness: {:>5}/20 {} {:>3.0}% │\n\
247 │ Stability: {:>5}/15 {} {:>3.0}% │\n\
248 ├──────────────────────────────────────────────────────┤\n\
249 │ TOTAL SCORE: {:>6}/100 Grade: {} │\n\
250 ╰──────────────────────────────────────────────────────╯",
251 self.brick_name(),
252 score.performance,
253 perf_bar,
254 score.performance_pct() * 100.0,
255 score.efficiency,
256 eff_bar,
257 score.efficiency_pct() * 100.0,
258 score.correctness,
259 corr_bar,
260 score.correctness_pct() * 100.0,
261 score.stability,
262 stab_bar,
263 score.stability_pct() * 100.0,
264 score.total(),
265 score.grade()
266 )
267 }
268}