Files
Lottery/docs/PRD-BIZ102-记录比对中奖金额展示.md
vincent e4e66f3826 BIZ-102 v1.1: 记录-比对 中奖金额展示 PRD 与 UI 原型
v1.0: 3 字段增量扩展(prize_amount/total_prize/prize_summary)+ 前端 3 处扩展
v1.1: 落实架构评审 3 点意见
- 金额字段统一'(单位:元)'与量级安全说明
- prize_summary key 改英文枚举(first_prize..sixth_prize),新增 prize_level 中文名
- floating 标记落 results[i] 与 prize_summary[key] 两粒度
- 兼容性条款:每次实时计算,不依赖持久化

Co-authored-by: multica-agent <github@multica.ai>
2026-08-30 15:19:33 +08:00

188 lines
12 KiB
Markdown
Raw Permalink 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.
# 产品需求文档(PRD):记录-比对 中奖金额展示
> **编号**BIZ-102BIZ-101 子任务1
> **负责人**:沈路明(productmanager
> **版本**v1.1
> **状态**:架构评审通过(条件式已落实),待设计与开发评审
> **父任务**BIZ-101「webui 记录-比对 中奖金额展示」(CO 已批准)
> **关联系统**:双色球 WebUI(后端 Flask `app.py` + 前端 `index.html`
---
## 1. 背景与目标
### 1.1 业务背景
当前 `POST/GET /api/records/<id>/compare` 已完成「是否中奖 + 几等奖」的比对,前端弹窗展示每注的奖项徽标与红球/蓝球命中情况,但**不展示中奖金额**。运营人员与刘总在查看比对结果时,只能看到「x 注中奖」,无法直观获知本次生成号码的**总中奖金额**与**各奖级分布金额**,不利于快速评估生成策略的价值与复盘。
### 1.2 解决的问题
- 运营/管理层查看比对结果时,缺少「中奖金额」这一核心经营指标。
- 现有比对只给出奖项枚举,无法按奖级聚合金额,复盘成本结构困难。
### 1.3 成功指标(可量化)
- [ ] 比对弹窗统计区新增「总中奖金额」与「各奖级金额分布」两项,覆盖率 100%。
- [ ] 每条中奖注在奖项徽标旁展示该注中奖金额,覆盖率 100%。
- [ ] 后端响应新增字段后,旧版前端(不消费新字段)功能不受影响,回归通过率 100%。
- [ ] 中奖金额计算口径在 UI 中对浮动奖显式标注「以官方公布为准」,避免误导。
---
## 2. 用户故事
- **作为运营人员**,我希望在比对弹窗中一眼看到「本次生成共中奖多少元、各奖级各多少元」,以便快速汇报生成效果,无需人工逐注累加。
- **作为刘总**,我希望查看每注的中奖金额与总额,以便评估号码生成策略的投入产出,并在浮动奖场景下明确知晓金额口径来源。
- **作为开发(徐聪)**,我希望后端以结构化字段(`prize_amount` / `total_prize` / `prize_summary`)一次性返回金额,前端直接消费,避免前端重复实现中奖金额计算逻辑。
---
## 3. 功能需求
### 3.1 后端 API 扩展(`app.py` `/api/records/<id>/compare`
现有响应 `data` 已包含:`status, gen_time, compare_type, compare_rule, draw{...}, results[], total, win_count, win_tickets[]`
每条 `results[i]` 已包含:`index, reds, blue, red_matches, blue_match, prize_level, prize_desc, is_win`
本需求在**不改动既有字段**前提下做增量扩展:
| 功能点 | 位置 | 字段 | 类型 | 描述 | 优先级 | 验收标准 |
|--------|------|------|------|------|--------|----------|
| F-1 每注中奖金额 | `results[i]` | `prize_amount` | int(单位:元) | 该注中奖金额(元),未中奖为 `0` | P0 | 中奖注 `prize_amount` = 该奖级单注金额;未中奖注恒为 `0` |
| F-2 总中奖金额 | `data` 顶层 | `total_prize` | int(单位:元) | 所有中奖注金额合计(元) | P0 | `total_prize` = Σ `results[i].prize_amount`,与前端展示一致 |
| F-3 按奖级汇总 | `data` 顶层 | `prize_summary` | dict | 按奖项分组汇总,仅含实际出现的奖项 | P0 | key 为英文枚举(见 3.3),每项含 `{count, amount, prize_level, floating}`;未出现奖级不出现 |
| F-3.1 单注浮动标记 | `results[i]` | `floating` | bool | 该注是否属浮动奖(一/二/三等奖);固定奖或未中奖为 `false` | P1 | 浮动奖注 `floating = true`,固定奖注 `floating = false` |
**字段类型与单位**v1.1 落实架构评审 #1):
- 所有金额字段(`prize_amount` / `total_prize` / `prize_summary[].amount`)均为 **整数****单位:元(非分)**。
- 量级说明:双色球一等奖历史最高单注超 1500 万元,Python int 无溢出(任意精度);前端 JS `Number``2^53 ≈ 9007 万亿` 以下安全,本场景安全。
**边界**
- `status === 'waiting'`(等待开奖)的响应**不新增**上述字段(无开奖数据,无法计算)。
- 浮动奖(一/二/三等奖)单注金额来源见第 4 节;若取不到动态金额,使用占位值并通过 F-3.1 的 `floating: true` 提示前端「金额以官方公布为准」。
### 3.2 前端展示扩展(`index.html` `compareRecord()` 比对弹窗)
现有弹窗结构(`.compare-summary` 含 4 项:生成时间 / 比对开奖期 / 总注数 / 中奖注数)保持不变,做增量插入:
| 功能点 | 位置 | 描述 | 优先级 | 验收标准 |
|--------|------|------|--------|----------|
| F-4 统计区-总额 | `.compare-summary` 内 | 新增「总中奖金额 `<total_prize>` 元」 | P0 | 使用 `data.total_prize``0` 时显示「0 元」 |
| F-5 统计区-分布 | `.compare-summary` 下方 | 遍历 `prize_summary`,逐行展示「`<count>``<奖级>``<amount>` 元」 | P0 | 仅展示 `count > 0` 的奖级;每行一条 |
| F-6 每注金额 | 每注 `.compare-ticket` | 在 `prize-badge`(几等奖)旁额外显示该注金额,如「`五等奖 10元`」 | P0 | 使用 `r.prize_amount`;未中奖注不显示金额 |
**展示文案规范**
- 统计区总额:`总中奖金额 25 元`
- 分布行:`1 注 五等奖 共 10 元` / `3 注 六等奖 共 15 元`
- 每注:`五等奖 10元`(浮动奖额外追加 `· 浮动以官方为准` 角标,角标数据源为 `r.floating === true`,见第 4 节)
**前端字段消费映射**
- 统计区总额 ← `data.total_prize`
- 分布行每条 ← `Object.entries(data.prize_summary)` 遍历,每项含 `count` / `amount` / `prize_level`(中文显示名)/ `floating`
- 每注金额 ← `r.prize_amount`,浮动角标 ← `r.floating`
---
## 3.3 `prize_summary` 字段结构与 key 命名规范(v1.1 落实架构评审 #2)
采用**方案 A(推荐)**`prize_summary` 的 key 使用**英文枚举**,每项内显式带中文 `prize_level` 字段供前端展示。
**理由**
- **[常识]** JSON key 使用中文在日志检索、grep、跨语言消费、后期 i18n 时易踩坑;英文枚举是更稳定的契约。
- 前端用映射表把英文 key 翻译成中文显示名,仅多写一处常量映射,零成本。
`prize_summary` 结构示例:
```json
{
"fifth_prize": {"count": 1, "amount": 10, "prize_level": "五等奖", "floating": false},
"sixth_prize": {"count": 3, "amount": 15, "prize_level": "六等奖", "floating": false}
}
```
| key | prize_level | 类型 | 中奖条件 |
|------|------|------|----------|
| `first_prize` | 一等奖 | 浮动 | 6红+1蓝 |
| `second_prize` | 二等奖 | 浮动 | 6红+0蓝 |
| `third_prize` | 三等奖 | 浮动 | 5红+1蓝 |
| `fourth_prize` | 四等奖 | 固定 | 5红+0蓝 或 4红+1蓝 |
| `fifth_prize` | 五等奖 | 固定 | 4红+0蓝 或 3红+1蓝 |
| `sixth_prize` | 六等奖 | 固定 | 2红+1蓝 或 1红+1蓝 或 0红+1蓝 |
字段含义:
- `prize_summary[key].count` = 该奖级中奖注数
- `prize_summary[key].amount` = `count × 该奖级单注金额`(元,整数)
- `prize_summary[key].prize_level` = 中文显示名(前端直接展示)
- `prize_summary[key].floating` = 是否浮动奖(前端决定是否展示「以官方公布为准」角标)
- 仅当 `count > 0` 的奖级出现在 `prize_summary` 中(未中奖注不计入)
## 4. 中奖金额口径与浮动奖说明
### 4.1 单注奖级金额表
| 奖项 | 单注金额(元) | 类型 | 中奖条件 | 声明 |
|------|---------------|------|----------|------|
| 一等奖 | 浮动(官方公布) | 浮动 | 6红+1蓝 | [已知] 双色球一等奖为浮动奖池 |
| 二等奖 | 浮动(官方公布) | 浮动 | 6红+0蓝 | [已知] 二等奖为浮动奖池 |
| 三等奖 | 浮动(官方公布) | 浮动 | 5红+1蓝 | [已知] 三等奖为浮动奖池 |
| 四等奖 | 200 | 固定 | 5红+0蓝 或 4红+1蓝 | [已知] 中彩中心固定奖级 |
| 五等奖 | 10 | 固定 | 4红+0蓝 或 3红+1蓝 | [已知] 中彩中心固定奖级 |
| 六等奖 | 5 | 固定 | 2红+1蓝 或 1红+1蓝 或 0红+1蓝 | [已知] 中彩中心固定奖级 |
> 固定奖级(四/五/六等奖)金额由中彩中心规则固定,可直接硬编码;浮动奖(一/二/三等奖)实际金额随奖池浮动。
### 4.2 浮动奖处理规则(关键决策点)
[推论] 本迭代先采用**单注固定/占位金额**驱动展示,浮动奖金额本期以「占位值 + UI 显式标注」呈现,待数据源(开奖数据接口)能提供每期实际奖金后再切换为动态读取。
- **数据源可读取实际奖金时**`prize_amount` 取接口返回的实际单注金额;`prize_summary``amount` 按实际金额计算。
- **数据源不可读取时(本期默认)**:浮动奖使用占位值(建议一等奖 5,000,000 / 二等奖 200,000 / 三等奖 3,000 作占位,**[猜测]** 具体占位值待刘总确认),并在 UI 该奖级旁追加角标「`浮动·以官方公布为准`」。
- **Fallback 策略**:优先实际金额 → 缺失时占位值 + 标注。
### 4.3 计算边界
- `prize_amount` 一律为非负整数(元),未中奖 = `0`
- `total_prize` = Σ `results[i].prize_amount`,由后端聚合,前端不做二次计算。
- `prize_summary[key].amount` = `count × 该奖级单注金额`,由后端聚合。
- 每次比对请求都**重新计算**上述字段,**不持久化到数据库**(v1.1 落实架构评审兼容性补充:避免老记录因历史缺字段导致新版本回放异常)。
---
## 5. 兼容性(不影响现有比对流程)
- [ ] 新增字段为**纯增量**,旧版前端未消费新字段时比对弹窗功能不变。
- [ ] 旧版后端(无新字段)对新版前端:前端需做字段存在性判断(`data.total_prize ?? 0`),避免 `undefined` 渲染异常。
- [ ] `status === 'waiting'` 响应结构不变,不新增字段。
- [ ] 比对核心逻辑(奖项判定、命中统计)零改动,仅追加金额计算分支。
---
## 6. 验收标准
### 后端
1. 中奖记录比对响应中,`results[i]``prize_amount`(int,单位:元),且中奖注 = 对应奖级单注金额、未中奖注 = `0`
2. `data.total_prize`int,单位:元)= 所有中奖注 `prize_amount` 之和。
3. `data.prize_summary` 仅含 `count > 0` 的奖级;key 为英文枚举(`first_prize` ... `sixth_prize`);每项含 `{count, amount, prize_level, floating}`,且 `amount = count × 单注金额`
4. `results[i].floating` 在浮动奖(一/二/三等奖)注上为 `true`,固定奖与未中奖注为 `false`
5. `waiting` 状态下不返回上述字段。
6. 字段每次实时计算,不依赖持久化(老记录重新比对也能正确返回新字段)。
### 前端
7. 比对弹窗统计区显示「总中奖金额 <total_prize> 元」。
8. 弹窗展示各奖级分布行「x 注 y 等奖 共 z 元」,仅 `count > 0` 奖级;显示名取 `prize_summary[key].prize_level`
9. 每条中奖注在奖项徽标旁显示该注金额;未中奖注不显示金额。
10. `floating: true` 的注/奖级在 UI 显式标注「以官方公布为准」。
### 回归
9. 不消费新字段的旧逻辑(命中统计、奖项徽标)行为不变。
10. 字段缺失时前端不报错(存在性兜底)。
---
## 7. 范围与边界(与 BIZ-101 的关系)
- **本卡(BIZ-102**:仅产出 PRD + UI 原型,不写后端/前端代码,不做部署。
- **后续链路**:本卡 `in_review` → 产研交叉评审(@梁思筑 @徐聪 @苏锦绘)→ 通过后进入 BIZ-101 子任务2(开发落地)。
- 中奖金额的**具体数值**(尤其浮动奖占位值)由本 PRD 决策,落地细节在子任务2 实现。
---
## 版本历史
| 版本 | 日期 | 作者 | 说明 |
|------|------|------|------|
| v1.0 | 2026-08-30 11:57 | 沈路明 | 首版 PRD,基于现有 `app.py` compare 与 `index.html` compareRecord 结构增量设计 |
| v1.1 | 2026-08-30 14:35 | 沈路明 | 落实架构评审(梁思筑)3 点意见:① 金额字段追加 `(单位:元)` 标注与量级安全说明;② `prize_summary` key 改用英文枚举(方案 A),新增 `prize_level` 中文显示名字段;③ 浮动标记 `floating` 落到 `results[i]``prize_summary[key]` 两处粒度;④ 验收标准补充兼容性「每次实时计算」条款 |