MAC / mac /routers /usage.py
Aaryan17's picture
chore: upload MAC codebase to HF Space
0e76632 verified
"""Usage endpoints β€” /usage β€” track token consumption."""
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from mac.database import get_db
from mac.schemas.usage import (
MyUsageResponse, QuotaStatus, HistoryResponse, RequestHistoryItem,
QuotaResponse, AdminAllUsageResponse, AdminUserUsage, AdminModelsResponse,
)
from mac.services import usage_service, auth_service
from mac.middleware.auth_middleware import get_current_user, require_admin
from mac.models.user import User
from mac.config import settings
router = APIRouter(prefix="/usage", tags=["Usage"])
@router.get("/me", response_model=MyUsageResponse)
async def my_usage(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
"""My token usage β€” today, this week, this month, by model."""
usage = await usage_service.get_my_usage(db, user.id)
tokens_today = usage["today"]["total_tokens"]
tomorrow = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
from datetime import timedelta
tomorrow += timedelta(days=1)
return MyUsageResponse(
roll_number=user.roll_number,
usage=usage,
quota=QuotaStatus(
daily_limit=settings.rate_limit_tokens_per_day,
remaining_today=max(0, settings.rate_limit_tokens_per_day - tokens_today),
resets_at=tomorrow.isoformat(),
),
)
@router.get("/me/history", response_model=HistoryResponse)
async def my_history(
page: int = Query(1, ge=1),
per_page: int = Query(50, ge=1, le=100),
model: str | None = Query(None),
date_from: str | None = Query(None),
date_to: str | None = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Paginated request history."""
logs, total = await usage_service.get_request_history(db, user.id, page, per_page, model, date_from, date_to)
return HistoryResponse(
requests=[RequestHistoryItem(
id=log.request_id,
model=log.model,
endpoint=log.endpoint,
tokens_in=log.tokens_in,
tokens_out=log.tokens_out,
latency_ms=log.latency_ms,
status_code=log.status_code,
created_at=log.created_at.isoformat(),
) for log in logs],
total=total,
page=page,
per_page=per_page,
)
@router.get("/me/quota", response_model=QuotaResponse)
async def my_quota(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
"""My quota limits and remaining balance."""
tokens_today = await usage_service.get_tokens_used_today(db, user.id)
reqs_hour = await usage_service.get_requests_this_hour(db, user.id)
now = datetime.now(timezone.utc)
from datetime import timedelta
next_hour = now.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)
tomorrow = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
return QuotaResponse(
role=user.role,
limits={
"daily_tokens": settings.rate_limit_tokens_per_day,
"requests_per_hour": settings.rate_limit_requests_per_hour,
"max_tokens_per_request": 4096 if user.role == "student" else 8192,
},
current={
"tokens_used_today": tokens_today,
"requests_this_hour": reqs_hour,
"remaining_tokens": max(0, settings.rate_limit_tokens_per_day - tokens_today),
"remaining_requests": max(0, settings.rate_limit_requests_per_hour - reqs_hour),
},
resets={
"daily_reset": tomorrow.isoformat(),
"hourly_reset": next_hour.isoformat(),
},
)
# ── Admin Endpoints ──────────────────────────────────────
@router.get("/admin/all", response_model=AdminAllUsageResponse)
async def admin_all_usage(
page: int = Query(1, ge=1),
per_page: int = Query(50, ge=1, le=100),
department: str | None = Query(None),
admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""All users usage summary (admin only)."""
users, total = await usage_service.get_all_users_usage(db, page, per_page, department)
return AdminAllUsageResponse(
users=[AdminUserUsage(**u) for u in users],
total_users=total,
page=page,
)
@router.get("/admin/user/{roll_number}")
async def admin_user_usage(
roll_number: str,
admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Specific student's usage (admin only)."""
user = await auth_service.get_user_by_roll(db, roll_number)
if not user:
raise HTTPException(status_code=404, detail={"code": "not_found", "message": "User not found"})
usage = await usage_service.get_my_usage(db, user.id)
return {"roll_number": user.roll_number, "name": user.name, "department": user.department, "usage": usage}
@router.get("/admin/models", response_model=AdminModelsResponse)
async def admin_models_usage(
admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Per-model usage stats (admin only)."""
from sqlalchemy import select, func
from mac.models.user import UsageLog
from mac.services.usage_service import _today_start
today = _today_start()
result = await db.execute(
select(
UsageLog.model,
func.count(),
func.coalesce(func.sum(UsageLog.tokens_in + UsageLog.tokens_out), 0),
func.coalesce(func.avg(UsageLog.latency_ms), 0),
func.count(func.distinct(UsageLog.user_id)),
).where(UsageLog.created_at >= today).group_by(UsageLog.model)
)
from mac.schemas.usage import AdminModelUsage
models = []
for row in result:
models.append(AdminModelUsage(
model_id=row[0],
requests_today=row[1],
tokens_today=int(row[2]),
avg_latency_ms=int(row[3]),
unique_users_today=row[4],
))
return AdminModelsResponse(models=models)