2ddea4159d
后台「领券数据」看板此前只有发起/完成数与耗时分位,缺少成功率视角。本 MR 补齐三档平台粒度成功率与一张按券(coupon_id)成功率表,并为看板增加领券状态多选过滤。 服务端埋点 领券每帧按 trace_id 把「成功平台」并集幂等写入 platform_success(merge_session_platform_success;读不到 session 行则静默跳过,不建兜底行;无新平台不写库)。 record_claims 按 session_app_env(trace_id) 反查并打 app_env 标。 新增平台推导:coupon_id_to_platform(前缀 mt_→美团 / tb_·ele_·elm_→淘宝 / jd_→京东,与客户端 couponIdToPlatform 同词表)、succeeded_platforms。成功语义统一为 success + already_claimed。 复用同一 SessionLocal、紧接 record_claims,不新增连接;fire-and-forget,异常已吞。 后台指标与接口 Summary 新增:②整单成功率(勾选平台全领到的 session 占发起数)、③点位成功率(Σ成功平台 / Σ勾选平台)、分平台点位成功率(恒含美团/淘宝/京东三档)。基数与「发起数」一致,含全部 session。 新端点 GET /coupon-data/coupons(coupon_slot_report):按券成功率表,数据源 coupon_claim_record,设备-天粒度,成功率 = 成功 /(成功+失败),skipped 排除,按尝试数倒序。 主表加 status 多选过滤(started/completed/failed/abandoned),汇总/成功率/趋势/明细整体按选中状态算,与 app_env 同级。 兼容性 / 风险 两个新列均可空、旧行按空集/不回填处理,无数据回填;埋点 fire-and-forget,失败不影响领券主流程。 部署务必 alembic upgrade head(因新增了 head 合并迁移)。 --------- Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #130
113 lines
5.3 KiB
Python
113 lines
5.3 KiB
Python
"""admin「领券数据」看板 schemas:汇总卡 + 按天/小时趋势 + 逐条领券明细。
|
||
|
||
数据源 coupon_session(一次领券一行)。耗时单位 ms(前端按需折秒);均值/分位只统计 completed。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class CouponDataSummary(BaseModel):
|
||
"""汇总卡:发起/完成数 + 耗时均值与分位(P5/P50/P95/P99,基于 completed 的 elapsed_ms)。"""
|
||
|
||
started_count: int = Field(..., description="发起数(区间内所有领券 session)")
|
||
completed_count: int = Field(..., description="完成数(status=completed)")
|
||
avg_elapsed_ms: int | None = Field(None, description="平均耗时(ms,仅 completed;无数据为空)")
|
||
p5_ms: int | None = Field(None, description="耗时 5 分位(ms)")
|
||
p50_ms: int | None = Field(None, description="耗时 50 分位(ms,中位数)")
|
||
p95_ms: int | None = Field(None, description="耗时 95 分位(ms)")
|
||
p99_ms: int | None = Field(None, description="耗时 99 分位(ms)")
|
||
# 平台粒度成功率(见 docs/guides/领券成功率指标-设计与埋点.md):基数含全部 session。
|
||
full_success_count: int = Field(0, description="整单成功数(勾选平台全部领到的 session 数)")
|
||
full_success_rate: float | None = Field(None, description="整单成功率②=整单成功数/发起数;无数据为空")
|
||
point_success_count: int = Field(0, description="成功平台点位数(Σ 每次成功的平台数)")
|
||
point_total_count: int = Field(0, description="总平台点位数(Σ 每次勾选平台数;空勾选=全领三档)")
|
||
point_success_rate: float | None = Field(None, description="点位成功率③=成功点位/总点位;无数据为空")
|
||
per_platform: dict[str, float | None] = Field(
|
||
default_factory=dict,
|
||
description="分平台点位成功率 {平台id: rate|None};恒含美团/淘宝/京东三档,区间内无人勾选的平台为 None",
|
||
)
|
||
|
||
|
||
class CouponDataDaily(BaseModel):
|
||
"""按天趋势(全量,不受分页影响):柱=发起/完成数,线=平均耗时。"""
|
||
|
||
date: str = Field(..., description="北京时间 YYYY-MM-DD")
|
||
started_count: int
|
||
completed_count: int
|
||
avg_elapsed_ms: int | None = Field(None, description="当天平均耗时(ms,仅 completed)")
|
||
|
||
|
||
class CouponDataHourly(BaseModel):
|
||
"""按北京小时(0–23)趋势(单日 granularity=hour 时非空)。"""
|
||
|
||
hour: int = Field(..., description="北京时间小时 0–23")
|
||
started_count: int
|
||
completed_count: int
|
||
avg_elapsed_ms: int | None = None
|
||
|
||
|
||
class CouponDataRow(BaseModel):
|
||
"""一条领券明细(一次领券任务)。"""
|
||
|
||
id: int = Field(..., description="coupon_session 主键(抽屉 rowKey 用)")
|
||
trace_id: str
|
||
user_id: int | None = None
|
||
user_phone: str | None = Field(None, description="手机号(admin 展示;匿名领券/查不到为空)")
|
||
user_nickname: str | None = Field(None, description="昵称")
|
||
status: str = Field(..., description="started / completed / failed / abandoned")
|
||
platforms: list[str] | None = Field(None, description="发起勾选平台")
|
||
origin_package: str | None = Field(None, description="发起来源 App 包名;null=App 内(傻瓜比价首页)发起")
|
||
elapsed_ms: int | None = Field(None, description="全程耗时(ms)")
|
||
platform_elapsed: dict[str, int] | None = Field(
|
||
None, description="各平台耗时 {meituan-waimai/taobao-shanguang/jd-waimai: ms}"
|
||
)
|
||
device_model: str | None = None
|
||
rom: str | None = None
|
||
app_env: str | None = None
|
||
started_at: datetime = Field(..., description="发起时刻(明细「时间」列)")
|
||
claimed_count: int | None = None
|
||
trace_url: str | None = Field(None, description="pricebot 公网 trace 链接(仅 completed 有);admin 渲染可点链接,无则显示可复制 trace_id")
|
||
|
||
|
||
class CouponDataOut(BaseModel):
|
||
"""领券数据看板响应:汇总卡 + 趋势 + 明细分页。"""
|
||
|
||
date_from: str
|
||
date_to: str
|
||
summary: CouponDataSummary
|
||
daily: list[CouponDataDaily] = Field(default_factory=list, description="按天趋势(全量)")
|
||
hourly: list[CouponDataHourly] = Field(
|
||
default_factory=list, description="按小时趋势(单日 hour 粒度时非空)"
|
||
)
|
||
total: int = Field(..., description="明细总条数(全量,不受分页)")
|
||
items: list[CouponDataRow] = Field(..., description="逐条领券明细(当前页)")
|
||
|
||
|
||
class CouponUserRecordsOut(BaseModel):
|
||
"""某用户全部领券记录(点手机号抽屉用):total=该用户领券总次数,items=记录列表(UserRecordsDrawer 渲染)。"""
|
||
|
||
items: list[CouponDataRow]
|
||
total: int
|
||
|
||
|
||
class CouponSlotRow(BaseModel):
|
||
"""按券成功率一行(§13):粒度=设备-天;成功率=成功/(成功+失败),skipped 排除。"""
|
||
|
||
coupon_id: str
|
||
coupon_name: str | None = None
|
||
platform: str | None = Field(None, description="美团/淘宝/京东 平台 id;无法识别为空")
|
||
tried: int = Field(..., description="尝试数(success+already_claimed+failed 的设备-天数)")
|
||
succeeded: int = Field(..., description="成功数(success+already_claimed)")
|
||
success_rate: float | None = Field(None, description="成功率=成功/尝试")
|
||
|
||
|
||
class CouponSlotsOut(BaseModel):
|
||
"""按券成功率表响应(§13)。"""
|
||
|
||
date_from: str
|
||
date_to: str
|
||
items: list[CouponSlotRow]
|