"""
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.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}")