Files
shaguabijia-app-server/docs/guides/领券成功率指标-设计与埋点.md
T
guke 2ddea4159d feat(coupon-data): 领券成功率看板(整单/点位/分平台 + 按券) (#130)
后台「领券数据」看板此前只有发起/完成数与耗时分位,缺少成功率视角。本 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
2026-07-09 17:31:48 +08:00

24 KiB
Raw Blame History

领券「整单成功率」与「点位成功率」指标 — 设计与埋点

  • 日期:2026-07-07
  • 状态:待评审
  • 涉及仓库:shaguabijia-app-server纯服务端;客户端零改动)
  • 数据源表:coupon_sessionadmin「领券数据」看板)

1. 背景与目标

admin「领券数据」看板(数据源 coupon_session,见 app/admin/repositories/coupon_data.py)当前能算:领券发起数、完成数、全程耗时均值/分位。产品还想要两个成功率指标,现有埋点算不出来:

  • ② 整单成功率 = 一次发起里勾选的平台全部领到券的次数 / 领券发起数
  • ③ 点位成功率 = 平台维度的领取成功率(每个平台「点位」成没成功)

口径决定(2026-07-07):「点位」= 平台粒度(美团 / 淘宝闪购 / 京东),不是「每张券」。

耗时中位数、发起数已由 coupon_session.elapsed_ms / status=started 计数满足,本设计只补 ②③。

2. 关键结论:per-slot 信号已在库,缺的是「按 session 可靠归因 + 看板可过滤」

服务端 /api/v1/coupon/stepapp/api/v1/coupon.py)每帧都调 record_claims,把每张券的结果(status ∈ success / already_claimed / failed / skipped)写进 coupon_claim_record,还带 trace_id。原始成败信号已经落库

但该表不能直接支撑本指标:

  1. 唯一键是 (device_id, coupon_id, claim_date)不含 trace_id;且 record_claims 冲突更新时不更新 trace_idapp/repositories/coupon_state.pyrecord_claims,仅 INSERT 时写 trace_id)。→ 同一张券当天跨多次 session 会折叠成一行、只归属最早那次 → 按 session 归因不可靠(直接砸 ②「整单全成功」)。
  2. coupon_claim_recordapp_env / origin_package → 无法像看板那样只看 prod、也无法拆 Path AApp 内发起)/ Path B(外卖侧弹券)。

因此采用 route B:在 /step 里服务端推导「本次 session 哪些平台成功」,直接写到 coupon_session 行——该表按 trace_id 唯一、已带 app_env / origin_package / platforms,指标干净可过滤、可拆路径。

3. 指标口径(平台粒度,已定)

记一次 session 为 s

  • sel(s) = 勾选平台集 = coupon_session.platforms;为空表示「全领」→ 取默认 {meituan-waimai, taobao-shanguang, jd-waimai}|sel(s)| = 该次「点位数」。
  • succ(s) = 成功平台集 = 本次 session 里至少领到一张status ∈ {success, already_claimed})的平台集合。

指标:

  • ③ 点位成功率 = Σ_s |succ(s) ∩ sel(s)| / Σ_s |sel(s)| (分母即「发起数 × 各自点位数」;全部全领时等于 发起数 × 3)
  • ② 整单成功率 = #{ s : sel(s) ⊆ succ(s) 且 sel(s) ≠ ∅ } / 发起数

已定边界:

  1. ③ 分母用「勾选平台」sel(s)。勾了淘宝但淘宝没领到 = 该点位未成功(不特判「平台没券」)。若日后要「真没券的平台不计入分母」,再引入 platform_attempted(见 §8)。
  2. ②③ 基数 = 区间内全部 session(含 started / failed / abandoned),与看板「发起数」同基数。失败 / 中途退出的 session 的 succ(s) 取它崩溃前真领到的平台(不一律算 0)。
  3. 成功语义status ∈ {success, already_claimed} = 成功;failed / skipped = 未成功。与 sum_claimed_countapp/repositories/coupon_state.py)一致。

    pricebot 协议文档把 status enum 写作 success|failed|skipped,但代码实际还会 emit already_claimedpricebot app/services/coupon_provider.py 等多处)——以代码为准,含 already_claimed 是对的。skipped 目前 MVP 阶段基本不出现。

4. 数据模型改动

coupon_sessionapp/models/coupon_state.py::CouponSession)新增一列:

类型 说明
platform_success _JSONPG→JSONB / SQLite→JSON),nullable 本次 session 至少领到一张的平台 id 列表,如 ["meituan-waimai","jd-waimai"]。旧行 = NULL → 视作空集。
  • alembic 新迁移:add_column coupon_session.platform_successnullable、无 server_default。
  • 分母 sel(s) 复用已有 platforms 列,不新增字段。 净新增仅此一列。
  • 同步更新表字典 docs/database/coupon_state.md

5. 服务端推导逻辑(/step

app/api/v1/coupon.py::coupon_step 内(已有 results = _extract_coupon_results(resp_json))新增:

  1. 对每条 result 求平台:coupon_id 前缀映射(与客户端 CouponForegroundService.couponIdToPlatform 对齐,且与 platforms / platform_elapsed 用同一套平台 id 词表):
    • mt_meituan-waimai
    • tb_ / ele_ / elm_taobao-shanguang
    • jd_jd-waimai
    • 其余 → 跳过(无法识别)

    不用券的 vendor 字段做映射:vendor 词表未必等于这三档平台 id,而 sel(s) 用的就是这三档,succ(s) 必须同词表。

  2. 收集 status ∈ {success, already_claimed} 的平台集合 ok_platforms
  3. device_idok_platforms 非空:调用新 repo 函数把 ok_platforms 并入 coupon_session.platform_success(按 trace_id)。

新增 app/repositories/coupon_state.py::merge_session_platform_success(db, trace_id, ok_platforms)

  • 读现有行 → platform_success = 现有 ok_platforms(去重、保序)→ 写回、commitIntegrityError 回滚忽略(同现有 upsert 兜底)。
  • 并集幂等:跨帧多次并入同一平台不会重复;done 帧的全量 coupon_results 保证完整;失败 / 中途退出的 session 靠中间帧 last_coupon_result 已并入的平台拿到「部分成功」。
  • fire-and-forget:包 run_in_threadpool + 整段 try/except 只 log,绝不连累 /step 返回(与现有 record_claims / mark_completed 同规格)。

建行 / 时序(已定:行不存在则跳过本次并入)

  • 客户端 /session startedstart()(任务发起那刻)就发,而首个带券的 /step 要等领券循环跑起来、晚几秒;到 /stepok_platforms 时,coupon_session 行几乎必然已存在。故 merge_session_platform_success 读不到行就跳过,不建兜底行——实现最简,也不引入 started_at 不精确的脏行。
  • 残留丢数窗口:仅当「started 上报丢失」/step done 先于 /session terminal 落库」两者同时成立,该 session 的 platform_success 才会缺(terminal 帧会建行但那之后没有 /step 再并入)。两条件叠加概率极低,且指标是聚合口径、可容忍个别缺失。
  • 若上线后观测到该缺失不可忽略,再降级为「读不到行则 upsert 建最小兜底行(started_at=now())」——届时改 merge_session_platform_success 一处即可,不影响其余设计。

写放大(重要,非每 stepmerge_session_platform_success 只在「本帧带券结果」时触发——即 pricebot 在单券完成帧last_coupon_result最终 done 帧给全量 coupon_results 的那些帧;领单张券途中的导航/点击帧(占 step 大头)_extract_coupon_results 返回空 → 不写。所以写频次 ≈ 本次领的券数(通常个位数),且落在服务端今天已有的 record_claims 写的同一批帧上,不新增写的帧。

实现:把 merge 放进现有 _record_claims_blocking 的同一个 SessionLocal(紧接 record_claims),边际成本 = 每张券完成时多一条 UPDATE coupon_session(按 trace_id 唯一索引),不新增连接 / 不新增 run_in_threadpool 调用。

可选降级(若要「一次 session 只写一次」):只在 done 帧写 platform_success(全量 coupon_results 一次算完)。代价:failed / abandoned(没 done 帧)拿不到「崩溃前已成平台」→ 失败单部分成功丢失,与 §3「失败单取实际成的平台」相悖。默认取每券帧并入(失败单也如实统计),此降级留作观测到写压力后再启用。

6. admin 聚合与呈现

app/admin/repositories/coupon_data.py::coupon_data_reportsummary 增加(沿用「全量拉区间 → Python 聚合」风格,与分位一致):

  • full_success_rate(②)、point_success_rate(③)
  • 可选 per_platform{platform: rate}(各平台点位成功率,拆美团/淘宝/京东)

计算:对区间内 sessionssel = platforms or 默认三档succ = set(platform_success) ∩ sel;按 §3 公式汇总。

  • 过滤:默认 app_env == 'prod'(同现有分位口径,防测试串台)。
  • 拆路径:origin_package 已在表上 → 看板后续可加「Path A / Path B」筛选项(本设计不含前端图表细节)。
  • schemaapp/admin/schemas/coupon_data.py 的 summary 加对应字段(+ 可选 per_platform)。

7. 改动清单

  • app/models/coupon_state.pyCouponSessionplatform_success
  • alembic/versions/:新迁移 add column coupon_session.platform_success
  • app/repositories/coupon_state.py:新增 merge_session_platform_success
  • app/api/v1/coupon.py/step 推导 ok_platforms 并 union(前缀映射 + fire-and-forget
  • app/admin/repositories/coupon_data.pysummary 加 ②③(+ 可选 per_platform
  • app/admin/schemas/coupon_data.pysummary schema 加字段
  • docs/database/coupon_state.md:补 platform_success 列说明(并补 coupon_session 整节)
  • tests/tests/test_coupon_platform_success.py10 测试,见 §9

8. 不做(YAGNI / 边界)

  • 客户端不改、历史不回填platform_success 只对新 session 生效(route B 的固有取舍,用户已接受)。
  • 不引入 platform_attempted:③ 分母用勾选平台。若日后要「排除真没券的平台」,注意 platform_elapsed.keys() 已近似「被处理过的平台」,可作 attempted 的现成来源,多半仍不必加列。
  • 不动 coupon_claim_record 的去重 / 归因:本指标绕开它,避免牵动频控 / 资产 / CPS 语义。
  • 不做券级(每张券)成功率:已选平台粒度。

9. 测试口径要点

  • union 幂等:同一 trace_id 多帧并入同一平台,platform_success 不重复、保序。
  • 失败单部分成功session failed,但美团已成 → succ = {meituan-waimai},计入 ③ 分子;② 仅当 sel ⊆ succ 才算整单成功。
  • platformssel 取默认三档(全领)。
  • 成功语义already_claimed 计成功;skipped 不计。
  • 基数abandoned 计入 ②③ 基数,succ 取实际成的平台。
  • prod 过滤dev 环境 session 不进指标。

10. 对 pricebot-backend 的影响

结论:不需要改 pricebot,也不改发往 pricebot 的请求 / 不加调用 / 不加负载。

  • /step 仍原样透传请求 bytes 给 pricebot;本设计只多解析 pricebot 的响应coupon_results / last_coupon_result),而这两个字段服务端今天已在 _extract_coupon_results / record_claims 里解析。零新增字段需求、零额外上游调用、pricebot 负载不变。
  • 只读依赖(既有耦合,非新引入):平台映射靠 pricebot 的 coupon_id 前缀约定(mt_ / tb_ / ele_ / elm_ / jd_)。客户端 couponIdToPlatform 早就依赖同一套;本设计只是加了这份映射的第二个消费者。维护耦合:pricebot 若改 coupon_id 前缀,客户端与本指标会一起失效——但这是既有风险,依赖方向不变。无法识别前缀的券按「跳过」处理(与客户端一致)。
  • 认账的 pricebot 事实(源:app/models/response.py + docs/projects/领券-客户端对接协议.md):
    • CouponResult = {coupon_id, name, vendor, status, reason?, duration_ms?}coupon_results 仅最终 done 帧全量,中间帧走 last_coupon_result(单张)。
    • vendor 是来源标签(meituan_internal / dianping_cps …),不等于三档平台 id;且 mt_dianping_xxx(大众点评 CPS)也带 mt_ 前缀归美团 → 印证「用 coupon_id 前缀、不用 vendor」正确。

11. 实现状态(交付记录 · 2026-07-07

状态:实现完成、TDD 全绿、未提交、迁移未应用。 纯服务端(shaguabijia-app-server),客户端 / pricebot 未动。

已交付改动

文件 改动
app/models/coupon_state.py CouponSessionplatform_success_JSON, nullable
app/repositories/coupon_state.py coupon_id_to_platform / succeeded_platforms / merge_session_platform_success + 常量 DEFAULT_PLATFORMS / _SUCCESS_STATUSES
app/api/v1/coupon.py _record_claims_blocking 内、record_claims 之后并入本帧成功平台(同一 SessionLocal
app/admin/repositories/coupon_data.py _success_rates(rows) → summary 加 full_success_rate② / point_success_rate③ + 3 个支撑计数
app/admin/schemas/coupon_data.py CouponDataSummary 加 5 字段
alembic/versions/coupon_session_platform_success.py add column;revision=coupon_session_platform_successdown=admin_user_plain_password(当前 head
docs/database/coupon_state.md coupon_session 整节(表原本无文档)+ 新列
tests/test_coupon_platform_success.py 10 个测试(TDD

§7 里「可选 per_platform」原标 YAGNI 延后;已在 §122026-07-08)补做(随 admin 前端看板卡一并接入,见下)。

测试与验证

  • 本特性:Set-Location e:\project\shaguabijia-app-server; & .\.venv\Scripts\python.exe -m pytest tests\test_coupon_platform_success.py -q10 passed
  • 全量 pytest -q:306 passed / 5 failed。5 个失败全部预存、与本次无关:
    • test_coupon_proxy.py::test_coupon_step_passes_body_through(测试断言 json= 但 handler 用 content= 转发;写代码前 sanity run 就红)。
    • test_invite.py + test_invite_compare_reward.py 共 4 个(单独跑也红;本次改动集零 invite 文件)。
  • 迁移:临时库 alembic upgrade head 通过、列已建、单一 head。
  • lint:新增代码 ruff 全清;coupon_state.py 剩 3 处 pre-existing UP017upsert_coupon_sessiontimezone.utc)未动。

待办(在后端项目里继续)

  1. 应用迁移:alembic upgrade head(DDL 已验;线上只前进统计、历史不回填)。
  2. 提交:尚未提交;建议先开分支再提交。
  3. admin 前端图表:后端指标已就绪(summary 的 full_success_rate / point_success_rate 等),看板卡 / 趋势展示待接前端。
  4. (可选)修预存 test_coupon_step_passes_body_through(一行:capture content 而非 json)。

续开发须知

  • platform_success 只在带券结果的帧写(/stepsucceeded_platforms(results) 非空才 merge),≈ 领券券数量级、非每 step;复用 record_claims 同一 SessionLocal、不新增连接。
  • merge 读不到 session 行则跳过(不建兜底行);并集幂等、无新平台不写。
  • 口径:成功=status∈{success,already_claimed};基数含 abandoned/failed;③ 分母=platforms(空→全领三档);coupon_id→平台走前缀,与客户端 couponIdToPlatform 同词表。

12. 续做:admin 前端看板卡 + 分平台点位成功率(设计 · 2026-07-08

承 §11 待办 #3(前端图表)与 §7「可选 per_platform」。本轮把 ②③ 接入 admin「领券数据」页,并把 ③ 按平台拆(per_platform)。改前端 + 后端 + 测试;客户端 / pricebot 仍零改动。

  • 状态:设计已评审通过(2026-07-08),待实现。
  • 涉及仓库:shaguabijia-app-server(后端)+ shaguabijia-admin-web(admin 前端,Next.js + antd)。

12.1 后端:per_platform 分平台点位成功率

app/admin/repositories/coupon_data.py::_success_rates(rows) 在现有合计基础上,对每个 p ∈ DEFAULT_PLATFORMS(美团/淘宝/京东)累加:

  • 分母 per_total[p] = 勾选了 p 的 session 数(p ∈ sel,空勾选 sel 按全领三档);
  • 分子 per_succ[p] = 其中 p ∈ succ(该平台至少领到一张)的 session 数;
  • per_platform[p] = round(per_succ[p] / per_total[p], 4);分母 0 → None

不变量:Σ_p per_succ[p] == point_success_countΣ_p per_total[p] == point_total_count(三档词表下恒成立;非三档平台 id 不计入 per_platform,由 if p in per_total 守卫)。用 §9 / test_coupon_data_success_rates 数据自检:美团 3/4=0.75、淘宝 2/3=0.6667、京东 1/2=0.5;合计仍 6/9=0.6667。

  • summary 加一项 per_platform:恒含三档键,如 {"meituan-waimai":0.75,"taobao-shanguang":0.6667,"jd-waimai":0.5}(区间内无人勾选的平台 → 值 None)。
  • schema app/admin/schemas/coupon_data.py::CouponDataSummaryper_platform: dict[str, float | None]

12.2 前端:admin-web「领券数据」汇总卡补一段

shaguabijia-admin-web/src/app/(main)/coupon-data/page.tsx(单文件,CouponDataSummary 为该页内联类型):

  • 内联 CouponDataSummary 补:full_success_count / full_success_rate / point_success_count / point_total_count / point_success_rate + per_platform: Record<string, number | null>
  • 新 helper fmtPct(v) = v == null ? '-' : ${(v*100).toFixed(1)}%(沿用本页 - 空值风格,数学同大盘 pct);antd 导入补 Tooltip,新增 import { InfoCircleOutlined } from '@ant-design/icons'
  • 汇总卡「耗时分位」行之后,Divider + 两行 Statistic(各 Col flex="1 1 0"):
    • 行1:整单成功率 fmtPct(full_success_rate) · 点位成功率(合计) fmtPct(point_success_rate);标题各带 ⓘ Tooltip(口径说明;合计率注明「平台粒度、= 三档之和,与『数据大盘』券粒度口径不同」)。
    • 行2:美团 / 淘宝 / 京东 点位成功率 fmtPct(per_platform['meituan-waimai' | 'taobao-shanguang' | 'jd-waimai'])(平台名同「美团耗时」列既有叫法)。
  • 不显示支撑数(合计与分平台均纯百分比);tooltip 不带分母。

12.3 测试

  • tests/test_coupon_platform_success.py::test_coupon_data_success_rates:断言 per_platform == {"meituan-waimai":0.75,"taobao-shanguang":round(2/3,4),"jd-waimai":0.5},并断言和不变量(Σ 分子 == point_success_count == 6Σ 分母 == point_total_count == 9)。
  • 前端 shaguabijia-admin-web lint / type-check 通过。
  • 端到端:随下一步「真实 /step 实测」在跑起来的 admin-web + 后端页面上核对卡片渲染。

12.4 不做(YAGNI)

  • 成功率趋势线:后端 daily / hourly 不含率字段,加趋势要另改聚合,超出本轮范围。
  • 合计 / 分平台的支撑数副文本、tooltip 带分母。
  • 客户端 / pricebot 改动;历史回填。

13. 续做:每券(coupon_id)成功率明细表(设计 · 2026-07-08)

产品要更细粒度:到具体领券点位(如「美团外卖红包天天领」「美团甄选好店」),即按 coupon_id 算成功/失败率,比 §12 的平台粒度再细一层。改前端 + 后端 + 测试;客户端 / pricebot 仍零改动。

  • 状态:设计已评审通过(2026-07-08),待实现。
  • 关键取舍(已定):数据源用 coupon_claim_record(它本就是「单张券一天一条」的系统记录,已带 coupon_name / status / vendor / trace_id),唯一缺 app_env → 补一列即可。§2 当初绕开它是因为平台指标要按 session 归因;而每券成功率是全局聚合、不需要 session 归因,(device,券,天) 折叠反而天然去重防刷,故这里用它是对的。

13.1 指标口径(已定)

  • 成功 = status ∈ {success, already_claimed};尝试(分母) = status ∈ {success, already_claimed, failed};skipped 排除(无券可领/不适用,不算尝试、不进分母、不展示)。
  • 成功率 = 成功 / 尝试(某券区间内无 tried 行 → 不出现在表里,无除零)。
  • 粒度 = 「设备-天」(非「每次点击」):coupon_claim_record 唯一键 (device, coupon_id, claim_date),同设备当天同券只留最后状态。所以「尝试」= 有多少设备-天尝试过该券,「成功」= 其中最终领到的。要每次点击级须换源(route B / 原始事件),本轮不做。
  • 环境:跟随页面「环境」(prod/dev/全部),按 app_env 过滤;旧行 app_env=NULL 不回填 → 仅「全部」视图可见。
  • 日期:按 claim_date(Asia/Shanghai 自然日),与页面日期范围一致。

13.2 数据模型

CouponClaimRecord(app/models/coupon_state.py)加一列:

类型 说明
app_env String(16),index,nullable prod / dev。/step 落库时按 session 的 app_env 打标;旧行 NULL(不回填)。
  • alembic 新迁移:add_column coupon_claim_record.app_env,nullable + index,无 server_default。
  • 同步更新 docs/database/coupon_state.md 的 coupon_claim_record 节。

13.3 写路径(/step)

  • app/repositories/coupon_state.py::record_claims 加参数 app_env: str | None = None;INSERT 时写入,UPDATE 时 if app_env is not None: row.app_env = app_env(不用 None 覆盖已有)。
  • 新增轻量 repo 助手 session_app_env(db, trace_id) -> str | None(按 trace_id 取 coupon_session.app_env,查不到返回 None)。
  • app/api/v1/coupon.py::_record_claims_blocking:trace_id 存在则先 app_env = coupon_repo.session_app_env(db, trace_id),传给 record_claims(..., app_env=app_env);merge_session_platform_success 保持原样(其自身 select 不变)。查不到 session / 无 trace_id → app_env=None(行为同旧)。

    写放大:仅「带券结果的帧」触发(同 §5),每帧多一次 session_app_env 小查询(按 trace_id 唯一索引),可忽略。

13.4 admin 聚合(新 repo 函数)

app/admin/repositories/coupon_data.py 新增 coupon_slot_report(db, *, date_from, date_to, app_env)(与 coupon_user_records 同居本文件,同属「领券数据」看板;更新模块 docstring 注明本文件现读 coupon_session + coupon_claim_record 两源):

  • SELECT coupon_id, MAX(coupon_name) AS coupon_name, COUNT(*) AS tried, SUM(CASE WHEN status IN (success,already_claimed) THEN 1 ELSE 0 END) AS succeeded WHERE claim_date ∈ [from,to] AND status IN (success,already_claimed,failed) (+ AND app_env = :env 当 env 非「全部」) GROUP BY coupon_id
  • 每行:platform = coupon_id_to_platform(coupon_id)(复用前缀映射,无法识别→None),success_rate = round(succeeded/tried, 4)
  • tried 倒序、coupon_id 次序返回 {"items": [...]}

13.5 接口 + schema(新子端点)

  • 路由 app/admin/routers/coupon_data.py:加 @router.get("/coupons")get_coupon_slots,参数 date_from / date_to / app_env(同主 report 的解析与 _MAX_RANGE_DAYS 校验,app_env="all"→None),调 coupon_slot_report。路径全称 /admin/api/coupon-data/coupons(与现有 /coupon-data/user-records 同款子端点)。
  • schema app/admin/schemas/coupon_data.py:
    • CouponSlotRow{coupon_id: str, coupon_name: str|None, platform: str|None, tried: int, succeeded: int, success_rate: float|None}
    • CouponSlotsOut{date_from: str, date_to: str, items: list[CouponSlotRow]}

13.6 前端

shaguabijia-admin-web/src/app/(main)/coupon-data/page.tsx:

  • 汇总卡下方(或趋势图下方)新增一张「按券成功率」表:列 券名(coupon_name || coupon_id)/ 平台(美团/淘宝/京东/其他)/ 尝试 / 成功 / 成功率(fmtPct)。默认按尝试倒序;支持 antd 列排序。
  • 点「查询」时,除主 report 外并行 api.get('/admin/api/coupon-data/coupons', {params:{date_from,date_to,app_env}});空则不显示表。
  • 新增内联类型 CouponSlotRow;平台名复用映射(meituan-waimai→美团 等,null→其他)。

13.7 测试

  • 后端:record_claims stamp app_env(INSERT/UPDATE 两路);session_app_env 助手;/step 集成写入 app_env;coupon_slot_report 聚合(多券 × success/already_claimed/failed/skipped × 两 env:断言 tried/succeeded/rate、skipped 排除、env 过滤、排序);schema 契约。
  • 前端:tsc --noEmit

13.8 不做(YAGNI)

  • 每次点击级成功率(需换数据源);成功率趋势线;app_env 历史回填(可选一次性 join trace_id→session.app_env,默认不做);券级 tooltip / 明细下钻。
  • 券表为全局聚合,不随页面「用户」搜索框过滤(用户维度非本需求;coupon_claim_record 虽有 user_id,YAGNI)。与主明细表按用户过滤的行为不同,属有意为之。