""" PDF Generator for TradingAgents Analysis Reports This module generates PDF reports from analysis results using WeasyPrint. """ import os import logging from pathlib import Path from typing import Dict, Any, Optional from datetime import datetime try: from playwright.sync_api import sync_playwright PLAYWRIGHT_AVAILABLE = True except (ImportError, OSError) as e: PLAYWRIGHT_AVAILABLE = False logging.warning(f"Playwright not available: {str(e)}. Trying WeasyPrint fallback.") try: from weasyprint import HTML, CSS from weasyprint.text.fonts import FontConfiguration WEASYPRINT_AVAILABLE = True except (ImportError, OSError) as e: WEASYPRINT_AVAILABLE = False HTML = None CSS = None FontConfiguration = None if not PLAYWRIGHT_AVAILABLE: logging.warning(f"WeasyPrint also not available: {str(e)}. PDF generation will be disabled.") from ..converters.html_converter import RichToHTMLConverter from ..formatters.report_formatter import ReportFormatter class TradingReportPDFGenerator: """Generates PDF reports from TradingAgents analysis results.""" def __init__(self, config: Optional[Dict[str, Any]] = None): """ Initialize PDF generator. Args: config: Configuration dictionary for PDF generation """ self.config = config or self._default_config() self.html_converter = RichToHTMLConverter() self.report_formatter = ReportFormatter() self.logger = logging.getLogger(__name__) if not WEASYPRINT_AVAILABLE: self.logger.warning("WeasyPrint not available. PDF generation disabled.") def _default_config(self) -> Dict[str, Any]: """Get default PDF generation configuration.""" return { "enabled": True, "output_dir": "results", "page_format": "A4", "margin": "2cm", "font_family": "Arial, sans-serif", "font_size": "12pt" } def generate_pdf(self, analysis_results: Dict[str, Any], ticker: str, date: str, output_path: Optional[str] = None) -> Optional[str]: """ Generate PDF report from analysis results. Args: analysis_results: Complete analysis results dictionary ticker: Stock ticker symbol date: Analysis date output_path: Optional custom output path Returns: Path to generated PDF file, or None if generation failed """ if not self.is_available(): self.logger.error("No PDF generation method available.") return None if not self.config.get("enabled", True): self.logger.info("PDF generation disabled in configuration.") return None try: # Generate output path if not provided if not output_path: output_path = self._generate_output_path(ticker, date) # Ensure output directory exists os.makedirs(os.path.dirname(output_path), exist_ok=True) # Format the report content html_content = self._create_html_report(analysis_results, ticker, date) # Generate PDF using available method success = self._html_to_pdf(html_content, output_path) if success: # Also save HTML backup html_path = output_path.replace('.pdf', '.html') with open(html_path, 'w', encoding='utf-8') as f: f.write(html_content) self.logger.info(f"PDF report generated: {output_path}") return output_path else: return None except Exception as e: self.logger.error(f"Failed to generate PDF report: {str(e)}") return None def _generate_output_path(self, ticker: str, date: str) -> str: """Generate output path for PDF file.""" output_dir = Path(self.config["output_dir"]) / ticker / date return str(output_dir / "analysis_report.pdf") def _create_html_report(self, analysis_results: Dict[str, Any], ticker: str, date: str) -> str: """ Create complete HTML report from analysis results. Args: analysis_results: Analysis results dictionary ticker: Stock ticker symbol date: Analysis date Returns: Complete HTML content for PDF generation """ # Format the main report content report_content = self.report_formatter.format_complete_report( analysis_results, ticker, date ) # Wrap in complete HTML document with styles html_template = f"""