"""
P88 — PDF-Export der Audit-Mail.
Rendert dieselbe HTML wie die Mail via WeasyPrint zu PDF. Endpoint:
GET /api/compliance/agent/snapshots/{snapshot_id}/pdf → application/pdf
Verwendung:
- GF/Lawyer-Uebergabe (kein E-Mail-Programm noetig)
- Archivierung
- Mandatsausgabe an externen Berater
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from sqlalchemy.orm import Session
from compliance.services.check_replay import replay_from_snapshot
logger = logging.getLogger(__name__)
_PDF_WRAPPER_HEAD = """
{title}
"""
def render_snapshot_as_pdf(
db: Session,
snapshot_id: str,
) -> bytes | None:
"""Returns PDF bytes or None on failure."""
try:
from weasyprint import HTML # noqa: WPS433 — Optional dep
except Exception as e:
logger.error("WeasyPrint nicht verfuegbar: %s", e)
return None
res = replay_from_snapshot(db, snapshot_id, recipient=None, dry_run=True)
if not res or res.get("error"):
logger.warning("PDF-Export: Snapshot %s nicht gefunden", snapshot_id)
return None
# The replay returns html via "preview" (truncated) — fetch the full
# render by injecting site_label into a wrapper.
full_html = _build_full_html(res, snapshot_id)
try:
pdf_bytes = HTML(string=full_html).write_pdf()
return pdf_bytes
except Exception as e:
logger.exception("WeasyPrint PDF render failed: %s", e)
return None
def _build_full_html(replay_result: dict, snapshot_id: str) -> str:
"""Wraps the replay's full_html in the PDF-print wrapper."""
full = replay_result.get("full_html") or replay_result.get("preview") or ""
site = replay_result.get("site_domain") or "—"
snap_short = snapshot_id[:8]
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
header = _PDF_WRAPPER_HEAD.format(
title=f"BreakPilot Audit — {site}",
site=site, snap_short=snap_short, ts=ts,
)
return header + full + ""