"""
Executive-Summary-Block — der oberste Email-Abschnitt.
Zeigt CFO / GF in 4 Zahlen den Gesamt-Mehrwert des Compliance-Checks:
1) Compliance-Score (Trend vs Vorlauf)
2) Anzahl analysierter Anbieter
3) Geschaetztes jaehrliches Sparpotenzial (Range)
4) Konsolidierungs-Potenzial (Anbieter koennen reduziert werden)
Plus zwei Big-CTA-Buttons:
- "Compliance-Maengel im Detail" → springt zum Doc-Pruefungs-Block
- "Konsolidierungs-Plan ansehen" → springt zum Redundanz-Block
Ziel: in 5 Sekunden sieht der Vorstand den ROI. Wenn neugierig, scrollt
er weiter in die Detail-Bloecke (die UNTER dieser Summary liegen).
"""
from __future__ import annotations
def _fmt_eur_range(low: int, high: int) -> str:
if not low and not high:
return "—"
if low == high:
return f"~{low:,} €".replace(",", ".")
return f"{low:,}–{high:,} €".replace(",", ".")
def _build_score_band_block(pct: int, color: str) -> list[str]:
"""P34 — eine Zeile unter den KPIs: Score-Einordnung."""
band, hint = _score_band_explanation(pct)
return [
f'
'
f'
'
f'{band} ({pct}%) — {hint}'
f'
',
]
def _score_band_explanation(pct: int) -> tuple[str, str]:
"""P34 — Was bedeutet der Score: wo MUESSTE man stehen.
Returns (label, what_to_expect)."""
if pct >= 85:
return (
"Sehr gut", "Praxis-uebliche DSGVO-Risikolage. "
"Standard-Pflege reicht — jaehrliche Pruefung empfohlen.",
)
if pct >= 70:
return (
"Akzeptabel", "Branchen-Median. Verbleibende Findings sind "
"meist Formalia — Empfehlung: einmaliges Aufraeumen, dann "
"Halbjahres-Check.",
)
if pct >= 50:
return (
"Handlungsbedarf", "Mehrere wesentliche Themen offen. "
"Empfehlung: priorisierte Abarbeitung der HIGH-Findings "
"binnen 4-8 Wochen mit DSB + Web-Team.",
)
return (
"Erhoehtes Risiko", "Mehrere Kern-Pflichten fehlen oder sind "
"veraltet. Empfehlung: kurzfristiger Termin mit DSB / Rechtsabteilung "
"und Web-Team zur Priorisierung.",
)
def build_exec_summary_html(
scorecard: dict | None,
previous_scorecard: dict | None,
cmp_vendors: list[dict] | None,
redundancy_report: dict | None,
site_name: str = "",
) -> str:
"""Build the top-of-email Executive Summary with 4 KPIs + 2 CTAs."""
# 1) Compliance-Score
pct = 0
delta_str = ""
score_color = "#94a3b8"
if scorecard:
totals = scorecard.get("totals") or {}
pct = int(totals.get("pct", 0))
score_color = ("#16a34a" if pct >= 80 else
"#d97706" if pct >= 50 else "#dc2626")
if previous_scorecard:
prev_pct = int((previous_scorecard.get("totals") or {}).get("pct", 0))
d = pct - prev_pct
if d:
trend_color = "#16a34a" if d > 0 else "#dc2626"
delta_str = (
f''
f'{"+" if d > 0 else ""}{d} pp'
)
# 2) Vendor-Count
n_vendors = len(cmp_vendors or [])
# 3+4) Saving + Konsolidierung
s = (redundancy_report or {}).get("summary") or {}
sav_low, sav_high = s.get("estimated_saving_year_eur", [0, 0])
n_consolidation = s.get("consolidation_potential", 0)
sav_pct = s.get("estimated_saving_pct", "—")
parts = [
'',
f'
Executive Summary
',
f'
'
f'Compliance-Check {site_name}
',
# 2x2 KPI grid
'
',
# Row 1: Compliance + Vendor count
'',
f'| '
f' DSGVO / TDDDG / TMG Score '
f''
f'{pct}%{delta_str} '
f''
f'aus {int((scorecard or {}).get("totals", {}).get("total", 0))} Pflicht-Pruefungen '
f' | ',
f''
f' Identifizierte Anbieter '
f'{n_vendors} '
f''
f'davon {n_consolidation} konsolidierbar '
f' | ',
'
',
# Row 2: Saving + CTA-Hinweis
'',
f'| '
f' '
f'Geschaetztes Sparpotenzial pro Jahr (Tool-Lizenzen, ohne Media-Spend) '
f''
f'{_fmt_eur_range(sav_low, sav_high)}'
f'({sav_pct}) '
f''
f'durch Konsolidierung redundanter Anbieter auf je 1 EU-Tool pro '
f'Funktions-Kategorie. Schaetzbereich, mit dem Einkauf zu verifizieren.'
f' | ',
'
',
'
',
# P34 — Score-Einordnung "wer wo stehen muss"
*(_build_score_band_block(pct, score_color) if scorecard else []),
# CTAs
'
',
'
',
]
return "".join(parts)