Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| beadce31ed | |||
| f39467ec08 |
+7
-3
@@ -281,7 +281,10 @@ def ecpm_report(payload: EcpmReportIn, user: CurrentUser, db: DbSession) -> Ecpm
|
|||||||
丢一两条不影响业务(穿山甲后台报表是结算权威)。eCPM 与发奖(S2S)是两条独立流,不逐条关联。
|
丢一两条不影响业务(穿山甲后台报表是结算权威)。eCPM 与发奖(S2S)是两条独立流,不逐条关联。
|
||||||
"""
|
"""
|
||||||
attributed_trace_id = crud_ecpm.attributable_trace_id(
|
attributed_trace_id = crud_ecpm.attributable_trace_id(
|
||||||
db, feed_scene=payload.feed_scene, trace_id=payload.trace_id
|
db,
|
||||||
|
feed_scene=payload.feed_scene,
|
||||||
|
trace_id=payload.trace_id,
|
||||||
|
exposure_ms=payload.exposure_ms,
|
||||||
)
|
)
|
||||||
if payload.trace_id and attributed_trace_id is None:
|
if payload.trace_id and attributed_trace_id is None:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -296,11 +299,12 @@ def ecpm_report(payload: EcpmReportIn, user: CurrentUser, db: DbSession) -> Ecpm
|
|||||||
feed_scene=payload.feed_scene,
|
feed_scene=payload.feed_scene,
|
||||||
trace_id=attributed_trace_id,
|
trace_id=attributed_trace_id,
|
||||||
app_env=payload.app_env, our_code_id=payload.our_code_id,
|
app_env=payload.app_env, our_code_id=payload.our_code_id,
|
||||||
|
exposure_ms=payload.exposure_ms,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"ad ecpm report user_id=%d type=%s scene=%s session=%s ecpm=%s adn=%s slot=%s app=%s code=%s",
|
"ad ecpm report user_id=%d type=%s scene=%s session=%s ecpm=%s exposure_ms=%s adn=%s slot=%s app=%s code=%s",
|
||||||
user.id, payload.ad_type, payload.feed_scene, payload.ad_session_id, payload.ecpm,
|
user.id, payload.ad_type, payload.feed_scene, payload.ad_session_id, payload.ecpm,
|
||||||
payload.adn, payload.slot_id, payload.app_env, payload.our_code_id,
|
payload.exposure_ms, payload.adn, payload.slot_id, payload.app_env, payload.our_code_id,
|
||||||
)
|
)
|
||||||
return EcpmReportOut(ok=True)
|
return EcpmReportOut(ok=True)
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,22 @@ from app.core.rewards import cn_today
|
|||||||
from app.models.ad_ecpm import AdEcpmRecord
|
from app.models.ad_ecpm import AdEcpmRecord
|
||||||
from app.models.coupon_state import CouponSession
|
from app.models.coupon_state import CouponSession
|
||||||
|
|
||||||
|
MIN_REVENUE_EXPOSURE_MS = 1000
|
||||||
|
|
||||||
|
|
||||||
|
def effective_ecpm_raw(ecpm_raw: str, exposure_ms: int | None) -> str:
|
||||||
|
"""曝光不足一秒时保留展示记录,但把该条有效 eCPM 归零。"""
|
||||||
|
if exposure_ms is not None and exposure_ms < MIN_REVENUE_EXPOSURE_MS:
|
||||||
|
return "0"
|
||||||
|
return ecpm_raw
|
||||||
|
|
||||||
|
|
||||||
def attributable_trace_id(
|
def attributable_trace_id(
|
||||||
db: Session, *, feed_scene: str | None, trace_id: str | None
|
db: Session,
|
||||||
|
*,
|
||||||
|
feed_scene: str | None,
|
||||||
|
trace_id: str | None,
|
||||||
|
exposure_ms: int | None = None,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""返回广告展示允许归属的业务 trace。
|
"""返回广告展示允许归属的业务 trace。
|
||||||
|
|
||||||
@@ -31,7 +44,12 @@ def attributable_trace_id(
|
|||||||
session_status = db.execute(
|
session_status = db.execute(
|
||||||
select(CouponSession.status).where(CouponSession.trace_id == trace_id)
|
select(CouponSession.status).where(CouponSession.trace_id == trace_id)
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
return None if session_status in {"failed", "abandoned"} else trace_id
|
if session_status not in {"failed", "abandoned"}:
|
||||||
|
return trace_id
|
||||||
|
# 已真实上墙但不足一秒的曝光要在终态明细中明确显示 0,而不是被误判成“未填充”。
|
||||||
|
if exposure_ms is not None and exposure_ms < MIN_REVENUE_EXPOSURE_MS:
|
||||||
|
return trace_id
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_ecpm_record(
|
def create_ecpm_record(
|
||||||
@@ -47,6 +65,7 @@ def create_ecpm_record(
|
|||||||
trace_id: str | None = None,
|
trace_id: str | None = None,
|
||||||
app_env: str | None = None,
|
app_env: str | None = None,
|
||||||
our_code_id: str | None = None,
|
our_code_id: str | None = None,
|
||||||
|
exposure_ms: int | None = None,
|
||||||
) -> AdEcpmRecord:
|
) -> AdEcpmRecord:
|
||||||
"""落一条 eCPM 上报记录。report_date 用北京时间当天,供按天聚合。
|
"""落一条 eCPM 上报记录。report_date 用北京时间当天,供按天聚合。
|
||||||
|
|
||||||
@@ -67,7 +86,7 @@ def create_ecpm_record(
|
|||||||
trace_id=trace_id,
|
trace_id=trace_id,
|
||||||
app_env=app_env,
|
app_env=app_env,
|
||||||
our_code_id=our_code_id,
|
our_code_id=our_code_id,
|
||||||
ecpm_raw=ecpm_raw,
|
ecpm_raw=effective_ecpm_raw(ecpm_raw, exposure_ms),
|
||||||
report_date=cn_today().isoformat(),
|
report_date=cn_today().isoformat(),
|
||||||
)
|
)
|
||||||
db.add(rec)
|
db.add(rec)
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ class EcpmReportIn(BaseModel):
|
|||||||
description="本次比价/领券 trace_id(信息流场景带上):把这条展示收益归属到对应比价/领券,"
|
description="本次比价/领券 trace_id(信息流场景带上):把这条展示收益归属到对应比价/领券,"
|
||||||
"供领券数据/比价记录看板聚合本场广告收益;激励视频/福利为空",
|
"供领券数据/比价记录看板聚合本场广告收益;激励视频/福利为空",
|
||||||
)
|
)
|
||||||
|
exposure_ms: int | None = Field(
|
||||||
|
None,
|
||||||
|
ge=0,
|
||||||
|
le=86_400_000,
|
||||||
|
description="本条广告真实在屏曝光毫秒数;小于 1000ms 时收益强制按 0 计算。旧客户端不传则保持原口径",
|
||||||
|
)
|
||||||
app_env: str | None = Field(
|
app_env: str | None = Field(
|
||||||
None, max_length=16, description="我们的穿山甲应用环境:prod(傻瓜比价正式) / test(测试应用)"
|
None, max_length=16, description="我们的穿山甲应用环境:prod(傻瓜比价正式) / test(测试应用)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,6 +58,32 @@ def test_revenue_yuan_by_trace_empty() -> None:
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_short_exposure_keeps_record_with_zero_revenue() -> None:
|
||||||
|
"""不足一秒仍落展示记录,以便后台显示 0 而不是未填充。"""
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
rec = crud_ecpm.create_ecpm_record(
|
||||||
|
db, 1, ad_type="draw", ecpm_raw="350",
|
||||||
|
ad_session_id="sess-short-exposure", feed_scene="coupon",
|
||||||
|
trace_id="trace-short-exposure", exposure_ms=999,
|
||||||
|
)
|
||||||
|
assert rec.ecpm_raw == "0"
|
||||||
|
assert crud_ecpm.revenue_yuan_by_trace(db, ["trace-short-exposure"]) == {
|
||||||
|
"trace-short-exposure": 0.0
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
db.execute(delete(AdEcpmRecord).where(
|
||||||
|
AdEcpmRecord.ad_session_id == "sess-short-exposure"
|
||||||
|
))
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_second_exposure_keeps_original_ecpm() -> None:
|
||||||
|
assert crud_ecpm.effective_ecpm_raw("350", 1000) == "350"
|
||||||
|
assert crud_ecpm.effective_ecpm_raw("350", None) == "350"
|
||||||
|
|
||||||
|
|
||||||
def test_terminal_coupon_trace_is_not_attributable_to_late_impression() -> None:
|
def test_terminal_coupon_trace_is_not_attributable_to_late_impression() -> None:
|
||||||
"""领券失败或被放弃后才到达的广告展示保留收益记录,但不再关联死亡 trace。"""
|
"""领券失败或被放弃后才到达的广告展示保留收益记录,但不再关联死亡 trace。"""
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@@ -80,6 +106,12 @@ def test_terminal_coupon_trace_is_not_attributable_to_late_impression() -> None:
|
|||||||
assert crud_ecpm.attributable_trace_id(
|
assert crud_ecpm.attributable_trace_id(
|
||||||
db, feed_scene="coupon", trace_id="abandoned-before-ad"
|
db, feed_scene="coupon", trace_id="abandoned-before-ad"
|
||||||
) is None
|
) is None
|
||||||
|
assert crud_ecpm.attributable_trace_id(
|
||||||
|
db, feed_scene="coupon", trace_id="failed-before-ad", exposure_ms=999
|
||||||
|
) == "failed-before-ad"
|
||||||
|
assert crud_ecpm.attributable_trace_id(
|
||||||
|
db, feed_scene="coupon", trace_id="abandoned-before-ad", exposure_ms=999
|
||||||
|
) == "abandoned-before-ad"
|
||||||
assert crud_ecpm.attributable_trace_id(
|
assert crud_ecpm.attributable_trace_id(
|
||||||
db, feed_scene="comparison", trace_id="failed-before-ad"
|
db, feed_scene="comparison", trace_id="failed-before-ad"
|
||||||
) == "failed-before-ad"
|
) == "failed-before-ad"
|
||||||
|
|||||||
Reference in New Issue
Block a user