Files
cursor/plans/stats_init_重复分析_3fc8ad4b.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

166 lines
7.4 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.

---
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% |