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