403e3c66d2
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>
71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
"""Deklaration-vs-Bibliothek-Diff.
|
|
|
|
Regroupt die `analyze_cookies`-Findings PRO COOKIE zu Feld-Diffs
|
|
(deklariert → Library) — nur für die Library-getroffene Teilmenge, denn nur
|
|
dort gibt es eine Ground-Truth. Plus ein ehrlicher Funnel (gesamt → geprüft →
|
|
abweichend), damit nie der Eindruck entsteht, ALLE Cookies seien geprüft
|
|
(passt zur BreakPilot-Tonalität: nicht-getroffene Cookies = nicht prüfbar,
|
|
kein Pass, kein Fail).
|
|
|
|
Single source of truth bleibt `analyze_cookies` (Erkennung); dieses Modul ist
|
|
reine Präsentations-Aggregation und damit isoliert testbar.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# Finding-Typ → Feld-Label. Nur Typen mit echtem Library-Soll ('expected').
|
|
# vague_duration/missing_retention/missing_opt_out haben KEINEN Library-Vergleich
|
|
# und third_country/eu_alternative sind Hinweise → bewusst NICHT im Diff.
|
|
_FIELD = {
|
|
"tracker_as_necessary": "Kategorie",
|
|
"excessive_lifetime": "Laufzeit",
|
|
"missing_purpose": "Zweck",
|
|
}
|
|
_SEV_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
|
|
|
|
|
|
def build_declaration_diff(analysis: dict) -> dict:
|
|
"""Aus dem `analyze_cookies`-Ergebnis die Diff-Sicht + Funnel bauen."""
|
|
findings = analysis.get("findings") or []
|
|
summary = analysis.get("summary") or {}
|
|
|
|
rows: dict[tuple, dict] = {}
|
|
for f in findings:
|
|
field = _FIELD.get(f.get("type"))
|
|
if not field:
|
|
continue
|
|
key = (f.get("vendor") or "", f.get("cookie") or "")
|
|
row = rows.get(key)
|
|
if row is None:
|
|
row = {
|
|
"cookie": f.get("cookie") or "",
|
|
"vendor": f.get("vendor") or "",
|
|
"diffs": [],
|
|
"measures": [],
|
|
"severity": "LOW",
|
|
}
|
|
rows[key] = row
|
|
row["diffs"].append({
|
|
"field": field,
|
|
"declared": str(f.get("declared") or "—"),
|
|
"expected": str(f.get("expected") or f.get("library_purpose") or "—"),
|
|
"severe": f.get("severity") == "HIGH",
|
|
})
|
|
rem = f.get("remediation")
|
|
if rem and rem not in row["measures"]:
|
|
row["measures"].append(rem)
|
|
if _SEV_ORDER.get(f.get("severity"), 3) < _SEV_ORDER.get(row["severity"], 3):
|
|
row["severity"] = f.get("severity") or "LOW"
|
|
|
|
out_rows = sorted(rows.values(), key=lambda r: _SEV_ORDER.get(r["severity"], 3))
|
|
total = int(summary.get("checked") or 0) # alle Cookies
|
|
checked = int(summary.get("in_library") or 0) # davon mit Library-Treffer
|
|
return {
|
|
"coverage": {
|
|
"total": total,
|
|
"checked": checked,
|
|
"discrepant": len(out_rows),
|
|
},
|
|
"rows": out_rows,
|
|
}
|