""" Vendor-Banner Sync — maps the 82-service registry to banner vendor configs. Automatically creates vendor entries in the cookie banner with correct category assignment and legally required retention periods. """ import logging import os import uuid logger = logging.getLogger(__name__) # Service category → Banner category mapping CATEGORY_MAP = { "tracking": "statistics", "heatmap": "statistics", "tag_manager": "statistics", "marketing": "marketing", "social": "marketing", "push": "marketing", "crm": "marketing", "chatbot": "functional", "support": "functional", "video": "functional", "testing": "functional", "cdn": "necessary", "payment": "necessary", "error_tracking": "necessary", "accessibility": "necessary", "cmp": "necessary", "other": "functional", } # Legally required max retention per category (in days) # Based on: DSGVO Art. 5(1)(e), CNIL guidelines, EDPB recommendations RETENTION_DEFAULTS = { "necessary": 365, # Session + functional = max 12 months "statistics": 790, # Max 26 months (Google Analytics default) "marketing": 90, # Max 90 days for retargeting "functional": 365, # Max 12 months } # Specific service retention overrides SERVICE_RETENTION = { "google_analytics": 790, # 26 months (GA4 default) "matomo": 790, # 26 months "hotjar": 365, # 12 months "facebook_pixel": 90, # 90 days (Meta default) "google_ads": 90, # 90 days "stripe": 0, # Session only (payment) "paypal": 0, # Session only "klarna": 0, # Session only } def get_banner_vendors_from_registry() -> list[dict]: """Convert service registry entries to banner vendor configs.""" from compliance.services.service_registry import SERVICE_REGISTRY vendors = [] for pattern, meta in SERVICE_REGISTRY.items(): service_id = meta.get("id", "") category = meta.get("category", "other") banner_category = CATEGORY_MAP.get(category, "functional") # Skip CMP — consent managers are not vendor entries if service_id == "cmp": continue retention = SERVICE_RETENTION.get(service_id, RETENTION_DEFAULTS.get(banner_category, 365)) vendors.append({ "vendor_name": meta["name"], "vendor_url": "", # Would need manual entry "category_key": banner_category, "description_de": f"{meta['name']} ({meta.get('provider', '')})", "description_en": f"{meta['name']} ({meta.get('provider', '')})", "cookie_names": [], # Service-specific, populated later "retention_days": retention, "is_active": True, "country": meta.get("country", ""), "eu_adequate": meta.get("eu_adequate", False), "requires_consent": meta.get("requires_consent", True), "legal_ref": meta.get("legal_ref", ""), "service_id": service_id, }) logger.info("Generated %d banner vendors from service registry", len(vendors)) return vendors async def sync_vendors_to_site(pool, site_config_id: str, tenant_id: str) -> dict: """Sync service registry vendors to a site's banner vendor configs.""" vendors = get_banner_vendors_from_registry() created = 0 updated = 0 async with pool.acquire() as conn: for v in vendors: # Check if vendor already exists for this site existing = await conn.fetchrow(""" SELECT id FROM compliance_banner_vendor_configs WHERE site_config_id = $1 AND vendor_name = $2 """, uuid.UUID(site_config_id), v["vendor_name"]) if existing: await conn.execute(""" UPDATE compliance_banner_vendor_configs SET category_key = $1, retention_days = $2, is_active = $3 WHERE id = $4 """, v["category_key"], v["retention_days"], v["is_active"], existing["id"]) updated += 1 else: import json await conn.execute(""" INSERT INTO compliance_banner_vendor_configs (site_config_id, vendor_name, category_key, description_de, description_en, cookie_names, retention_days, is_active) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) """, uuid.UUID(site_config_id), v["vendor_name"], v["category_key"], v["description_de"], v["description_en"], json.dumps(v["cookie_names"]), v["retention_days"], v["is_active"]) created += 1 logger.info("Synced vendors to site %s: %d created, %d updated", site_config_id, created, updated) return {"created": created, "updated": updated, "total": len(vendors)}