f7d86011c1
- 新增 GET /admin/api/ad-revenue-report:展示条数/收益 + 复用金币审计逐条复算做发奖对账 - ad_ecpm/ad_reward/ad_feed_reward 各加 app_env + our_code_id 两列(alembic 迁移) - ecpm-report / feed-reward 接收并落库 app_env/our_code_id;激励发奖按 ad_session_id 回填 - ad_audit 抽出 audit_rows,报表与逐条审计复用同一复算口径 - 组级 matched 改「组内逐条全一致」,避免应发和==实发和的互相抵消掩盖错误 - list_feedbacks 改 offset 分页并返回 total(配合 admin 页码分页) - 反馈正文上限 _CONTENT_MAX 2000→200 - 文档:新增 admin-ad-revenue-report,更新 ecpm/feed-reward/feedback 及对应 db docs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #54 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
91 lines
5.5 KiB
Python
91 lines
5.5 KiB
Python
"""广告收益报表 schemas。
|
||
|
||
按 用户 / 日期 / 广告类型 / 应用 / 代码位 聚合的只读报表:展示条数、收益(元)、金币、来源。
|
||
字段 snake_case;收益按元(float),金币按整数。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class AdRevenueImpression(BaseModel):
|
||
"""聚合行下钻的单条**展示**明细(每次广告展示一条,展开该组时展示)。"""
|
||
|
||
id: int = Field(..., description="ad_ecpm_record 主键")
|
||
created_at: datetime
|
||
ecpm: str = Field(..., description="本次展示 eCPM 原始值(分/千次展示)")
|
||
revenue_yuan: float = Field(..., description="本次展示预估收益(元)= eCPM元 ÷ 1000")
|
||
adn: str | None = Field(None, description="实际填充 ADN 子渠道(pangle/gdt…)")
|
||
slot_id: str | None = Field(None, description="底层 mediation rit(非我们配置的广告位 ID)")
|
||
|
||
|
||
class AdRevenueRecord(BaseModel):
|
||
"""聚合行下钻的单条发奖复算明细(与金币审计同源,展开该组时展示)。"""
|
||
|
||
record_id: int
|
||
created_at: datetime
|
||
status: str = Field(..., description="granted / capped / ecpm_missing")
|
||
ecpm: str | None = Field(None, description="本次采用的 eCPM 原始值(分/千次展示)")
|
||
ecpm_factor: float | None = Field(None, description="因子1(eCPM 档);非 granted 为空")
|
||
units: int = Field(..., description="折算份数:激励视频恒 1;信息流 = 满 10 秒份数")
|
||
lt_index_start: int | None = Field(None, description="本条占用「账号累计第几份」的起")
|
||
lt_index_end: int | None = Field(None, description="本条占用「账号累计第几份」的止;激励视频 = 起")
|
||
lt_factor_start: float | None = Field(None, description="因子2(LT)起值")
|
||
lt_factor_end: float | None = Field(None, description="因子2(LT)止值;激励视频 = 起")
|
||
expected_coin: int = Field(..., description="按公式复算应发金币")
|
||
actual_coin: int = Field(..., description="实际入账金币")
|
||
matched: bool = Field(..., description="复算与实发是否一致")
|
||
|
||
|
||
class AdRevenueDaily(BaseModel):
|
||
"""按日期汇总的一天(供前端按天趋势图;全量,不受 limit 影响)。"""
|
||
|
||
date: str = Field(..., description="北京时间 YYYY-MM-DD")
|
||
impressions: int = Field(..., description="当天展示条数合计")
|
||
revenue_yuan: float = Field(..., description="当天预估收益合计(元)")
|
||
expected_coin: int = Field(..., description="当天应发金币合计")
|
||
actual_coin: int = Field(..., description="当天实发金币合计")
|
||
|
||
|
||
class AdRevenueRow(BaseModel):
|
||
"""一个聚合组(report_date × user × ad_type × app_env × our_code_id)的汇总。"""
|
||
|
||
report_date: str = Field(..., description="该组所属日期(北京时间 YYYY-MM-DD)")
|
||
user_id: int
|
||
ad_type: str = Field(..., description="reward_video(激励视频) / feed(信息流) / draw(历史 Draw 信息流)")
|
||
app_env: str | None = Field(None, description="我们的应用:prod(傻瓜比价正式) / test(测试应用);旧数据为空")
|
||
our_code_id: str | None = Field(None, description="我们后台配置的代码位 ID(104xxx);旧数据为空")
|
||
hour: int | None = Field(None, description="北京时间小时 0–23(granularity=hour 时有值;按天为 null)")
|
||
impressions: int = Field(..., description="展示条数(每条广告展示一条;轮播每条各计一次)")
|
||
revenue_yuan: float = Field(..., description="收益(元)= Σ(eCPM元 ÷ 1000);测试应用多为 0")
|
||
expected_coin: int = Field(..., description="应发金币(按公式复算,与金币审计同源)")
|
||
actual_coin: int = Field(..., description="实发金币(实际入账,按现发奖算法)")
|
||
matched: bool = Field(..., description="该组应发==实发(组内任一条不符则 false)")
|
||
adns: list[str] = Field(default_factory=list, description="实际填充的底层 ADN 子渠道集合(如 pangle/gdt)")
|
||
impression_records: list[AdRevenueImpression] = Field(
|
||
default_factory=list,
|
||
description="该组逐条展示明细(时间/eCPM/收益/adn);展开下钻用,无发奖也有(只要有展示)",
|
||
)
|
||
records: list[AdRevenueRecord] = Field(
|
||
default_factory=list,
|
||
description="该组逐条发奖复算明细(eCPM/因子1/份数/LT/因子2/应发/实发/一致);展开下钻用,纯展示无发奖记录的组为空",
|
||
)
|
||
|
||
|
||
class AdRevenueReportOut(BaseModel):
|
||
"""报表响应:全量统计 + 按天趋势 + 聚合明细。"""
|
||
|
||
date_from: str = Field(..., description="报表起始日期(北京时间 YYYY-MM-DD)")
|
||
date_to: str = Field(..., description="报表结束日期(北京时间 YYYY-MM-DD,闭区间;单日时与 date_from 相同)")
|
||
daily: list[AdRevenueDaily] = Field(..., description="按日期汇总序列(全量,供按天趋势图)")
|
||
total: int = Field(..., description="聚合组总数(全量,不受 limit 影响)")
|
||
truncated: bool = Field(..., description="明细是否被 limit 截断")
|
||
total_impressions: int = Field(..., description="全量展示条数合计")
|
||
total_revenue_yuan: float = Field(..., description="全量收益合计(元)")
|
||
total_expected_coin: int = Field(..., description="全量应发金币合计")
|
||
total_actual_coin: int = Field(..., description="全量实发金币合计")
|
||
mismatch_count: int = Field(..., description="应发≠实发的组数(=0 说明全部按公式发放)")
|
||
items: list[AdRevenueRow] = Field(..., description="聚合明细(按 用户→类型→代码位 排序)")
|