From 72fec2ca615318df69a5928e2e11bb56408f7a44 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 4 Aug 2026 01:36:41 -0600 Subject: [PATCH] =?UTF-8?q?fix(plan):=20=E9=87=8D=E7=AE=97=E5=BF=85?= =?UTF-8?q?=E9=A1=BB=E9=87=8D=E5=BB=BA=E5=A2=9E=E9=87=8F=E8=BE=85=E5=8A=A9?= =?UTF-8?q?=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原计划断言四个依赖辅助键的指标都受 max 语义保护、重算后不会回退, 实测只有 max_ac_streak_days 的最大值成立。另外三个的 on_submission 完全从辅助结构推导,辅助键一丢下次提交就打回 1,上线当天即触发。 新增 BaseMetric.recompute_state 钩子由指标各自重建。 Co-Authored-By: Claude Opus 5 --- .../plans/2026-08-03-achievement-system.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-08-03-achievement-system.md b/docs/superpowers/plans/2026-08-03-achievement-system.md index 7ad0433..6071c8e 100644 --- a/docs/superpowers/plans/2026-08-03-achievement-system.md +++ b/docs/superpowers/plans/2026-08-03-achievement-system.md @@ -296,6 +296,15 @@ class BaseMetric: """全量重算。返回 None 表示该用户此指标无有效值。""" raise NotImplementedError + def recompute_state(self, user): + """重算时一并重建增量所需的辅助键,返回 {key: value}。 + + 默认无辅助状态。凡是 on_submission 依赖 `_` 前缀辅助键的指标都必须重写 + 本方法:否则全量重算把辅助键丢掉后,下一次判题会从零重新累积,指标当场 + 回退(active_days 从 45 掉回 1),且要等到下一次重算才恢复。 + """ + return {} + def metric(key, name, help_text="", meta=False): def deco(cls): @@ -1567,6 +1576,9 @@ class Command(BaseCommand): # None 表示该指标无有效值,key 必须缺席而不是置 0 if value is not None: metrics[key] = value + # 一并重建增量辅助键,否则重算后的第一次判题会把 + # active_days / languages_used / max_ac_in_one_day 打回 1 + metrics.update(m.recompute_state(user)) stat.metrics = metrics stat.save(update_fields=["metrics", "update_time"]) @@ -1590,7 +1602,11 @@ class Command(BaseCommand): self.stdout.write(self.style.SUCCESS(f"完成:{total_users} 个用户,新解锁 {total_unlocked} 条")) ``` -> 注意:重算时丢弃了 `_active_dates` / `_languages` 等下划线前缀的增量辅助 key。这是有意的——`recompute` 直接算出终值,辅助 key 会在下一次判题的 `on_submission` 中自然重建。但 `active_days`、`languages_used`、`max_ac_in_one_day`、`max_ac_streak_days` 这四个指标在重算后的第一次判题会**从辅助 key 缺失的状态重新累积**,可能导致数值短暂偏低。**因此重算后应立刻再跑一次重算是无效的;正确做法是重算命令只在低峰期执行,且执行后不依赖增量值的绝对精确性——这些指标的成就阈值判定用的是 max 语义,不会回退。** +> **重算必须同时重建 `_` 前缀的增量辅助键**,这就是每个指标 `recompute_state()` 的职责。 +> +> 早先这里写着"辅助键会在下一次判题时自然重建、这些指标是 max 语义不会回退"——**这个说法是错的**,四个指标里只有 `max_ac_streak_days` 的最大值受 max 保护。`active_days`、`languages_used`、`max_ac_in_one_day` 的 `on_submission` 完全从辅助结构推导新值、不参考已有顶层值,辅助键一丢,下一次提交就把它们打回 1,且要等下次重算才恢复。`max_ac_streak_days` 虽然最大值不退,但当前连续天数会清零,连了 20 天的学生得重新连 20 天才能刷新纪录。 +> +> 上线当天就会踩到:`--silent` 补发跑完,每个学生的下一次提交都会让这三个指标崩掉。 - [ ] **Step 2: 单用户试跑**