Files
shaguabijia-admin-web/docs/superpowers/specs/2026-07-11-coupon-point-rate-as-coupon-mean-design.md
T
guke 7269e27e24 feat(coupon-data): 点位成功率改为按券成功率的算术平均(前端从按券明细上卷) (#46)
点位成功率改为按券成功率的算术平均(前端从按券明细上卷)

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #46
2026-07-11 18:48:54 +08:00

102 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 点位成功率改为「按券成功率的算术平均」设计
- **日期**2026-07-11
- **范围**:纯前端 `shaguabijia-admin-web`,**后端零改动、零 schema 改动、零迁移**
- **涉及文件**`src/app/(main)/coupon-data/page.tsx`(单文件)
## 目标
「领券数据」看板汇总卡里的 **分平台点位成功率**(美团/淘宝/京东)与 **点位成功率(合计)**,改为
**该平台(/全部)几张券成功率的算术平均**,让平台数字与下方「按券明细」自洽,便于快速揪出坏券。
## 背景
现状:分平台 / 合计点位成功率来自后端 `_success_rates`**会话口径**,数据源 `coupon_session.platform_success`),
与页面下方「按券成功率」(数据源 `coupon_claim_record`)口径不同,导致同一平台的平台数(如淘宝 71.4%)
与其券明细(100%/80%/80%)对不上,甚至出现「合计低于每个平台」的反直觉现象。
关键点:**本页已经把全量按券数据拉到前端**——`couponSlots`(来自 `/admin/api/coupon-data/coupons`
后端 `coupon_slot_report` 不分页、返回全量),每行含 `platform``success_rate`。因此直接在前端
对其求算术平均即可,**无需后端改动**。
## 口径(改动后)
- **分平台点位成功率** = 该平台所有券 `success_rate` 的算术平均(每张券等权;`success_rate` 为空的券跳过;无券 → 显示 `-`)。
- **点位成功率(合计)** = `couponSlots` 全部券 `success_rate` 的算术平均。
- **每张券成功率**(按券明细,不变):成功(success + already_claimed) ÷ 尝试(+ failed)skipped 排除(后端 `coupon_slot_report`)。
例:淘宝三张券 100% / 80% / 80% → 淘宝点位 = (100+80+80)/3 = **86.7%**
## 不改(其他保持不变)
- **后端**`coupon_slot_report` / `_success_rates` / `CouponDataSummary` schema 全不动。
`summary.per_platform``summary.point_success_rate` 仍会返回(会话口径),只是前端这两处不再引用它们(留着不删,接口稳定)。
- **整单成功率**:会话口径不变。
- **按券明细表**:排序、列等不动。
- **筛选联动**:整单 / 发起 / 完成 / 耗时仍随「用户 / 状态 / 日期 / 环境」全部筛选;
点位随 `couponSlots` 走(= 日期 + 环境,不随「用户 / 状态」搜索变——与按券表现状一致)。
## 实现(单文件 `page.tsx`
### 1. 加一个求均值 helper(放 `fmtPct` 附近)
```ts
// 一组按券行的 success_rate 算术平均;无有效券 → null(显示 -)
const slotRateMean = (rows: CouponSlotRow[]): number | null => {
const rates = rows
.map((r) => r.success_rate)
.filter((v): v is number => v != null);
return rates.length ? rates.reduce((a, b) => a + b, 0) / rates.length : null;
};
```
### 2. 合计:改 value
```tsx
value={fmtPct(summary.point_success_rate)}
```
改成
```tsx
value={fmtPct(slotRateMean(couponSlots))}
```
### 3. 分平台卡:改 rate 来源
把每个平台卡里的
```tsx
const rate = summary.per_platform?.[pid];
```
改成
```tsx
const rate = slotRateMean(couponSlots.filter((r) => r.platform === pid));
```
### 4. 更新口径 tooltip 文案
`POINT_RATE_HINT` 改成(去掉旧的会话口径描述):
```ts
const POINT_RATE_HINT =
'点位成功率(合计):当前区间内全部券成功率的算术平均(每张券等权);' +
'单券成功率=成功(含已领)÷尝试(不含跳过),源自领券每券记录。';
```
分平台卡标题(`${SLOT_PLATFORM[pid]}点位成功率`)如需补口径,注明「该平台各券成功率的算术平均」。
## 边角 / 注意
- **精度**`success_rate` 后端已 round 到 4 位,前端再平均,展示只到 1 位小数(`fmtPct`),误差可忽略。
- **依赖按券请求**:点位现在依赖 `couponSlots`(独立请求,失败时 `setCouponSlots([])`)。若该请求失败,点位卡显示 `-`(优雅降级)。此前它来自主 `summary` 请求。
- **platform=null 的券**coupon_id 前缀无法识别的券(实践中 pricebot 都是 `mt_`/`tb_`/`ele_`/`elm_`/`jd_`,基本不出现)会计入 **合计**、但不在任何分平台卡里。可接受。
## 验证
- **手动**:选一天,展开某平台「查看明细」,核对该平台卡数字 = 明细表各券成功率的算术平均;合计 = 全部券的算术平均。
- `npx tsc --noEmit` 通过;`npm run lint` 无新增 error。
## 不做(YAGNI
- 最小尝试数阈值 / 低量券滤噪 / 坏券标红。
- 按券表默认升序(坏券置顶)。
- 「数据大盘」overview 的领券点位成功率(另一页、另一口径)。
- 删除后端已不再被前端使用的 `per_platform` / `point_success_rate` 字段。