b622f76a02
- 新增穿山甲 GroMore「聚合数据报告 API」对接,按天 T+1 拉取后台 revenue(预估)与 api_revenue(收益API,更接近结算),在广告收益报表大盘 + 按天趋势级展示,与客户端自报 eCPM 折算的预估并列对照;逐条广告事件行不动(仍是客户端预估)。 - 链路:integrations/pangle_report.py(MD5 签名,与官方文档两个测试向量逐字节一致)→ scripts/sync_pangle_revenue.py(拉昨天/--days 回补)→ 新表 ad_pangle_daily_revenue (repositories/ad_pangle_revenue.py:upsert + 按日聚合)→ admin/repositories/ad_revenue.py 汇总,新增 total_pangle_revenue_yuan / total_pangle_api_revenue_yuan / daily[].pangle_*。 - 穿山甲无用户/类型/场景维度:仅全量视图(未按 user/ad_type/feed_scene 过滤)给值,否则置 None;join key 用 ad_unit_id(=客户端配的 104xxx),非 code_id。 - 新增配置 PANGLE_REPORT_USER_ID/ROLE_ID/SECURITY_KEY(≠发奖 m-key)+ site_id→应用映射; 含单测(签名向量+分页解析);已真连穿山甲验证。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #92 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
74 lines
3.9 KiB
Python
74 lines
3.9 KiB
Python
"""穿山甲 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"<AdPangleDailyRevenue {self.report_date} {self.app_env} "
|
|
f"code={self.our_code_id} revenue={self.revenue_yuan}>"
|
|
)
|