feat(cookie): Deklaration-vs-Bibliothek-Diff-Sicht + Funnel-KPI

Für die Library-getroffene Teilmesse (~32%) pro Cookie die Feld-
Abweichungen deklariert→Library (Kategorie/Laufzeit/Zweck) als Diff-Karte,
plus ehrlicher Funnel (gesamt → geprüft → abweichend) — nicht-getroffene
Cookies sind nicht prüfbar (kein Pass/Fail), passend zur Tonalität.

- analyze_cookies: 'expected'-Soll-Wert an tracker_as_necessary/
  excessive_lifetime/missing_purpose (+ _CAT_LABEL_DE).
- neues cookie_declaration_diff.build_declaration_diff: reine Regroup-
  Aggregation der Findings pro Cookie (single source = analyze_cookies),
  Hinweis-Typen (third_country/eu_alternative) bewusst ausgeschlossen.
- cookie-check exponiert out['declaration_diff'].
- CookieDeclarationDiff.tsx oben im Cookie-Tab (vor Panel/ResultView).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-11 21:00:50 +02:00
parent c35977c925
commit 403e3c66d2
7 changed files with 265 additions and 1 deletions
@@ -24,6 +24,12 @@ from sqlalchemy import text
from compliance.services.cookie_knowledge_db import lookup_cookie
_TRACKER_CATS = {"marketing", "statistics", "social_media", "targeting"}
# Library-Kategorie → deutsches Label (für die Deklaration-vs-Library-Diff-Sicht).
_CAT_LABEL_DE = {
"marketing": "Marketing", "statistics": "Statistik",
"social_media": "Social Media", "targeting": "Targeting",
"functional": "Funktional", "necessary": "Notwendig",
}
# A — auditfeste Verdrahtung: jeder Befund-Typ → echter Control (control_id aus
# doc_check_controls) + legal_basis. Die Controls tragen regulation/article noch
@@ -238,7 +244,9 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict:
findings.append({
"vendor": vname, "cookie": name, "type": "tracker_as_necessary",
"severity": "HIGH" if rich.get("reid_risk") == "high" else "MEDIUM",
"declared": vcat_label, "library_purpose": purpose,
"declared": vcat_label,
"expected": _CAT_LABEL_DE.get(actual_cat, "einwilligungspflichtig"),
"library_purpose": purpose,
"remediation": rem + ".",
})
# 2) Kein Zweck deklariert, Library kennt ihn.
@@ -246,6 +254,7 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict:
findings.append({
"vendor": vname, "cookie": name, "type": "missing_purpose",
"severity": "MEDIUM", "declared": "(kein Zweck angegeben)",
"expected": purpose,
"library_purpose": purpose,
"remediation": f"Zweck für '{name}' ergänzen. Laut Library: {purpose}",
})
@@ -264,6 +273,7 @@ def analyze_cookies(vendors: list[dict], big_lib: dict | None = None) -> dict:
"vendor": vname, "cookie": name, "type": "excessive_lifetime",
"severity": "LOW",
"declared": c.get("expiry", "") or "",
"expected": typ,
"library_purpose": f"typisch: {typ}",
"remediation": (
f"Speicherdauer von '{name}' ({c.get('expiry', '')}) "