feat(tcf-vendors): GVL cache + vendor extraction + VVT mapping
CI / loc-budget (push) Failing after 16s
Build + Deploy / build-admin-compliance (push) Successful in 14s
Build + Deploy / build-backend-compliance (push) Successful in 16s
Build + Deploy / build-ai-sdk (push) Successful in 20s
Build + Deploy / build-developer-portal (push) Successful in 12s
Build + Deploy / build-tts (push) Successful in 15s
Build + Deploy / build-document-crawler (push) Successful in 13s
Build + Deploy / build-dsms-gateway (push) Successful in 13s
Build + Deploy / build-dsms-node (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / test-python-document-crawler (push) Successful in 26s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m49s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 45s
CI / test-python-backend (push) Successful in 38s
CI / test-python-dsms-gateway (push) Successful in 23s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 2m23s
CI / loc-budget (push) Failing after 16s
Build + Deploy / build-admin-compliance (push) Successful in 14s
Build + Deploy / build-backend-compliance (push) Successful in 16s
Build + Deploy / build-ai-sdk (push) Successful in 20s
Build + Deploy / build-developer-portal (push) Successful in 12s
Build + Deploy / build-tts (push) Successful in 15s
Build + Deploy / build-document-crawler (push) Successful in 13s
Build + Deploy / build-dsms-gateway (push) Successful in 13s
Build + Deploy / build-dsms-node (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / test-python-document-crawler (push) Successful in 26s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m49s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 45s
CI / test-python-backend (push) Successful in 38s
CI / test-python-dsms-gateway (push) Successful in 23s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 2m23s
Phase 1-2 of the closed quality loop: - GVL cache (consent-tester/services/gvl_cache.py): downloads and caches IAB Global Vendor List with 24h TTL, resolves vendor IDs to names, purposes, policy URLs, retention, country - Vendor extraction (consent_interceptor.py): extract_tcf_vendors() reads __tcfapi after accept phase, resolves via GVL - Scan response: tcf_vendors field added to /scan endpoint - VVT mapper (vendor_vvt_mapper.py): maps TCF vendors to VVT format with purpose labels, Rechtsgrundlage, Drittland detection - Vendor cross-check (banner_cookie_cross_check.py): checks all TCF vendors against DSI text — missing vendors, undocumented transfers - Compliance check integrates Step 3d: TCF vendors vs DSI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Vendor VVT Mapper — map TCF vendors to VVT entries.
|
||||
|
||||
Converts resolved TCF vendor data (from GVL) into the format
|
||||
needed for the Verarbeitungsverzeichnis (VVT) and for DSI
|
||||
cross-checking.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# IAB TCF v2.2 Purpose definitions (German)
|
||||
TCF_PURPOSE_LABELS = {
|
||||
1: "Speicherung/Zugriff auf Endgeraet",
|
||||
2: "Auswahl einfacher Anzeigen",
|
||||
3: "Personalisiertes Anzeigenprofil erstellen",
|
||||
4: "Personalisierte Anzeigen auswaehlen",
|
||||
5: "Personalisiertes Inhaltsprofil erstellen",
|
||||
6: "Personalisierte Inhalte auswaehlen",
|
||||
7: "Anzeigenleistung messen",
|
||||
8: "Inhaltsleistung messen",
|
||||
9: "Marktforschung",
|
||||
10: "Produkte entwickeln und verbessern",
|
||||
11: "Geraeteeigenschaften zur Identifizierung nutzen",
|
||||
}
|
||||
|
||||
# Purpose → Banner-Kategorie Mapping
|
||||
PURPOSE_CATEGORY = {
|
||||
1: "necessary",
|
||||
2: "marketing", 3: "marketing", 4: "marketing",
|
||||
5: "marketing", 6: "marketing",
|
||||
7: "statistics", 8: "statistics",
|
||||
9: "statistics", 10: "functional", 11: "functional",
|
||||
}
|
||||
|
||||
# EWR countries
|
||||
_EU_EWR = {
|
||||
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
|
||||
"DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
|
||||
"PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO",
|
||||
"CH", "GB",
|
||||
}
|
||||
|
||||
|
||||
def tcf_vendor_to_vvt(vendor: dict) -> dict:
|
||||
"""Map a resolved TCF vendor to a VVT entry.
|
||||
|
||||
Args:
|
||||
vendor: Resolved GVL vendor dict with name, purposes, country, etc.
|
||||
|
||||
Returns:
|
||||
VVT-compatible dict with name, zweck, rechtsgrundlage, drittland, etc.
|
||||
"""
|
||||
purposes = vendor.get("purposes", [])
|
||||
country = vendor.get("country")
|
||||
is_eu = vendor.get("is_eu", country in _EU_EWR if country else None)
|
||||
|
||||
# Determine primary category from purposes
|
||||
categories = set()
|
||||
for p in purposes:
|
||||
cat = PURPOSE_CATEGORY.get(p, "functional")
|
||||
categories.add(cat)
|
||||
|
||||
# Rechtsgrundlage depends on category
|
||||
if "marketing" in categories or "statistics" in categories:
|
||||
rechtsgrundlage = "Einwilligung (Art. 6(1)(a) DSGVO, §25 Abs. 1 TDDDG)"
|
||||
else:
|
||||
rechtsgrundlage = "Berechtigtes Interesse (Art. 6(1)(f) DSGVO, §25 Abs. 2 TDDDG)"
|
||||
|
||||
return {
|
||||
"vendor_id": vendor.get("vendor_id"),
|
||||
"name": vendor.get("name", ""),
|
||||
"zweck": [TCF_PURPOSE_LABELS.get(p, f"Zweck {p}") for p in purposes],
|
||||
"zweck_kurz": _summarize_purposes(purposes),
|
||||
"kategorie": sorted(categories)[0] if categories else "functional",
|
||||
"rechtsgrundlage": rechtsgrundlage,
|
||||
"drittland": not is_eu if is_eu is not None else None,
|
||||
"land": country,
|
||||
"transfermechanismus": "SCC/DPF" if (not is_eu and is_eu is not None) else None,
|
||||
"speicherdauer_tage": vendor.get("retention_days"),
|
||||
"policy_url": vendor.get("policy_url", ""),
|
||||
"uses_cookies": vendor.get("uses_cookies", False),
|
||||
}
|
||||
|
||||
|
||||
def map_vendors_to_vvt(vendors: list[dict]) -> list[dict]:
|
||||
"""Map a list of TCF vendors to VVT entries."""
|
||||
return [tcf_vendor_to_vvt(v) for v in vendors]
|
||||
|
||||
|
||||
def _summarize_purposes(purposes: list[int]) -> str:
|
||||
"""Short German summary of purposes."""
|
||||
if not purposes:
|
||||
return "Keine Zwecke angegeben"
|
||||
cats = set(PURPOSE_CATEGORY.get(p, "sonstig") for p in purposes)
|
||||
labels = {
|
||||
"marketing": "Marketing/Werbung",
|
||||
"statistics": "Analyse/Messung",
|
||||
"functional": "Funktional",
|
||||
"necessary": "Technisch notwendig",
|
||||
"sonstig": "Sonstige",
|
||||
}
|
||||
return ", ".join(labels.get(c, c) for c in sorted(cats))
|
||||
Reference in New Issue
Block a user