"""Token-Usage & Budget-Verwaltung.""" import logging from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from auth import get_current_admin from database import get_db logger = logging.getLogger("verwaltung.token_usage") router = APIRouter(prefix="/api/token-usage", tags=["Token-Usage"]) @router.get("/overview") async def get_usage_overview(admin=Depends(get_current_admin)): """Token-Verbrauch aller Organisationen.""" db = await get_db() try: cursor = await db.execute(""" SELECT o.id, o.name, o.slug, l.credits_total, l.credits_used, l.cost_per_credit, l.token_budget_usd, l.budget_warning_percent, COALESCE(SUM(r.total_cost_usd), 0) as total_cost, COALESCE(SUM(r.input_tokens), 0) as total_input_tokens, COALESCE(SUM(r.output_tokens), 0) as total_output_tokens, COALESCE(SUM(r.api_calls), 0) as total_api_calls, COUNT(r.id) as total_refreshes FROM organizations o LEFT JOIN licenses l ON l.organization_id = o.id AND l.status = 'active' LEFT JOIN refresh_log r ON r.tenant_id = o.id AND r.status = 'completed' GROUP BY o.id """) rows = await cursor.fetchall() result = [] for row in rows: credits_total = row["credits_total"] or 0 credits_used = row["credits_used"] or 0 credits_remaining = max(0, int(credits_total - credits_used)) if credits_total else None percent_used = round((credits_used / credits_total) * 100, 1) if credits_total and credits_total > 0 else None budget_usd = row["token_budget_usd"] cost = row["total_cost"] budget_percent = round((cost / budget_usd) * 100, 1) if budget_usd and budget_usd > 0 else None result.append({ "org_id": row["id"], "org_name": row["name"], "org_slug": row["slug"], "credits_total": credits_total, "credits_used": round(credits_used, 1), "credits_remaining": credits_remaining, "credits_percent_used": percent_used, "token_budget_usd": budget_usd, "total_cost_usd": round(cost, 2), "budget_percent_used": budget_percent, "budget_warning_percent": row["budget_warning_percent"] or 80, "total_input_tokens": row["total_input_tokens"], "total_output_tokens": row["total_output_tokens"], "total_api_calls": row["total_api_calls"], "total_refreshes": row["total_refreshes"], "cost_per_credit": row["cost_per_credit"], }) return result finally: await db.close() @router.get("/{org_id}") async def get_org_usage(org_id: int, admin=Depends(get_current_admin)): """Monatliche Token-Nutzung einer Organisation.""" db = await get_db() try: cursor = await db.execute( "SELECT * FROM token_usage_monthly WHERE organization_id = ? ORDER BY year_month DESC", (org_id,)) rows = await cursor.fetchall() return [{ "year_month": row["year_month"], "input_tokens": row["input_tokens"], "output_tokens": row["output_tokens"], "cache_creation_tokens": row["cache_creation_tokens"], "cache_read_tokens": row["cache_read_tokens"], "total_cost_usd": round(row["total_cost_usd"], 2), "api_calls": row["api_calls"], "refresh_count": row["refresh_count"], } for row in rows] finally: await db.close() @router.get("/{org_id}/current") async def get_org_current_usage(org_id: int, admin=Depends(get_current_admin)): """Aktueller Monat + Budget-Auslastung.""" db = await get_db() try: year_month = datetime.now().strftime("%Y-%m") cursor = await db.execute( "SELECT * FROM token_usage_monthly WHERE organization_id = ? AND year_month = ?", (org_id, year_month)) usage = await cursor.fetchone() cursor = await db.execute( "SELECT credits_total, credits_used, cost_per_credit, token_budget_usd, budget_warning_percent FROM licenses WHERE organization_id = ? AND status = 'active' ORDER BY id DESC LIMIT 1", (org_id,)) lic = await cursor.fetchone() credits_total = lic["credits_total"] if lic else None credits_used = lic["credits_used"] if lic else 0 return { "year_month": year_month, "usage": { "input_tokens": usage["input_tokens"] if usage else 0, "output_tokens": usage["output_tokens"] if usage else 0, "total_cost_usd": round(usage["total_cost_usd"], 2) if usage else 0, "api_calls": usage["api_calls"] if usage else 0, "refresh_count": usage["refresh_count"] if usage else 0, }, "budget": { "credits_total": credits_total, "credits_used": round(credits_used, 1) if credits_used else 0, "credits_remaining": max(0, int(credits_total - credits_used)) if credits_total else None, "credits_percent_used": round((credits_used / credits_total) * 100, 1) if credits_total and credits_total > 0 else None, "token_budget_usd": lic["token_budget_usd"] if lic else None, "cost_per_credit": lic["cost_per_credit"] if lic else None, "budget_warning_percent": lic["budget_warning_percent"] if lic else 80, }, } finally: await db.close() @router.put("/budget/{license_id}") async def update_budget(license_id: int, data: dict, admin=Depends(get_current_admin)): """Budget einer Lizenz setzen/ändern.""" db = await get_db() try: cursor = await db.execute("SELECT id FROM licenses WHERE id = ?", (license_id,)) if not await cursor.fetchone(): raise HTTPException(status_code=404, detail="Lizenz nicht gefunden") fields = [] values = [] for key in ("token_budget_usd", "credits_total", "cost_per_credit", "budget_warning_percent"): if key in data: fields.append(f"{key} = ?") values.append(data[key]) if "credits_used" in data: fields.append("credits_used = ?") values.append(data["credits_used"]) if not fields: raise HTTPException(status_code=400, detail="Keine Felder zum Aktualisieren") values.append(license_id) await db.execute(f"UPDATE licenses SET {', '.join(fields)} WHERE id = ?", values) await db.commit() logger.info(f"Budget für Lizenz {license_id} aktualisiert: {data}") return {"ok": True} finally: await db.close()