""" PDF report generator for Vault Audit Reports. Uses Jinja2 + WeasyPrint to produce branded PDFs. """ import os import json import logging from datetime import datetime from pathlib import Path from jinja2 import Environment, PackageLoader, select_autoescape, DictLoader from app.db.session import AsyncSessionLocal from app.models.models import AuditReport, Tenant, Finding, FindingStatus from app.core.config import settings from sqlalchemy import select, desc logger = logging.getLogger(__name__) REPORT_HTML_TEMPLATE = """
⚠ CONFIDENTIAL — This report contains sensitive security information. Do not distribute without authorization.
{{ report.title }}
Vault Audit Report · {{ tenant.name }} · Generated {{ report.report_date.strftime('%B %d, %Y') }}
{{ report.baseline_score | int }}
Cyber Health Score at Audit Date
100 = Optimal · 0 = Critical Risk
{% if report.executive_summary %}

Executive Summary

{{ report.executive_summary }}
{% endif %} {% if report.scope_description %}

Scope

{{ report.scope_description }}

{% endif %}

Key Findings

{% for f in findings %}
{{ f.title }} {{ f.severity | upper }}
{% if f.ai_summary %}
{{ f.ai_summary }}
{% endif %} {% if f.ai_business_impact %}
Business Impact: {{ f.ai_business_impact }}
{% endif %}
{% endfor %} """ async def generate_pdf_for_report(report_id: str): """Generate a branded PDF for a Vault Audit Report and store the path.""" async with AsyncSessionLocal() as db: try: r_result = await db.execute(select(AuditReport).where(AuditReport.id == report_id)) report = r_result.scalar_one_or_none() if not report: return t_result = await db.execute(select(Tenant).where(Tenant.id == report.tenant_id)) tenant = t_result.scalar_one_or_none() # Get findings snapshot f_result = await db.execute( select(Finding) .where( Finding.tenant_id == report.tenant_id, Finding.status.in_([FindingStatus.open, FindingStatus.in_progress]) ) .order_by(Finding.created_at) .limit(20) ) findings = f_result.scalars().all() # Render HTML env = Environment(loader=DictLoader({"report.html": REPORT_HTML_TEMPLATE})) template = env.get_template("report.html") html = template.render(report=report, tenant=tenant, findings=findings) # Write PDF storage = Path(settings.STORAGE_PATH) / "reports" storage.mkdir(parents=True, exist_ok=True) pdf_path = storage / f"vault-audit-{report_id}.pdf" from weasyprint import HTML as WH WH(string=html).write_pdf(str(pdf_path)) report.pdf_path = str(pdf_path) await db.commit() logger.info(f"PDF generated: {pdf_path}") except Exception as e: logger.error(f"PDF generation failed for report {report_id}: {e}")