This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

View File

@@ -0,0 +1,165 @@
---
name: Stats init 重复分析
overview: 已用生产数据确认auto_first 以 spin_source=1、prize_type=1、prize_amount_qf=init 落库,当前 statistics 会把同一笔开宝箱金额算两遍。修复方向是在 aggregateSpinStats 排除 spin_source=AUTO_FIRST保留 init + 手动 Spin 随机入账相加。
todos:
- id: verify-auto-first-row
content: 确认 slot_console 开宝箱 auto_first 记录的 prize_type / spin_source / prize_amount_qf 落库值
status: completed
- id: exclude-auto-first-stats
content: aggregateSpinStats 排除 spin_source=AUTO_FIRST并补 LuckyRewardSpinRecordModel 常量
status: completed
- id: add-stats-test
content: 补统计用例:仅开宝箱无手动 Spin 时 total_reward_amount_qf=init 总和,且 spin_total/random_hit_count=0
status: completed
isProject: false
---
# LuckyRewardStatsLogic 统计口径分析
## 直接回答
**不是。** `$spinStats` 里**没有**完整包含 `$initAmountTotalQf`,两者是**刻意分开**再相加的:
```php
// backend/slot_admin/app/game/logic/LuckyRewardStatsLogic.php
$initAmountTotalQf = (int) (clone $playerQuery)->sum('p.init_amount_qf');
$spinStats = $this->aggregateSpinStats($playerQuery);
$prizeAmountTotalQf = (int) ($spinStats['prize_amount_total_qf'] ?? 0);
$totalRewardAmountQf = $initAmountTotalQf + $prizeAmountTotalQf;
```
`$spinStats` 返回的是 **Spin 记录维度** 的聚合,与 player 表上的 `init_amount_qf` 来源不同:
| 字段 | 数据来源 | 含义 |
|------|----------|------|
| `$initAmountTotalQf` | `lucky_reward_player.init_amount_qf` | 开宝箱命中初始金额汇总 |
| `$spinStats['prize_amount_total_qf']` | `lucky_reward_spin_record.prize_amount_qf``prize_type=1` | 随机金额类 Spin **实际入账**汇总 |
| `$spinStats['spin_total']` 等 | `lucky_reward_spin_record` 行数 | 命中次数/比率,**不是金额** |
需求文档口径([02_管理后台方案.md](docs/requirements/lucky_rewards/02_管理后台方案.md) §7.2
> **总奖励金额** = `sum(player.init_amount_qf)` + `sum(spin_record.prize_amount_qf)`(开宝箱初始 + Spin 随机入账 = 实际投放)
因此 **`$initAmountTotalQf` 不能删**`total_reward_amount_qf` 本应是「init + **手动** Spin 随机入账」,不应把 auto_first 展示记录算进 prize 部分。
---
## 生产数据已确认重复计数
用户提供的真实行:
```sql
INSERT INTO lucky_reward_spin_record
(uid, cycle_id, spin_id, spin_source, prize_type, prize_amount_qf, my_amount_after_qf)
VALUES
(1545906, 46, 'auto_first:1545906:46', 1, 1, 9603, 9603);
```
字段解读(与 DDL 一致):
| 字段 | 值 | 含义 |
|------|-----|------|
| `spin_id` | `auto_first:1545906:46` | 开宝箱自动首次 SpinRecord 展示) |
| `spin_source` | `1` | 自动首次 |
| `prize_type` | `1` | 随机金额 |
| `prize_amount_qf` | `9603` | 与 `player.init_amount_qf` 相同 |
对 uid=1545906、cycle=46 若仅开宝箱、无手动 Spin
- `$initAmountTotalQf` = **9603**(来自 player
- `$spinStats['prize_amount_total_qf']` = **9603**auto_first 被 `prize_type=1` 命中)
- `$totalRewardAmountQf` = **19206**(应为 **9603**
同时 **`spin_total` / `random_hit_count`** 也会把 auto_first 算进 Spin 次数与命中率,导致指标失真(每个参与用户天然 +1 次 Spin、+1 次 random 命中)。
---
## `spin_total` 同样算错
**是的。** `$spinStats['spin_total']``lucky_reward_spin_record` **全表 count**,没有任何 `spin_source` 过滤:
```php
$spinTotal = (int) (clone $spinQuery)->count(); // 含 auto_first
```
对你给的 uid `1545906`:只开宝箱、从未手动 Spin 时:
| 指标 | 当前错误值 | 修正后应为 |
|------|------------|------------|
| `spin_total` | **1**auto_first | **0** |
| `random_hit_count` | **1**auto_first 且 prize_type=1 | **0** |
| `random_hit_rate` | **100%** | **0%** |
需求文档 [02_管理后台方案.md](docs/requirements/lucky_rewards/02_管理后台方案.md) §7.3 明细列 **Spin** 用的是 `player.spin_used`,注释为 **「不含初始自动」**;总览 `spin_total` 应与之一致,只统计**真实消耗 Spin 次数**的记录(`spin_source` 为每日/邀请/手动),不应含开宝箱 auto_first。
连带影响的字段(同一 `$spinQuery` 未过滤):
- `spin_total` — 分母偏大
- `random_hit_count` / `random_hit_rate` — 分子、分母都含 auto_first
- `cashout_hit_count` / `cashout_hit_rate` — 若 auto_first 不会 prize_type=3通常不受影响但仍应统一排除
- `spin_prize_hit_count` / `spin_prize_hit_rate` — 同上
- `prize_amount_total_qf` — 金额重复(见上)
修复时在 `$spinQuery` 基查询一次性加 `spin_source <> AUTO_FIRST`,上述字段一并修正。
---
## 根因
[`aggregateSpinStats()`](backend/slot_admin/app/game/logic/LuckyRewardStatsLogic.php) 只按 `prize_type=1` 过滤金额/次数,**未排除 `spin_source=1`auto_first**
```php
$prizeAmountTotalQf = (int) (clone $spinQuery)
->where('sr.prize_type', LuckyRewardSpinRecordModel::PRIZE_TYPE_RANDOM)
->sum('sr.prize_amount_qf');
```
C 端开宝箱写 auto_first 时为了 Record 展示使用了 `prize_type=1` + `prize_amount_qf=init`,与 My Amount 入账无关admin 统计误将其当作「Spin 随机入账」。
需求 [04_slot_console_活动主流程方案.md](docs/requirements/lucky_rewards/04_slot_console_活动主流程方案.md)`auto_first` **仅动画/Record不叠加金额**
```mermaid
flowchart TD
openBox[openBox 开宝箱]
playerInit["player.init_amount_qf = 9603"]
autoFirst["spin_record auto_first prize_amount_qf = 9603"]
statsInit["initAmountTotalQf += 9603"]
statsPrize["prize_amount_total_qf += 9603"]
doubleCount["total_reward = 19206 重复"]
openBox --> playerInit
openBox --> autoFirst
playerInit --> statsInit
autoFirst --> statsPrize
statsInit --> doubleCount
statsPrize --> doubleCount
```
---
## 修复方案(待执行)
在 [`aggregateSpinStats()`](backend/slot_admin/app/game/logic/LuckyRewardStatsLogic.php) 中,**所有** spin 记录聚合(次数与金额)统一排除 auto_first
1. [LuckyRewardSpinRecordModel](backend/slot_admin/app/game/model/common/LuckyRewardSpinRecordModel.php) 增加常量:
- `SPIN_SOURCE_AUTO_FIRST = 1`
- `SPIN_SOURCE_DAILY = 2`
- `SPIN_SOURCE_INVITE = 3`(与 DDL 注释对齐)
2. `$spinQuery` 基查询增加:`->where('sr.spin_source', '<>', SPIN_SOURCE_AUTO_FIRST)`
- 影响:`spin_total``random_hit_count``cashout_hit_count``spin_prize_hit_count``prize_amount_total_qf`
3. **保持** `$totalRewardAmountQf = $initAmountTotalQf + $prizeAmountTotalQf` 不变
4. 补测试:仅开宝箱用户 → `total_reward_amount_qf === sum(init_amount_qf)`auto_first 行存在但不计入 prize
**不改 C 端落库**auto_first 继续用于 Record 展示;仅修正 admin 统计过滤。
**不建议**删掉 `$initAmountTotalQf` 或改为只 sum spin_record。
---
## 小结
| 问题 | 答案 |
|------|------|
| `$spinStats` 是否已包含 `$initAmountTotalQf` | **否**;但 `prize_amount_total_qf` 对 auto_first **重复计入了同一份 init** |
| 两处相加是否合理? | **公式合理**;当前实现因未排除 auto_first **结果错误** |
| 生产数据是否证实? | **是**uid 1545906 示例9603 被算成 19206 |
| `spin_total` 是否也算错? | **是**;仅开宝箱用户会显示 spin_total=1、random_hit_rate=100% |