"""Report engine — renders Jinja2 HTML templates to PDF, DOCX, and HTML. Uses WeasyPrint for PDF generation and docxtpl for DOCX. """ # Import logging import logging # Import os import os # Import uuid import uuid # Import datetime from datetime from datetime import datetime # Import Environment, FileSystemLoader from jinja2 from jinja2 import Environment, FileSystemLoader # Import settings from app.config from app.config import settings # Assign logger = logging.getLogger(__name__) logger = logging.getLogger(__name__) # Define class ReportEngine class ReportEngine: """Template-based report generator supporting PDF, DOCX, and HTML output.""" # Define function __init__ def __init__(self) -> None: """Initialise the Jinja2 environment.""" # Assign self.jinja_env = Environment( self.jinja_env = Environment( # Keyword argument: loader loader=FileSystemLoader(settings.REPORT_TEMPLATES_DIR), # Keyword argument: autoescape autoescape=True, ) def _ensure_output_dir(self) -> None: os.makedirs(settings.REPORT_OUTPUT_DIR, exist_ok=True) # Define function render_html def render_html(self, template_name: str, context: dict) -> str: """Render a Jinja2 template to an HTML string.""" # Assign template = self.jinja_env.get_template(f"{template_name}.html") template = self.jinja_env.get_template(f"{template_name}.html") # Call context.setdefault() context.setdefault("company_name", settings.COMPANY_NAME) # Call context.setdefault() context.setdefault("generated_at", datetime.utcnow().strftime("%B %d, %Y %H:%M UTC")) # Return template.render(context) return template.render(context) # Define function generate_pdf def generate_pdf(self, template_name: str, context: dict) -> str: """Render HTML and convert to PDF with WeasyPrint.""" self._ensure_output_dir() # Import CSS, HTML from weasyprint from weasyprint import CSS, HTML # Assign html_content = self.render_html(template_name, context) html_content = self.render_html(template_name, context) # Assign css_path = os.path.join(settings.REPORT_TEMPLATES_DIR, "styles", "report.css") css_path = os.path.join(settings.REPORT_TEMPLATES_DIR, "styles", "report.css") # Assign output_path = os.path.join( output_path = os.path.join( settings.REPORT_OUTPUT_DIR, f"{template_name}_{uuid.uuid4().hex[:8]}.pdf", ) # Assign stylesheets = [] stylesheets = [] # Check: os.path.exists(css_path) if os.path.exists(css_path): # Call stylesheets.append() stylesheets.append(CSS(filename=css_path)) # Call HTML() HTML( # Keyword argument: string string=html_content, # Keyword argument: base_url base_url=settings.REPORT_TEMPLATES_DIR, ).write_pdf(output_path, stylesheets=stylesheets) # Log info: "PDF generated: %s", output_path logger.info("PDF generated: %s", output_path) # Return output_path return output_path # Define function generate_docx def generate_docx(self, template_name: str, context: dict) -> str: """Render a .docx template with docxtpl.""" self._ensure_output_dir() # Import DocxTemplate from docxtpl from docxtpl import DocxTemplate # Assign template_path = os.path.join( template_path = os.path.join( settings.REPORT_TEMPLATES_DIR, f"{template_name}.docx" ) # Assign output_path = os.path.join( output_path = os.path.join( settings.REPORT_OUTPUT_DIR, f"{template_name}_{uuid.uuid4().hex[:8]}.docx", ) # Assign doc = DocxTemplate(template_path) doc = DocxTemplate(template_path) # Call context.setdefault() context.setdefault("company_name", settings.COMPANY_NAME) # Call context.setdefault() context.setdefault("generated_at", datetime.utcnow().strftime("%B %d, %Y")) # Call doc.render() doc.render(context) # Call doc.save() doc.save(output_path) # Log info: "DOCX generated: %s", output_path logger.info("DOCX generated: %s", output_path) # Return output_path return output_path # Define function generate_html def generate_html(self, template_name: str, context: dict) -> str: """Render and save a standalone HTML report (alias for spec compatibility).""" # Return self.generate_html_file(template_name, context) return self.generate_html_file(template_name, context) # Define function generate_html_file def generate_html_file(self, template_name: str, context: dict) -> str: """Render and save a standalone HTML report.""" self._ensure_output_dir() # Assign html_content = self.render_html(template_name, context) html_content = self.render_html(template_name, context) # Assign output_path = os.path.join( output_path = os.path.join( settings.REPORT_OUTPUT_DIR, f"{template_name}_{uuid.uuid4().hex[:8]}.html", ) # Open context manager with open(output_path, "w", encoding="utf-8") as f: # Call f.write() f.write(html_content) # Log info: "HTML report generated: %s", output_path logger.info("HTML report generated: %s", output_path) # Return output_path return output_path # Assign report_engine = ReportEngine() report_engine = ReportEngine()