""" Генератор PDF-отчетов из JSON-данных сканирования. Использует Jinja2 + WeasyPrint для создания красивых отчетов. """ from __future__ import annotations import os from datetime import datetime from pathlib import Path from typing import Any from jinja2 import Environment, FileSystemLoader from weasyprint import HTML # Путь к корню проекта BASE_DIR = Path(__file__).parent.parent TEMPLATE_DIR = Path(__file__).parent / "templates" class PDFReportGenerator: """Генерирует PDF-отчеты из результатов сканирования.""" def __init__(self): self.jinja_env = Environment( loader=FileSystemLoader(str(TEMPLATE_DIR)), autoescape=True, ) def generate(self, scan_result: dict[str, Any], output_dir: str | None = None) -> Path: """ Создать PDF-отчет из результатов сканирования. Args: scan_result: dict с данными сканирования (SiteScan.model_dump()) output_dir: папка для сохранения (по умолчанию data/output/reports) Returns: Path до созданного PDF-файла """ if output_dir is None: output_dir = BASE_DIR / "data" / "output" / "reports" output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # Подставляем форматированные данные в шаблон template = self.jinja_env.get_template("report.html.j2") html_content = template.render(**self._prepare_context(scan_result)) # Генерируем PDF timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") domain_safe = scan_result.get("url", "unknown").replace("://", "_").replace("/", "_")[:30] pdf_path = output_dir / f"audit_{timestamp}_{domain_safe}.pdf" HTML(string=html_content, base_url=str(TEMPLATE_DIR)).write_pdf(str(pdf_path)) return pdf_path @staticmethod def _prepare_context(scan_result: dict) -> dict: """Подготавливает контекст для шаблона.""" checks = scan_result.get("checks", []) is_compliant = scan_result.get("is_compliant", True) score = scan_result.get("score", 0) scanned_at = scan_result.get("scanned_at", "") # Форматируем дату if isinstance(scanned_at, str): try: dt = datetime.fromisoformat(scanned_at.replace("Z", "+00:00")) scanned_at = dt.strftime("%d.%m.%Y %H:%M") except (ValueError, AttributeError): pass return { "site_scan": scan_result, "url": scan_result.get("url", ""), "score": score, "is_compliant": is_compliant, "summary": scan_result.get("summary", ""), "checks": checks, "scanned_at": scanned_at, "scanner_version": "1.0.0", }