Files
breakpilot-compliance/backend-compliance/compliance/services/_banner_serializers.py
T
Benjamin Admin c3fcfe88ee feat: Vendor-level consent + Consent analytics (F4 + F6)
F4: Granular Vendor-Level Consent
- Migration 113: vendor_consents JSONB on banner_consents + audit_log
- ConsentCreate schema + BannerConsentDB model extended
- banner_consent_service stores vendor_consents alongside categories
- Audit trail includes vendor-level decisions + user_agent

F6: Consent Rate Analytics
- Migration 114: user_agent on audit_log + time-series index
- BannerAnalyticsService: time series, category breakdown, device stats
- banner_analytics_routes: 4 endpoints (overview, time-series, categories, devices)
- AnalyticsDashboard.tsx: KPIs, bar chart, category bars, device breakdown
- New "Analytik" tab in cookie-banner page

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 20:58:06 +02:00

85 lines
2.7 KiB
Python

"""
Internal ORM-to-dict serializers shared by the banner services.
Kept as plain module-level functions (not part of either service class) so
they can be imported by both ``banner_consent_service`` and
``banner_admin_service`` without cyclic dependencies.
"""
from typing import Any
from compliance.db.banner_models import (
BannerCategoryConfigDB,
BannerConsentDB,
BannerSiteConfigDB,
BannerVendorConfigDB,
)
def consent_to_dict(c: BannerConsentDB) -> dict[str, Any]:
return {
"id": str(c.id),
"site_id": c.site_id,
"device_fingerprint": c.device_fingerprint,
"categories": c.categories or [],
"vendors": c.vendors or [],
"vendor_consents": c.vendor_consents or {},
"ip_hash": c.ip_hash,
"consent_string": c.consent_string,
"linked_email": c.linked_email,
"expires_at": c.expires_at.isoformat() if c.expires_at else None,
"created_at": c.created_at.isoformat() if c.created_at else None,
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
}
def site_config_to_dict(s: BannerSiteConfigDB) -> dict[str, Any]:
return {
"id": str(s.id),
"site_id": s.site_id,
"site_name": s.site_name,
"site_url": s.site_url,
"banner_title": s.banner_title,
"banner_description": s.banner_description,
"privacy_url": s.privacy_url,
"imprint_url": s.imprint_url,
"dsb_name": s.dsb_name,
"dsb_email": s.dsb_email,
"theme": s.theme or {},
"tcf_enabled": s.tcf_enabled,
"config_version": s.config_version,
"is_active": s.is_active,
"created_at": s.created_at.isoformat() if s.created_at else None,
"updated_at": s.updated_at.isoformat() if s.updated_at else None,
}
def category_to_dict(c: BannerCategoryConfigDB) -> dict[str, Any]:
return {
"id": str(c.id),
"site_config_id": str(c.site_config_id),
"category_key": c.category_key,
"name_de": c.name_de,
"name_en": c.name_en,
"description_de": c.description_de,
"description_en": c.description_en,
"is_required": c.is_required,
"sort_order": c.sort_order,
"is_active": c.is_active,
}
def vendor_to_dict(v: BannerVendorConfigDB) -> dict[str, Any]:
return {
"id": str(v.id),
"site_config_id": str(v.site_config_id),
"vendor_name": v.vendor_name,
"vendor_url": v.vendor_url,
"category_key": v.category_key,
"description_de": v.description_de,
"description_en": v.description_en,
"cookie_names": v.cookie_names or [],
"retention_days": v.retention_days,
"is_active": v.is_active,
}