"""Mail-V2 compose — single entrypoint that returns the full HTML. Call `compose_v2(state)` from the email-dispatch phase when `MAIL_RENDER_V2=true`. Default remains the legacy compose so we can A/B in Mailpit. """ from __future__ import annotations import os from ._blocks import ( render_attachments, render_caveats, render_header, render_per_doc, render_per_theme, render_sofortmassnahmen, render_toc, ) from ._blocks_findings import ( render_critical, render_internal_reminders, render_manual_review, ) from ._vendor_cards import ( render_info_box_rechtsrahmen, render_vendor_cards, ) from ._executive_summary import collapsible, render_executive_summary from ._legacy_wrappers import render_all_legacy from ._style import page_close, page_open def compose_v2(state: dict) -> str: """Build the full audit-mail HTML in the V2 layout. Struktur: 1. Header (Site-Name + Datum) 2. Executive Summary (Compliance-Score + Top-3 + Cookie-Stats) 3. Critical Findings (immer offen, max 5) 4. Alle anderen Sektionen als
-Akkordeons (kollabiert) 5. Caveats + Attachments + Page-Close """ site = state.get("site_name") or "—" parts = [ page_open(site), render_header(state), render_executive_summary(state), # IMMER OFFEN: kritische Findings + Sofortmaßnahmen render_critical(state), render_sofortmassnahmen(state), # AKKORDEON-Sektionen (kollabiert, Reviewer öffnet selektiv) collapsible("🍪 Cookie-Inventar (alle deklarierten + im Browser)", state.get("cookie_inventory_html", "") + _render_per_theme_inventory_only(state)), collapsible("🏷️ Vendor-Übersicht (aggregiert nach Anbieter)", render_vendor_cards( state.get("cmp_vendors") or [], state.get("cookie_coherence_findings") or [], )), collapsible("🍪 Cookie-Kohärenz (Salesforce-Pattern, Pseudo-Zwecke)", state.get("cookie_coherence_html", "")), collapsible("💬 Chatbot-Cookie-Klassifikation", state.get("chatbot_cookie_html", "")), collapsible("📜 Widerrufsbelehrung-Reachability (B2C)", state.get("widerruf_reach_html", "")), collapsible("⏱️ Widersprüchliche Speicherdauer", state.get("retention_conflict_html", "")), collapsible("🤖 AI-Act Rechtsgrundlage (LLM-Vendor)", state.get("ai_legal_basis_html", "")), collapsible("🔗 URL-Slug-Drift (SEO / Bookmarks)", state.get("url_slug_drift_html", "")), collapsible("🎥 Audit-Walk-Video (Beweis-Aufzeichnung)", state.get("audit_walk_html", "")), collapsible("🤖 Impressum-Agent (Pattern + LLM)", state.get("impressum_agent_html", "")), collapsible("📑 Mehrere DSE-Versionen erkannt", state.get("multi_version_dse_html", "")), collapsible("🗂️ Legacy-URL-Inventar", state.get("legacy_url_html", "")), collapsible("🌐 Vertragsdoc auf Fremd-Domain (Cross-Domain)", state.get("cross_domain_doc_html", "")), collapsible("🔍 Cross-Doc Vendor-Konsistenz", state.get("vendor_consistency_html", "")), collapsible("⚖️ AI-Act Art. 50 Transparenzpflicht", state.get("ai_act_html", "")), collapsible("📌 Cross-Doc-Befunde (DPO, Staleness, CMP, Transfer)", state.get("extra_findings_html", "")), collapsible("🌐 Browser-Matrix (per-Browser-Verhalten)", state.get("browser_matrix_html", "")), collapsible("📋 Manuell zu prüfen", render_manual_review(state)), collapsible("🔧 Interne Erinnerungen", render_internal_reminders(state)), collapsible("📄 Per-Dokument-Befunde", render_per_doc(state)), collapsible("🧩 Per-Thema-Übersicht (Sub-Sektionen)", render_per_theme(state)), collapsible("📚 Rechtsrahmen-Info (Art. 13 DSGVO, § 25 TDDDG, …)", render_info_box_rechtsrahmen()), collapsible("📑 Inhaltsverzeichnis (alt)", render_toc(state)), collapsible("🗃️ Vollständige Legacy-Blöcke (Banner-Screenshot, " "VVT, Redundancy, Solutions, Diff)", render_all_legacy(state)), render_caveats(state), render_attachments(state), page_close(state.get("check_id", ""), os.environ.get("BUILD_SHA", "unknown")), ] return "".join(p for p in parts if p) def _render_per_theme_inventory_only(state: dict) -> str: """Extrahiert nur die Cookie-Inventar-Tabelle aus per_theme (die 742er-Tabelle). per_theme rendert sonst ALL themes — wir wollen hier nur das Inventory-Theme.""" try: from ._blocks import render_theme_cookie_inventory return render_theme_cookie_inventory(state) except Exception: return "" def is_v2_enabled() -> bool: return os.environ.get("MAIL_RENDER_V2", "false").lower() in ( "true", "1", "yes", "on", )