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>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
FastAPI routes for Banner Consent Analytics.
|
||||
|
||||
Endpoints:
|
||||
GET /banner/analytics/{site_id}/overview — high-level stats
|
||||
GET /banner/analytics/{site_id}/time-series — opt-in rate over time
|
||||
GET /banner/analytics/{site_id}/categories — acceptance per category
|
||||
GET /banner/analytics/{site_id}/devices — mobile/desktop/tablet breakdown
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from classroom_engine.database import get_db
|
||||
from .tenant_utils import get_tenant_id as _get_tenant_id
|
||||
from compliance.services.banner_analytics_service import BannerAnalyticsService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/banner/analytics", tags=["banner-analytics"])
|
||||
|
||||
|
||||
@router.get("/{site_id}/overview")
|
||||
def analytics_overview(
|
||||
site_id: str,
|
||||
days: int = Query(30, le=365),
|
||||
db: Session = Depends(get_db),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
):
|
||||
service = BannerAnalyticsService(db)
|
||||
return service.get_overview_stats(tenant_id, site_id, days)
|
||||
|
||||
|
||||
@router.get("/{site_id}/time-series")
|
||||
def analytics_time_series(
|
||||
site_id: str,
|
||||
period: str = Query("daily"),
|
||||
days: int = Query(30, le=365),
|
||||
db: Session = Depends(get_db),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
):
|
||||
service = BannerAnalyticsService(db)
|
||||
return service.get_time_series(tenant_id, site_id, period, days)
|
||||
|
||||
|
||||
@router.get("/{site_id}/categories")
|
||||
def analytics_categories(
|
||||
site_id: str,
|
||||
days: int = Query(30, le=365),
|
||||
db: Session = Depends(get_db),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
):
|
||||
service = BannerAnalyticsService(db)
|
||||
return service.get_category_breakdown(tenant_id, site_id, days)
|
||||
|
||||
|
||||
@router.get("/{site_id}/devices")
|
||||
def analytics_devices(
|
||||
site_id: str,
|
||||
days: int = Query(30, le=365),
|
||||
db: Session = Depends(get_db),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
):
|
||||
service = BannerAnalyticsService(db)
|
||||
return service.get_device_breakdown(tenant_id, site_id, days)
|
||||
Reference in New Issue
Block a user