"""User self-service usage endpoints.""" import logging from datetime import datetime, timezone from quart import Blueprint, jsonify, request from app.auth.quart_jwt import jwt_required, get_jwt_identity from app.utils import active_required from app.models.usage_event import UsageEvent from app.db import get_db logger = logging.getLogger(__name__) usage_bp = Blueprint('usage', __name__) def _month_start() -> datetime: now = datetime.now(timezone.utc) return now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) @usage_bp.route('/me', methods=['GET']) @jwt_required() async def my_usage(): """GET /api/usage/me — current user's own MTD cost summary.""" user_id = get_jwt_identity() try: period_start = _month_start() db = await get_db() pipeline = [ {'$match': {'user_id': user_id, 'ts': {'$gte': period_start}}}, {'$group': { '_id': None, 'total_cost': {'$sum': '$cost_usd.total'}, 'prompt_tokens': {'$sum': '$prompt_tokens'}, 'completion_tokens': {'$sum': '$completion_tokens'}, 'calls': {'$sum': 1}, }}, ] agg = await db.usage_events.aggregate(pipeline).to_list(1) totals = agg[0] if agg else {'total_cost': 0, 'prompt_tokens': 0, 'completion_tokens': 0, 'calls': 0} totals.pop('_id', None) # By feature feat_pipeline = [ {'$match': {'user_id': user_id, 'ts': {'$gte': period_start}}}, {'$group': { '_id': '$feature', 'total_cost': {'$sum': '$cost_usd.total'}, 'calls': {'$sum': 1}, }}, {'$sort': {'total_cost': -1}}, ] by_feature = await db.usage_events.aggregate(feat_pipeline).to_list(20) from app.utils import make_serializable return jsonify({ 'totals': make_serializable(totals), 'by_feature': make_serializable(by_feature), 'period_start': period_start.isoformat(), }), 200 except Exception as e: logger.error(f"my_usage error: {e}", exc_info=True) return jsonify({'error': str(e)}), 500 @usage_bp.route('/focus-groups/', methods=['GET']) @jwt_required() async def focus_group_usage(fg_id: str): """GET /api/usage/focus-groups/ — usage for a specific focus group (owner or admin).""" user_id = get_jwt_identity() try: # Auth check — owner or admin db = await get_db() from bson import ObjectId try: fg = await db.focus_groups.find_one({'_id': ObjectId(fg_id)}) except Exception: return jsonify({'error': 'Invalid id'}), 400 if not fg: return jsonify({'error': 'Not found'}), 404 from app.models.user import User user = await User.find_by_id(user_id) is_admin = user and user.get('role') == 'admin' is_owner = fg.get('created_by') == user_id or str(fg.get('user_id', '')) == user_id if not is_admin and not is_owner: return jsonify({'error': 'Forbidden'}), 403 pipeline = [ {'$match': {'focus_group_id': fg_id}}, {'$group': { '_id': None, 'total_cost': {'$sum': '$cost_usd.total'}, 'prompt_tokens': {'$sum': '$prompt_tokens'}, 'completion_tokens': {'$sum': '$completion_tokens'}, 'calls': {'$sum': 1}, }}, ] agg = await db.usage_events.aggregate(pipeline).to_list(1) totals = agg[0] if agg else {'total_cost': 0, 'prompt_tokens': 0, 'completion_tokens': 0, 'calls': 0} totals.pop('_id', None) from app.utils import make_serializable return jsonify({'totals': make_serializable(totals), 'focus_group_id': fg_id}), 200 except Exception as e: logger.error(f"focus_group_usage error: {e}", exc_info=True) return jsonify({'error': str(e)}), 500