Files
OnlineJudge/achievement/management/commands/recompute_achievements.py
yuetsh 13e2f6df08 feat(achievement): 添加全量重算管理命令
新增 recompute_achievements 管理命令:全量重算所有指标并按补发语义
解锁成就,用于首次上线补发存量用户、指标口径变更后重算,以及增量
逻辑漂移时的安全网。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 01:33:20 -06:00

63 lines
2.5 KiB
Python
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.
"""全量重算指标并补发成就。
用途:
1. 系统首次上线,给存量用户补发(用 --silent否则学生一登录被 30 个奖杯糊脸)
2. 新增指标或改了口径后重算(不带 --silent新解锁会正常弹出
这也是增量逻辑的安全网:怀疑 UserStat 漂移时跑一遍即可。
"""
from django.core.management.base import BaseCommand
from account.models import User
from achievement import checker
from achievement.metrics import META_METRICS, METRIC_REGISTRY
from achievement.models import UserStat
class Command(BaseCommand):
help = "重算全部用户的成就指标并补发成就"
def add_arguments(self, parser):
parser.add_argument("--user", type=int, default=None, help="只处理指定用户 id")
parser.add_argument("--silent", action="store_true", help="补发的成就标记为已通知,不弹窗")
def handle(self, *args, **options):
users = User.objects.filter(is_disabled=False)
if options["user"]:
users = users.filter(id=options["user"])
silent = options["silent"]
total_users = users.count()
total_unlocked = 0
for index, user in enumerate(users.iterator(), start=1):
stat, _ = UserStat.objects.get_or_create(user=user)
metrics = {}
for key, m in METRIC_REGISTRY.items():
if key in META_METRICS:
continue
value = m.recompute(user)
# None 表示该指标无有效值key 必须缺席而不是置 0
if value is not None:
metrics[key] = value
stat.metrics = metrics
stat.save(update_fields=["metrics", "update_time"])
records = checker.unlock(user, checker.evaluate(user, metrics), backfilled=True, notified=silent)
# 元指标第二轮
for key in META_METRICS:
value = METRIC_REGISTRY[key].recompute(user)
if value is not None:
metrics[key] = value
stat.metrics = metrics
stat.save(update_fields=["metrics", "update_time"])
records += checker.unlock(user, checker.evaluate(user, metrics, only_metrics=META_METRICS), backfilled=True, notified=silent)
total_unlocked += len(records)
if index % 50 == 0:
self.stdout.write(f"processed {index}/{total_users}")
self.stdout.write(self.style.SUCCESS(f"完成:{total_users} 个用户,新解锁 {total_unlocked}"))