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)
561 .expect("memory_efficiency should always serialize to JSON"),
562 );
563 } else {
564 content.push_str("No memory data available.\n");
565 }
566
567 Ok(GeneratedSection {
568 section_type: ReportSection::Memory,
569 title: "Memory Analysis".to_string(),
570 content,
571 data,
572 visualizations: vec!["memory_chart".to_string()],
573 })
574 }
575
576 fn generate_gradients_section(&self) -> Result<GeneratedSection, ReportError> {
578 let mut content = String::new();
579 let mut data = HashMap::new();
580
581 content.push_str("## Gradient Analysis\n\n");
582
583 if let Some(debug_data) = &self.debug_data {
584 content.push_str("### Gradient Health Summary\n\n");
585
586 let healthy_count = debug_data
587 .flow_analysis
588 .layer_analyses
589 .iter()
590 .filter(|(_name, l)| !l.is_vanishing && !l.is_exploding)
591 .count();
592 let problematic_count = debug_data.flow_analysis.layer_analyses.len() - healthy_count;
593
594 content.push_str(&format!("- **Healthy Layers**: {}\n", healthy_count));
595 content.push_str(&format!(
596 "- **Problematic Layers**: {}\n",
597 problematic_count
598 ));
599
600 if problematic_count > 0 {
601 content.push_str("\n### Issues Detected\n\n");
602 for (i, (layer_name, layer)) in
603 debug_data.flow_analysis.layer_analyses.iter().enumerate()
604 {
605 if layer.is_vanishing || layer.is_exploding {
606 let status = if layer.is_vanishing {
607 "Vanishing gradients"
608 } else {
609 "Exploding gradients"
610 };
611 content
612 .push_str(&format!("- **Layer {}** ({}): {}\n", i, layer_name, status));
613 }
614 }
615 }
616
617 data.insert(
618 "gradient_analysis".to_string(),
619 serde_json::to_value(debug_data)
620 .expect("debug_data should always serialize to JSON"),
621 );
622 } else {
623 content.push_str("No gradient data available.\n");
624 }
625
626 Ok(GeneratedSection {
627 section_type: ReportSection::Gradients,
628 title: "Gradient Analysis".to_string(),
629 content,
630 data,
631 visualizations: vec!["gradient_flow_chart".to_string()],
632 })
633 }
634
635 fn generate_training_section(&self) -> Result<GeneratedSection, ReportError> {
637 let content =
638 "## Training Dynamics\n\nTraining dynamics analysis would go here.".to_string();
639 let data = HashMap::new();
640
641 Ok(GeneratedSection {
642 section_type: ReportSection::Training,
643 title: "Training Dynamics".to_string(),
644 content,
645 data,
646 visualizations: vec!["training_curves".to_string()],
647 })
648 }
649
650 fn generate_errors_section(&self) -> Result<GeneratedSection, ReportError> {
652 let content = "## Error Analysis\n\nError analysis would go here.".to_string();
653 let data = HashMap::new();
654
655 Ok(GeneratedSection {
656 section_type: ReportSection::Errors,
657 title: "Error Analysis".to_string(),
658 content,
659 data,
660 visualizations: Vec::new(),
661 })
662 }
663
664 fn generate_recommendations_section(&self) -> Result<GeneratedSection, ReportError> {
666 let mut content = String::new();
667 let data = HashMap::new();
668
669 content.push_str("## Recommendations\n\n");
670 content.push_str("Based on the analysis, here are our recommendations:\n\n");
671
672 if let Some(debug_data) = &self.debug_data {
674 let problematic_layers = debug_data
675 .flow_analysis
676 .layer_analyses
677 .iter()
678 .filter(|(_name, l)| l.is_vanishing || l.is_exploding)
679 .count();
680
681 if problematic_layers > 0 {
682 content.push_str("### Gradient Issues\n\n");
683 content.push_str("- Consider adjusting learning rate\n");
684 content.push_str("- Review gradient clipping settings\n");
685 content.push_str("- Check for numerical instabilities\n\n");
686 }
687 }
688
689 if let Some(profiling_data) = &self.profiling_data {
690 if profiling_data.memory_efficiency.efficiency_score < 80.0 {
691 content.push_str("### Memory Optimization\n\n");
692 content.push_str("- Consider using gradient checkpointing\n");
693 content.push_str("- Review batch size settings\n");
694 content.push_str("- Consider model quantization\n\n");
695 }
696 }
697
698 content.push_str("### General Recommendations\n\n");
699 content.push_str("- Monitor training regularly\n");
700 content.push_str("- Validate on diverse test sets\n");
701 content.push_str("- Keep detailed training logs\n");
702
703 Ok(GeneratedSection {
704 section_type: ReportSection::Recommendations,
705 title: "Recommendations".to_string(),
706 content,
707 data,
708 visualizations: Vec::new(),
709 })
710 }
711
712 fn generate_visualizations_section(&self) -> Result<GeneratedSection, ReportError> {
714 let content = "## Visualizations\n\nVisualization section content.".to_string();
715 let data = HashMap::new();
716
717 Ok(GeneratedSection {
718 section_type: ReportSection::Visualizations,
719 title: "Visualizations".to_string(),
720 content,
721 data,
722 visualizations: vec!["all_charts".to_string()],
723 })
724 }
725
726 fn generate_raw_data_section(&self) -> Result<GeneratedSection, ReportError> {
728 let content = "## Raw Data\n\nRaw data section content.".to_string();
729 let data = HashMap::new();
730
731 Ok(GeneratedSection {
732 section_type: ReportSection::RawData,
733 title: "Raw Data".to_string(),
734 content,
735 data,
736 visualizations: Vec::new(),
737 })
738 }
739
740 fn generate_visualizations(&self) -> Result<HashMap<String, PlotData>, ReportError> {
742 let mut visualizations = HashMap::new();
743
744 if self.profiling_data.is_some() {
746 let plot_data = PlotData {
747 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()],
750 title: "Performance Chart".to_string(),
751 x_label: "Time".to_string(),
752 y_label: "Performance".to_string(),
753 };
754 visualizations.insert("performance_chart".to_string(), plot_data);
755 }
756
757 Ok(visualizations)
758 }
759
760 fn generate_raw_data(&self) -> Result<HashMap<String, serde_json::Value>, ReportError> {
762 let mut raw_data = HashMap::new();
763
764 if let Some(debug_data) = &self.debug_data {
765 raw_data.insert(
766 "debug_data".to_string(),
767 serde_json::to_value(debug_data)
768 .expect("debug_data should always serialize to JSON"),
769 );
770 }
771
772 if let Some(profiling_data) = &self.profiling_data {
773 raw_data.insert(
774 "profiling_data".to_string(),
775 serde_json::to_value(profiling_data)
776 .expect("profiling_data should always serialize to JSON"),
777 );
778 }
779
780 Ok(raw_data)
781 }
782
783 pub fn export_report(&self, report: &Report) -> Result<(), ReportError> {
785 match self.config.format {
786 ReportFormat::Html => self.export_html(report),
787 ReportFormat::Markdown => self.export_markdown(report),
788 ReportFormat::Json => self.export_json(report),
789 ReportFormat::Pdf => self.export_pdf(report),
790 ReportFormat::Jupyter => self.export_jupyter(report),
791 ReportFormat::Latex => self.export_latex(report),
792 ReportFormat::Excel => self.export_excel(report),
793 ReportFormat::PowerPoint => self.export_powerpoint(report),
794 }
795 }
796
797 fn export_html(&self, report: &Report) -> Result<(), ReportError> {
799 let mut html = String::new();
800
801 html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
802 html.push_str(&format!("<title>{}</title>\n", report.metadata.title));
803 html.push_str("<style>body { font-family: Arial, sans-serif; margin: 40px; }</style>\n");
804 html.push_str("</head>\n<body>\n");
805
806 html.push_str(&format!("<h1>{}</h1>\n", report.metadata.title));
807 if let Some(subtitle) = &report.metadata.subtitle {
808 html.push_str(&format!("<h2>{}</h2>\n", subtitle));
809 }
810
811 for section in &report.sections {
812 html.push_str(§ion.content);
813 }
814
815 html.push_str("</body>\n</html>");
816
817 std::fs::write(format!("{}.html", self.config.output_path), html)
818 .map_err(|e| ReportError::FileError(e.to_string()))?;
819
820 Ok(())
821 }
822
823 fn export_markdown(&self, report: &Report) -> Result<(), ReportError> {
825 let mut markdown = String::new();
826
827 markdown.push_str(&format!("# {}\n\n", report.metadata.title));
828 if let Some(subtitle) = &report.metadata.subtitle {
829 markdown.push_str(&format!("## {}\n\n", subtitle));
830 }
831
832 markdown.push_str(&format!("**Author**: {}\n", report.metadata.author));
833 markdown.push_str(&format!(
834 "**Generated**: {}\n\n",
835 report.generated_at.format("%Y-%m-%d %H:%M:%S UTC")
836 ));
837
838 for section in &report.sections {
839 markdown.push_str(§ion.content);
840 markdown.push('\n');
841 }
842
843 std::fs::write(format!("{}.md", self.config.output_path), markdown)
844 .map_err(|e| ReportError::FileError(e.to_string()))?;
845
846 Ok(())
847 }
848
849 fn export_json(&self, report: &Report) -> Result<(), ReportError> {
851 let json = serde_json::to_string_pretty(report)
852 .map_err(|e| ReportError::SerializationError(e.to_string()))?;
853
854 std::fs::write(format!("{}.json", self.config.output_path), json)
855 .map_err(|e| ReportError::FileError(e.to_string()))?;
856
857 Ok(())
858 }
859
860 fn export_pdf(&self, _report: &Report) -> Result<(), ReportError> {
862 Err(ReportError::UnsupportedFormat(
864 "PDF export not implemented".to_string(),
865 ))
866 }
867
868 fn export_jupyter(&self, report: &Report) -> Result<(), ReportError> {
870 let mut notebook = serde_json::json!({
871 "cells": [],
872 "metadata": {
873 "kernelspec": {
874 "display_name": "Python 3",
875 "language": "python",
876 "name": "python3"
877 }
878 },
879 "nbformat": 4,
880 "nbformat_minor": 4
881 });
882
883 let title_cell = serde_json::json!({
885 "cell_type": "markdown",
886 "metadata": {},
887 "source": [format!("# {}\n\n**Generated**: {}",
888 report.metadata.title,
889 report.generated_at.format("%Y-%m-%d %H:%M:%S UTC"))]
890 });
891 let cells = notebook["cells"].as_array_mut().expect("notebook cells should be an array");
892 cells.push(title_cell);
893
894 for section in &report.sections {
896 let cell = serde_json::json!({
897 "cell_type": "markdown",
898 "metadata": {},
899 "source": [section.content]
900 });
901 cells.push(cell);
902 }
903
904 let notebook_str = serde_json::to_string_pretty(¬ebook)
905 .map_err(|e| ReportError::SerializationError(e.to_string()))?;
906
907 std::fs::write(format!("{}.ipynb", self.config.output_path), notebook_str)
908 .map_err(|e| ReportError::FileError(e.to_string()))?;
909
910 Ok(())
911 }
912
913 fn export_latex(&self, report: &Report) -> Result<(), ReportError> {
915 let mut latex = String::new();
916
917 latex.push_str("\\documentclass{article}\n");
918 latex.push_str("\\begin{document}\n");
919 latex.push_str(&format!("\\title{{{}}}\n", report.metadata.title));
920 latex.push_str(&format!("\\author{{{}}}\n", report.metadata.author));
921 latex.push_str("\\maketitle\n\n");
922
923 for section in &report.sections {
924 let latex_content = section
926 .content
927 .replace("##", "\\section{")
928 .replace("###", "\\subsection{")
929 .replace("#", "\\section{");
930 latex.push_str(&latex_content);
931 }
932
933 latex.push_str("\\end{document}\n");
934
935 std::fs::write(format!("{}.tex", self.config.output_path), latex)
936 .map_err(|e| ReportError::FileError(e.to_string()))?;
937
938 Ok(())
939 }
940
941 fn export_excel(&self, _report: &Report) -> Result<(), ReportError> {
943 Err(ReportError::UnsupportedFormat(
944 "Excel export not implemented".to_string(),
945 ))
946 }
947
948 fn export_powerpoint(&self, _report: &Report) -> Result<(), ReportError> {
950 Err(ReportError::UnsupportedFormat(
951 "PowerPoint export not implemented".to_string(),
952 ))
953 }
954}
955
956#[derive(Debug, Clone)]
958pub enum ReportError {
959 FileError(String),
961 SerializationError(String),
963 UnsupportedFormat(String),
965 MissingData(String),
967 GenerationError(String),
969}
970
971impl std::fmt::Display for ReportError {
972 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
973 match self {
974 ReportError::FileError(msg) => write!(f, "File error: {}", msg),
975 ReportError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
976 ReportError::UnsupportedFormat(msg) => write!(f, "Unsupported format: {}", msg),
977 ReportError::MissingData(msg) => write!(f, "Missing data: {}", msg),
978 ReportError::GenerationError(msg) => write!(f, "Generation error: {}", msg),
979 }
980 }
981}
982
983impl std::error::Error for ReportError {}
984
985#[cfg(test)]
986mod tests {
987 use super::*;
988
989 #[test]
990 fn test_report_config_default() {
991 let config = ReportConfig::default();
992 assert_eq!(config.title, "TrustformeRS Debug Report");
993 assert_eq!(config.author, "TrustformeRS Debugger");
994 assert!(matches!(config.format, ReportFormat::Html));
995 assert!(matches!(
996 config.report_type,
997 ReportType::ComprehensiveReport
998 ));
999 }
1000
1001 #[test]
1002 fn test_report_generator_creation() {
1003 let config = ReportConfig::default();
1004 let generator = ReportGenerator::new(config);
1005 assert!(generator.debug_data.is_none());
1006 assert!(generator.profiling_data.is_none());
1007 }
1008
1009 #[test]
1010 fn test_report_generation() {
1011 let config = ReportConfig {
1012 title: "Test Report".to_string(),
1013 format: ReportFormat::Json,
1014 report_type: ReportType::DebugReport,
1015 ..Default::default()
1016 };
1017
1018 let generator = ReportGenerator::new(config);
1019 let report = generator.generate().expect("operation failed in test");
1020
1021 assert_eq!(report.metadata.title, "Test Report");
1022 assert!(!report.sections.is_empty());
1023 }
1024
1025 #[test]
1026 fn test_section_generation() {
1027 let config = ReportConfig::default();
1028 let generator = ReportGenerator::new(config);
1029
1030 let summary = generator.generate_summary_section().expect("operation failed in test");
1031 assert!(matches!(summary.section_type, ReportSection::Summary));
1032 assert_eq!(summary.title, "Executive Summary");
1033 assert!(!summary.content.is_empty());
1034 }
1035
1036 #[test]
1037 fn test_custom_report_type() {
1038 let config = ReportConfig {
1039 report_type: ReportType::CustomReport(vec![
1040 ReportSection::Summary,
1041 ReportSection::Performance,
1042 ]),
1043 ..Default::default()
1044 };
1045
1046 let generator = ReportGenerator::new(config);
1047 let report = generator.generate().expect("operation failed in test");
1048
1049 assert_eq!(report.sections.len(), 2);
1050 assert!(matches!(
1051 report.sections[0].section_type,
1052 ReportSection::Summary
1053 ));
1054 assert!(matches!(
1055 report.sections[1].section_type,
1056 ReportSection::Performance
1057 ));
1058 }
1059
1060 #[test]
1061 fn test_report_serialization() {
1062 let config = ReportConfig::default();
1063 let generator = ReportGenerator::new(config);
1064 let report = generator.generate().expect("operation failed in test");
1065
1066 let json = serde_json::to_string(&report).expect("JSON serialization failed");
1067 let deserialized: Report =
1068 serde_json::from_str(&json).expect("JSON deserialization failed");
1069
1070 assert_eq!(report.metadata.title, deserialized.metadata.title);
1071 assert_eq!(report.sections.len(), deserialized.sections.len());
1072 }
1073}