""" Dashboard endpoints for comprehensive usage monitoring. """ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import Dict, Any from datetime import datetime from loguru import logger import sqlite3 from services.database import get_db from services.subscription import UsageTrackingService, PricingService from services.subscription.schema_utils import ensure_subscription_plan_columns, ensure_usage_summaries_columns from models.subscription_models import UsageAlert from middleware.auth_middleware import get_current_user from ..dependencies import verify_user_access from ..cache import get_cached_dashboard, set_cached_dashboard router = APIRouter() @router.get("/dashboard/{user_id}") async def get_dashboard_data( user_id: str, billing_period: str = None, db: Session = Depends(get_db), current_user: Dict[str, Any] = Depends(get_current_user) ) -> Dict[str, Any]: """Get comprehensive dashboard data for usage monitoring.""" verify_user_access(user_id, current_user) try: ensure_subscription_plan_columns(db) ensure_usage_summaries_columns(db) # Check cache first (skip if billing_period is specified) if not billing_period: cached_data = get_cached_dashboard(user_id) if cached_data: return cached_data usage_service = UsageTrackingService(db) pricing_service = PricingService(db) # Get current usage stats (for the requested period) current_usage = usage_service.get_user_usage_stats(user_id, billing_period) # Get usage trends (last 6 months) trends = usage_service.get_usage_trends(user_id, 6) # Get user limits limits = pricing_service.get_user_limits(user_id) # Get unread alerts alerts_query = db.query(UsageAlert).filter( UsageAlert.user_id == user_id, UsageAlert.is_read == False ) if billing_period: alerts_query = alerts_query.filter(UsageAlert.billing_period == billing_period) alerts = alerts_query.order_by(UsageAlert.created_at.desc()).limit(5).all() alerts_data = [ { "id": alert.id, "type": alert.alert_type, "title": alert.title, "message": alert.message, "severity": alert.severity, "created_at": alert.created_at.isoformat() } for alert in alerts ] # Calculate cost projections (only relevant for current month) current_cost = current_usage.get('total_cost', 0) days_in_period = 30 current_day = datetime.now().day # Only project costs if viewing current month is_current_month = not billing_period or billing_period == datetime.now().strftime("%Y-%m") if is_current_month: projected_cost = (current_cost / current_day) * days_in_period if current_day > 0 else 0 else: projected_cost = current_cost # For past months, projected is actual response_payload = { "success": True, "data": { "current_usage": current_usage, "trends": trends, "limits": limits, "alerts": alerts_data, "projections": { "projected_monthly_cost": round(projected_cost, 2), "cost_limit": limits.get('limits', {}).get('monthly_cost', 0) if limits else 0, "projected_usage_percentage": (projected_cost / max(limits.get('limits', {}).get('monthly_cost', 1), 1)) * 100 if limits else 0 }, "summary": { "total_api_calls_this_month": current_usage.get('total_calls', 0), "total_cost_this_month": current_usage.get('total_cost', 0), "usage_status": current_usage.get('usage_status', 'active'), "unread_alerts": len(alerts_data) } } } # Cache the response only for default view if not billing_period: set_cached_dashboard(user_id, response_payload) return response_payload except (sqlite3.OperationalError, Exception) as e: error_str = str(e).lower() if 'no such column' in error_str and ('exa_calls' in error_str or 'exa_cost' in error_str or 'video_calls' in error_str or 'video_cost' in error_str or 'image_edit_calls' in error_str or 'image_edit_cost' in error_str or 'audio_calls' in error_str or 'audio_cost' in error_str): logger.warning("Missing column detected in dashboard query, attempting schema fix...") try: import services.subscription.schema_utils as schema_utils schema_utils._checked_usage_summaries_columns = False schema_utils._checked_subscription_plan_columns = False # Use the already imported functions from top of file ensure_usage_summaries_columns(db) ensure_subscription_plan_columns(db) db.expire_all() # Retry the query usage_service = UsageTrackingService(db) pricing_service = PricingService(db) current_usage = usage_service.get_user_usage_stats(user_id) trends = usage_service.get_usage_trends(user_id, 6) limits = pricing_service.get_user_limits(user_id) alerts = db.query(UsageAlert).filter( UsageAlert.user_id == user_id, UsageAlert.is_read == False ).order_by(UsageAlert.created_at.desc()).limit(5).all() alerts_data = [ { "id": alert.id, "type": alert.alert_type, "title": alert.title, "message": alert.message, "severity": alert.severity, "created_at": alert.created_at.isoformat() } for alert in alerts ] current_cost = current_usage.get('total_cost', 0) days_in_period = 30 current_day = datetime.now().day projected_cost = (current_cost / current_day) * days_in_period if current_day > 0 else 0 response_payload = { "success": True, "data": { "current_usage": current_usage, "trends": trends, "limits": limits, "alerts": alerts_data, "projections": { "projected_monthly_cost": round(projected_cost, 2), "cost_limit": limits.get('limits', {}).get('monthly_cost', 0) if limits else 0, "projected_usage_percentage": (projected_cost / max(limits.get('limits', {}).get('monthly_cost', 1), 1)) * 100 if limits else 0 }, "summary": { "total_api_calls_this_month": current_usage.get('total_calls', 0), "total_cost_this_month": current_usage.get('total_cost', 0), "usage_status": current_usage.get('usage_status', 'active'), "unread_alerts": len(alerts_data) } } } # Cache the response after successful retry set_cached_dashboard(user_id, response_payload) return response_payload except Exception as retry_err: logger.error(f"Schema fix and retry failed: {retry_err}") return { "success": False, "error": str(retry_err), "data": { "current_usage": {"total_calls": 0, "total_cost": 0, "usage_status": "error", "provider_breakdown": {}}, "trends": [], "limits": {"limits": {"monthly_cost": 0}}, "alerts": [], "projections": {"projected_monthly_cost": 0, "cost_limit": 0, "projected_usage_percentage": 0}, "summary": {"total_api_calls_this_month": 0, "total_cost_this_month": 0, "usage_status": "error", "unread_alerts": 0} } } logger.error(f"Error getting dashboard data: {e}") return { "success": False, "error": str(e), "data": { "current_usage": {"total_calls": 0, "total_cost": 0, "usage_status": "error", "provider_breakdown": {}}, "trends": [], "limits": {"limits": {"monthly_cost": 0}}, "alerts": [], "projections": {"projected_monthly_cost": 0, "cost_limit": 0, "projected_usage_percentage": 0}, "summary": {"total_api_calls_this_month": 0, "total_cost_this_month": 0, "usage_status": "error", "unread_alerts": 0} } }