"""广告展示 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"" )