""" FastAPI routes for Banner A/B Testing. Endpoints: GET /banner/ab/{site_config_id}/variants — list variants POST /banner/ab/{site_config_id}/variants — create variant PUT /banner/ab/variants/{variant_id} — update variant DELETE /banner/ab/variants/{variant_id} — delete variant GET /banner/ab/{site_config_id}/stats — per-variant stats GET /banner/ab/assign — assign variant for device """ import logging from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel 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_ab_service import BannerABService logger = logging.getLogger(__name__) router = APIRouter(prefix="/banner/ab", tags=["banner-ab-testing"]) class VariantCreate(BaseModel): variant_name: str variant_key: str = "A" traffic_percent: int = 50 is_control: bool = False banner_title: Optional[str] = None banner_description: Optional[str] = None position: Optional[str] = None style: Optional[str] = None primary_color: Optional[str] = None show_decline_all: Optional[bool] = None theme_overrides: Optional[dict] = None class VariantUpdate(BaseModel): variant_name: Optional[str] = None traffic_percent: Optional[int] = None is_control: Optional[bool] = None banner_title: Optional[str] = None banner_description: Optional[str] = None position: Optional[str] = None style: Optional[str] = None primary_color: Optional[str] = None show_decline_all: Optional[bool] = None is_active: Optional[bool] = None @router.get("/{site_config_id}/variants") def list_variants( site_config_id: str, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): service = BannerABService(db) return service.list_variants(tenant_id, site_config_id) @router.post("/{site_config_id}/variants") def create_variant( site_config_id: str, body: VariantCreate, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): service = BannerABService(db) return service.create_variant(tenant_id, site_config_id, body.model_dump()) @router.put("/variants/{variant_id}") def update_variant( variant_id: str, body: VariantUpdate, db: Session = Depends(get_db), ): service = BannerABService(db) result = service.update_variant(variant_id, body.model_dump(exclude_none=True)) if not result: raise HTTPException(404, "Variant not found") return result @router.delete("/variants/{variant_id}") def delete_variant( variant_id: str, db: Session = Depends(get_db), ): service = BannerABService(db) if not service.delete_variant(variant_id): raise HTTPException(404, "Variant not found") return {"deleted": True} @router.get("/{site_config_id}/stats") def variant_stats( site_config_id: str, db: Session = Depends(get_db), tenant_id: str = Depends(_get_tenant_id), ): service = BannerABService(db) return service.get_variant_stats(tenant_id, site_config_id) @router.get("/assign") def assign_variant( site_config_id: str = Query(...), device_fingerprint: str = Query(...), db: Session = Depends(get_db), ): service = BannerABService(db) variant = service.assign_variant(site_config_id, device_fingerprint) if not variant: return {"variant": None, "message": "No active A/B test"} return {"variant": variant}