"""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