1#![allow(dead_code)]
10
11use crate::{
12 gradient_debugger::GradientDebugReport,
13 profiler::ProfilerReport,
14 visualization::{DebugVisualizer, PlotData, VisualizationConfig},
15};
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum ReportFormat {
23 Pdf,
25 Markdown,
27 Html,
29 Json,
31 Jupyter,
33 Latex,
35 Excel,
37 PowerPoint,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub enum ReportType {
44 DebugReport,
46 PerformanceReport,
48 TrainingReport,
50 GradientReport,
52 MemoryReport,
54 ComprehensiveReport,
56 CustomReport(Vec<ReportSection>),
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub enum ReportSection {
63 Summary,
65 Architecture,
67 Performance,
69 Memory,
71 Gradients,
73 Training,
75 Errors,
77 Recommendations,
79 Visualizations,
81 RawData,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ReportConfig {
88 pub title: String,
90 pub subtitle: Option<String>,
92 pub author: String,
94 pub organization: Option<String>,
96 pub format: ReportFormat,
98 pub report_type: ReportType,
100 pub include_visualizations: bool,
102 pub include_raw_data: bool,
104 pub output_path: String,
106 pub metadata: HashMap<String, String>,
108}
109
110impl Default for ReportConfig {
111 fn default() -> Self {
112 Self {
113 title: "TrustformeRS Debug Report".to_string(),
114 subtitle: None,
115 author: "TrustformeRS Debugger".to_string(),
116 organization: None,
117 format: ReportFormat::Html,
118 report_type: ReportType::ComprehensiveReport,
119 include_visualizations: true,
120 include_raw_data: false,
121 output_path: "debug_report".to_string(),
122 metadata: HashMap::new(),
123 }
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct Report {
130 pub metadata: ReportMetadata,
132 pub sections: Vec<GeneratedSection>,
134 pub visualizations: HashMap<String, PlotData>,
136 pub raw_data: HashMap<String, serde_json::Value>,
138 pub generated_at: DateTime<Utc>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ReportMetadata {
145 pub title: String,
147 pub subtitle: Option<String>,
149 pub author: String,
151 pub organization: Option<String>,
153 pub version: String,
155 pub generation_time_ms: f64,
157 pub additional_metadata: HashMap<String, String>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct GeneratedSection {
164 pub section_type: ReportSection,
166 pub title: String,
168 pub content: String,
170 pub data: HashMap<String, serde_json::Value>,
172 pub visualizations: Vec<String>,
174}
175
176#[derive(Debug)]
178pub struct ReportGenerator {
179 config: ReportConfig,
181 debug_data: Option<GradientDebugReport>,
183 profiling_data: Option<ProfilerReport>,
185 visualizer: DebugVisualizer,
187}
188
189impl ReportGenerator {
190 pub fn new(config: ReportConfig) -> Self {
192 Self {
193 config,
194 debug_data: None,
195 profiling_data: None,
196 visualizer: DebugVisualizer::new(VisualizationConfig::default()),
197 }
198 }
199
200 pub fn with_debug_data(mut self, data: GradientDebugReport) -> Self {
202 self.debug_data = Some(data);
203 self
204 }
205
206 pub fn with_profiling_data(mut self, data: ProfilerReport) -> Self {
208 self.profiling_data = Some(data);
209 self
210 }
211
212 pub fn generate(&self) -> Result<Report, ReportError> {
214 let start_time = std::time::Instant::now();
215
216 let sections = match &self.config.report_type {
217 ReportType::DebugReport => self.generate_debug_sections()?,
218 ReportType::PerformanceReport => self.generate_performance_sections()?,
219 ReportType::TrainingReport => self.generate_training_sections()?,
220 ReportType::GradientReport => self.generate_gradient_sections()?,
221 ReportType::MemoryReport => self.generate_memory_sections()?,
222 ReportType::ComprehensiveReport => self.generate_comprehensive_sections()?,
223 ReportType::CustomReport(section_types) => {
224 self.generate_custom_sections(section_types)?
225 },
226 };
227
228 let visualizations = if self.config.include_visualizations {
229 self.generate_visualizations()?
230 } else {
231 HashMap::new()
232 };
233
234 let raw_data = if self.config.include_raw_data {
235 self.generate_raw_data()?
236 } else {
237 HashMap::new()
238 };
239
240 let generation_time = start_time.elapsed().as_secs_f64() * 1000.0;
241
242 let report = Report {
243 metadata: ReportMetadata {
244 title: self.config.title.clone(),
245 subtitle: self.config.subtitle.clone(),
246 author: self.config.author.clone(),
247 organization: self.config.organization.clone(),
248 version: "1.0".to_string(),
249 generation_time_ms: generation_time,
250 additional_metadata: self.config.metadata.clone(),
251 },
252 sections,
253 visualizations,
254 raw_data,
255 generated_at: Utc::now(),
256 };
257
258 Ok(report)
259 }
260
261 fn generate_debug_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
263 let mut sections = Vec::new();
264
265 sections.push(self.generate_summary_section()?);
267
268 sections.push(self.generate_architecture_section()?);
270
271 if self.debug_data.is_some() {
273 sections.push(self.generate_gradients_section()?);
274 }
275
276 sections.push(self.generate_errors_section()?);
278
279 sections.push(self.generate_recommendations_section()?);
281
282 Ok(sections)
283 }
284
285 fn generate_performance_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
287 let mut sections = Vec::new();
288
289 sections.push(self.generate_summary_section()?);
290 sections.push(self.generate_performance_section()?);
291
292 if self.profiling_data.is_some() {
293 sections.push(self.generate_memory_section()?);
294 }
295
296 sections.push(self.generate_recommendations_section()?);
297
298 Ok(sections)
299 }
300
301 fn generate_training_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
303 let mut sections = Vec::new();
304
305 sections.push(self.generate_summary_section()?);
306 sections.push(self.generate_training_section()?);
307 sections.push(self.generate_gradients_section()?);
308 sections.push(self.generate_recommendations_section()?);
309
310 Ok(sections)
311 }
312
313 fn generate_gradient_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
315 let mut sections = Vec::new();
316
317 sections.push(self.generate_summary_section()?);
318 sections.push(self.generate_gradients_section()?);
319 sections.push(self.generate_recommendations_section()?);
320
321 Ok(sections)
322 }
323
324 fn generate_memory_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
326 let mut sections = Vec::new();
327
328 sections.push(self.generate_summary_section()?);
329 sections.push(self.generate_memory_section()?);
330 sections.push(self.generate_recommendations_section()?);
331
332 Ok(sections)
333 }
334
335 fn generate_comprehensive_sections(&self) -> Result<Vec<GeneratedSection>, ReportError> {
337 let mut sections = Vec::new();
338
339 sections.push(self.generate_summary_section()?);
340 sections.push(self.generate_architecture_section()?);
341 sections.push(self.generate_performance_section()?);
342 sections.push(self.generate_memory_section()?);
343 sections.push(self.generate_gradients_section()?);
344 sections.push(self.generate_training_section()?);
345 sections.push(self.generate_errors_section()?);
346 sections.push(self.generate_recommendations_section()?);
347
348 Ok(sections)
349 }
350
351 fn generate_custom_sections(
353 &self,
354 section_types: &[ReportSection],
355 ) -> Result<Vec<GeneratedSection>, ReportError> {
356 let mut sections = Vec::new();
357
358 for section_type in section_types {
359 let section = match section_type {
360 ReportSection::Summary => self.generate_summary_section()?,
361 ReportSection::Architecture => self.generate_architecture_section()?,
362 ReportSection::Performance => self.generate_performance_section()?,
363 ReportSection::Memory => self.generate_memory_section()?,
364 ReportSection::Gradients => self.generate_gradients_section()?,
365 ReportSection::Training => self.generate_training_section()?,
366 ReportSection::Errors => self.generate_errors_section()?,
367 ReportSection::Recommendations => self.generate_recommendations_section()?,
368 ReportSection::Visualizations => self.generate_visualizations_section()?,
369 ReportSection::RawData => self.generate_raw_data_section()?,
370 };
371 sections.push(section);
372 }
373
374 Ok(sections)
375 }
376
377 fn generate_summary_section(&self) -> Result<GeneratedSection, ReportError> {
379 let mut content = String::new();
380 let mut data = HashMap::new();
381
382 content.push_str("## Executive Summary\n\n");
383 content.push_str("This report provides a comprehensive analysis of the TrustformeRS model debugging session.\n\n");
384
385 if let Some(debug_data) = &self.debug_data {
387 content.push_str(&format!(
388 "- **Total Layers Analyzed**: {}\n",
389 debug_data.flow_analysis.layer_analyses.len()
390 ));
391
392 let healthy_layers = debug_data
393 .flow_analysis
394 .layer_analyses
395 .iter()
396 .filter(|(_name, l)| !l.is_vanishing && !l.is_exploding)
397 .count();
398 content.push_str(&format!("- **Healthy Layers**: {}\n", healthy_layers));
399
400 data.insert(
401 "total_layers".to_string(),
402 serde_json::json!(debug_data.flow_analysis.layer_analyses.len()),
403 );
404 data.insert(
405 "healthy_layers".to_string(),
406 serde_json::json!(healthy_layers),
407 );
408 }
409
410 if let Some(profiling_data) = &self.profiling_data {
411 content.push_str(&format!(
412 "- **Total Memory Usage**: {:.2} MB\n",
413 profiling_data.memory_efficiency.peak_memory_mb
414 ));
415 content.push_str(&format!(
416 "- **Execution Time**: {:.2} ms\n",
417 profiling_data.total_runtime.as_millis() as f64
418 ));
419
420 data.insert(
421 "peak_memory_mb".to_string(),
422 serde_json::json!(profiling_data.memory_efficiency.peak_memory_mb),
423 );
424 data.insert(
425 "total_time_ms".to_string(),
426 serde_json::json!(profiling_data.total_runtime.as_millis() as f64),
427 );
428 }
429
430 Ok(GeneratedSection {
431 section_type: ReportSection::Summary,
432 title: "Executive Summary".to_string(),
433 content,
434 data,
435 visualizations: Vec::new(),
436 })
437 }
438
439 fn generate_architecture_section(&self) -> Result<GeneratedSection, ReportError> {
441 let mut content = String::new();
442 let data = HashMap::new();
443
444 content.push_str("## Model Architecture Analysis\n\n");
445 content.push_str("This section provides detailed analysis of the model architecture.\n\n");
446
447 content.push_str("### Layer Structure\n\n");
449 if let Some(debug_data) = &self.debug_data {
450 content.push_str("| Layer | Type | Parameters | Health Status |\n");
451 content.push_str("|-------|------|------------|---------------|\n");
452
453 for (i, (layer_name, layer)) in
454 debug_data.flow_analysis.layer_analyses.iter().enumerate()
455 {
456 let health = if layer.is_vanishing {
457 "Vanishing"
458 } else if layer.is_exploding {
459 "Exploding"
460 } else {
461 "Healthy"
462 };
463 content.push_str(&format!(
464 "| {} | {} | {} | {} |\n",
465 i,
466 layer_name,
467 "N/A", health
469 ));
470 }
471 } else {
472 content.push_str("No architecture data available.\n");
473 }
474
475 Ok(GeneratedSection {
476 section_type: ReportSection::Architecture,
477 title: "Model Architecture Analysis".to_string(),
478 content,
479 data,
480 visualizations: vec!["architecture_diagram".to_string()],
481 })
482 }
483
484 fn generate_performance_section(&self) -> Result<GeneratedSection, ReportError> {
486 let mut content = String::new();
487 let mut data = HashMap::new();
488
489 content.push_str("## Performance Analysis\n\n");
490
491 if let Some(profiling_data) = &self.profiling_data {
492 content.push_str("### Timing Statistics\n\n");
493 content.push_str(&format!(
494 "- **Total Execution Time**: {:.2} ms\n",
495 profiling_data.total_runtime.as_millis() as f64
496 ));
497 content.push_str(&format!(
498 "- **Forward Pass Time**: {:.2} ms\n",
499 profiling_data.total_runtime.as_millis() as f64 * 0.6
500 )); content.push_str(&format!(
502 "- **Backward Pass Time**: {:.2} ms\n",
503 profiling_data.total_runtime.as_millis() as f64 * 0.4
504 )); content.push_str("\n### Throughput\n\n");
507 let tokens_per_sec = 1000.0 / (profiling_data.total_runtime.as_millis() as f64 + 1.0); content.push_str(&format!("- **Tokens per Second**: {:.2}\n", tokens_per_sec));
509 content.push_str(&format!(
510 "- **Samples per Second**: {:.2}\n",
511 tokens_per_sec * 10.0
512 )); let timing_stats = serde_json::json!({
516 "total_time_ms": profiling_data.total_runtime.as_millis() as f64,
517 "forward_pass_ms": profiling_data.total_runtime.as_millis() as f64 * 0.6,
518 "backward_pass_ms": profiling_data.total_runtime.as_millis() as f64 * 0.4
519 });
520 let throughput_stats = serde_json::json!({
521 "tokens_per_second": tokens_per_sec,
522 "samples_per_second": tokens_per_sec * 10.0
523 });
524 data.insert("timing_stats".to_string(), timing_stats);
525 data.insert("throughput_stats".to_string(), throughput_stats);
526 } else {
527 content.push_str("No performance data available.\n");
528 }
529
530 Ok(GeneratedSection {
531 section_type: ReportSection::Performance,
532 title: "Performance Analysis".to_string(),
533 content,
534 data,
535 visualizations: vec!["performance_chart".to_string()],
536 })
537 }
538
539 fn generate_memory_section(&self) -> Result<GeneratedSection, ReportError> {
541 let mut content = String::new();
542 let mut data = HashMap::new();
543
544 content.push_str("## Memory Analysis\n\n");
545
546 if let Some(profiling_data) = &self.profiling_data {
547 content.push_str("### Memory Usage\n\n");
548 content.push_str(&format!(
549 "- **Peak Memory**: {:.2} MB\n",
550 profiling_data.memory_efficiency.peak_memory_mb
551 ));
552 content.push_str(&format!(
553 "- **Current Memory**: {:.2} MB\n",
554 profiling_data.memory_efficiency.avg_memory_mb
555 ));
556 content.push_str(&format!(
557 "- **Memory Efficiency**: {:.2}%\n",
558 profiling_data.memory_efficiency.efficiency_score
559 ));
560
561 data.insert(
562 "memory_stats".to_string(),
563 serde_json::to_value(&profiling_data.memory_efficiency)
564 .map_err(|e| ReportError::SerializationError(e.to_string()))?,
565 );
566 } else {
567 content.push_str("No memory data available.\n");
568 }
569
570 Ok(GeneratedSection {
571 section_type: ReportSection::Memory,
572 title: "Memory Analysis".to_string(),
573 content,
574 data,
575 visualizations: vec!["memory_chart".to_string()],
576 })
577 }
578
579 fn generate_gradients_section(&self) -> Result<GeneratedSection, ReportError> {
581 let mut content = String::new();
582 let mut data = HashMap::new();
583
584 content.push_str("## Gradient Analysis\n\n");
585
586 if let Some(debug_data) = &self.debug_data {
587 content.push_str("### Gradient Health Summary\n\n");
588
589 let healthy_count = debug_data
590 .flow_analysis
591 .layer_analyses
592 .iter()
593 .filter(|(_name, l)| !l.is_vanishing && !l.is_exploding)
594 .count();
595 let problematic_count = debug_data.flow_analysis.layer_analyses.len() - healthy_count;
596
597 content.push_str(&format!("- **Healthy Layers**: {}\n", healthy_count));
598 content.push_str(&format!(
599 "- **Problematic Layers**: {}\n",
600 problematic_count
601 ));
602
603 if problematic_count > 0 {
604 content.push_str("\n### Issues Detected\n\n");
605 for (i, (layer_name, layer)) in
606 debug_data.flow_analysis.layer_analyses.iter().enumerate()
607 {
608 if layer.is_vanishing || layer.is_exploding {
609 let status = if layer.is_vanishing {
610 "Vanishing gradients"
611 } else {
612 "Exploding gradients"
613 };
614 content
615 .push_str(&format!("- **Layer {}** ({}): {}\n", i, layer_name, status));
616 }
617 }
618 }
619
620 data.insert(
621 "gradient_analysis".to_string(),
622 serde_json::to_value(debug_data)
623 .map_err(|e| ReportError::SerializationError(e.to_string()))?,
624 );
625 } else {
626 content.push_str("No gradient data available.\n");
627 }
628
629 Ok(GeneratedSection {
630 section_type: ReportSection::Gradients,
631 title: "Gradient Analysis".to_string(),
632 content,
633 data,
634 visualizations: vec!["gradient_flow_chart".to_string()],
635 })
636 }
637
638 fn generate_training_section(&self) -> Result<GeneratedSection, ReportError> {
640 let content =
641 "## Training Dynamics\n\nTraining dynamics analysis would go here.".to_string();
642 let data = HashMap::new();
643
644 Ok(GeneratedSection {
645 section_type: ReportSection::Training,
646 title: "Training Dynamics".to_string(),
647 content,
648 data,
649 visualizations: vec!["training_curves".to_string()],
650 })
651 }
652
653 fn generate_errors_section(&self) -> Result<GeneratedSection, ReportError> {
655 let content = "## Error Analysis\n\nError analysis would go here.".to_string();
656 let data = HashMap::new();
657
658 Ok(GeneratedSection {
659 section_type: ReportSection::Errors,
660 title: "Error Analysis".to_string(),
661 content,
662 data,
663 visualizations: Vec::new(),
664 })
665 }
666
667 fn generate_recommendations_section(&self) -> Result<GeneratedSection, ReportError> {
669 let mut content = String::new();
670 let data = HashMap::new();
671
672 content.push_str("## Recommendations\n\n");
673 content.push_str("Based on the analysis, here are our recommendations:\n\n");
674
675 if let Some(debug_data) = &self.debug_data {
677 let problematic_layers = debug_data
678 .flow_analysis
679 .layer_analyses
680 .iter()
681 .filter(|(_name, l)| l.is_vanishing || l.is_exploding)
682 .count();
683
684 if problematic_layers > 0 {
685 content.push_str("### Gradient Issues\n\n");
686 content.push_str("- Consider adjusting learning rate\n");
687 content.push_str("- Review gradient clipping settings\n");
688 content.push_str("- Check for numerical instabilities\n\n");
689 }
690 }
691
692 if let Some(profiling_data) = &self.profiling_data {
693 if profiling_data.memory_efficiency.efficiency_score < 80.0 {
694 content.push_str("### Memory Optimization\n\n");
695 content.push_str("- Consider using gradient checkpointing\n");
696 content.push_str("- Review batch size settings\n");
697 content.push_str("- Consider model quantization\n\n");
698 }
699 }
700
701 content.push_str("### General Recommendations\n\n");
702 content.push_str("- Monitor training regularly\n");
703 content.push_str("- Validate on diverse test sets\n");
704 content.push_str("- Keep detailed training logs\n");
705
706 Ok(GeneratedSection {
707 section_type: ReportSection::Recommendations,
708 title: "Recommendations".to_string(),
709 content,
710 data,
711 visualizations: Vec::new(),
712 })
713 }
714
715 fn generate_visualizations_section(&self) -> Result<GeneratedSection, ReportError> {
717 let content = "## Visualizations\n\nVisualization section content.".to_string();
718 let data = HashMap::new();
719
720 Ok(GeneratedSection {
721 section_type: ReportSection::Visualizations,
722 title: "Visualizations".to_string(),
723 content,
724 data,
725 visualizations: vec!["all_charts".to_string()],
726 })
727 }
728
729 fn generate_raw_data_section(&self) -> Result<GeneratedSection, ReportError> {
731 let content = "## Raw Data\n\nRaw data section content.".to_string();
732 let data = HashMap::new();
733
734 Ok(GeneratedSection {
735 section_type: ReportSection::RawData,
736 title: "Raw Data".to_string(),
737 content,
738 data,
739 visualizations: Vec::new(),
740 })
741 }
742
743 fn generate_visualizations(&self) -> Result<HashMap<String, PlotData>, ReportError> {
745 let mut visualizations = HashMap::new();
746
747 if self.profiling_data.is_some() {
749 let plot_data = PlotData {
750 x_values: vec![1.0, 2.0, 3.0], y_values: vec![10.0, 15.0, 12.0], labels: vec!["A".to_string(), "B".to_string(), "C".to_string()],
753 title: "Performance Chart".to_string(),
754 x_label: "Time".to_string(),
755 y_label: "Performance".to_string(),
756 };
757 visualizations.insert("performance_chart".to_string(), plot_data);
758 }
759
760 Ok(visualizations)
761 }
762
763 fn generate_raw_data(&self) -> Result<HashMap<String, serde_json::Value>, ReportError> {
765 let mut raw_data = HashMap::new();
766
767 if let Some(debug_data) = &self.debug_data {
768 raw_data.insert(
769 "debug_data".to_string(),
770 serde_json::to_value(debug_data)
771 .map_err(|e| ReportError::SerializationError(e.to_string()))?,
772 );
773 }
774
775 if let Some(profiling_data) = &self.profiling_data {
776 raw_data.insert(
777 "profiling_data".to_string(),
778 serde_json::to_value(profiling_data)
779 .map_err(|e| ReportError::SerializationError(e.to_string()))?,
780 );
781 }
782
783 Ok(raw_data)
784 }
785
786 pub fn export_report(&self, report: &Report) -> Result<(), ReportError> {
788 match self.config.format {
789 ReportFormat::Html => self.export_html(report),
790 ReportFormat::Markdown => self.export_markdown(report),
791 ReportFormat::Json => self.export_json(report),
792 ReportFormat::Pdf => self.export_pdf(report),
793 ReportFormat::Jupyter => self.export_jupyter(report),
794 ReportFormat::Latex => self.export_latex(report),
795 ReportFormat::Excel => self.export_excel(report),
796 ReportFormat::PowerPoint => self.export_powerpoint(report),
797 }
798 }
799
800 fn export_html(&self, report: &Report) -> Result<(), ReportError> {
802 let mut html = String::new();
803
804 html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
805 html.push_str(&format!("<title>{}</title>\n", report.metadata.title));
806 html.push_str("<style>body { font-family: Arial, sans-serif; margin: 40px; }</style>\n");
807 html.push_str("</head>\n<body>\n");
808
809 html.push_str(&format!("<h1>{}</h1>\n", report.metadata.title));
810 if let Some(subtitle) = &report.metadata.subtitle {
811 html.push_str(&format!("<h2>{}</h2>\n", subtitle));
812 }
813
814 for section in &report.sections {
815 html.push_str(§ion.content);
816 }
817
818 html.push_str("</body>\n</html>");
819
820 std::fs::write(format!("{}.html", self.config.output_path), html)
821 .map_err(|e| ReportError::FileError(e.to_string()))?;
822
823 Ok(())
824 }
825
826 fn export_markdown(&self, report: &Report) -> Result<(), ReportError> {
828 let mut markdown = String::new();
829
830 markdown.push_str(&format!("# {}\n\n", report.metadata.title));
831 if let Some(subtitle) = &report.metadata.subtitle {
832 markdown.push_str(&format!("## {}\n\n", subtitle));
833 }
834
835 markdown.push_str(&format!("**Author**: {}\n", report.metadata.author));
836 markdown.push_str(&format!(
837 "**Generated**: {}\n\n",
838 report.generated_at.format("%Y-%m-%d %H:%M:%S UTC")
839 ));
840
841 for section in &report.sections {
842 markdown.push_str(§ion.content);
843 markdown.push('\n');
844 }
845
846 std::fs::write(format!("{}.md", self.config.output_path), markdown)
847 .map_err(|e| ReportError::FileError(e.to_string()))?;
848
849 Ok(())
850 }
851
852 fn export_json(&self, report: &Report) -> Result<(), ReportError> {
854 let json = serde_json::to_string_pretty(report)
855 .map_err(|e| ReportError::SerializationError(e.to_string()))?;
856
857 std::fs::write(format!("{}.json", self.config.output_path), json)
858 .map_err(|e| ReportError::FileError(e.to_string()))?;
859
860 Ok(())
861 }
862
863 fn export_pdf(&self, _report: &Report) -> Result<(), ReportError> {
865 Err(ReportError::UnsupportedFormat(
867 "PDF export not implemented".to_string(),
868 ))
869 }
870
871 fn export_jupyter(&self, report: &Report) -> Result<(), ReportError> {
873 let mut notebook = serde_json::json!({
874 "cells": [],
875 "metadata": {
876 "kernelspec": {
877 "display_name": "Python 3",
878 "language": "python",
879 "name": "python3"
880 }
881 },
882 "nbformat": 4,
883 "nbformat_minor": 4
884 });
885
886 let title_cell = serde_json::json!({
888 "cell_type": "markdown",
889 "metadata": {},
890 "source": [format!("# {}\n\n**Generated**: {}",
891 report.metadata.title,
892 report.generated_at.format("%Y-%m-%d %H:%M:%S UTC"))]
893 });
894 let cells = notebook["cells"].as_array_mut().ok_or_else(|| {
895 ReportError::SerializationError("notebook cells should be an array".to_string())
896 })?;
897 cells.push(title_cell);
898
899 for section in &report.sections {
901 let cell = serde_json::json!({
902 "cell_type": "markdown",
903 "metadata": {},
904 "source": [section.content]
905 });
906 cells.push(cell);
907 }
908
909 let notebook_str = serde_json::to_string_pretty(¬ebook)
910 .map_err(|e| ReportError::SerializationError(e.to_string()))?;
911
912 std::fs::write(format!("{}.ipynb", self.config.output_path), notebook_str)
913 .map_err(|e| ReportError::FileError(e.to_string()))?;
914
915 Ok(())
916 }
917
918 fn export_latex(&self, report: &Report) -> Result<(), ReportError> {
920 let mut latex = String::new();
921
922 latex.push_str("\\documentclass{article}\n");
923 latex.push_str("\\begin{document}\n");
924 latex.push_str(&format!("\\title{{{}}}\n", report.metadata.title));
925 latex.push_str(&format!("\\author{{{}}}\n", report.metadata.author));
926 latex.push_str("\\maketitle\n\n");
927
928 for section in &report.sections {
929 let latex_content = section
931 .content
932 .replace("##", "\\section{")
933 .replace("###", "\\subsection{")
934 .replace("#", "\\section{");
935 latex.push_str(&latex_content);
936 }
937
938 latex.push_str("\\end{document}\n");
939
940 std::fs::write(format!("{}.tex", self.config.output_path), latex)
941 .map_err(|e| ReportError::FileError(e.to_string()))?;
942
943 Ok(())
944 }
945
946 fn export_excel(&self, _report: &Report) -> Result<(), ReportError> {
948 Err(ReportError::UnsupportedFormat(
949 "Excel export not implemented".to_string(),
950 ))
951 }
952
953 fn export_powerpoint(&self, _report: &Report) -> Result<(), ReportError> {
955 Err(ReportError::UnsupportedFormat(
956 "PowerPoint export not implemented".to_string(),
957 ))
958 }
959}
960
961#[derive(Debug, Clone)]
963pub enum ReportError {
964 FileError(String),
966 SerializationError(String),
968 UnsupportedFormat(String),
970 MissingData(String),
972 GenerationError(String),
974}
975
976impl std::fmt::Display for ReportError {
977 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
978 match self {
979 ReportError::FileError(msg) => write!(f, "File error: {}", msg),
980 ReportError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
981 ReportError::UnsupportedFormat(msg) => write!(f, "Unsupported format: {}", msg),
982 ReportError::MissingData(msg) => write!(f, "Missing data: {}", msg),
983 ReportError::GenerationError(msg) => write!(f, "Generation error: {}", msg),
984 }
985 }
986}
987
988impl std::error::Error for ReportError {}
989
990#[cfg(test)]
991mod tests {
992 use super::*;
993
994 #[test]
995 fn test_report_config_default() {
996 let config = ReportConfig::default();
997 assert_eq!(config.title, "TrustformeRS Debug Report");
998 assert_eq!(config.author, "TrustformeRS Debugger");
999 assert!(matches!(config.format, ReportFormat::Html));
1000 assert!(matches!(
1001 config.report_type,
1002 ReportType::ComprehensiveReport
1003 ));
1004 }
1005
1006 #[test]
1007 fn test_report_generator_creation() {
1008 let config = ReportConfig::default();
1009 let generator = ReportGenerator::new(config);
1010 assert!(generator.debug_data.is_none());
1011 assert!(generator.profiling_data.is_none());
1012 }
1013
1014 #[test]
1015 fn test_report_generation() {
1016 let config = ReportConfig {
1017 title: "Test Report".to_string(),
1018 format: ReportFormat::Json,
1019 report_type: ReportType::DebugReport,
1020 ..Default::default()
1021 };
1022
1023 let generator = ReportGenerator::new(config);
1024 let report = generator.generate().expect("operation failed in test");
1025
1026 assert_eq!(report.metadata.title, "Test Report");
1027 assert!(!report.sections.is_empty());
1028 }
1029
1030 #[test]
1031 fn test_section_generation() {
1032 let config = ReportConfig::default();
1033 let generator = ReportGenerator::new(config);
1034
1035 let summary = generator.generate_summary_section().expect("operation failed in test");
1036 assert!(matches!(summary.section_type, ReportSection::Summary));
1037 assert_eq!(summary.title, "Executive Summary");
1038 assert!(!summary.content.is_empty());
1039 }
1040
1041 #[test]
1042 fn test_custom_report_type() {
1043 let config = ReportConfig {
1044 report_type: ReportType::CustomReport(vec![
1045 ReportSection::Summary,
1046 ReportSection::Performance,
1047 ]),
1048 ..Default::default()
1049 };
1050
1051 let generator = ReportGenerator::new(config);
1052 let report = generator.generate().expect("operation failed in test");
1053
1054 assert_eq!(report.sections.len(), 2);
1055 assert!(matches!(
1056 report.sections[0].section_type,
1057 ReportSection::Summary
1058 ));
1059 assert!(matches!(
1060 report.sections[1].section_type,
1061 ReportSection::Performance
1062 ));
1063 }
1064
1065 #[test]
1066 fn test_report_serialization() {
1067 let config = ReportConfig::default();
1068 let generator = ReportGenerator::new(config);
1069 let report = generator.generate().expect("operation failed in test");
1070
1071 let json = serde_json::to_string(&report).expect("JSON serialization failed");
1072 let deserialized: Report =
1073 serde_json::from_str(&json).expect("JSON deserialization failed");
1074
1075 assert_eq!(report.metadata.title, deserialized.metadata.title);
1076 assert_eq!(report.sections.len(), deserialized.sections.len());
1077 }
1078}