feat(ad): 记录不发金币的原因(closed_early / too_short)
让广告收益报表能呈现「有展示、没发金币」的原因: - grant_feed_reward 改整场结算:aborted→closed_early、整场总时长<10s→too_short(均 coin=0) - 新增 record_reward_noshow + POST /api/v1/ad/reward-noshow(激励视频提前关留痕; 同 ad_session_id 已 granted 则跳过,防与 S2S 发奖重复) - feed-reward 加 aborted 入参 - 文档:feed-reward / 两张表 status 取值、新增 ad-reward-noshow、README 索引 - alembic: 合并 ad_revenue_report_cols 与 store_mapping_tb_dl_invalid 双 head(空迁移) 报表复算无需改(非 granted 状态走 else 分支 expected=0 自动归类)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+37
-5
@@ -121,17 +121,18 @@ class TestGrantOut(BaseModel):
|
||||
|
||||
|
||||
class FeedRewardIn(BaseModel):
|
||||
"""信息流广告完成后结算奖励。
|
||||
"""比价/领券一整场信息流(轮播多条)结束后结算奖励。
|
||||
|
||||
每展示满 10 秒累计一份奖励,视频完成后一次性入账。client_event_id 用于客户端超时重试幂等。
|
||||
规则:全程不关广告才发,金额按整场**总观看时长**折份(每 10 秒 1 份)。client_event_id 用于
|
||||
客户端超时重试幂等。中途被用户关闭时传 aborted=True,整场不发(只记 closed_early)。
|
||||
"""
|
||||
|
||||
client_event_id: str = Field(..., min_length=8, max_length=64, description="客户端生成的幂等事件 id")
|
||||
ad_session_id: str | None = Field(
|
||||
None, min_length=8, max_length=64, description="客户端生成的一次信息流广告会话 id"
|
||||
)
|
||||
ecpm: str = Field(..., description="本条信息流广告 eCPM,按分/千次展示处理(SDK getEcpm 原值,非元)")
|
||||
duration_seconds: int = Field(..., ge=0, description="本条广告实际展示/播放秒数")
|
||||
ecpm: str = Field(..., description="本场信息流 eCPM(代表值,按分/千次展示处理;SDK getEcpm 原值,非元)")
|
||||
duration_seconds: int = Field(..., ge=0, description="整场累计观看秒数(轮播各条相加)")
|
||||
adn: str | None = Field(None, description="实际投放 ADN")
|
||||
slot_id: str | None = Field(None, description="实际展示代码位")
|
||||
app_env: str | None = Field(
|
||||
@@ -140,11 +141,42 @@ class FeedRewardIn(BaseModel):
|
||||
our_code_id: str | None = Field(
|
||||
None, max_length=64, description="我们后台配置的代码位 ID(AdConfig 的 104xxx,非底层 rit)"
|
||||
)
|
||||
aborted: bool = Field(
|
||||
False, description="用户中途 ✕ 关闭广告(未走完比价):整场不发,记 closed_early"
|
||||
)
|
||||
|
||||
|
||||
class FeedRewardOut(BaseModel):
|
||||
granted: bool = Field(..., description="本次是否入账。达上限时 false")
|
||||
status: str = Field(..., description="granted / capped")
|
||||
status: str = Field(..., description="granted / capped / too_short / closed_early")
|
||||
coin: int = Field(..., description="本次发放金币")
|
||||
unit_count: int = Field(..., description="按 10 秒折算出的奖励份数")
|
||||
daily_limit: int = Field(..., description="每日信息流展示次数上限")
|
||||
|
||||
|
||||
class RewardNoShowIn(BaseModel):
|
||||
"""激励视频展示了但用户提前关闭/跳过、未触发发奖——上报留痕(不发金币)。
|
||||
|
||||
供广告收益报表把「有展示、没发金币」的原因也记录下来。user_id 由 JWT 取,不在 body。
|
||||
"""
|
||||
|
||||
ad_session_id: str = Field(
|
||||
..., min_length=8, max_length=64, description="本次展示会话 id(与 ecpm 上报、S2S extra 一致)"
|
||||
)
|
||||
watched_seconds: int = Field(0, ge=0, description="关闭前已观看秒数(仅留痕参考,不入库)")
|
||||
ecpm: str | None = Field(None, description="本次展示 eCPM(分/千次,SDK getEcpm 原值);可空")
|
||||
adn: str | None = Field(None, description="实际投放 ADN")
|
||||
slot_id: str | None = Field(None, description="实际展示代码位(底层 mediation rit)")
|
||||
app_env: str | None = Field(
|
||||
None, max_length=16, description="我们的穿山甲应用环境:prod / test"
|
||||
)
|
||||
our_code_id: str | None = Field(
|
||||
None, max_length=64, description="我们后台配置的代码位 ID(104xxx)"
|
||||
)
|
||||
|
||||
|
||||
class RewardNoShowOut(BaseModel):
|
||||
ok: bool = Field(..., description="是否处理成功(落库或幂等命中)")
|
||||
status: str = Field(
|
||||
..., description="closed_early(已留痕) / granted(该次其实已发奖,跳过未写)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user