""" 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'', '', # Row 2: Saving + CTA-Hinweis '', 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'
Identifizierte Anbieter
' f'
{n_vendors}
' f'
' f'davon {n_consolidation} konsolidierbar
' 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 '
', '' 'Compliance-Maengel im Detail →', '' 'Konsolidierungs-Plan →', '
', '
', ] return "".join(parts)