""" AI Insights API Endpoints Serves pre-computed daily insights to the dashboard and allows manual generation. """ import logging from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from database import get_db from auth import get_current_user logger = logging.getLogger(__name__) router = APIRouter() # Rate limit: minimum minutes between manual generations MANUAL_RATE_LIMIT_MINUTES = 5 @router.get("/latest") async def get_latest_insight( db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Get the most recent AI insight for the dashboard card.""" result = await db.execute( text(""" SELECT id, generated_at, insight_type, headline, content, model, input_tokens, output_tokens, triggered_by FROM ai_insights ORDER BY generated_at DESC LIMIT 1 """) ) row = result.fetchone() if not row: return None return { "id": row.id, "generated_at": row.generated_at.isoformat(), "insight_type": row.insight_type, "headline": row.headline, "content": row.content, "model": row.model, "input_tokens": row.input_tokens, "output_tokens": row.output_tokens, "triggered_by": row.triggered_by, } @router.get("/history") async def get_insight_history( limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Get historical AI insights with pagination.""" result = await db.execute( text(""" SELECT id, generated_at, insight_type, headline, content, model, input_tokens, output_tokens, triggered_by FROM ai_insights ORDER BY generated_at DESC LIMIT :limit OFFSET :offset """), {"limit": limit, "offset": offset} ) count_result = await db.execute(text("SELECT COUNT(*) FROM ai_insights")) total = count_result.scalar() insights = [] for row in result.fetchall(): insights.append({ "id": row.id, "generated_at": row.generated_at.isoformat(), "insight_type": row.insight_type, "headline": row.headline, "content": row.content, "model": row.model, "input_tokens": row.input_tokens, "output_tokens": row.output_tokens, "triggered_by": row.triggered_by, }) return {"insights": insights, "total": total} @router.get("/usage") async def get_usage_stats( db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Get token usage statistics for the current month.""" result = await db.execute( text(""" SELECT COUNT(*) as generation_count, COALESCE(SUM(input_tokens), 0) as total_input_tokens, COALESCE(SUM(output_tokens), 0) as total_output_tokens FROM ai_insights WHERE generated_at >= DATE_TRUNC('month', CURRENT_DATE) """) ) row = result.fetchone() # Also get today's usage for budget display today_result = await db.execute( text(""" SELECT COALESCE(SUM(input_tokens + output_tokens), 0) as today_total FROM ai_insights WHERE generated_at >= CURRENT_DATE """) ) today_row = today_result.fetchone() return { "month": { "generations": row.generation_count, "input_tokens": row.total_input_tokens, "output_tokens": row.total_output_tokens, "total_tokens": row.total_input_tokens + row.total_output_tokens, }, "today_tokens": today_row.today_total if today_row else 0, } @router.post("/generate") async def generate_insight_manual( db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user) ): """ Manually trigger AI insight generation. Rate-limited to prevent accidental spam. """ # Check rate limit result = await db.execute( text(""" SELECT generated_at FROM ai_insights WHERE triggered_by = 'manual' ORDER BY generated_at DESC LIMIT 1 """) ) last_manual = result.fetchone() if last_manual: elapsed = datetime.now(timezone.utc) - last_manual.generated_at.replace(tzinfo=timezone.utc) if elapsed.total_seconds() < MANUAL_RATE_LIMIT_MINUTES * 60: remaining = MANUAL_RATE_LIMIT_MINUTES - (elapsed.total_seconds() / 60) raise HTTPException( status_code=429, detail=f"Rate limited. Please wait {remaining:.0f} more minutes." ) # Run generation from jobs.ai_insights import generate_insight result = await generate_insight(db, triggered_by="manual") if result.get("success"): return { "success": True, "content": result["content"], "headline": result.get("headline"), "input_tokens": result["input_tokens"], "output_tokens": result["output_tokens"], "model": result["model"], } else: raise HTTPException( status_code=400, detail=result.get("error", "Failed to generate insight") )