Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f77d1437d |
@@ -81,6 +81,10 @@ def _date_range(date_from: str, date_to: str) -> list[str]:
|
||||
# ad_feed_reward_record,由 audit 内部按 ad_type 区分(feed 含历史 NULL,draw 仅 ad_type=="draw")。
|
||||
_AUDIT_SCENES = {"reward_video", "feed", "draw"}
|
||||
|
||||
# 激励视频未满足有效播放条件时不计客户端预估收益。客户端仍会在 onAdShow
|
||||
# 上报 eCPM,随后才在关闭时补报以下终态,因此必须在展示/发奖合并后修正收益。
|
||||
_ZERO_REVENUE_REWARD_VIDEO_STATUSES = frozenset({"closed_early", "too_short"})
|
||||
|
||||
|
||||
# 发奖复算明细字段(展开下钻看「金币怎么算出来的」)——从 audit 行原样取这些 key。
|
||||
_REWARD_DETAIL_KEYS = (
|
||||
@@ -202,6 +206,11 @@ def ad_revenue_report(
|
||||
"matched": bool(rwd["matched"]),
|
||||
"reward_detail": _reward_detail(rwd),
|
||||
})
|
||||
if (
|
||||
rec.ad_type == "reward_video"
|
||||
and rwd["status"] in _ZERO_REVENUE_REWARD_VIDEO_STATUSES
|
||||
):
|
||||
ev["revenue_yuan"] = 0.0
|
||||
else:
|
||||
# 纯展示(信息流逐条展示、激励视频缺发奖记录):不计对账,matched=True。
|
||||
ev.update({
|
||||
|
||||
@@ -26,7 +26,10 @@ class AdRevenueRecord(BaseModel):
|
||||
|
||||
record_id: int
|
||||
created_at: datetime
|
||||
status: str = Field(..., description="granted / capped / ecpm_missing")
|
||||
status: str = Field(
|
||||
...,
|
||||
description="granted / capped / ecpm_missing / closed_early / too_short",
|
||||
)
|
||||
ecpm: str | None = Field(None, description="本次采用的 eCPM 原始值(分/千次展示)")
|
||||
ecpm_factor: float | None = Field(None, description="因子1(eCPM 档);非 granted 为空")
|
||||
units: int = Field(..., description="折算份数:激励视频恒 1;信息流 = 满 10 秒份数")
|
||||
@@ -44,7 +47,7 @@ class AdRevenueDaily(BaseModel):
|
||||
|
||||
date: str = Field(..., description="北京时间 YYYY-MM-DD")
|
||||
impressions: int = Field(..., description="当天展示条数合计")
|
||||
revenue_yuan: float = Field(..., description="当天客户端预估收益合计(元;eCPM 折算)")
|
||||
revenue_yuan: float = Field(..., description="当天客户端有效预估收益合计(元;eCPM 折算)")
|
||||
pangle_revenue_yuan: float | None = Field(
|
||||
None, description="当天穿山甲后台预估收益(元;GroMore revenue);非全量视图/无数据为空"
|
||||
)
|
||||
@@ -93,7 +96,10 @@ class AdRevenueRow(BaseModel):
|
||||
has_impression: bool = Field(..., description="是否有广告展示(信息流逐条展示=True,纯发奖行=False)")
|
||||
impressions: int = Field(..., description="本行展示条数:有展示=1 / 纯发奖=0(供日汇总、趋势图复用)")
|
||||
ecpm: str | None = Field(None, description="eCPM 原始值(分/千次);展示行取展示值,纯发奖行取发奖采用值")
|
||||
revenue_yuan: float = Field(..., description="本次展示预估收益(元)= eCPM元 ÷ 1000;纯发奖行=0")
|
||||
revenue_yuan: float = Field(
|
||||
...,
|
||||
description="本次有效展示预估收益(元)= eCPM元 ÷ 1000;纯发奖、激励视频提前关闭/时长不足=0",
|
||||
)
|
||||
row_revenue_yuan: float | None = Field(
|
||||
None,
|
||||
description="主表逐行展示用的预估收益(元):一次比价/领券聚合行=该次发奖广告 eCPM 折算之和;"
|
||||
@@ -150,7 +156,7 @@ class AdRevenueReportOut(BaseModel):
|
||||
total: int = Field(..., description="广告事件总数(全量,不受分页影响;= 当前筛选下的分页总条数)")
|
||||
truncated: bool = Field(..., description="当前页之后是否还有更多事件(len(events) > offset + limit)")
|
||||
total_impressions: int = Field(..., description="全量展示条数合计")
|
||||
total_revenue_yuan: float = Field(..., description="全量客户端预估收益合计(元;eCPM 折算)")
|
||||
total_revenue_yuan: float = Field(..., description="全量客户端有效预估收益合计(元;eCPM 折算)")
|
||||
total_pangle_revenue_yuan: float | None = Field(
|
||||
None,
|
||||
description="全量穿山甲后台预估收益合计(元;GroMore revenue)。穿山甲无用户/类型/场景维度,"
|
||||
|
||||
@@ -9,9 +9,11 @@ from app.admin.repositories import ad_revenue
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.ad_ecpm import AdEcpmRecord
|
||||
from app.models.ad_pangle_revenue import AdPangleDailyRevenue
|
||||
from app.models.ad_reward import AdRewardRecord
|
||||
from app.models.user import User
|
||||
|
||||
REPORT_DATE = "2040-02-03"
|
||||
PLAYBACK_DATE = "2040-02-04"
|
||||
|
||||
|
||||
def test_business_scope_filters_client_and_pangle_by_env_and_code(monkeypatch) -> None:
|
||||
@@ -127,3 +129,82 @@ def test_business_scope_filters_client_and_pangle_by_env_and_code(monkeypatch) -
|
||||
db.execute(delete(User).where(User.phone == "18800009991"))
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
|
||||
def test_reward_video_incomplete_playback_has_zero_revenue() -> None:
|
||||
db = SessionLocal()
|
||||
phone = "18800009992"
|
||||
sessions = {
|
||||
"closed_early": "rv-zero-closed",
|
||||
"too_short": "rv-zero-short",
|
||||
"capped": "rv-keep-capped",
|
||||
"granted": "rv-keep-granted",
|
||||
}
|
||||
try:
|
||||
user = User(phone=phone, username="29999999992", register_channel="sms")
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
for index, (status, session_id) in enumerate(sessions.items(), start=1):
|
||||
created_at = datetime(2040, 2, 4, index, tzinfo=UTC)
|
||||
db.add(AdEcpmRecord(
|
||||
user_id=user.id,
|
||||
ad_type="reward_video",
|
||||
ad_session_id=session_id,
|
||||
app_env="prod",
|
||||
our_code_id="prod-reward",
|
||||
ecpm_raw="10000",
|
||||
report_date=PLAYBACK_DATE,
|
||||
created_at=created_at,
|
||||
))
|
||||
db.add(AdRewardRecord(
|
||||
trans_id=f"{session_id}-trans",
|
||||
user_id=user.id,
|
||||
coin=0,
|
||||
status=status,
|
||||
reward_scene="reward_video",
|
||||
ad_session_id=session_id,
|
||||
app_env="prod",
|
||||
our_code_id="prod-reward",
|
||||
ecpm_raw="10000",
|
||||
reward_date=PLAYBACK_DATE,
|
||||
created_at=created_at,
|
||||
))
|
||||
db.commit()
|
||||
|
||||
result = ad_revenue.ad_revenue_report(
|
||||
db,
|
||||
date_from=PLAYBACK_DATE,
|
||||
date_to=PLAYBACK_DATE,
|
||||
user_id=user.id,
|
||||
ad_type="reward_video",
|
||||
app_env="prod",
|
||||
revenue_scope="all",
|
||||
granularity="hour",
|
||||
)
|
||||
|
||||
revenue_by_status = {row["status"]: row["revenue_yuan"] for row in result["items"]}
|
||||
assert revenue_by_status == {
|
||||
"closed_early": 0.0,
|
||||
"too_short": 0.0,
|
||||
"capped": 0.1,
|
||||
"granted": 0.1,
|
||||
}
|
||||
assert result["total_impressions"] == 4
|
||||
assert result["total_revenue_yuan"] == 0.2
|
||||
assert len(result["daily"]) == 1
|
||||
assert result["daily"][0]["date"] == PLAYBACK_DATE
|
||||
assert result["daily"][0]["impressions"] == 4
|
||||
assert result["daily"][0]["revenue_yuan"] == 0.2
|
||||
assert sum(row["revenue_yuan"] for row in result["hourly"]) == 0.2
|
||||
assert result["type_stats"]["reward_video"] == {
|
||||
"impressions": 4,
|
||||
"revenue_yuan": 0.2,
|
||||
}
|
||||
finally:
|
||||
db.rollback()
|
||||
db.execute(delete(AdRewardRecord).where(AdRewardRecord.reward_date == PLAYBACK_DATE))
|
||||
db.execute(delete(AdEcpmRecord).where(AdEcpmRecord.report_date == PLAYBACK_DATE))
|
||||
db.execute(delete(User).where(User.phone == phone))
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user