Files
shaguabijia-app-server/app/admin/schemas/comparison.py
T
guke 930eff822c feat(ad-revenue): 领券/比价看板逐次广告收益(ad_ecpm.trace_id + 逐页聚合) (#131)
## 背景
admin「领券数据」「比价记录」两个看板此前只能看到场景级(所有领券/比价)的广告收益,
无法定位「这一次领券/比价具体赚了多少」。根因:收益表 `ad_ecpm_record` 缺 `trace_id`,
无法与领券会话 / 比价记录按 trace 关联。

## 改动
- **模型/迁移**:`ad_ecpm_record` 新增 `trace_id`(String(64), index, nullable);
  迁移 `ad_ecpm_trace_id` 加列 + 索引 `ix_ad_ecpm_record_trace_id`,并**收敛当前两个
  alembic head**(`11c44afbea58` selfstat + `merge_pages_override_coupon_slot`)为单 head。
- **上报链路**:`EcpmReportIn` 增 `trace_id` 字段;`/api/v1/ad/ecpm-report` 透传;
  `create_ecpm_record` 落库。
- **收益聚合**:新增 `revenue_yuan_by_trace(db, trace_ids)`——按 trace_id 聚合展示收益,
  单条 = `min(eCPM元, ¥500钳顶)/1000`,与广告收益报表 `ad_revenue.py` **同口径**;
  只吃当前页的 trace_id(逐页批量,索引命中,无 N+1)。
- **两个看板**:`CouponDataRow` / `AdminComparisonListItem` 增 `ad_revenue_yuan`;
  `coupon_data_report`(主表 + 用户抽屉)与 `list_comparison_records` 分页后逐页补该字段。
- **测试**:`tests/test_ad_ecpm_trace_revenue.py`(聚合/钳顶/落库)、
  `tests/test_board_ad_revenue.py`(两看板 + 抽屉)。

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #131
2026-07-10 22:14:00 +08:00

75 lines
3.1 KiB
Python

"""admin 比价记录 debug 页 schema(独立于 C 端 app/schemas/compare_record:admin 看任意用户、
无 debug_trace 权限闸、展示全量 debug 字段)。phone/nickname 由 queries 瞬态挂在 ORM 实例上。"""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, ConfigDict
class AdminComparisonListItem(BaseModel):
"""列表项:概要 + debug 维度概览(不含大字段 raw_payload/llm_calls/明细数组)。"""
model_config = ConfigDict(from_attributes=True)
id: int
user_id: int
phone: str | None = None # join User 瞬态(非 DB 列)
nickname: str | None = None # join User 瞬态
business_type: str
trace_id: str
# admin 是 debug 工具,无条件下发 trace_url(不看 user.debug_trace_enabled)
trace_url: str | None = None
status: str
information: str | None = None
store_name: str | None = None
product_names: str | None = None # 下单商品名派生串(顿号分隔;「商品」列展示 + 商品搜索)
source_platform_name: str | None = None
best_platform_name: str | None = None
source_price_cents: int | None = None
best_price_cents: int | None = None
saved_amount_cents: int | None = None
# debug 概览
total_ms: int | None = None
step_count: int | None = None
llm_call_count: int | None = None
retry_count: int | None = None
input_tokens: int | None = None # Σ usage.prompt_tokens(server 派生)
output_tokens: int | None = None # Σ usage.completion_tokens(server 派生)
device_model: str | None = None
rom_vendor: str | None = None
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
class AdminComparisonDetail(AdminComparisonListItem):
"""详情:概要 + 全量明细(逐平台对比 / LLM 每次调用 / 原始 payload)。"""
source_platform_id: str | None = None
source_package: str | None = None
best_platform_id: str | None = None
best_deeplink: str | None = None
is_source_best: bool | None = None
total_dish_count: int | None = None
skipped_dish_count: int | None = None
device_id: str | None = None
items: list = []
comparison_results: list = [] # 逐平台对比(价格/rank/coupon/打烊...)
skipped_dish_names: list = []
# 全量环境
device_manufacturer: str | None = None
rom_version: int | None = None
android_sdk: int | None = None
app_version_code: int | None = None
source_app_version: str | None = None
longitude: float | None = None
latitude: float | None = None
# 每次 LLM 调用明细 [{scene,model,input_messages,output,usage,latency_ms,error}]
llm_calls: list | None = None
# 原始上报全量;「卡在哪一步」从 raw_payload.platform_results[*].status 读
# (store_not_found/items_not_found/below_minimum/unsupported = 卡在 找店/加菜/起送/读价)。
raw_payload: dict | None = None