feat(BIZ-105): 记录-比对 中奖金额展示 v1.2 实施
PRD v1.2 §4.3 落地,参考 BIZ-102 PRD 与 UI 原型。
后端(app.py):
- 新增 PRIZE_AMOUNT / PRIZE_KEY_MAP 常量双表(PRD §4.3)
- 新增 format_amount 千分位格式化(PRD §3.2:紧邻「元」不空格)
- 新增 compute_prize_summary 聚合函数
- api_compare_record 接口扩展:
- 单注:prize_amount + prize_amount_display 字段
- 顶层:total_prize / total_prize_display / has_variable_prize /
prize_summary / prize_display 字段
- 一/二等奖标记为浮动奖(历史数据无奖池字段),不计入 total_prize
前端(index.html):
- 总中奖金额字段(橙色「含浮动奖」角标)
- 中奖奖项汇总表(不含浮动奖金额)
- 浮动奖橙色提示条(区别于固定奖绿色 #1e8e4a)
- 每注金额 badge(固定奖绿 / 浮动奖橙)
测试(tests/test_compare_prize.py):
- 31 个用例,100% 通过
- 覆盖:6 档奖金档 / 未中奖 / waiting 契约 / 0 注 / 全中奖 /
仅有浮动奖 / 混合 / Mock 一致性(验收 #16)/ 响应结构契约
- running: pytest tests/test_compare_prize.py -v → 31 passed
不变项:比对算法 / 归期规则 / Excel 解析 / 认证 — 全部不动。
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -31,6 +31,100 @@ from lottery import DoubleColorBallGenerator
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# ============================================================
|
||||
# 中奖金额常量表(PRD v1.2 BIZ-105 §4.3)
|
||||
# ============================================================
|
||||
# PRIZE_AMOUNT: 奖项 → (单注金额, 是否浮动奖)
|
||||
# 一/二等奖金额官方按奖池浮动,历史数据无奖池字段,标记为浮动,
|
||||
# 不参与 total_prize 汇总,仅在 UI 用橙色角标展示。
|
||||
PRIZE_AMOUNT = {
|
||||
'一等奖': (5000000, True), # 浮动:实际金额按奖池浮动
|
||||
'二等奖': (200000, True), # 浮动:实际金额按奖池浮动
|
||||
'三等奖': (3000, False), # 固定:3,000 元
|
||||
'四等奖': (200, False), # 固定:200 元
|
||||
'五等奖': (10, False), # 固定:10 元
|
||||
'六等奖': (5, False), # 固定:5 元
|
||||
'未中奖': (0, False),
|
||||
}
|
||||
|
||||
# PRIZE_KEY_MAP: 中文奖级 → JSON key(便于前端按固定 key 访问)
|
||||
PRIZE_KEY_MAP = {
|
||||
'一等奖': 'first',
|
||||
'二等奖': 'second',
|
||||
'三等奖': 'third',
|
||||
'四等奖': 'fourth',
|
||||
'五等奖': 'fifth',
|
||||
'六等奖': 'sixth',
|
||||
}
|
||||
|
||||
|
||||
def format_amount(amount):
|
||||
"""金额千分位格式化(PRD v1.2 §3.2:数字紧邻「元」不空格)。
|
||||
|
||||
>>> format_amount(3000)
|
||||
'3,000元'
|
||||
>>> format_amount(5000000)
|
||||
'5,000,000元'
|
||||
>>> format_amount(0)
|
||||
'0元'
|
||||
"""
|
||||
return f'{int(amount):,}元'
|
||||
|
||||
|
||||
def compute_prize_summary(results):
|
||||
"""按 PRD v1.2 §4.3 计算中奖汇总。
|
||||
|
||||
入参:results — 比对结果列表,每项含 'prize_level' 字段。
|
||||
返回:dict,含 total_prize / has_variable_prize / prize_summary / prize_display
|
||||
- total_prize: 固定奖级总金额(不含浮动)
|
||||
- has_variable_prize: 是否存在一/二等奖命中
|
||||
- prize_summary: {key: {count, amount}} 仅含 count > 0 的奖项
|
||||
- prize_display: [{level, count, amount}] 浮动奖不进入展示列表
|
||||
"""
|
||||
# 1. 统计各奖级命中数
|
||||
prize_counts = {}
|
||||
for r in results:
|
||||
level = r.get('prize_level', '未中奖')
|
||||
prize_counts[level] = prize_counts.get(level, 0) + 1
|
||||
|
||||
# 2. 聚合
|
||||
total_prize = 0
|
||||
has_variable_prize = False
|
||||
prize_summary = {}
|
||||
prize_display = []
|
||||
|
||||
for level, count in prize_counts.items():
|
||||
if level == '未中奖' or count <= 0:
|
||||
continue
|
||||
amount, is_float = PRIZE_AMOUNT.get(level, (0, False))
|
||||
key = PRIZE_KEY_MAP.get(level, '')
|
||||
|
||||
if is_float:
|
||||
# 浮动奖:仅标记,不计入 total_prize
|
||||
has_variable_prize = True
|
||||
prize_summary[key] = {'count': count, 'amount': 0}
|
||||
else:
|
||||
# 固定奖:计入 total_prize
|
||||
total_prize += amount * count
|
||||
prize_summary[key] = {'count': count, 'amount': amount * count}
|
||||
|
||||
# 展示列表:浮动奖不进入(按 PRD v1.2 决策 9)
|
||||
if not is_float:
|
||||
prize_display.append({
|
||||
'level': level,
|
||||
'count': count,
|
||||
'amount': format_amount(amount),
|
||||
})
|
||||
|
||||
return {
|
||||
'total_prize': total_prize,
|
||||
'total_prize_display': format_amount(total_prize),
|
||||
'has_variable_prize': has_variable_prize,
|
||||
'prize_summary': prize_summary,
|
||||
'prize_display': prize_display,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 配置
|
||||
# ============================================================
|
||||
@@ -562,6 +656,9 @@ def api_compare_record(record_id):
|
||||
prize_level = '六等奖'
|
||||
prize_desc = f'{red_matches}红+1蓝'
|
||||
|
||||
# 该注中奖金额(PRD v1.2 §后端扩展 1)
|
||||
# 浮动奖实际金额未知,按占位金额展示(前端用橙色角标)
|
||||
ticket_amount, _is_float = PRIZE_AMOUNT.get(prize_level, (0, False))
|
||||
results.append({
|
||||
'index': i + 1,
|
||||
'reds': t['reds'],
|
||||
@@ -570,11 +667,16 @@ def api_compare_record(record_id):
|
||||
'blue_match': blue_match,
|
||||
'prize_level': prize_level,
|
||||
'prize_desc': prize_desc,
|
||||
'prize_amount': ticket_amount, # 该注金额(元,整数)
|
||||
'prize_amount_display': format_amount(ticket_amount), # 该注金额(千分位字符串)
|
||||
'is_win': prize_level != '未中奖'
|
||||
})
|
||||
|
||||
win_count = sum(1 for r in results if r['is_win'])
|
||||
|
||||
# 中奖金额汇总(PRD v1.2 §4.3 + §后端扩展 2)
|
||||
prize_info = compute_prize_summary(results)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
@@ -591,7 +693,13 @@ def api_compare_record(record_id):
|
||||
'results': results,
|
||||
'total': len(results),
|
||||
'win_count': win_count,
|
||||
'win_tickets': [r for r in results if r['is_win']]
|
||||
'win_tickets': [r for r in results if r['is_win']],
|
||||
# PRD v1.2 新增字段
|
||||
'total_prize': prize_info['total_prize'],
|
||||
'total_prize_display': prize_info['total_prize_display'],
|
||||
'has_variable_prize': prize_info['has_variable_prize'],
|
||||
'prize_summary': prize_info['prize_summary'],
|
||||
'prize_display': prize_info['prize_display'],
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user