Arag / app /admin /routers /analytics.py
AuthorBot
Refactor admin API into service and repository layers.
d6061d6
Raw
History Blame Contribute Delete
4.91 kB
"""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)