from __future__ import annotations import json import os import tempfile import textwrap from datetime import datetime, timezone from typing import Any, Dict, Tuple from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas def _safe_text(value: Any) -> str: if value is None: return "" return str(value).encode("ascii", "replace").decode("ascii") def _wrap_lines(text: str, width: int = 95) -> list[str]: lines: list[str] = [] for raw in (text or "").splitlines(): if not raw: lines.append("") continue lines.extend(textwrap.wrap(raw, width=width)) return lines def build_pdf(cas: str, evidence: Dict[str, Any], ai_summary: str | None = None) -> Tuple[str, str]: """Generate a PDF + JSON evidence packet and return file paths.""" stamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") safe_cas = _safe_text(cas or "unknown").replace("/", "_").replace(" ", "_") out_dir = os.path.join(tempfile.gettempdir(), "toxrai_reports") os.makedirs(out_dir, exist_ok=True) pdf_path = os.path.join(out_dir, f"toxrai_{safe_cas}_{stamp}.pdf") json_path = os.path.join(out_dir, f"toxrai_{safe_cas}_{stamp}.json") # JSON evidence with open(json_path, "w", encoding="utf-8") as f: json.dump(evidence, f, ensure_ascii=False, indent=2, default=str) # PDF c = canvas.Canvas(pdf_path, pagesize=letter) width, height = letter y = height - 40 def draw_line(line: str): nonlocal y if y < 50: c.showPage() y = height - 40 c.drawString(40, y, _safe_text(line)) y -= 14 draw_line("ToxRAI Evidence Report") draw_line(f"CAS / Query: {safe_cas}") draw_line(f"Generated: {datetime.now(timezone.utc).isoformat()} UTC") draw_line("") if ai_summary: draw_line("AI Summary") for line in _wrap_lines(ai_summary, width=90): draw_line(line) draw_line("") # Include a compact evidence snapshot evidence_text = json.dumps(evidence, ensure_ascii=False, indent=2, default=str) if len(evidence_text) > 8000: evidence_text = evidence_text[:8000] + "\n... (truncated)" draw_line("Evidence JSON (truncated)") for line in _wrap_lines(evidence_text, width=95): draw_line(line) c.save() return pdf_path, json_path