# -*- coding: utf-8 -*- """compute 引擎 → server-core 内部回调接口。 compute 扣费完成后异步回调此接口,按引擎实际费用(微元)扣平台来源账本。 鉴权:Authorization: Bearer (与引擎侧 PINEAGENTS_INTERNAL_TOKEN 一致)。 幂等:engine_log_id 去重,防网络重试重复扣费。 """ from __future__ import annotations import logging from fastapi import APIRouter, Depends, Header, HTTPException from pydantic import BaseModel from ... import config from ...infrastructure.repositories import Database from ..dependencies import get_db from ...services.compute_pricing_service import deduct_by_engine_cost logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/compute/internal", tags=["compute-internal"]) class EngineDeductRequest(BaseModel): username: str actual_cost_micro: int engine_log_id: int model_name: str = "" token_count: int = 0 cost_micro: int = 0 # 采购成本(微元),补贴体系数据底座 model_cost_ratio: float = 1.0 # 模型成本比例快照 subsidy_eligible: int = 1 # 是否参与补贴 def _verify_internal_token(authorization: str = Header(default="")) -> None: """校验 compute 引擎内部回调令牌。""" token = authorization[7:].strip() if authorization.lower().startswith("bearer ") else authorization.strip() expected = config.COMPUTE_ADMIN_TOKEN or "" if not expected or token != expected: raise HTTPException(status_code=401, detail="invalid internal token") class SubsidyGrantRequest(BaseModel): username: str amount_micro: int period: str = "" record_id: int = 0 @router.post("/subsidy-grant", summary="compute 补贴发放(增加用户算力余额)") async def subsidy_grant( req: SubsidyGrantRequest, db: Database = Depends(get_db), _auth: None = Depends(_verify_internal_token), ): """补贴发放:amount_micro(微元)→ 增加用户个人算力余额 + 记录审计。""" if req.amount_micro <= 0: return {"ok": True, "amount": 0, "reason": "zero_amount"} amount_fen = max(0, int(req.amount_micro) // 10000) # 微元→分 if amount_fen <= 0: return {"ok": True, "amount": 0, "reason": "zero_fen"} user = await db.users.get_by_username(req.username) if not user: return {"ok": False, "amount": 0, "reason": "user_not_found"} user_id = user["id"] # 增加个人算力余额 from ...services.compute_pricing_service import adjust_user_quota await adjust_user_quota(db, user_id, amount_fen, reason=f"subsidy_grant period={req.period} record_id={req.record_id}") logger.info("[compute-internal] subsidy granted username=%s amount_fen=%s period=%s record_id=%s", req.username, amount_fen, req.period, req.record_id) return {"ok": True, "amount": amount_fen, "username": req.username, "period": req.period} @router.post("/deduct", summary="compute 引擎扣费回调(按实际费用扣平台账本)") async def engine_deduct( req: EngineDeductRequest, db: Database = Depends(get_db), _auth: None = Depends(_verify_internal_token), ): """compute 扣费后回调:actual_cost_micro(微元)→ 扣企业分配/个人余额,engine_log_id 幂等。""" try: result = await deduct_by_engine_cost( db, username=req.username, actual_cost_micro=req.actual_cost_micro, engine_log_id=req.engine_log_id, model_name=req.model_name, token_count=req.token_count, cost_micro=req.cost_micro, model_cost_ratio=req.model_cost_ratio, subsidy_eligible=req.subsidy_eligible, ) return {"ok": result.get("ok", True), **result} except Exception as exc: # noqa: BLE001 logger.warning("[compute-internal] deduct callback failed username=%s engine_log_id=%s err=%s", req.username, req.engine_log_id, exc) raise HTTPException(status_code=500, detail=str(exc)) from exc