fix(achievement): 重算时重建增量辅助键,避免指标回退
This commit is contained in:
@@ -41,6 +41,9 @@ class Command(BaseCommand):
|
|||||||
# None 表示该指标无有效值,key 必须缺席而不是置 0
|
# None 表示该指标无有效值,key 必须缺席而不是置 0
|
||||||
if value is not None:
|
if value is not None:
|
||||||
metrics[key] = value
|
metrics[key] = value
|
||||||
|
# 一并重建增量辅助键,否则重算后的第一次判题会把
|
||||||
|
# active_days / languages_used / max_ac_in_one_day 打回 1
|
||||||
|
metrics.update(m.recompute_state(user))
|
||||||
stat.metrics = metrics
|
stat.metrics = metrics
|
||||||
stat.save(update_fields=["metrics", "update_time"])
|
stat.save(update_fields=["metrics", "update_time"])
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ class BaseMetric:
|
|||||||
"""全量重算。返回 None 表示该用户此指标无有效值。"""
|
"""全量重算。返回 None 表示该用户此指标无有效值。"""
|
||||||
raise NotImplementedError
|
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 metric(key, name, help_text="", meta=False):
|
||||||
def deco(cls):
|
def deco(cls):
|
||||||
@@ -119,6 +128,10 @@ class ActiveDays(BaseMetric):
|
|||||||
dates = {timezone.localtime(t).date().isoformat() for t in _practice_submissions(user.id).values_list("create_time", flat=True)}
|
dates = {timezone.localtime(t).date().isoformat() for t in _practice_submissions(user.id).values_list("create_time", flat=True)}
|
||||||
return len(dates)
|
return len(dates)
|
||||||
|
|
||||||
|
def recompute_state(self, user):
|
||||||
|
dates = sorted({timezone.localtime(t).date().isoformat() for t in _practice_submissions(user.id).values_list("create_time", flat=True)})
|
||||||
|
return {"_active_dates": dates}
|
||||||
|
|
||||||
|
|
||||||
@metric("max_ac_streak_days", "最长连续 AC 天数", "连续每天至少 AC 一题的最长天数")
|
@metric("max_ac_streak_days", "最长连续 AC 天数", "连续每天至少 AC 一题的最长天数")
|
||||||
class MaxAcStreakDays(BaseMetric):
|
class MaxAcStreakDays(BaseMetric):
|
||||||
@@ -148,6 +161,15 @@ class MaxAcStreakDays(BaseMetric):
|
|||||||
best = max(best, current)
|
best = max(best, current)
|
||||||
return best
|
return best
|
||||||
|
|
||||||
|
def recompute_state(self, user):
|
||||||
|
dates = sorted({timezone.localtime(t).date() for t in _practice_submissions(user.id).filter(result__in=ACCEPTED_RESULTS).values_list("create_time", flat=True)})
|
||||||
|
if not dates:
|
||||||
|
return {}
|
||||||
|
current = 1
|
||||||
|
for prev, cur in zip(dates, dates[1:]):
|
||||||
|
current = current + 1 if (cur - prev).days == 1 else 1
|
||||||
|
return {"_last_ac_date": dates[-1].isoformat(), "_current_ac_streak": current}
|
||||||
|
|
||||||
|
|
||||||
@metric("languages_used", "使用语言数", "用过多少种编程语言")
|
@metric("languages_used", "使用语言数", "用过多少种编程语言")
|
||||||
class LanguagesUsed(BaseMetric):
|
class LanguagesUsed(BaseMetric):
|
||||||
@@ -161,6 +183,9 @@ class LanguagesUsed(BaseMetric):
|
|||||||
def recompute(self, user):
|
def recompute(self, user):
|
||||||
return _practice_submissions(user.id).values("language").distinct().count()
|
return _practice_submissions(user.id).values("language").distinct().count()
|
||||||
|
|
||||||
|
def recompute_state(self, user):
|
||||||
|
return {"_languages": list(_practice_submissions(user.id).values_list("language", flat=True).distinct())}
|
||||||
|
|
||||||
|
|
||||||
@metric("contest_joined", "参赛场次", "参加过的比赛数量(本指标是比赛维度,不受比赛提交不计入的限制)")
|
@metric("contest_joined", "参赛场次", "参加过的比赛数量(本指标是比赛维度,不受比赛提交不计入的限制)")
|
||||||
class ContestJoined(BaseMetric):
|
class ContestJoined(BaseMetric):
|
||||||
@@ -275,6 +300,17 @@ class MaxAcInOneDay(BaseMetric):
|
|||||||
counts[day] = counts.get(day, 0) + 1
|
counts[day] = counts.get(day, 0) + 1
|
||||||
return max(counts.values()) if counts else None
|
return max(counts.values()) if counts else None
|
||||||
|
|
||||||
|
def recompute_state(self, user):
|
||||||
|
counts = {}
|
||||||
|
seen = set()
|
||||||
|
for s in _practice_submissions(user.id).filter(result__in=ACCEPTED_RESULTS).order_by("create_time").values("problem_id", "create_time"):
|
||||||
|
if s["problem_id"] in seen:
|
||||||
|
continue
|
||||||
|
seen.add(s["problem_id"])
|
||||||
|
day = timezone.localtime(s["create_time"]).date().isoformat()
|
||||||
|
counts[day] = counts.get(day, 0) + 1
|
||||||
|
return {"_ac_per_day": counts}
|
||||||
|
|
||||||
|
|
||||||
@metric("min_ac_code_chars", "最短 AC 代码", "通过的代码里最短的字符数(配小于等于使用)")
|
@metric("min_ac_code_chars", "最短 AC 代码", "通过的代码里最短的字符数(配小于等于使用)")
|
||||||
class MinAcCodeChars(BaseMetric):
|
class MinAcCodeChars(BaseMetric):
|
||||||
|
|||||||
Reference in New Issue
Block a user