ruvector_scipix/cli/
output.rs1use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
2use console::style;
3
4use super::commands::OcrResult;
5
6pub fn print_ocr_summary(result: &OcrResult) {
8 println!("\n{}", style("OCR Processing Summary").bold().cyan());
9 println!("{}", style("─".repeat(60)).dim());
10
11 let mut table = Table::new();
12 table
13 .load_preset(UTF8_FULL)
14 .apply_modifier(UTF8_ROUND_CORNERS)
15 .set_header(vec![
16 Cell::new("Property").fg(Color::Cyan),
17 Cell::new("Value").fg(Color::Green),
18 ]);
19
20 table.add_row(vec![
21 Cell::new("File"),
22 Cell::new(result.file.display().to_string()),
23 ]);
24
25 table.add_row(vec![
26 Cell::new("Confidence"),
27 Cell::new(format!("{:.2}%", result.confidence * 100.0))
28 .fg(confidence_color(result.confidence)),
29 ]);
30
31 table.add_row(vec![
32 Cell::new("Processing Time"),
33 Cell::new(format!("{}ms", result.processing_time_ms)),
34 ]);
35
36 if let Some(latex) = &result.latex {
37 table.add_row(vec![
38 Cell::new("LaTeX"),
39 Cell::new(if latex.len() > 50 {
40 format!("{}...", &latex[..50])
41 } else {
42 latex.clone()
43 }),
44 ]);
45 }
46
47 if !result.errors.is_empty() {
48 table.add_row(vec![
49 Cell::new("Errors").fg(Color::Red),
50 Cell::new(result.errors.len().to_string()).fg(Color::Red),
51 ]);
52 }
53
54 println!("{table}");
55
56 if !result.errors.is_empty() {
57 println!("\n{}", style("Errors:").bold().red());
58 for (i, error) in result.errors.iter().enumerate() {
59 println!(" {}. {}", i + 1, style(error).red());
60 }
61 }
62
63 println!();
64}
65
66pub fn print_batch_summary(passed: &[OcrResult], failed: &[OcrResult], threshold: f64) {
68 println!("\n{}", style("Batch Processing Summary").bold().cyan());
69 println!("{}", style("═".repeat(60)).dim());
70
71 let total = passed.len() + failed.len();
72 let avg_confidence = if !passed.is_empty() {
73 passed.iter().map(|r| r.confidence).sum::<f64>() / passed.len() as f64
74 } else {
75 0.0
76 };
77 let total_time: u64 = passed.iter().map(|r| r.processing_time_ms).sum();
78 let avg_time = if !passed.is_empty() {
79 total_time / passed.len() as u64
80 } else {
81 0
82 };
83
84 let mut table = Table::new();
85 table
86 .load_preset(UTF8_FULL)
87 .apply_modifier(UTF8_ROUND_CORNERS)
88 .set_header(vec![
89 Cell::new("Metric").fg(Color::Cyan),
90 Cell::new("Value").fg(Color::Green),
91 ]);
92
93 table.add_row(vec![
94 Cell::new("Total Files"),
95 Cell::new(total.to_string()),
96 ]);
97
98 table.add_row(vec![
99 Cell::new("Passed").fg(Color::Green),
100 Cell::new(format!("{} ({:.1}%)", passed.len(), (passed.len() as f64 / total as f64) * 100.0))
101 .fg(Color::Green),
102 ]);
103
104 table.add_row(vec![
105 Cell::new("Failed").fg(Color::Red),
106 Cell::new(format!("{} ({:.1}%)", failed.len(), (failed.len() as f64 / total as f64) * 100.0))
107 .fg(if failed.is_empty() { Color::Green } else { Color::Red }),
108 ]);
109
110 table.add_row(vec![
111 Cell::new("Threshold"),
112 Cell::new(format!("{:.2}%", threshold * 100.0)),
113 ]);
114
115 table.add_row(vec![
116 Cell::new("Avg Confidence"),
117 Cell::new(format!("{:.2}%", avg_confidence * 100.0))
118 .fg(confidence_color(avg_confidence)),
119 ]);
120
121 table.add_row(vec![
122 Cell::new("Avg Processing Time"),
123 Cell::new(format!("{}ms", avg_time)),
124 ]);
125
126 table.add_row(vec![
127 Cell::new("Total Processing Time"),
128 Cell::new(format!("{:.2}s", total_time as f64 / 1000.0)),
129 ]);
130
131 println!("{table}");
132
133 if !failed.is_empty() {
134 println!("\n{}", style("Failed Files:").bold().red());
135
136 let mut failed_table = Table::new();
137 failed_table
138 .load_preset(UTF8_FULL)
139 .apply_modifier(UTF8_ROUND_CORNERS)
140 .set_header(vec![
141 Cell::new("#").fg(Color::Cyan),
142 Cell::new("File").fg(Color::Cyan),
143 Cell::new("Confidence").fg(Color::Cyan),
144 ]);
145
146 for (i, result) in failed.iter().enumerate() {
147 failed_table.add_row(vec![
148 Cell::new((i + 1).to_string()),
149 Cell::new(result.file.display().to_string()),
150 Cell::new(format!("{:.2}%", result.confidence * 100.0))
151 .fg(Color::Red),
152 ]);
153 }
154
155 println!("{failed_table}");
156 }
157
158 println!("\n{}", style("Statistics:").bold().cyan());
160
161 if !passed.is_empty() {
162 let confidences: Vec<f64> = passed.iter().map(|r| r.confidence).collect();
163 let min_confidence = confidences.iter().cloned().fold(f64::INFINITY, f64::min);
164 let max_confidence = confidences.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
165
166 println!(" Min confidence: {}", style(format!("{:.2}%", min_confidence * 100.0)).green());
167 println!(" Max confidence: {}", style(format!("{:.2}%", max_confidence * 100.0)).green());
168
169 let times: Vec<u64> = passed.iter().map(|r| r.processing_time_ms).collect();
170 let min_time = times.iter().min().unwrap_or(&0);
171 let max_time = times.iter().max().unwrap_or(&0);
172
173 println!(" Min processing time: {}ms", style(min_time).cyan());
174 println!(" Max processing time: {}ms", style(max_time).cyan());
175 }
176
177 println!();
178}
179
180fn confidence_color(confidence: f64) -> Color {
182 if confidence >= 0.9 {
183 Color::Green
184 } else if confidence >= 0.7 {
185 Color::Yellow
186 } else {
187 Color::Red
188 }
189}
190
191pub fn create_progress_style() -> indicatif::ProgressStyle {
193 indicatif::ProgressStyle::default_bar()
194 .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}")
195 .unwrap()
196 .progress_chars("█▓▒░ ")
197}
198
199pub fn create_spinner_style() -> indicatif::ProgressStyle {
201 indicatif::ProgressStyle::default_spinner()
202 .template("{spinner:.cyan} {msg}")
203 .unwrap()
204 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
205}