from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_admin_user, get_db from app.enums.llm_billing import LLM_BILLING_SCENE_LABELS from app.models.llm_billing.policy import LlmBillingPolicyModel from app.models.user import User from app.schemas.llm_billing import LlmBillingPolicyCreate, LlmBillingPolicyUpdate from app.services.llm_billing.query_service import list_executions_with_calls from app.services.operation_log_service import log_operation_event from app.utils.id_gen import generate_id router = APIRouter(prefix="/admin/llm-billing", tags=["admin-llm-billing"]) @router.get("/policies") async def list_policies( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LlmBillingPolicyModel).order_by(LlmBillingPolicyModel.scene_code)) return list(result.scalars().all()) @router.post("/policies") async def create_policy( data: LlmBillingPolicyCreate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): if data.scene_code not in LLM_BILLING_SCENE_LABELS: raise HTTPException(status_code=400, detail="不支持的LLM业务场景") existing = await db.execute(select(LlmBillingPolicyModel.id).where(LlmBillingPolicyModel.scene_code == data.scene_code).limit(1)) if existing.scalar_one_or_none(): raise HTTPException(status_code=409, detail="该LLM业务场景已存在") policy = LlmBillingPolicyModel( id=generate_id(), scene_code=data.scene_code, scene_name=LLM_BILLING_SCENE_LABELS[data.scene_code], pre_deduct_credits=data.pre_deduct_credits, is_active=data.is_active, version=1, created_by=admin.id, updated_by=admin.id, ) db.add(policy) await db.flush() log_operation_event(domain="llm_billing", module="admin", event_type="LLM_BILLING_POLICY_CREATED", user_id=admin.id, detail={"scene_code": policy.scene_code, "credits": float(policy.pre_deduct_credits)}) return policy @router.put("/policies/{policy_id}") async def update_policy( policy_id: str, data: LlmBillingPolicyUpdate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LlmBillingPolicyModel).where(LlmBillingPolicyModel.id == policy_id).limit(1).with_for_update()) policy = result.scalar_one_or_none() if not policy: raise HTTPException(status_code=404, detail="LLM计费场景不存在") payload = data.model_dump(exclude_unset=True) payload.pop("scene_name", None) for key, value in payload.items(): setattr(policy, key, value) policy.scene_name = LLM_BILLING_SCENE_LABELS.get(policy.scene_code, policy.scene_name) policy.version += 1 policy.updated_by = admin.id await db.flush() log_operation_event(domain="llm_billing", module="admin", event_type="LLM_BILLING_POLICY_UPDATED", user_id=admin.id, detail={"scene_code": policy.scene_code, "version": policy.version}) return policy @router.get("/executions") async def list_executions( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), scene_code: str | None = Query(default=None), status: str | None = Query(default=None), user_id: str | None = Query(default=None), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): items, total = await list_executions_with_calls(db, page=page, page_size=page_size, scene_code=scene_code, status=status, user_id=user_id) return {"items": items, "total": total}