Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""API balance / consumption endpoints."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.deps import require_readonly, require_support
|
|
from app.database import get_db
|
|
from app.models.balance import BalanceSnapshot
|
|
from app.models.user import User
|
|
from app.schemas import BalanceOut
|
|
from app.services import balance as balance_service
|
|
|
|
router = APIRouter(prefix="/api/balance", tags=["balance"])
|
|
|
|
|
|
async def _latest_snapshot(db: AsyncSession, provider: str) -> BalanceSnapshot | None:
|
|
"""Prefer the newest successful snapshot; fall back to latest error row."""
|
|
res = await db.execute(
|
|
select(BalanceSnapshot)
|
|
.where(BalanceSnapshot.provider == provider)
|
|
.where(BalanceSnapshot.error.is_(None))
|
|
.where(BalanceSnapshot.remaining.is_not(None))
|
|
.order_by(BalanceSnapshot.created_at.desc())
|
|
.limit(1)
|
|
)
|
|
snap = res.scalar_one_or_none()
|
|
if snap:
|
|
return snap
|
|
res = await db.execute(
|
|
select(BalanceSnapshot)
|
|
.where(BalanceSnapshot.provider == provider)
|
|
.order_by(BalanceSnapshot.created_at.desc())
|
|
.limit(1)
|
|
)
|
|
return res.scalar_one_or_none()
|
|
|
|
|
|
@router.get("", response_model=list[BalanceOut])
|
|
async def latest_balances(db: AsyncSession = Depends(get_db), _: User = Depends(require_readonly)):
|
|
"""Latest snapshot per provider."""
|
|
out: list[BalanceSnapshot] = []
|
|
for provider in ("openrouter", "deepseek"):
|
|
snap = await _latest_snapshot(db, provider)
|
|
if snap:
|
|
out.append(snap)
|
|
return out
|
|
|
|
|
|
@router.post("/refresh", response_model=list[BalanceOut])
|
|
async def refresh_balances(db: AsyncSession = Depends(get_db), _: User = Depends(require_support)):
|
|
await balance_service.poll_once()
|
|
return await latest_balances(db) # type: ignore[arg-type]
|