ruvector_scipix/cli/
output.rs

1use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
2use console::style;
3
4use super::commands::OcrResult;
5
6/// Print a summary of a single OCR result
7pub 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
66/// Print a summary of batch processing results
67pub 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    // Summary statistics
159    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
180/// Get color based on confidence value
181fn 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
191/// Create a progress bar style for batch processing
192pub 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
199/// Create a spinner style for individual file processing
200pub fn create_spinner_style() -> indicatif::ProgressStyle {
201    indicatif::ProgressStyle::default_spinner()
202        .template("{spinner:.cyan} {msg}")
203        .unwrap()
204        .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
205}