"""PDF Report Generator — creates professional security reports.""" from datetime import datetime from typing import List, Optional from jinja2 import Template from weasyprint import HTML, CSS from io import BytesIO from app.models.models import Finding, RiskScore import logging logger = logging.getLogger(__name__) HTML_TEMPLATE = """

{{ tenant_name }} — Cyber Risk Report

Report generated on {{ report_date }}

{{ cyber_score }}
Cyber Health Score

Risk Summary

{{ critical_count }}
Critical
{{ high_count }}
High
{{ medium_count }}
Medium
{{ low_count }}
Low

Executive Summary

{{ summary }}

Findings ({{ findings_count }})

{% for finding in findings %}
{{ loop.index }}. {{ finding.title }}
{{ finding.severity | upper }}
Category: {{ finding.category }}
{% if finding.ai_summary %}
{{ finding.ai_summary }}
{% endif %}
{% endfor %}
""" async def generate_findings_pdf( tenant_name: str, cyber_score: float, findings: List[Finding], risk_scores: Optional[List[RiskScore]] = None, ) -> BytesIO: """Generate a professional PDF report of security findings.""" critical = sum(1 for f in findings if f.severity.value == "critical") high = sum(1 for f in findings if f.severity.value == "high") medium = sum(1 for f in findings if f.severity.value == "medium") low = sum(1 for f in findings if f.severity.value == "low") context = { "tenant_name": tenant_name, "cyber_score": round(cyber_score, 1), "report_date": datetime.utcnow().strftime("%B %d, %Y"), "critical_count": critical, "high_count": high, "medium_count": medium, "low_count": low, "findings_count": len(findings), "findings": [ { "title": f.title, "severity": f.severity.value, "category": f.category.value, "ai_summary": f.ai_summary, } for f in findings ], "summary": f"This report contains {len(findings)} security findings affecting {tenant_name}, with {critical} critical issues requiring immediate attention.", } template = Template(HTML_TEMPLATE) html_string = template.render(**context) html = HTML(string=html_string, base_url=".") pdf_bytes = html.write_pdf() pdf_io = BytesIO(pdf_bytes) pdf_io.seek(0) return pdf_io