"""
Vendor Assessment Pruefprotokoll — HTML report builder.
Generates a professional assessment report styled like a real DSB
Pruefprotokoll for vendor contract analysis (Art. 28 DSGVO).
"""
from datetime import datetime, timezone
def build_pruefprotokoll(result: dict) -> str:
"""Build HTML Pruefprotokoll from assessment result."""
vendor = result.get("vendor_name", "Unbekannt")
docs = result.get("documents", [])
findings = result.get("findings", [])
cross = result.get("cross_check_findings", [])
cat_scores = result.get("category_scores", {})
overall = result.get("overall_score", 0)
checked_at = result.get("checked_at", datetime.now(timezone.utc).isoformat())
verdict = _verdict(overall)
now_str = _format_date(checked_at)
protocol_nr = f"VP-{datetime.now().strftime('%Y')}-{abs(hash(vendor)) % 10000:04d}"
html = [_style(), '
']
# ── 1. Kopfdaten ────────────────────────────────────────────────
html.append(f'''
''')
# ── 2. Zusammenfassung ──────────────────────────────────────────
critical_count = sum(1 for f in findings if _get(f, "severity") == "CRITICAL")
critical_count += sum(1 for f in cross if f.get("severity") == "CRITICAL")
total_findings = len(findings) + len(cross)
html.append(f'''
{overall}%
{verdict["label"]}
{len(docs)}Dokumente
{total_findings}Findings
{critical_count}Kritisch
''')
# ── Kategorie-Scores ────────────────────────────────────────────
if cat_scores:
html.append('
Kategorie-Uebersicht
')
html.append('| Kategorie | Score | Status |
')
for cat, score in sorted(cat_scores.items(), key=lambda x: x[1]):
status = _cat_status(score)
html.append(f'''
| {_cat_label(cat)} |
{_bar(score)} |
{status["label"]} |
''')
html.append('
')
# ── 3. Gepruefte Dokumente ──────────────────────────────────────
html.append('
Gepruefte Dokumente
')
for i, doc in enumerate(docs):
_render_document(html, doc, i + 1)
# ── 4. Cross-Check Findings ─────────────────────────────────────
if cross:
html.append('
Dokumenten-Cross-Check
')
for f in cross:
sev = f.get("severity", "MEDIUM")
html.append(f'''
{sev}
{f.get("label", "")}
{f.get("hint", "")}
''')
# ── 5. Findings ─────────────────────────────────────────────────
if findings:
html.append('
Findings (sortiert nach Schweregrad)
')
sorted_findings = sorted(findings,
key=lambda f: {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}.get(
_get(f, "severity"), 4))
for f in sorted_findings:
sev = _get(f, "severity")
html.append(f'''
{sev}
{_get(f, "title")}
{_get(f, "category")} | {_get(f, "document_label")}
{_get(f, "description")}
''')
# ── 6. Freigabe-Block ───────────────────────────────────────────
html.append(f'''
''')
html.append('
')
return "\n".join(html)
def _render_document(html: list[str], doc: dict, num: int):
"""Render a single document section with L1/L2 checks."""
label = doc.get("label", "Dokument")
dtype = doc.get("doc_type", "").upper()
comp = doc.get("completeness_pct", 0)
corr = doc.get("correctness_pct", 0)
error = doc.get("error", "")
checks = doc.get("checks", [])
html.append(f'''