0bddce516f
- eCPM 口径修正:getEcpm 原值是分/千次,新增 parse_ecpm_fen;parse_ecpm_yuan 改为 ÷100 转元,因子1 阈值按元判档(100/200/400),收益换算用元 - test-grant 用客户端按 ad_session_id 上报的真实 eCPM 发奖(取不到/≤0 兜底 200) - 新增 GET /admin/api/ad-coin-audit 只读复算对账(router/repo/schema),复用发奖公式 - 同步更新 docs/api、docs/database 的分/元口径 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #32 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
58 lines
3.0 KiB
Python
58 lines
3.0 KiB
Python
"""看广告金币审计 schemas。
|
||
|
||
只读对账视图:把"看视频"(ad_reward_record)和"信息流"(ad_feed_reward_record)两类发奖记录,
|
||
用与正式发奖相同的公式 [app.core.rewards.calculate_ad_reward_coin] 复算一遍 expected_coin,
|
||
和实际入账的 actual_coin 对比,核对金币公式是否生效。字段 snake_case、金额按金币整数。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class AdCoinAuditRow(BaseModel):
|
||
"""单条发奖记录的复算明细。"""
|
||
|
||
scene: str = Field(..., description="reward_video(看视频) / feed(信息流)")
|
||
record_id: int = Field(..., description="对应记录表主键")
|
||
user_id: int
|
||
created_at: datetime
|
||
status: str = Field(..., description="granted / capped / ecpm_missing")
|
||
ecpm: str | None = Field(None, description="本次采用的 eCPM 原始值(分/千次展示,SDK getEcpm 原值)")
|
||
ecpm_factor: float | None = Field(None, description="因子1(eCPM 档)")
|
||
units: int = Field(..., description="折算份数:看视频恒为 1;信息流 = 满 10 秒的份数")
|
||
lt_index_start: int | None = Field(None, description="本条占用的当日第几份(起)")
|
||
lt_index_end: int | None = Field(None, description="本条占用的当日第几份(止);看视频 = 起")
|
||
lt_factor_start: float | None = Field(None, description="因子2(LT)起值")
|
||
lt_factor_end: float | None = Field(None, description="因子2(LT)止值;看视频 = 起值")
|
||
expected_coin: int = Field(..., description="按公式复算应发金币")
|
||
actual_coin: int = Field(..., description="实际入账金币")
|
||
matched: bool = Field(..., description="复算与实发是否一致(capped/ecpm_missing 校验是否确为 0)")
|
||
|
||
|
||
class AdCoinFormulaOut(BaseModel):
|
||
"""当前金币公式参数(给前端展示规则参照)。"""
|
||
|
||
description: str = Field(
|
||
"eCPM元 = getEcpm分 ÷ 100;单份金币 = round(eCPM元 ÷ 1000 × 因子1 × 因子2 × coin_per_yuan);"
|
||
"因子1 按 eCPM元 判档(阈值 100/200/400 元)",
|
||
description="公式说明",
|
||
)
|
||
coin_per_yuan: int = Field(..., description="金币:元 汇率")
|
||
ecpm_unit: str = Field("分/千次展示(SDK getEcpm 原值)", description="eCPM 口径")
|
||
feed_unit_seconds: int = Field(..., description="信息流每多少秒折 1 份")
|
||
# [(因子值, 区间下限, 区间上限或 null)]
|
||
ecpm_factor_tiers: list[tuple[float, int, int | None]] = Field(..., description="因子1 档位表")
|
||
lt_factor_tiers: list[tuple[float, int, int | None]] = Field(..., description="因子2 LT 档位表")
|
||
|
||
|
||
class AdCoinAuditOut(BaseModel):
|
||
"""审计响应:公式参照 + 命中条数 + 明细。"""
|
||
|
||
date: str = Field(..., description="审计日期(北京时间 YYYY-MM-DD)")
|
||
formula: AdCoinFormulaOut
|
||
total: int = Field(..., description="返回的明细条数")
|
||
mismatch_count: int = Field(..., description="其中 matched=false 的条数(=0 说明公式全部生效)")
|
||
items: list[AdCoinAuditRow]
|