from __future__ import annotations import json from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_admin_user, get_db from app.models.user import User from app.schemas.model_pricing import ( ModelPricingPreviewOut, ModelPricingPreviewRequest, ModelPricingRuleCreate, ModelPricingRuleListOut, ModelPricingRuleOut, ModelPricingRuleUpdate, ) from app.services.model_pricing.calculator import PricingCalculationError, calculate_pricing from app.services.model_pricing.rule_service import ( PricingRuleError, create_rule, disable_rule, get_rule_snapshot, list_rules, publish_rule, update_draft_rule, ) from app.services.operation_log import log_operation from app.services.operation_log_service import log_model_pricing_event router = APIRouter(prefix="/admin/model-pricing", tags=["admin-model-pricing"]) def _http_error(exc: Exception) -> HTTPException: return HTTPException(status_code=400, detail=str(exc)) @router.get("/rules", response_model=ModelPricingRuleListOut) async def admin_list_model_pricing_rules( page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=500), provider: str | None = Query(None), model_name: str | None = Query(None), model_category: str | None = Query(None), publish_status: str | None = Query(None), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): return await list_rules( db, page=page, page_size=page_size, provider=provider, model_name=model_name, model_category=model_category, publish_status=publish_status, ) @router.get("/rules/{rule_id}", response_model=ModelPricingRuleOut) async def admin_get_model_pricing_rule( rule_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): try: snapshot = await get_rule_snapshot(db, rule_id) return {**snapshot, "referenced_count": 0} except PricingRuleError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @router.post("/rules", response_model=ModelPricingRuleOut) async def admin_create_model_pricing_rule( req: ModelPricingRuleCreate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): admin_id = str(admin.id) admin_username = str(admin.username or "") try: snapshot = await create_rule(db, payload=req.model_dump(), operator_id=admin_id) await log_operation( db, admin_id, admin_username, f"创建模型计价草稿 {snapshot['model_name']}/{snapshot['version_code']}", "POST", "/admin/model-pricing/rules", detail=json.dumps(snapshot, ensure_ascii=False, default=str), ) await db.commit() log_model_pricing_event( event_type="pricing_rule_validate", user_id=admin_id, pricing_rule_id=snapshot["id"], pricing_version=snapshot["version_code"], provider=snapshot["provider"], model_name=snapshot["model_name"], billing_mode=snapshot["billing_mode"], message="模型计价草稿创建成功", ) return {**snapshot, "referenced_count": 0} except IntegrityError as exc: await db.rollback() raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc except PricingRuleError as exc: await db.rollback() raise _http_error(exc) from exc @router.put("/rules/{rule_id}", response_model=ModelPricingRuleOut) async def admin_update_model_pricing_rule( rule_id: str, req: ModelPricingRuleUpdate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): admin_id = str(admin.id) admin_username = str(admin.username or "") try: snapshot = await update_draft_rule( db, rule_id=rule_id, payload=req.model_dump(exclude_unset=True), operator_id=admin_id, ) await log_operation( db, admin_id, admin_username, f"更新模型计价草稿 {snapshot['model_name']}/{snapshot['version_code']}", "PUT", f"/admin/model-pricing/rules/{rule_id}", detail=json.dumps(snapshot, ensure_ascii=False, default=str), ) await db.commit() return {**snapshot, "referenced_count": 0} except IntegrityError as exc: await db.rollback() raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc except PricingRuleError as exc: await db.rollback() raise _http_error(exc) from exc @router.post("/rules/{rule_id}/publish", response_model=ModelPricingRuleOut) async def admin_publish_model_pricing_rule( rule_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): admin_id = str(admin.id) admin_username = str(admin.username or "") try: snapshot = await publish_rule(db, rule_id=rule_id, operator_id=admin_id) await log_operation( db, admin_id, admin_username, f"发布模型计价版本 {snapshot['model_name']}/{snapshot['version_code']}", "POST", f"/admin/model-pricing/rules/{rule_id}/publish", detail=json.dumps(snapshot, ensure_ascii=False, default=str), ) await db.commit() log_model_pricing_event( event_type="pricing_rule_publish", user_id=admin_id, pricing_rule_id=snapshot["id"], pricing_version=snapshot["version_code"], provider=snapshot["provider"], model_name=snapshot["model_name"], billing_mode=snapshot["billing_mode"], ) return {**snapshot, "referenced_count": 0} except IntegrityError as exc: await db.rollback() raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc except PricingRuleError as exc: await db.rollback() raise _http_error(exc) from exc @router.post("/rules/{rule_id}/disable", response_model=ModelPricingRuleOut) async def admin_disable_model_pricing_rule( rule_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): admin_id = str(admin.id) admin_username = str(admin.username or "") try: snapshot = await disable_rule(db, rule_id=rule_id, operator_id=admin_id) await log_operation( db, admin_id, admin_username, f"停用模型计价版本 {snapshot['model_name']}/{snapshot['version_code']}", "POST", f"/admin/model-pricing/rules/{rule_id}/disable", detail=json.dumps(snapshot, ensure_ascii=False, default=str), ) await db.commit() return {**snapshot, "referenced_count": 0} except IntegrityError as exc: await db.rollback() raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc except PricingRuleError as exc: await db.rollback() raise _http_error(exc) from exc @router.post("/preview", response_model=ModelPricingPreviewOut) async def admin_preview_model_pricing( req: ModelPricingPreviewRequest, admin: User = Depends(get_admin_user), ): try: result = calculate_pricing( billing_mode=req.billing_mode, calculator_version=req.calculator_version, rule_json=req.rule_json, usage=req.usage, currency=req.currency, ) return { "amount": str(result.amount), "currency": result.currency, "is_estimated": result.is_estimated, "selected_rate": str(result.selected_rate) if result.selected_rate is not None else None, "usage_source": result.usage_source, "breakdown": result.breakdown, } except PricingCalculationError as exc: raise _http_error(exc) from exc