e38120ad49
收益报表(/admin/api/ad-revenue-report): - 明细改按时间倒序;新增 offset 真分页(limit 作每页大小、total 为全量),可翻页看当前筛选下全部数据,突破原 1000 条上限。 - 「场景」(feed_scene)下推后端做全局筛选,同时作用于明细/合计/daily·hourly 趋势(原为前端仅过滤明细)。 - 新增全量 hourly 序列,按小时趋势改用它,不再受分页截断影响。 修复信息流金币审计复算口径漂移(ad_audit): - 发奖侧 grant_feed_reward 早已是「一条广告=1份、LT 按账号累计条数(COUNT)」,但审计仍按 unit_count 逐份累加 + SUM(unit_count) 做 LT 基线,导致单条停留>20s(份数>1)时应发虚高、必然「✗ 不符」。 - 审计改为每条 granted 按 1 份复算、LT 基线用 COUNT,与发奖对齐;金币审计页与收益报表(复用同一复算)一并恢复正确。纯复算口径修正,不改实际发奖、不动钱。 文档同步更新 admin-ad-revenue-report.md。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #84 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
9.1 KiB
9.1 KiB
Admin 广告收益报表
所属:Admin 组(前缀
/admin/api/ad-revenue-report) | 鉴权:Admin Bearer(任意已登录 admin,只读) | ← 返回 API 索引
按 用户 × 日期 × 广告类型 × 我们的应用 × 我们的代码位 聚合,回答「每个用户某天、每类广告(激励视频 / 信息流 / 历史 Draw)分别看了多少条、收益多少、按现算法发了多少金币、广告来自哪个应用的哪个代码位」。纯只读,不发币、不改数据,也不改发奖逻辑。
相关表:ad_ecpm_record、ad_reward_record、ad_feed_reward_record。
数据来源(三流合并,聚合键 = user × ad_type × app_env × our_code_id)
| 指标 | 来源表 | 口径 |
|---|---|---|
展示条数 impressions |
ad_ecpm_record |
每行 = 客户端一次广告展示。激励视频每次展示上报一条;信息流轮播每条展示各上报一条(每条独立 ad_session_id) |
收益 revenue_yuan |
ad_ecpm_record |
Σ(eCPM元 ÷ 1000),即每条展示预估收益累加(eCPM 原值是分,÷100 转元;÷1000 是每千次→单次)。预估口径,非结算;测试应用多为 0 |
应发/实发金币 expected_coin/actual_coin |
ad_reward_record(reward_video)+ ad_feed_reward_record(feed) |
复用金币审计逐条复算(ad_audit.audit_rows,与正式发奖同一公式口径,不另写公式),按同维度求和;matched = 应发==实发。只读复算,不改发奖 |
来源应用/代码位 app_env/our_code_id |
上述各表回填 | prod(傻瓜比价)/test(测试);代码位是我们后台配的 104xxx,非底层 rit |
底层渠道 adns |
ad_ecpm_record |
实际填充的 ADN 子渠道集合(pangle/gdt/...),附加参考 |
展示与金币来自不同表,做并集:有展示无金币(用户中途关、未达发奖)、有金币无展示(未上报 eCPM)各自成行。
GET /admin/api/ad-revenue-report — 聚合报表
-
入参(均 query,可选):
参数 类型 默认 说明 date_fromstring 今天 起始日 北京时间 YYYY-MM-DDdate_tostring = date_from结束日 北京时间 YYYY-MM-DD,闭区间;单日时与date_from相同user_idint 全部 只看某用户;不传=所有用户 ad_typestring 全部 reward_video/feed/draw;不传=全部类型feed_scenestring 全部 comparison(比价)/coupon(领券)/welfare(福利);全局筛选,同时作用于明细 / 合计 /daily·hourly趋势;不传=全部场景granularitystring dayday=按天 /hour=按小时(聚合键再加北京时间小时 0–23);区间>1 天建议用 daylimitint(1~1000) 500 每页条数(分页大小); total/total_*/daily/hourly按全量统计不受分页影响offsetint(≥0) 0 分页偏移(已跳过条数)=(页码−1)× limitsortstring time明细排序: time=按时间倒序(新→旧) /ecpm=按 eCPM 数值倒序约束:
date_to不早于date_from、区间最长 92 天、日期须YYYY-MM-DD,否则422。 -
出参
200:AdRevenueReportOut字段 类型 说明 date_from/date_tostring 报表起止日期(闭区间) dailyAdRevenueDaily[]按日期汇总序列(全量,供按天趋势图;不受分页影响) hourlyAdRevenueHourly[]按小时汇总序列(全量,供按小时趋势图;仅 granularity=hour时非空;不受分页影响)type_stats{[ad_type]: AdRevenueTypeStat}按广告类型( ad_type)小计(全量);前端取draw/reward_video做分类大盘dauint | null 今日活跃用户数(复用大盘口径 last_login_at,今日登录过);仅查询=今日单天时有值,历史/多天为nulltotalint 当前筛选下的分页总条数(全量,不受分页影响;= 前端分页器 total) truncatedbool 当前页之后是否还有更多事件( len(events) > offset + limit)total_impressionsint 全量展示条数合计 total_revenue_yuanfloat 全量收益合计(元) total_expected_coinint 全量应发金币合计 total_actual_coinint 全量实发金币合计 mismatch_countint 应发≠实发的组数(=0 说明全部按公式发放) itemsAdRevenueRow[]逐条广告事件(按时间倒序:新→旧); limit/offset对全量做分页切片,返回当前页
AdRevenueDaily(daily[] — 按天趋势)
| 字段 | 类型 | 说明 |
|---|---|---|
date |
string | 北京时间 YYYY-MM-DD |
impressions |
int | 当天展示条数合计 |
revenue_yuan |
float | 当天预估收益合计(元) |
expected_coin |
int | 当天应发金币合计 |
actual_coin |
int | 当天实发金币合计 |
AdRevenueHourly(hourly[] — 按小时趋势,仅 granularity=hour 时非空)
| 字段 | 类型 | 说明 |
|---|---|---|
hour |
int | 北京时间小时 0–23 |
impressions |
int | 该小时展示条数合计 |
revenue_yuan |
float | 该小时预估收益合计(元) |
expected_coin |
int | 该小时应发金币合计 |
actual_coin |
int | 该小时实发金币合计 |
AdRevenueTypeStat(type_stats[ad_type] — 分广告类型小计,供大盘第二行)
| 字段 | 类型 | 说明 |
|---|---|---|
impressions |
int | 该类型展示条数合计 |
revenue_yuan |
float | 该类型预估收益合计(元);eCPM 由前端用 收益÷展示×1000 算 |
AdRevenueRow(items[])
| 字段 | 类型 | 说明 |
|---|---|---|
report_date |
string | 该组所属日期 北京时间 YYYY-MM-DD |
user_id |
int | |
ad_type |
string | reward_video / feed / draw |
app_env |
string | null | 我们的应用:prod(傻瓜比价)/test(测试);旧数据为空 |
our_code_id |
string | null | 我们配置的代码位 104xxx;旧数据为空 |
hour |
int | null | 北京时间小时 0–23(granularity=hour 时有值;按天为 null) |
impressions |
int | 展示条数 |
revenue_yuan |
float | 收益(元),预估口径 |
expected_coin |
int | 应发金币(公式复算,与金币审计同源) |
actual_coin |
int | 实发金币(实际入账) |
matched |
bool | 该组应发==实发(组内任一条不符则 false) |
adns |
string[] | 底层填充 ADN 子渠道集合 |
impression_records |
AdRevenueImpression[] |
该组逐条展示明细(前端展开下钻);只要有展示就非空 |
records |
AdRevenueRecord[] |
该组逐条发奖复算明细(前端展开下钻);纯展示无发奖的组为空 |
AdRevenueImpression(items[].impression_records[] — 展开「展示明细」)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int | ad_ecpm_record 主键 |
created_at |
datetime | |
ecpm |
string | 本次展示 eCPM 原始值(分/千次展示) |
revenue_yuan |
float | 本次展示预估收益(元)= eCPM元 ÷ 1000 |
adn |
string | null | 实际填充 ADN 子渠道 |
slot_id |
string | null | 底层 mediation rit(非我们配置的广告位 ID) |
AdRevenueRecord(items[].records[] — 展开「发奖明细」)
还原金币审计的逐条列,与发奖同一复算口径。
| 字段 | 类型 | 说明 |
|---|---|---|
record_id |
int | 发奖记录主键 |
created_at |
datetime | |
status |
string | granted / capped / ecpm_missing |
ecpm |
string | null | 本次采用的 eCPM 原始值 |
ecpm_factor |
float | null | 因子1(eCPM 档);非 granted 为空 |
units |
int | 折算份数:激励视频恒 1;信息流 = 满 10 秒份数 |
lt_index_start / lt_index_end |
int | null | 占用「账号累计第几份」的起止 |
lt_factor_start / lt_factor_end |
float | null | 因子2(LT)起止值 |
expected_coin |
int | 应发金币 |
actual_coin |
int | 实发金币 |
matched |
bool | 该条复算与实发是否一致 |
说明与局限
- 展示 vs 发奖分离:信息流轮播一会话可展示多条(都计入
impressions),但发奖仍按现规则(一会话发一次),coin不因展示条数变化——这是有意设计(用户中途关只记展示不发奖)。 - 历史 Draw 不可拆:迁移(Draw→普通信息流)前,Draw 发奖混在
ad_feed_reward_record且无类型标记,金币侧统一记feed;迁移后 Draw 不再产生新数据。展示侧ad_type由客户端上报区分,故draw桶基本为空。 - 来源字段从上线起齐全:
app_env/our_code_id是本期新增列,历史记录为 NULL(报表来源列留空)。 - 收益是预估:基于客户端上报的 eCPM,非穿山甲后台结算值;以后台报表为结算权威。
- 对账聚合级 + 逐条下钻:行级
matched给出该组(用户×类型×应用×代码位)应发是否==实发;展开records即可看该组逐条明细(eCPM/因子1/份数/LT/因子2/应发/实发/一致)定位到具体记录。独立逐条审计接口 admin-ad-coin-audit 仍保留(同一复算口径,可全局按场景/只看不符筛选)。