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