239 lines
8.2 KiB
Python
239 lines
8.2 KiB
Python
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
|