eb5f0dc460
- 新增 POST /api/v1/ad/ecpm-report:激励视频展示后客户端上报本次 eCPM,落 ad_ecpm_record 做内部收益统计(model/repository/schema/alembic 迁移 + 接口文档) - 看广告冷却策略抽到 app/core/ad_cooldown.py 纯函数;ad_reward.today_status 只取数据,换策略只改这一处 - savings.dishes 改 JSON().with_variant(JSONB,"postgresql"):SQLite 无 visit_JSONB 会让 create_all 编译崩,修复后测试套件恢复 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
2.4 KiB
Python
50 lines
2.4 KiB
Python
"""广告展示 eCPM 上报记录(内部收益统计/对账)。
|
|
|
|
每条 = 客户端一次广告展示(`onAdShow`)后读到的 eCPM 信息。和发奖记录
|
|
[ad_reward.AdRewardRecord] 是**两条独立的数据流**:
|
|
- 发奖走穿山甲 S2S 回调(后端 → 有 trans_id、无 ecpm);
|
|
- eCPM 走客户端上报(客户端 → 有 ecpm、无 trans_id)。
|
|
两者没有公共键,无法逐条一一对应,所以本表用于**按用户/按天聚合收益**口径的对账,
|
|
不做"这条发奖 = 这条 ecpm"的精确关联。穿山甲后台报表才是结算权威,本表是细粒度补充。
|
|
|
|
⚠️ `ecpm_raw` 原样存客户端上报的字符串——eCPM 单位(分 / 元)截至 2026-05-31 尚未最终确认,
|
|
确认后再加一列解析好的数值;在此之前对账按"待定单位"处理。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class AdEcpmRecord(Base):
|
|
__tablename__ = "ad_ecpm_record"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
user_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("user.id"), index=True, nullable=False
|
|
)
|
|
# 广告类型:reward_video(激励视频) / draw(Draw 信息流) 等;不强行统一代码位,各类型各自上报
|
|
ad_type: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
# 实际投放的 ADN(穿山甲 getShowEcpm().getSdkName(),如 pangle / gdt)
|
|
adn: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
# 实际展示用的代码位(底层 mediation rit,非客户端配置位)
|
|
slot_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
# 客户端上报的 eCPM 原始字符串(单位待确认,原样存)
|
|
ecpm_raw: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
# 北京时间日期串 'YYYY-MM-DD',按它等值做"按天聚合"(不在 SQL 里做跨时区 date 比较)
|
|
report_date: Mapped[str] = mapped_column(String(10), index=True, nullable=False)
|
|
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
|
|
)
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return (
|
|
f"<AdEcpmRecord user_id={self.user_id} {self.ad_type} "
|
|
f"ecpm={self.ecpm_raw} adn={self.adn}>"
|
|
)
|