fix(ad-revenue): 报表收益按满额钳顶 + 新增 app_env 过滤 (#93)

广告收益报表两处隐患修复(admin/repositories/ad_revenue.py + routers/ad_revenue.py):

- 收益钳顶:单次展示收益原用裸 parse_ecpm_yuan、未钳上限;发奖侧 calculate_ad_reward_coin
  已钳 AD_ECPM_MAX_FEN(¥500 CPM)。异常/伪造天价 eCPM 会让报表预估收益虚高任意大。改为
  min(parse_ecpm_yuan, AD_ECPM_MAX_FEN/100)/1000,与发奖同口径。
- app_env 过滤:报表聚合原无 app_env 过滤,测试应用假 eCPM(如 ¥678 CPM)会进正式收益合计/平均。
  新增 app_env 参数(repository + router Query),显式传 prod/test 才过滤;默认仍全部(不擅自改成
  默认排除 test:本地 dev 库多为 test 会空、且属产品口径)。穿山甲后台收益列暂未联动此过滤。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #93
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
This commit was merged in pull request #93.
This commit is contained in:
2026-06-29 23:11:41 +08:00
committed by marco
parent 5efe624340
commit 81639ed388
2 changed files with 23 additions and 3 deletions
+15 -2
View File
@@ -87,6 +87,7 @@ def ad_revenue_report(
user_id: int | None = None,
ad_type: str | None = None,
feed_scene: str | None = None,
app_env: str | None = None,
granularity: str = "day",
limit: int = 500,
offset: int = 0,
@@ -156,8 +157,12 @@ def ad_revenue_report(
"has_impression": True,
"impressions": 1,
"ecpm": rec.ecpm_raw,
# 单次展示收益(元)= eCPM元 ÷ 1000(每千次→单次);与发奖同源解析,口径一致。
"revenue_yuan": round(rewards.parse_ecpm_yuan(rec.ecpm_raw) / 1000.0, 6),
# 单次展示收益(元)= eCPM元 ÷ 1000(每千次→单次)。eCPM 先钳到 AD_ECPM_MAX_FEN(¥500 CPM)
# 再折收益,与发奖口径 [rewards.calculate_ad_reward_coin] 一致(2026-06-29 修:原裸 parse_ecpm_yuan
# 不钳,伪造/异常天价 eCPM 会把报表预估收益冲到任意大;金币侧已钳、收益侧漏钳)。
"revenue_yuan": round(
min(rewards.parse_ecpm_yuan(rec.ecpm_raw), rewards.AD_ECPM_MAX_FEN / 100.0) / 1000.0, 6,
),
"adn": rec.adn,
"slot_id": rec.slot_id,
}
@@ -213,6 +218,14 @@ def ad_revenue_report(
if feed_scene is not None:
events = [e for e in events if e.get("feed_scene") == feed_scene]
# app_env 过滤(2026-06-29 新增能力,修隐患:测试应用上报的假 eCPM 如 ¥678 CPM 会污染正式收益合计/平均):
# 显式传 "prod"/"test" 只看该环境;不传=全部(维持现状)。**不擅自把默认改成排除 test**——本地 dev 库多为
# test 数据、默认排除会使本地报表空,且「正式报表是否含 test」属产品口径。建议前端报表页加 app_env 筛选器
# (默认选 prod),或产品确认后再把默认改成排除 test。注:穿山甲后台收益列(total_pangle_*)暂未联动此过滤
# (它是独立对照列,且 pangle 的 test 是真实小额、非客户端那种假值)。
if app_env is not None:
events = [e for e in events if e.get("app_env") == app_env]
# 排序:time=按时间倒序(新→旧);ecpm=按 eCPM 数值倒序(eCPM 原值是字符串「分」,转数值排;
# 纯发奖行用其发奖采用的 eCPM,缺失/非法计 0 排末尾)。
if sort == "ecpm":
+8 -1
View File
@@ -56,6 +56,13 @@ def get_ad_revenue_report(
"全局筛选,同时影响明细 / 合计 / 趋势"
),
] = None,
app_env: Annotated[
str | None,
Query(
description="prod(正式应用) / test(测试应用);不传=全部。"
"建议正式收益报表选 prod,避免测试应用的假 eCPM 污染收益合计/平均"
),
] = None,
granularity: Annotated[
str, Query(description="day=按天 / hour=按小时(北京时间);区间>1 天建议用 day")
] = "day",
@@ -75,7 +82,7 @@ def get_ad_revenue_report(
result = ad_revenue.ad_revenue_report(
db, date_from=d_from.isoformat(), date_to=d_to.isoformat(),
user_id=user_id, ad_type=ad_type, feed_scene=feed_scene,
user_id=user_id, ad_type=ad_type, feed_scene=feed_scene, app_env=app_env,
granularity=granularity, limit=limit, offset=offset, sort=sort,
)
return AdRevenueReportOut(