Skip to main content

quantrs2_device/unified_benchmarking/
reporting.rs

1//! Report generator for automated reporting
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::time::SystemTime;
6
7use super::config::ExportDestination;
8
9/// Report generator for automated reporting
10pub struct ReportGenerator {
11    report_templates: HashMap<String, ReportTemplate>,
12    export_handlers: HashMap<ExportDestination, Box<dyn ExportHandler + Send + Sync>>,
13    visualization_engine: VisualizationEngine,
14}
15
16pub trait ExportHandler {
17    fn export(&self, report: &GeneratedReport) -> Result<String, String>;
18}
19
20#[derive(Debug, Clone)]
21pub struct ReportTemplate {
22    pub template_id: String,
23    pub template_name: String,
24    pub sections: Vec<ReportSection>,
25    pub styling: ReportStyling,
26}
27
28#[derive(Debug, Clone)]
29pub struct ReportSection {
30    pub section_id: String,
31    pub title: String,
32    pub content_type: SectionContentType,
33    pub data_queries: Vec<DataQuery>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum SectionContentType {
38    Text,
39    Table,
40    Chart,
41    Statistical,
42    Comparison,
43    Custom(String),
44}
45
46#[derive(Debug, Clone)]
47pub struct DataQuery {
48    pub query_id: String,
49    pub query_type: QueryType,
50    pub filters: HashMap<String, String>,
51    pub aggregations: Vec<String>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum QueryType {
56    PerformanceMetrics,
57    CostAnalysis,
58    TrendAnalysis,
59    Comparison,
60    Statistical,
61}
62
63#[derive(Debug, Clone)]
64pub struct ReportStyling {
65    pub theme: String,
66    pub color_scheme: Vec<String>,
67    pub font_family: String,
68    pub custom_css: Option<String>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct GeneratedReport {
73    pub report_id: String,
74    pub report_type: String,
75    pub generation_time: SystemTime,
76    pub content: ReportContent,
77    pub metadata: ReportMetadata,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ReportContent {
82    pub sections: Vec<ReportSectionContent>,
83    pub attachments: Vec<ReportAttachment>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ReportSectionContent {
88    pub section_id: String,
89    pub title: String,
90    pub content: String,
91    pub visualizations: Vec<VisualizationData>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ReportAttachment {
96    pub attachment_id: String,
97    pub filename: String,
98    pub content_type: String,
99    pub data: Vec<u8>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ReportMetadata {
104    pub report_id: String,
105    pub title: String,
106    pub description: String,
107    pub author: String,
108    pub keywords: Vec<String>,
109    pub data_sources: Vec<String>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct VisualizationData {
114    pub visualization_id: String,
115    pub visualization_type: String,
116    pub data: Vec<u8>,
117    pub format: String,
118}
119
120#[derive(Debug, Clone)]
121pub struct VisualizationEngine {
122    pub chart_library: String,
123    pub default_width: u32,
124    pub default_height: u32,
125    pub color_palette: Vec<String>,
126    pub font_size: f32,
127    pub marker_size: f32,
128}
129
130impl Default for ReportGenerator {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136impl ReportGenerator {
137    pub fn new() -> Self {
138        Self {
139            report_templates: HashMap::new(),
140            export_handlers: HashMap::new(),
141            visualization_engine: VisualizationEngine {
142                chart_library: "plotters".to_string(),
143                default_width: 800,
144                default_height: 600,
145                color_palette: vec![
146                    "#1f77b4".to_string(),
147                    "#ff7f0e".to_string(),
148                    "#2ca02c".to_string(),
149                    "#d62728".to_string(),
150                ],
151                font_size: 12.0,
152                marker_size: 5.0,
153            },
154        }
155    }
156
157    /// Generate a benchmark report from a `UnifiedBenchmarkResult`.
158    ///
159    /// Returns a `GeneratedReport` containing a Markdown-formatted summary of
160    /// the benchmark run.  The report includes a header section, a per-platform
161    /// performance summary table, timing statistics, and a list of optimisation
162    /// recommendations.
163    pub fn generate_report(
164        &self,
165        result: &super::results::UnifiedBenchmarkResult,
166    ) -> GeneratedReport {
167        let report_id = format!("report_{}", result.execution_id);
168        let mut sections = Vec::new();
169
170        // ── Section 1: header / overview ────────────────────────────────────
171        let overview_md = format!(
172            "# Unified Quantum Benchmark Report\n\n\
173             **Execution ID:** `{}`  \n\
174             **Platforms tested:** {}  \n\
175             **Benchmarks executed:** {}  \n\
176             **Total duration:** {:.3}s\n",
177            result.execution_id,
178            result.execution_metadata.platforms_tested.len(),
179            result.execution_metadata.benchmarks_executed,
180            result.execution_metadata.total_duration.as_secs_f64(),
181        );
182        sections.push(ReportSectionContent {
183            section_id: "overview".to_string(),
184            title: "Overview".to_string(),
185            content: overview_md,
186            visualizations: vec![],
187        });
188
189        // ── Section 2: per-platform performance table ────────────────────────
190        let mut table_md = String::from(
191            "## Platform Performance Summary\n\n\
192             | Platform | Fidelity | Error Rate | Throughput | Availability |\n\
193             |---|---|---|---|---|\n",
194        );
195        for (platform, pr) in &result.platform_results {
196            let m = &pr.performance_metrics;
197            table_md.push_str(&Self::generate_markdown_table_row(
198                &format!("{platform:?}"),
199                m,
200            ));
201        }
202        sections.push(ReportSectionContent {
203            section_id: "platform_summary".to_string(),
204            title: "Platform Summary".to_string(),
205            content: table_md,
206            visualizations: vec![],
207        });
208
209        // ── Section 3: cross-platform analysis ───────────────────────────────
210        let mut cpa_md = String::from(
211            "## Cross-Platform Analysis\n\n| Platform | Composite Score |\n|---|---|\n",
212        );
213        for (label, score) in &result.cross_platform_analysis.platform_comparison {
214            cpa_md.push_str(&format!("| {label} | {score:.4} |\n"));
215        }
216        if let Some((metric, platform)) = result
217            .cross_platform_analysis
218            .best_platform_per_metric
219            .iter()
220            .next()
221        {
222            cpa_md.push_str(&format!(
223                "\nBest platform for **{metric}**: `{platform:?}`\n"
224            ));
225        }
226        sections.push(ReportSectionContent {
227            section_id: "cross_platform".to_string(),
228            title: "Cross-Platform Analysis".to_string(),
229            content: cpa_md,
230            visualizations: vec![],
231        });
232
233        // ── Section 4: optimisation recommendations ──────────────────────────
234        let mut rec_md = String::from("## Optimisation Recommendations\n\n");
235        for (i, rec) in result.optimization_recommendations.iter().enumerate() {
236            rec_md.push_str(&format!(
237                "{}. **{}** (priority {}): {}  \n   Expected improvement: {:.4}  \n   Effort: {}\n\n",
238                i + 1,
239                rec.recommendation_type,
240                rec.priority,
241                rec.description,
242                rec.expected_improvement,
243                rec.implementation_effort,
244            ));
245        }
246        sections.push(ReportSectionContent {
247            section_id: "recommendations".to_string(),
248            title: "Optimisation Recommendations".to_string(),
249            content: rec_md,
250            visualizations: vec![],
251        });
252
253        // ── Section 5: resource and cost summary ─────────────────────────────
254        let cost_md = format!(
255            "## Cost Analysis\n\n\
256             **Total estimated cost:** ${:.4}  \n\
257             **Potential savings:** ${:.4}\n",
258            result.cost_analysis.total_cost,
259            result.cost_analysis.cost_optimization.potential_savings,
260        );
261        sections.push(ReportSectionContent {
262            section_id: "cost".to_string(),
263            title: "Cost Analysis".to_string(),
264            content: cost_md,
265            visualizations: vec![],
266        });
267
268        GeneratedReport {
269            report_id: report_id.clone(),
270            report_type: "unified_benchmark".to_string(),
271            generation_time: std::time::SystemTime::now(),
272            content: ReportContent {
273                sections,
274                attachments: vec![],
275            },
276            metadata: ReportMetadata {
277                report_id,
278                title: format!("Benchmark Report – {}", result.execution_id),
279                description: "Automated unified quantum benchmark report".to_string(),
280                author: "quantrs2-device".to_string(),
281                keywords: vec!["quantum".to_string(), "benchmark".to_string()],
282                data_sources: vec![result.execution_id.clone()],
283            },
284        }
285    }
286
287    /// Build a Markdown table row from a single platform label and its metrics.
288    fn generate_markdown_table_row(
289        label: &str,
290        m: &super::results::PlatformPerformanceMetrics,
291    ) -> String {
292        format!(
293            "| {} | {:.4} | {:.4} | {:.2} ops/s | {:.3} |\n",
294            label, m.overall_fidelity, m.error_rate, m.throughput, m.availability,
295        )
296    }
297
298    /// Convenience wrapper: render the full Markdown text from a report.
299    pub fn report_to_markdown(report: &GeneratedReport) -> String {
300        report
301            .content
302            .sections
303            .iter()
304            .map(|s| s.content.as_str())
305            .collect::<Vec<_>>()
306            .join("\n---\n\n")
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use crate::unified_benchmarking::results::PlatformPerformanceMetrics;
314    use std::time::Duration;
315
316    #[test]
317    fn test_report_generator_new_is_default() {
318        let g1 = ReportGenerator::new();
319        let g2 = ReportGenerator::default();
320        assert_eq!(
321            g1.visualization_engine.chart_library,
322            g2.visualization_engine.chart_library
323        );
324    }
325
326    #[test]
327    fn test_generate_markdown_table_row_format() {
328        let m = PlatformPerformanceMetrics {
329            overall_fidelity: 0.99,
330            average_execution_time: Duration::from_millis(50),
331            throughput: 100.0,
332            error_rate: 0.01,
333            availability: 0.995,
334        };
335        let row = ReportGenerator::generate_markdown_table_row("TestPlatform", &m);
336        assert!(
337            row.contains("0.9900"),
338            "row should contain formatted fidelity"
339        );
340        assert!(
341            row.contains("TestPlatform"),
342            "row should contain platform label"
343        );
344    }
345
346    #[test]
347    fn test_report_to_markdown_combines_sections() {
348        let report = GeneratedReport {
349            report_id: "test_r1".to_string(),
350            report_type: "test".to_string(),
351            generation_time: std::time::SystemTime::UNIX_EPOCH,
352            content: ReportContent {
353                sections: vec![
354                    ReportSectionContent {
355                        section_id: "s1".to_string(),
356                        title: "Section 1".to_string(),
357                        content: "Hello world".to_string(),
358                        visualizations: vec![],
359                    },
360                    ReportSectionContent {
361                        section_id: "s2".to_string(),
362                        title: "Section 2".to_string(),
363                        content: "Second part".to_string(),
364                        visualizations: vec![],
365                    },
366                ],
367                attachments: vec![],
368            },
369            metadata: ReportMetadata {
370                report_id: "test_r1".to_string(),
371                title: "Test".to_string(),
372                description: "desc".to_string(),
373                author: "test".to_string(),
374                keywords: vec![],
375                data_sources: vec![],
376            },
377        };
378        let md = ReportGenerator::report_to_markdown(&report);
379        assert!(
380            md.contains("Hello world"),
381            "combined markdown must include section 1"
382        );
383        assert!(
384            md.contains("Second part"),
385            "combined markdown must include section 2"
386        );
387    }
388}