"""积分 & 会员相关 Pydantic Schema (#1895)""" from __future__ import annotations from datetime import datetime from typing import Any, Optional from pydantic import BaseModel, Field # ============ 余额 & 账户 ============ class PointsBalanceResponse(BaseModel): """积分余额 + 会员状态""" balance: int = Field(..., description="当前积分余额") total_earned: int = Field(..., description="累计获得积分") total_spent: int = Field(..., description="累计消耗积分") is_member: bool = Field(default=False, description="是否付费会员") member_type: Optional[str] = Field(None, description="会员类型: monthly/quarterly/yearly") member_expires_at: Optional[datetime] = Field(None, description="会员到期时间") # ============ 流水 ============ class PointsTransactionItem(BaseModel): """单条积分流水""" id: str type: str = Field(..., description="类型: add/deduct") source: str = Field(..., description="来源场景") amount: int balance_after: int description: str = "" ref_id: str = "" created_at: Optional[str] = None class PointsTransactionsResponse(BaseModel): """积分流水分页响应""" items: list[PointsTransactionItem] total: int page: int page_size: int # ============ 规则 & 积分包 ============ class PointRuleItem(BaseModel): """单条积分规则""" scene_key: str name: str base_points: int unit: str extra_per_30s: Optional[int] = None class PointsRulesResponse(BaseModel): """所有积分消耗规则""" rules: list[PointRuleItem] free_user_multiplier: float = Field(..., description="免费用户积分上浮系数") class PointsPackageItem(BaseModel): """积分包信息""" code: str name: str points: int price_cents: int unit_price: str = Field("", description="单价描述,如 ¥0.099/积分") class PointsPackagesResponse(BaseModel): """可购买的积分包列表""" packages: list[PointsPackageItem] user_discount: Optional[float] = Field(None, description="当前用户折扣(会员)") # ============ 消费前检查 ============ class PointsCheckRequest(BaseModel): """消费前余额检查请求""" scene_key: str duration_minutes: Optional[float] = None quantity: Optional[int] = 1 class PointsCheckResponse(BaseModel): """消费前余额检查响应""" allowed: bool required_points: int current_balance: int remaining_after: int is_free_quota: bool = False # ============ 手动扣减 / 退还(内部接口) ============ class PointsDeductRequest(BaseModel): """积分扣减请求""" scene_key: str amount: int description: Optional[str] = "" ref_id: Optional[str] = "" class PointsRefundRequest(BaseModel): """积分退还请求""" transaction_id: str reason: Optional[str] = "" class PointsRechargeRequest(BaseModel): """积分充值请求""" package_id: str = Field(..., description="积分包 code,如 starter_pack") # ============ 订单 ============ class PointsOrderResponse(BaseModel): """订单信息""" id: str order_type: str product_code: str amount_cents: int status: str created_at: Optional[str] = None # ============ 每日额度 ============ class DailyUsageResponse(BaseModel): """今日免费额度使用情况""" free_clips_used: int free_clips_limit: int free_clips_remaining: int reset_at: str # ============ 会员状态(聚合) ============ class MembershipStatusResponse(BaseModel): """当前用户会员状态(聚合信息)""" is_member: bool member_type: Optional[str] = None member_expires_at: Optional[datetime] = None points_balance: int max_resolution: str = Field( default="1080p", description="可用最高分辨率: 720p(free) / 1080p(paid)", ) # ============ 通用响应 ============ class SimpleMessageResponse(BaseModel): """简单消息响应""" success: bool message: str data: Optional[dict[str, Any]] = None