--- 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` | 开宝箱自动首次 Spin(Record 展示) | | `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% |