"""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 = """
{{ cyber_score }}
Cyber Health Score
Risk Summary
{{ critical_count }}
Critical
{{ medium_count }}
Medium
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