"""穿山甲 GroMore 天级收益报表(后台结算口径,定时拉取入库)。 每行 = GroMore 数据 API 返回的一条「日期 × 应用 × 代码位」聚合收益(`integrations/pangle_report` + `scripts/sync_pangle_revenue` 落库)。**权威/预估收益的来源**,与 `ad_ecpm_record`(客户端自报 eCPM 折算的预估)互为对照: - `revenue_yuan` ← 接口 `revenue`(预估收益,元;排序价×展示/1000,所有 ADN 都有); - `api_revenue_yuan` ← 接口 `api_revenue`(收益Api,元;各 ADN 经 Reporting 回传、更接近结算; 未配置该 ADN 的 Reporting 或查当天时为空)。 ⚠️ 穿山甲不提供分用户/设备维度,故本表最细只到 日期×应用×代码位,**无法挂到逐条广告事件**; 广告收益报表里只用于汇总/趋势级的「穿山甲后台收益」,不改逐条行的客户端预估。 """ from __future__ import annotations from datetime import datetime from sqlalchemy import ( DateTime, Float, Integer, String, UniqueConstraint, func, ) from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base class AdPangleDailyRevenue(Base): __tablename__ = "ad_pangle_daily_revenue" __table_args__ = ( # 一行 = (日期, 应用, 代码位, 广告源);adn 用 "" 表示「未分广告源、该代码位汇总」, # 避免 NULL 在唯一约束里被视为各不相同导致 upsert 重复(SQLite/PG 行为一致)。 UniqueConstraint( "report_date", "app_env", "our_code_id", "adn", name="uq_ad_pangle_daily", ), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # 北京时间日期串 'YYYY-MM-DD'(拉取时 time_zone=8),与 ad_ecpm_record.report_date 同口径,可直接 join。 report_date: Mapped[str] = mapped_column(String(10), index=True, nullable=False) # 我们的应用环境:prod(傻瓜比价正式)/ test(测试);由 site_id 经 settings 映射而来。 app_env: Mapped[str] = mapped_column(String(16), nullable=False) # 原始 GroMore AppId(site_id),留痕便于排查映射。 site_id: Mapped[str | None] = mapped_column(String(32), nullable=True) # 广告位 ID(= 接口 ad_unit_id = 我们客户端配的 104xxx = ad_ecpm_record.our_code_id),join key。 # ⚠️ 不是接口的 code_id —— 那是底层各 ADN 的代码位(如 983674557),对不上我们的口径。 our_code_id: Mapped[str] = mapped_column(String(64), index=True, nullable=False) # 广告源(接口 network 数字→名,如 pangle/gdt);"" = 未分广告源的代码位汇总行(当前默认口径)。 adn: Mapped[str] = mapped_column(String(16), nullable=False, default="") # 预估收益(元)← 接口 revenue。 revenue_yuan: Mapped[float] = mapped_column(Float, nullable=False, default=0.0) # 收益Api(元)← 接口 api_revenue;未配 Reporting / 当天 等情况接口不返回 → NULL。 api_revenue_yuan: Mapped[float | None] = mapped_column(Float, nullable=True) # 预估 eCPM 原值(接口 ecpm,单位元/千次,**与客户端 getEcpm 的「分」不同**),参考用原样存。 ecpm: Mapped[str | None] = mapped_column(String(32), nullable=True) # 展示次数 ← 接口 imp_cnt。 impressions: Mapped[int] = mapped_column(Integer, nullable=False, default=0) # 货币类型(接口 currency,正常为 cny)。 currency: Mapped[str] = mapped_column(String(8), nullable=False, default="cny") # 最近一次同步写入时间(同一行可被多次回补覆盖;T+1 数据穿山甲会订正)。 synced_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False ) def __repr__(self) -> str: # pragma: no cover return ( f"" )