feat(admin): 两看板逐行补「本次广告收益」(ad_revenue_yuan)
按本页 trace_id 一次性聚合 ad_ecpm_record 展示收益(元),挂到领券数据 看板明细行(dict 字段)与比价记录列表项(ORM 瞬态属性),防 N+1 范式与 现有 _attach_user_info / _ad_coins_by_trace 一致。 - CouponDataRow: 新增 ad_revenue_yuan: float = 0.0 字段 - AdminComparisonListItem: 新增 ad_revenue_yuan: float = 0.0 字段 - coupon_data.py: _session_to_row 增 ad_revenue_yuan 参数;coupon_data_report 在分页后调用 crud_ecpm.revenue_yuan_by_trace 按本页批量聚合 - queries.py: list_comparison_records 在 _attach_user_info 后瞬态挂 ad_revenue_yuan - 新增 tests/test_board_ad_revenue.py:两个看板各一个集成测试 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ from sqlalchemy.orm import Session
|
||||
from app.core import rewards
|
||||
from app.models.coupon_state import CouponClaimRecord, CouponSession
|
||||
from app.models.user import User
|
||||
from app.repositories import ad_ecpm as crud_ecpm
|
||||
from app.repositories.coupon_state import DEFAULT_PLATFORMS, coupon_id_to_platform
|
||||
|
||||
|
||||
@@ -85,7 +86,7 @@ def _success_rates(rows: list) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _session_to_row(r, phone: str | None = None, nickname: str | None = None) -> dict:
|
||||
def _session_to_row(r, phone: str | None = None, nickname: str | None = None, ad_revenue_yuan: float = 0.0) -> dict:
|
||||
"""CouponSession ORM → 明细行 dict(主表「领券数据」与「用户全部领券」抽屉共用)。"""
|
||||
return {
|
||||
"id": r.id,
|
||||
@@ -104,6 +105,7 @@ def _session_to_row(r, phone: str | None = None, nickname: str | None = None) ->
|
||||
"started_at": r.started_at,
|
||||
"claimed_count": r.claimed_count,
|
||||
"trace_url": r.trace_url,
|
||||
"ad_revenue_yuan": ad_revenue_yuan,
|
||||
}
|
||||
|
||||
|
||||
@@ -246,10 +248,11 @@ def coupon_data_report(
|
||||
select(User.id, User.phone, User.nickname).where(User.id.in_(uids))
|
||||
).all()
|
||||
}
|
||||
rev_map = crud_ecpm.revenue_yuan_by_trace(db, [r.trace_id for r in page])
|
||||
items = []
|
||||
for r in page:
|
||||
phone, nickname = user_map.get(r.user_id, (None, None)) if r.user_id is not None else (None, None)
|
||||
items.append(_session_to_row(r, phone, nickname))
|
||||
items.append(_session_to_row(r, phone, nickname, ad_revenue_yuan=rev_map.get(r.trace_id, 0.0)))
|
||||
|
||||
return {
|
||||
"summary": summary,
|
||||
|
||||
@@ -32,6 +32,7 @@ from app.models.wallet import (
|
||||
InviteCashTransaction,
|
||||
WithdrawOrder,
|
||||
)
|
||||
from app.repositories import ad_ecpm
|
||||
|
||||
# 「最近活跃」计入的行为事件(与大盘 DAU/留存活跃口径一致:开始比价 + 开始领券)
|
||||
_ACTIVE_EVENTS = (COMPARE_START_EVENT, COUPON_START_EVENT)
|
||||
@@ -298,6 +299,11 @@ def list_comparison_records(
|
||||
limit=limit, cursor=cursor,
|
||||
)
|
||||
_attach_user_info(db, items)
|
||||
# 「本次比价看广告的预估收益」:按本页 trace_id 一次性聚合(同 _attach_user_info 逐页范式)。
|
||||
# ad_revenue_yuan 非 ORM 列,仅瞬态挂实例上供 AdminComparisonListItem(from_attributes)读出。
|
||||
rev = ad_ecpm.revenue_yuan_by_trace(db, [it.trace_id for it in items])
|
||||
for it in items:
|
||||
it.ad_revenue_yuan = rev.get(it.trace_id, 0.0)
|
||||
return items, next_cursor, total
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class AdminComparisonListItem(BaseModel):
|
||||
rom_name: str | None = None
|
||||
android_version: str | None = None
|
||||
app_version: str | None = None
|
||||
ad_revenue_yuan: float = 0.0 # 本次比价看的信息流广告预估收益(元),queries 瞬态挂 ORM 实例上
|
||||
created_at: datetime
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ class CouponDataRow(BaseModel):
|
||||
started_at: datetime = Field(..., description="发起时刻(明细「时间」列)")
|
||||
claimed_count: int | None = None
|
||||
trace_url: str | None = Field(None, description="pricebot 公网 trace 链接(仅 completed 有);admin 渲染可点链接,无则显示可复制 trace_id")
|
||||
ad_revenue_yuan: float = Field(
|
||||
0.0, description="本次领券看的信息流广告预估收益(元);按 trace_id 聚合 ad_ecpm_record"
|
||||
)
|
||||
|
||||
|
||||
class CouponDataOut(BaseModel):
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""两个看板逐行「本次广告收益」(元):按 trace_id 聚合 ad_ecpm_record。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, date, datetime
|
||||
|
||||
from sqlalchemy import delete
|
||||
|
||||
from app.admin.repositories import queries
|
||||
from app.admin.repositories.coupon_data import coupon_data_report
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.ad_ecpm import AdEcpmRecord
|
||||
from app.models.comparison import ComparisonRecord
|
||||
from app.models.coupon_state import CouponSession
|
||||
|
||||
|
||||
def _ecpm(trace_id: str, ecpm_raw: str, session_id: str, scene: str) -> AdEcpmRecord:
|
||||
return AdEcpmRecord(
|
||||
user_id=1, ad_type="draw", feed_scene=scene, ad_session_id=session_id,
|
||||
ecpm_raw=ecpm_raw, trace_id=trace_id, report_date="2020-01-02",
|
||||
created_at=datetime(2020, 1, 2, tzinfo=UTC),
|
||||
)
|
||||
|
||||
|
||||
def test_coupon_data_report_includes_ad_revenue() -> None:
|
||||
"""领券看板明细行带本次广告收益;200+300 分 → 0.005 元。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
db.add(CouponSession(
|
||||
trace_id="rev-cp-1", device_id="d1", status="completed", app_env="prod",
|
||||
platforms=["meituan-waimai"], platform_success=["meituan-waimai"],
|
||||
started_at=datetime(2020, 1, 2, tzinfo=UTC), started_date=date(2020, 1, 2),
|
||||
))
|
||||
db.add_all([
|
||||
_ecpm("rev-cp-1", "200", "cp-sess-a", "coupon"),
|
||||
_ecpm("rev-cp-1", "300", "cp-sess-b", "coupon"),
|
||||
])
|
||||
db.flush()
|
||||
res = coupon_data_report(db, date_from="2020-01-02", date_to="2020-01-02", app_env="prod")
|
||||
row = next(r for r in res["items"] if r["trace_id"] == "rev-cp-1")
|
||||
assert row["ad_revenue_yuan"] == 0.005
|
||||
finally:
|
||||
db.rollback()
|
||||
db.close()
|
||||
|
||||
|
||||
def test_comparison_list_includes_ad_revenue() -> None:
|
||||
"""比价记录列表项带本次广告收益;200 分 → 0.002 元。
|
||||
|
||||
用独有 user_id 过滤,确保本行必落在第一页(避免共享测试库里同 user 记录多、分页把它挤掉)。
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
db.add(ComparisonRecord(
|
||||
trace_id="rev-cmp-1", user_id=987654, status="success", business_type="food",
|
||||
created_at=datetime(2020, 1, 2, tzinfo=UTC),
|
||||
))
|
||||
db.add(_ecpm("rev-cmp-1", "200", "cmp-sess-a", "comparison"))
|
||||
db.commit()
|
||||
items, _next, _total = queries.list_comparison_records(db, user_id=987654)
|
||||
row = next(it for it in items if it.trace_id == "rev-cmp-1")
|
||||
assert row.ad_revenue_yuan == 0.002
|
||||
finally:
|
||||
db.execute(delete(AdEcpmRecord).where(AdEcpmRecord.trace_id.in_(["rev-cmp-1"])))
|
||||
db.execute(delete(ComparisonRecord).where(ComparisonRecord.trace_id == "rev-cmp-1"))
|
||||
db.commit()
|
||||
db.close()
|
||||
Reference in New Issue
Block a user