"""admin/routers/analytics.py — Analytics and reporting routes. Routes: GET /{slug}/analytics — Daily session counts GET /{slug}/analytics/funnel — Conversion funnel GET /{slug}/analytics/intents — Intent label distribution GET /{slug}/analytics/unanswered — Phase 10A: Unanswered/boundary questions GET /{slug}/analytics/books — Phase 10B: Per-book engagement breakdown GET /{slug}/analytics/heatmap — Phase 10C: Hourly × weekday activity heatmap GET /{slug}/analytics/visitors — Unique visitor counts (new vs returning) GET /{slug}/analytics/geo — Country and city distribution GET /{slug}/analytics/devices — Device, browser, OS breakdown GET /{slug}/analytics/sessions/stats — Avg turns, avg rating, session stats """ from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, get_current_author_scoped from app.services.admin_analytics_service import AdminAnalyticsService router = APIRouter() @router.get("/{author_slug}/analytics") async def get_analytics( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return daily session counts for dashboard charts.""" return await AdminAnalyticsService(db).daily_sessions(current_user.id, days) @router.get("/{author_slug}/analytics/funnel") async def get_conversion_funnel( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return conversion funnel data (sessions → chat → book → link → click).""" return await AdminAnalyticsService(db).conversion_funnel(current_user.id, days) @router.get("/{author_slug}/analytics/intents") async def get_intent_distribution( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return distribution of detected intent labels.""" return await AdminAnalyticsService(db).intent_distribution(current_user.id, days) @router.get("/{author_slug}/analytics/unanswered") async def get_unanswered_questions( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return sessions where boundary/fallback was triggered.""" return await AdminAnalyticsService(db).unanswered_questions(current_user.id, days) @router.get("/{author_slug}/analytics/books") async def get_book_analytics( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return per-book engagement breakdown.""" return await AdminAnalyticsService(db).book_engagement(current_user.id, days) @router.get("/{author_slug}/analytics/heatmap") async def get_activity_heatmap( author_slug: str, days: int = Query(90, ge=7, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return hourly × weekday activity heatmap.""" return await AdminAnalyticsService(db).activity_heatmap(current_user.id, days) @router.get("/{author_slug}/analytics/visitors") async def get_visitor_stats( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return unique visitor counts with new vs returning breakdown.""" return await AdminAnalyticsService(db).visitor_stats(current_user.id, days) @router.get("/{author_slug}/analytics/geo") async def get_geo_breakdown( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return geographic distribution of visitors.""" return await AdminAnalyticsService(db).geo_breakdown(current_user.id, days) @router.get("/{author_slug}/analytics/devices") async def get_device_breakdown( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return device, browser, and OS distribution of visitors.""" return await AdminAnalyticsService(db).device_breakdown(current_user.id, days) @router.get("/{author_slug}/analytics/sessions/stats") async def get_session_stats( author_slug: str, days: int = Query(30, ge=1, le=365), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return session-level aggregate statistics.""" return await AdminAnalyticsService(db).session_stats(current_user.id, days)