feat(achievement): 添加按难度的 AC 题数指标,删除 min_ac_code_chars
新增 mid_ac_count / hard_ac_count,去重统计中等和困难难度的 AC 题数。 在此之前所有成就对水题和难题一视同仁,这是最缺的一个维度。 增量走 build_ctx 新增的 problem_difficulty,它只在首次 AC 时才查库—— 绝大多数提交都不是首次 AC,无条件预查等于给每次判题白加一条 SQL。 删除 min_ac_code_chars:线上实测 1314 个用户的分布,最小值 8、p5=10, 有道题 8 个字符就能通过,这个指标测的是"谁做过那道水题"而不是 "谁写得简洁",配不出有意义的成就。 自检里写死 min_ac_code_chars 的两处改成按 lte 成就自动发现: _check_registry 不再要求某个具体指标存在,_check_min_metric_absent 改为遍历所有上架的 lte 成就检查其指标。没有 lte 成就时 SKIP, 将来配了自动开始检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ from django.db.models import Count, Q
|
||||
from django.utils import timezone
|
||||
|
||||
from submission.models import JudgeStatus, Submission, is_accepted
|
||||
from utils.constants import Difficulty
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -81,6 +82,16 @@ def build_ctx(user_id, sub):
|
||||
)
|
||||
local_now = timezone.localtime(sub.create_time)
|
||||
sub_is_accepted = is_accepted(sub.result)
|
||||
is_first_ac_of_problem = sub_is_accepted and prior_stats["accepted"] == 0
|
||||
|
||||
# 难度只有首次 AC 时才用得上,其余情况不查这一次库——
|
||||
# 绝大多数提交都不是首次 AC,放在外面等于给每次判题白加一条 SQL
|
||||
difficulty = None
|
||||
if is_first_ac_of_problem:
|
||||
from problem.models import Problem
|
||||
|
||||
difficulty = Problem.objects.filter(id=sub.problem_id).values_list("difficulty", flat=True).first()
|
||||
|
||||
return {
|
||||
"skip": False,
|
||||
"is_accepted": sub_is_accepted,
|
||||
@@ -88,9 +99,11 @@ def build_ctx(user_id, sub):
|
||||
"prior_count": prior_stats["total"],
|
||||
"prior_accepted": prior_stats["accepted"],
|
||||
# 首次 AC 这道题(此前从未 AC 过)
|
||||
"is_first_ac_of_problem": sub_is_accepted and prior_stats["accepted"] == 0,
|
||||
"is_first_ac_of_problem": is_first_ac_of_problem,
|
||||
# 一发入魂:此前无任何提交且本次 AC
|
||||
"is_first_try_ac": sub_is_accepted and prior_stats["total"] == 0,
|
||||
# 本题难度,仅首次 AC 时有值
|
||||
"problem_difficulty": difficulty,
|
||||
"local_date": local_now.date().isoformat(),
|
||||
"local_hour": local_now.hour,
|
||||
}
|
||||
@@ -106,6 +119,33 @@ class AcceptedCount(BaseMetric):
|
||||
return _practice_submissions(user.id).filter(result__in=ACCEPTED_RESULTS).order_by().values("problem_id").distinct().count()
|
||||
|
||||
|
||||
class _DifficultyAcCount(BaseMetric):
|
||||
"""按难度去重统计 AC 题数。子类只需指定 difficulty。
|
||||
|
||||
增量靠 ctx["problem_difficulty"],它只在首次 AC 时才有值——与
|
||||
is_first_ac_of_problem 是同一个条件,所以两者一起判即可。
|
||||
"""
|
||||
|
||||
difficulty = ""
|
||||
|
||||
def on_submission(self, metrics, sub, ctx):
|
||||
if ctx["is_first_ac_of_problem"] and ctx["problem_difficulty"] == self.difficulty:
|
||||
metrics[self.key] = metrics.get(self.key, 0) + 1
|
||||
|
||||
def recompute(self, user):
|
||||
return _practice_submissions(user.id).filter(result__in=ACCEPTED_RESULTS, problem__difficulty=self.difficulty).order_by().values("problem_id").distinct().count()
|
||||
|
||||
|
||||
@metric("mid_ac_count", "中等题 AC 数", "去重后通过的中等难度题目数(不含比赛)")
|
||||
class MidAcCount(_DifficultyAcCount):
|
||||
difficulty = Difficulty.MID
|
||||
|
||||
|
||||
@metric("hard_ac_count", "困难题 AC 数", "去重后通过的困难题目数(不含比赛)")
|
||||
class HardAcCount(_DifficultyAcCount):
|
||||
difficulty = Difficulty.HIGH
|
||||
|
||||
|
||||
@metric("submission_count", "提交总数", "提交次数(不含比赛)")
|
||||
class SubmissionCount(BaseMetric):
|
||||
def on_submission(self, metrics, sub, ctx):
|
||||
@@ -328,18 +368,10 @@ class MaxAcInOneDay(BaseMetric):
|
||||
return {"_ac_per_day": counts}
|
||||
|
||||
|
||||
@metric("min_ac_code_chars", "最短 AC 代码", "通过的代码里最短的字符数(配小于等于使用)")
|
||||
class MinAcCodeChars(BaseMetric):
|
||||
def on_submission(self, metrics, sub, ctx):
|
||||
if not ctx["is_accepted"]:
|
||||
return
|
||||
length = len(sub.code)
|
||||
cur = metrics.get("min_ac_code_chars")
|
||||
metrics["min_ac_code_chars"] = length if cur is None else min(cur, length)
|
||||
|
||||
def recompute(self, user):
|
||||
lengths = [len(c) for c in _practice_submissions(user.id).filter(result__in=ACCEPTED_RESULTS).values_list("code", flat=True)]
|
||||
return min(lengths) if lengths else None
|
||||
# 曾经这里有 min_ac_code_chars(最短 AC 代码)。线上实测 1314 个用户的分布,
|
||||
# 最小值 8、p5=10:有道题 8 个字符就能通过,于是它测的是"谁做过那道水题"
|
||||
# 而不是"谁写得简洁",配不出有意义的成就,2026-08-05 删除。
|
||||
# 要重新引入,得先按题目难度加权,或排除掉那类水题。
|
||||
|
||||
|
||||
@metric("max_code_lines", "最长代码行数", "提交过的最长代码有多少行")
|
||||
|
||||
Reference in New Issue
Block a user