|
|
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") |
|
|
|
|
|
|
|
|
with open(json_path, "w", encoding="utf-8") as f: |
|
|
json.dump(evidence, f, ensure_ascii=False, indent=2, default=str) |
|
|
|
|
|
|
|
|
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("") |
|
|
|
|
|
|
|
|
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 |
|
|
|