52a95cebc8
- 新表 coupon_session(一次领券一行,trace_id 唯一):POST /api/v1/coupon/session 两段 upsert(发起 started / 收尾 completed·failed·abandoned),记全程耗时、各平台耗时、 机型/ROM、app_env、trace_url、origin_package(发起来源)。 - admin GET /admin/api/coupon-data:发起/完成数 + 平均耗时 + P5/P50/P95/P99(Python 算分位, SQLite 无 percentile)+ 按天/小时趋势 + 明细分页 + join 用户手机号/昵称,app_env 默认 prod; 另加 GET /admin/api/coupon-data/user-records 供「点手机号看该用户全部领券」抽屉。 - 迁移拆 3 个:建表 coupon_session_table + trace_url 加列 + origin_package 加列 (建表迁移已被某环境应用后改它不重跑,故新列单独加列迁移)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
3.7 KiB
Python
84 lines
3.7 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)")
|
||
|
||
|
||
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
|