1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::time::SystemTime;
6
7use super::config::ExportDestination;
8
9pub 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 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 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 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 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 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 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 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 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}