""" P18 — Erweiterter Banner-Block fuer die Email. Rendert die Daten aus dem consent-tester die heute weggeworfen wurden: - 3-Phasen-Cookie-Tabelle (before_consent / after_reject / after_accept) - Banner-Quality-Score (completeness/correctness/violations) - Per-Category-Tracker-Listing - Violations-Liste mit Rechtsgrundlagen """ from __future__ import annotations def _color_for(pct: int) -> str: return ("#16a34a" if pct >= 80 else "#d97706" if pct >= 50 else "#dc2626") def _short_phase_label(key: str) -> str: return { "before_consent": "Vor Consent", "after_reject": "Nach Ablehnung", "after_accept": "Nach Annahme", }.get(key, key) def _phase_color(key: str, cookie_count: int) -> str: if key == "before_consent": return "#16a34a" if cookie_count == 0 else "#dc2626" if key == "after_reject": return "#16a34a" if cookie_count <= 1 else "#d97706" return "#94a3b8" def build_banner_deep_html(banner_result: dict | None) -> str: """Render: Banner-Quality + Phases + Violations. Konsumiert das volle consent-tester-Response. Komplementiert `build_provider_list_html` (das nur Summary + TCF-Vendor-Tabelle macht). """ if not banner_result: return "" parts: list[str] = [ '
' '

' 'Cookie-Banner — technische Analyse

' ] # 1) Quality-Score-Cards compl = banner_result.get("completeness_pct") corr = banner_result.get("correctness_pct") summary = banner_result.get("summary") or {} n_critical = summary.get("critical", 0) n_high = summary.get("high", 0) if compl is not None or corr is not None: parts.append( '' ) if compl is not None: c = _color_for(int(compl)) parts.append( f'' ) if corr is not None: c = _color_for(int(corr)) parts.append( f'' ) viol_c = ("#dc2626" if n_critical + n_high > 0 else "#d97706" if (summary.get("total_violations") or 0) > 0 else "#16a34a") parts.append( f'' ) parts.append('
' f'
' f'Vollstaendigkeit
' f'
{compl}%
' f'
' f'
' f'Korrektheit
' f'
{corr}%
' f'
' f'
' f'Verstoesse
' f'
' f'{summary.get("total_violations", 0)}' f'' f'(crit:{n_critical} high:{n_high})
') # 2) 3-Phasen-Tabelle phases = banner_result.get("phases") or {} if phases: parts.append( '
Cookie-Setzungen pro Phase ' '(echter Browser-Test):
' '' '' '' '' '' '' '' ) for key in ("before_consent", "after_reject", "after_accept"): ph = phases.get(key) or {} if not isinstance(ph, dict): continue cookies = ph.get("cookies") or [] trackers = ph.get("tracking_services") or [] new_track = ph.get("new_tracking") or [] violations = ph.get("violations") or [] undoc = ph.get("undocumented") or [] color = _phase_color(key, len(cookies)) issues_parts = [] if violations: issues_parts.append(f"{len(violations)} Verstoss") if new_track: issues_parts.append(f"{len(new_track)} neue Tracker") if undoc: issues_parts.append(f"{len(undoc)} undokumentiert") issues_str = ", ".join(issues_parts) or "—" parts.append( f'' f'' f'' f'' f'' f'' ) parts.append('
PhaseCookiesTrackerAuffaelligkeiten
' f'' f'{_short_phase_label(key)}{len(cookies)}{len(trackers)}{issues_str}
') # 3) Per-Category-Tracker cats = banner_result.get("category_tests") or [] if cats: non_essential = [c for c in cats if c.get("category") != "necessary"] if non_essential: parts.append( '
Provider-Listing pro Banner-Kategorie:
' '' '' '' '' '' '' ) for c in non_essential: n = len(c.get("tracking_services") or []) label = c.get("category_label") or c.get("category", "?") pdv = c.get("provider_details_visible") # P19: echtes Signal aus Click-Through-Test if pdv is False: color, hint = "#dc2626", ("Banner zeigt KEINE Provider-" "Details — keine informierte Einwilligung") elif pdv is True: color, hint = "#16a34a", "" elif n == 0: color, hint = "#d97706", ("Keine Anbieter erkannt (vermutlich " "kein Provider-Listing im Banner)") else: color, hint = "#16a34a", "" parts.append( f'' f'' f'' f'' ) parts.append('
KategorieAnbieterHinweis
{label}{n}' f'{hint}
') # 4) Violations mit Rechtsgrundlage violations = (banner_result.get("banner_checks") or {}).get("violations", []) if violations: parts.append( '
Erkannte Banner-Verstoesse:
' '') # 5) P59b: Cookie-Behavior-Findings (deklariert vs. tatsaechlich) cb_findings = banner_result.get("cookie_behavior_findings") or [] if cb_findings: parts.append( '
' '
Cookie-Verhaltens-Check ' '(P59 — deklarierter Zweck vs. tatsaechliches Verhalten)
' '
') # 6) P61: Untergeschobene Cookies/Vendors (Vendor-Package) impl_findings = banner_result.get("implicit_vendor_findings") or [] if impl_findings: # Gruppiert nach primary_vendor: pro Primary die mitgelaufenen Items by_primary: dict[str, list[dict]] = {} for f in impl_findings: by_primary.setdefault(f["primary_vendor"], []).append(f["implicit"]) parts.append( '
' '
Untergeschobene Cookies / Vendors ' '(P61 — mit Hauptanbieter automatisch mitgeladen)
' '
' 'Diese Cookies/Vendors kommen automatisch mit dem deklarierten ' 'Hauptanbieter mit — Marketing-Manager waehlen sie oft nicht ' 'bewusst aus, sie sind aber zustimmungspflichtig.
' ) for primary, impls in by_primary.items(): parts.append( f'
' f'{primary} bringt automatisch:
' '') parts.append('
') parts.append('
') return "".join(parts)