# Admin 广告收益报表 > 所属:Admin 组(前缀 `/admin/api/ad-revenue-report`) | 鉴权:Admin Bearer(任意已登录 admin,只读) | [← 返回 API 索引](./README.md) 按 **用户 × 日期 × 广告类型 × 我们的应用 × 我们的代码位** 聚合,回答「每个用户某天、每类广告(激励视频 / 信息流 / 历史 Draw)分别**看了多少条**、**收益多少**、按现算法**发了多少金币**、广告来自**哪个应用的哪个代码位**」。**纯只读**,不发币、不改数据,也**不改发奖逻辑**。 相关表:[ad_ecpm_record](../database/ad_ecpm_record.md)、[ad_reward_record](../database/ad_reward_record.md)、[ad_feed_reward_record](../database/ad_feed_reward_record.md)。 ## 数据来源(三流合并,聚合键 = 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_from` | string | 今天 | 起始日 北京时间 `YYYY-MM-DD` | | `date_to` | string | =`date_from` | 结束日 北京时间 `YYYY-MM-DD`,**闭区间**;单日时与 `date_from` 相同 | | `user_id` | int | 全部 | 只看某用户;不传=所有用户 | | `ad_type` | string | 全部 | `reward_video` / `feed` / `draw`;不传=全部类型 | | `granularity` | string | `day` | `day`=按天 / `hour`=按小时(聚合键再加北京时间小时 0–23);**区间>1 天建议用 day** | | `limit` | int(1~1000) | 500 | **展示**明细组数(截断;`total`/`total_*`/`daily` 按全量统计不受影响) | 约束:`date_to` 不早于 `date_from`、区间最长 **92 天**、日期须 `YYYY-MM-DD`,否则 `422`。 - 出参 `200`:`AdRevenueReportOut` | 字段 | 类型 | 说明 | |---|---|---| | `date_from` / `date_to` | string | 报表起止日期(闭区间) | | `daily` | `AdRevenueDaily[]` | 按日期汇总序列(全量,供按天趋势图;不受 `limit` 影响) | | `total` | int | 聚合组**总数**(全量,不受 `limit` 影响) | | `truncated` | bool | 明细是否被 `limit` 截断 | | `total_impressions` | int | 全量展示条数合计 | | `total_revenue_yuan` | float | 全量收益合计(元) | | `total_expected_coin` | int | 全量应发金币合计 | | `total_actual_coin` | int | 全量实发金币合计 | | `mismatch_count` | int | 应发≠实发的组数(=0 说明全部按公式发放) | | `items` | `AdRevenueRow[]` | 聚合明细(按 日期→用户→类型→代码位 排序) | ### AdRevenueDaily(`daily[]` — 按天趋势) | 字段 | 类型 | 说明 | |---|---|---| | `date` | string | 北京时间 `YYYY-MM-DD` | | `impressions` | int | 当天展示条数合计 | | `revenue_yuan` | float | 当天预估收益合计(元) | | `expected_coin` | int | 当天应发金币合计 | | `actual_coin` | int | 当天实发金币合计 | ### 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](./admin-ad-coin-audit.md) 仍保留(同一复算口径,可全局按场景/只看不符筛选)。