chore: 归档fix-horse-racing-issues提案
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 统一积分服务接入
|
||||
系统 SHALL 在插件加载时声明对 `danding_points` 的依赖,并通过 `points_api` 完成所有积分相关操作。所有积分流水的 `source` 参数 MUST 固定为 `horse_race`。`points_api` 调用失败时系统 MUST 先区分失败类型:余额不足直接拒绝不重试,网络/服务异常则重试 1 次并间隔 1 秒;仍然失败则记录错误日志并向用户返回服务异常提示,不进行后续状态变更。
|
||||
|
||||
#### Scenario: 插件加载时声明积分依赖
|
||||
- **WHEN** 群赛马插件初始化
|
||||
- **THEN** 系统先执行 `require("danding_points")`,并从 `danding_bot.plugins.danding_points` 导入 `points_api`
|
||||
|
||||
#### Scenario: 下注扣款写入统一来源
|
||||
- **WHEN** 用户成功下注 100 积分
|
||||
- **THEN** 系统调用 `points_api.spend_points(user_id, 100, "horse_race", "赛马下注")`
|
||||
|
||||
#### Scenario: 积分服务余额不足时直接拒绝
|
||||
- **WHEN** 系统调用 `points_api.spend_points` 首次返回 `(False, balance)` 且余额低于下注额
|
||||
- **THEN** 系统直接向用户提示积分不足,不进行重试
|
||||
|
||||
#### Scenario: 积分服务网络异常时带延时重试
|
||||
- **WHEN** 系统调用 `points_api.spend_points` 首次抛出网络异常或服务异常
|
||||
- **THEN** 系统等待 1 秒后重试同一调用 1 次;若仍失败,则向用户返回"积分服务异常,请稍后重试",不写入下注记录、不变更房间状态
|
||||
|
||||
### Requirement: 赔率计算与展示
|
||||
系统 SHALL 使用标准池赔率公式计算每匹马的赔率:`odds = total_pool / horse_pool`,其中 `total_pool` 为当前比赛所有马匹的下注总额,`horse_pool` 为目标马匹的下注总额。当 `horse_pool = 0` 时(无人下注的马匹),赔率 MUST 设为 `None` 且不对外展示。系统 SHALL 在展示和结算时应用 `MIN_ODDS` 最低赔率保护,但仅当应用后总赔付不超过总池子时生效;若应用 `MIN_ODDS` 会导致该马赔付总额超过 `total_pool`,则 MUST 使用真实赔率。赔率展示 MUST 对当前房间内所有已下注马匹可用,精度保留到小数点后两位。结算金额 MUST 向下取整为整数积分。结算时 MUST 使用锁内快照赔率而非重新计算,确保与用户下注时看到的赔率一致。
|
||||
|
||||
#### Scenario: 标准赔率计算
|
||||
- **WHEN** 总下注池为 800,某马下注额为 500
|
||||
- **THEN** 该马赔率计算为 `800 / 500 = 1.6`
|
||||
|
||||
#### Scenario: 无人下注的马匹
|
||||
- **WHEN** 某匹马没有收到任何下注(`horse_pool = 0`)
|
||||
- **THEN** 该马赔率为 `None`,不在赔率列表中展示
|
||||
|
||||
#### Scenario: 热门马赔率触发下限保护且池子可承担
|
||||
- **WHEN** 某匹马计算赔率为 `1.08`,`MIN_ODDS=1.2`,该马下注总额为 500,总池为 800
|
||||
- **THEN** 应用保底赔率 1.2 后该马赔付总额为 600(≤800),系统对外展示和结算均使用 `1.2`
|
||||
|
||||
#### Scenario: 热门马赔率触发下限但池子不可承担
|
||||
- **WHEN** 某匹马计算赔率为 `1.08`,`MIN_ODDS=1.2`,该马下注总额为 750,总池为 800
|
||||
- **THEN** 应用保底赔率 1.2 后该马赔付总额为 900(>800),系统不应用保底,使用真实赔率 `1.08`
|
||||
|
||||
#### Scenario: 赔率精度与取整
|
||||
- **WHEN** 赔率计算结果为 `1.5238...`
|
||||
- **THEN** 展示为 `1.52`,结算时 `bet_amount × 1.52` 向下取整为整数积分
|
||||
|
||||
#### Scenario: 显示当前赔率
|
||||
- **WHEN** 用户发送显示赔率命令
|
||||
- **THEN** 系统返回当前房间所有已下注马匹的赔率明细,格式为"马匹名: 赔率值"
|
||||
|
||||
#### Scenario: 结算使用快照赔率
|
||||
- **WHEN** 比赛结束进入结算流程
|
||||
- **THEN** 系统在持锁状态下计算赔率快照并用于所有赔付计算,不依赖比赛开始前保存的可能已过时的赔率
|
||||
@@ -0,0 +1,46 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 房间作用域与创建
|
||||
系统 SHALL 按会话作用域管理赛马房间。群聊场景 MUST 以 `group_id` 作为房间键;测试模式私聊场景 MUST 以 `test_<user_id>` 作为房间键。单一作用域同一时间 SHALL 最多存在一个活动房间。房间在首位用户发送报名命令且该作用域不存在活动房间时自动创建。`finished` 或 `interrupted` 房间在结果公告后自动释放,允许同作用域立即创建新房间。正式模式下群聊 MUST 仅对 `ALLOWED_GROUPS` 配置列表中的群开放。
|
||||
|
||||
#### Scenario: 群聊首次报名自动建房
|
||||
- **WHEN** 群 `10001` 中第一位用户发送报名命令且该群不存在活动房间
|
||||
- **THEN** 系统创建属于 `group_id=10001` 的新房间,并将该用户登记为首位参赛者
|
||||
|
||||
#### Scenario: 私聊测试使用独立房间键
|
||||
- **WHEN** 测试人员在私聊中发送报名命令且 `TEST_MODE=True`
|
||||
- **THEN** 系统创建房间键为 `test_<user_id>` 的测试房间,不影响任何群聊房间
|
||||
|
||||
#### Scenario: 正式模式下非白名单群拒绝
|
||||
- **WHEN** `TEST_MODE=False` 且用户在不在 `ALLOWED_GROUPS` 列表中的群发送赛马命令
|
||||
- **THEN** 系统拒绝请求,并提示该群未开放赛马功能
|
||||
|
||||
#### Scenario: 比赛结束后立即可建新房
|
||||
- **WHEN** 某群的房间状态为 `finished` 且结果已公告
|
||||
- **THEN** 系统释放该房间,用户再次发送报名命令可创建新房间
|
||||
|
||||
### Requirement: 房间状态恢复
|
||||
系统 SHALL 在关键状态变化后持久化活动房间快照,并在插件重启后重建可恢复房间。关键持久化点包括:房间创建、报名/取消报名、下注/退还、比赛开始、比赛结束、房间取消。若重启后发现 `running` 状态的房间,系统 MUST 将其标记为 `interrupted`,自动退还所有有效下注,并向用户发送中断通知。持久化数据 MUST 包含:房间元数据(scope、状态、创建时间)、马匹列表(owner、名称、位置、状态)、下注记录(user_id、马匹名、金额)、当前赔率快照。数据库操作 MUST 使用 `aiosqlite` 进行异步 IO,避免阻塞 NoneBot 事件循环。
|
||||
|
||||
#### Scenario: 重启后恢复等待中的房间
|
||||
- **WHEN** 插件重启前某群房间状态为 `waiting` 且快照已落盘
|
||||
- **THEN** 插件启动时重新加载该房间,使用户可以继续报名、下注或开赛
|
||||
|
||||
#### Scenario: 重启后处理中断中的比赛
|
||||
- **WHEN** 插件重启前某房间状态为 `running` 但推进任务已丢失
|
||||
- **THEN** 插件启动时将房间标记为 `interrupted`,退还所有有效下注,并通知用户该场比赛已因中断取消
|
||||
|
||||
#### Scenario: 数据库操作异步化
|
||||
- **WHEN** 系统执行任何 SQLite 持久化操作
|
||||
- **THEN** 系统通过 `aiosqlite` 异步连接执行,不阻塞事件循环
|
||||
|
||||
### Requirement: 比赛结果持久化
|
||||
系统 SHALL 在每场比赛结束后将结果持久化到 `data/group_horse_racing/race.db` 的历史记录表中。记录 MUST 包含:比赛 ID、作用域、冠军马匹名称和主人、所有参赛者列表、下注分布概要(各马下注总额和人次)、比赛时长(tick 数)、完成时间戳、积分变化详情(每位用户的赔付/奖励/退还金额及原因摘要)。历史数据不设置自动清理策略。
|
||||
|
||||
#### Scenario: 比赛结束后保存历史
|
||||
- **WHEN** 一场比赛完成结算
|
||||
- **THEN** 系统将比赛结果写入历史记录表,包含冠军信息、参赛者、下注分布、比赛时长和积分变化详情
|
||||
|
||||
#### Scenario: 查询历史时显示积分变化
|
||||
- **WHEN** 用户查询某场比赛的历史记录
|
||||
- **THEN** 系统返回包含每位用户的赔付/奖励/退还金额及原因的积分变化摘要
|
||||
@@ -0,0 +1,20 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 测试命令权限与隔离
|
||||
测试命令 MUST 仅在 `TEST_MODE=True` 时可用。测试房间键 MUST 使用 `test_<user_id>` 格式,与群聊房间完全隔离。`_FakeBot`、`_InMemoryRoomStore`、`_InMemoryPointsService` 等测试替身 MUST 仅在测试上下文中实例化,不得替换或污染生产环境的 `room_store` 和 `points_service` 实例。
|
||||
|
||||
#### Scenario: 测试模式开启时测试命令可用
|
||||
- **WHEN** `TEST_MODE=True` 且用户具备 `TESTER_IDS` 权限
|
||||
- **THEN** 系统接受测试命令并执行
|
||||
|
||||
#### Scenario: 测试模式关闭时测试命令不可用
|
||||
- **WHEN** `TEST_MODE=False`
|
||||
- **THEN** 系统拒绝测试命令,提示未开放
|
||||
|
||||
#### Scenario: 测试房间与群聊房间隔离
|
||||
- **WHEN** 测试用户在私聊创建测试房间
|
||||
- **THEN** 该房间使用 `test_<user_id>` 作为键,不影响任何群聊 `group_id` 房间
|
||||
|
||||
#### Scenario: 测试实例不污染生产数据
|
||||
- **WHEN** 测试命令被执行
|
||||
- **THEN** 系统使用独立的 `_InMemoryRoomStore` 和 `_InMemoryPointsService` 实例,不修改生产环境的 `room_store` 和 `points_service` 对象
|
||||
Reference in New Issue
Block a user