From 74d5c7256dd5e3f2db55a8951b6dfd88c5d7c860 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 11 May 2026 00:19:16 -0600 Subject: [PATCH] feat: add problem yearly AC rate API endpoint Co-Authored-By: Claude Sonnet 4.6 --- problem/urls/oj.py | 2 ++ problem/views/oj.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ utils/constants.py | 1 + 3 files changed, 53 insertions(+) diff --git a/problem/urls/oj.py b/problem/urls/oj.py index 7e11893..4bbef4f 100644 --- a/problem/urls/oj.py +++ b/problem/urls/oj.py @@ -7,6 +7,7 @@ from ..views.oj import ( ProblemAuthorAPI, ProblemSolvedPeopleCount, ProblemTagAPI, + ProblemYearlyACRateAPI, SimilarProblemAPI, ) @@ -16,6 +17,7 @@ urlpatterns = [ path("problem/beat_count", ProblemSolvedPeopleCount.as_view()), path("problem/similar", SimilarProblemAPI.as_view()), path("problem/author", ProblemAuthorAPI.as_view()), + path("problem/yearly_ac", ProblemYearlyACRateAPI.as_view()), path("pickone", PickOneAPI.as_view()), path("contest/problem", ContestProblemAPI.as_view()), ] diff --git a/problem/views/oj.py b/problem/views/oj.py index 02d409d..c0823c0 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -3,6 +3,7 @@ from datetime import datetime from django.core.cache import cache from django.db.models import Count, Q +from django.db.models.functions import ExtractYear from account.decorators import check_contest_permission from account.models import User @@ -279,4 +280,53 @@ class ProblemAuthorAPI(APIView): ] cache.set(cache_key, result, 7200) + + +class ProblemYearlyACRateAPI(APIView): + def get(self, request): + problem_id = request.GET.get("problem_id") + if not problem_id: + return self.error("problem_id is required") + + cache_key = f"{CacheKey.problem_yearly_ac}:{problem_id}" + cached = cache.get(cache_key) + if cached is not None: + return self.success(cached) + + try: + problem = Problem.objects.get( + _id=problem_id, contest_id__isnull=True, visible=True + ) + except Problem.DoesNotExist: + return self.error("Problem does not exist") + + rows = ( + Submission.objects.filter( + problem_id=problem.id, + contest_id__isnull=True, + ) + .exclude(result__in=[JudgeStatus.PENDING, JudgeStatus.JUDGING]) + .annotate(year=ExtractYear("create_time")) + .values("year") + .annotate( + total=Count("id"), + accepted=Count("id", filter=Q(result=JudgeStatus.ACCEPTED)), + ) + .order_by("year") + ) + + data = [ + { + "year": row["year"], + "total": row["total"], + "accepted": row["accepted"], + "ac_rate": round(row["accepted"] / row["total"] * 100, 2) + if row["total"] > 0 + else 0.0, + } + for row in rows + ] + + cache.set(cache_key, data, 3600) + return self.success(data) return self.success(result) diff --git a/utils/constants.py b/utils/constants.py index 320ffb6..8d7cc5f 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -25,6 +25,7 @@ class CacheKey: problem_tags = "problem_tags" comment_stats = "comment_stats" user_activity_rank = "user_activity_rank" + problem_yearly_ac = "problem_yearly_ac" class Difficulty(models.TextChoices):