add flowchart list
This commit is contained in:
138
ai/views/oj.py
138
ai/views/oj.py
@@ -17,19 +17,19 @@ from utils.shortcuts import get_env
|
|||||||
from account.models import User
|
from account.models import User
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from submission.models import Submission, JudgeStatus
|
from submission.models import Submission, JudgeStatus
|
||||||
|
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
from ai.models import AIAnalysis
|
from ai.models import AIAnalysis
|
||||||
|
|
||||||
|
|
||||||
CACHE_TIMEOUT = 300
|
CACHE_TIMEOUT = 300
|
||||||
DIFFICULTY_MAP = {"Low": "简单", "Mid": "中等", "High": "困难"}
|
DIFFICULTY_MAP = {"Low": "简单", "Mid": "中等", "High": "困难"}
|
||||||
DEFAULT_CLASS_SIZE = 45
|
DEFAULT_CLASS_SIZE = 45
|
||||||
|
|
||||||
# 评级阈值配置:(百分位上限, 评级)
|
# 评级阈值配置:(百分位上限, 评级)
|
||||||
GRADE_THRESHOLDS = [
|
GRADE_THRESHOLDS = [
|
||||||
(10, "S"), # 前10%: S级 - 卓越
|
(10, "S"), # 前10%: S级 - 卓越
|
||||||
(35, "A"), # 前35%: A级 - 优秀
|
(35, "A"), # 前35%: A级 - 优秀
|
||||||
(75, "B"), # 前75%: B级 - 良好
|
(75, "B"), # 前75%: B级 - 良好
|
||||||
(100, "C"), # 其余: C级 - 及格
|
(100, "C"), # 其余: C级 - 及格
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -51,41 +51,41 @@ def get_difficulty(difficulty):
|
|||||||
def get_grade(rank, submission_count):
|
def get_grade(rank, submission_count):
|
||||||
"""
|
"""
|
||||||
计算题目完成评级
|
计算题目完成评级
|
||||||
|
|
||||||
评级标准:
|
评级标准:
|
||||||
- S级:前10%,卓越水平(10%的人)
|
- S级:前10%,卓越水平(10%的人)
|
||||||
- A级:前35%,优秀水平(25%的人)
|
- A级:前35%,优秀水平(25%的人)
|
||||||
- B级:前75%,良好水平(40%的人)
|
- B级:前75%,良好水平(40%的人)
|
||||||
- C级:75%之后,及格水平(25%的人)
|
- C级:75%之后,及格水平(25%的人)
|
||||||
|
|
||||||
特殊规则:
|
特殊规则:
|
||||||
- 参与人数少于10人时,S级降为A级,A级降为B级(避免因人少而评级虚高)
|
- 参与人数少于10人时,S级降为A级,A级降为B级(避免因人少而评级虚高)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rank: 用户排名(1表示第一名)
|
rank: 用户排名(1表示第一名)
|
||||||
submission_count: 总AC人数
|
submission_count: 总AC人数
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
评级字符串 ("S", "A", "B", "C")
|
评级字符串 ("S", "A", "B", "C")
|
||||||
"""
|
"""
|
||||||
# 边界检查
|
# 边界检查
|
||||||
if not rank or rank <= 0 or submission_count <= 0:
|
if not rank or rank <= 0 or submission_count <= 0:
|
||||||
return "C"
|
return "C"
|
||||||
|
|
||||||
# 计算百分位(0-100),使用 (rank-1) 使第一名的百分位为0
|
# 计算百分位(0-100),使用 (rank-1) 使第一名的百分位为0
|
||||||
percentile = (rank - 1) / submission_count * 100
|
percentile = (rank - 1) / submission_count * 100
|
||||||
|
|
||||||
# 根据百分位确定基础评级
|
# 根据百分位确定基础评级
|
||||||
base_grade = "C"
|
base_grade = "C"
|
||||||
for threshold, grade in GRADE_THRESHOLDS:
|
for threshold, grade in GRADE_THRESHOLDS:
|
||||||
if percentile < threshold:
|
if percentile < threshold:
|
||||||
base_grade = grade
|
base_grade = grade
|
||||||
break
|
break
|
||||||
|
|
||||||
# 小规模参与惩罚:人数太少时降低评级
|
# 小规模参与惩罚:人数太少时降低评级
|
||||||
if submission_count < SMALL_SCALE_PENALTY["threshold"]:
|
if submission_count < SMALL_SCALE_PENALTY["threshold"]:
|
||||||
base_grade = SMALL_SCALE_PENALTY["downgrade"].get(base_grade, base_grade)
|
base_grade = SMALL_SCALE_PENALTY["downgrade"].get(base_grade, base_grade)
|
||||||
|
|
||||||
return base_grade
|
return base_grade
|
||||||
|
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ class AIDetailDataAPI(APIView):
|
|||||||
"start": start,
|
"start": start,
|
||||||
"end": end,
|
"end": end,
|
||||||
"solved": [],
|
"solved": [],
|
||||||
|
"flowcharts": [],
|
||||||
"grade": "",
|
"grade": "",
|
||||||
"tags": {},
|
"tags": {},
|
||||||
"difficulty": {},
|
"difficulty": {},
|
||||||
@@ -179,9 +180,79 @@ class AIDetailDataAPI(APIView):
|
|||||||
solved, contest_ids = self._build_solved_records(
|
solved, contest_ids = self._build_solved_records(
|
||||||
user_first_ac, by_problem, problems, user.id
|
user_first_ac, by_problem, problems, user.id
|
||||||
)
|
)
|
||||||
|
# 查找 flowchart submissions
|
||||||
|
flowcharts_query = FlowchartSubmission.objects.filter(
|
||||||
|
user_id=user,
|
||||||
|
status=FlowchartSubmissionStatus.COMPLETED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加时间范围过滤
|
||||||
|
if start:
|
||||||
|
flowcharts_query = flowcharts_query.filter(create_time__gte=start)
|
||||||
|
if end:
|
||||||
|
flowcharts_query = flowcharts_query.filter(create_time__lte=end)
|
||||||
|
|
||||||
|
flowcharts = flowcharts_query.select_related("problem").only(
|
||||||
|
"id",
|
||||||
|
"create_time",
|
||||||
|
"ai_score",
|
||||||
|
"ai_grade",
|
||||||
|
"problem___id",
|
||||||
|
"problem__title",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 按problem分组
|
||||||
|
problem_groups = defaultdict(list)
|
||||||
|
for flowchart in flowcharts:
|
||||||
|
problem_id = flowchart.problem._id
|
||||||
|
problem_groups[problem_id].append(flowchart)
|
||||||
|
|
||||||
|
flowcharts_data = []
|
||||||
|
for problem_id, submissions in problem_groups.items():
|
||||||
|
if not submissions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 获取第一个提交的基本信息
|
||||||
|
first_submission = submissions[0]
|
||||||
|
|
||||||
|
# 计算统计数据
|
||||||
|
scores = [s.ai_score for s in submissions if s.ai_score is not None]
|
||||||
|
times = [s.create_time for s in submissions]
|
||||||
|
|
||||||
|
# 找到最高分和对应的等级
|
||||||
|
best_score = max(scores) if scores else 0
|
||||||
|
best_submission = next(
|
||||||
|
(s for s in submissions if s.ai_score == best_score), submissions[0]
|
||||||
|
)
|
||||||
|
best_grade = best_submission.ai_grade or ""
|
||||||
|
|
||||||
|
# 计算平均分
|
||||||
|
avg_score = sum(scores) / len(scores) if scores else 0
|
||||||
|
|
||||||
|
# 最新提交时间
|
||||||
|
latest_time = max(times) if times else first_submission.create_time
|
||||||
|
|
||||||
|
merged_item = {
|
||||||
|
"problem__id": problem_id,
|
||||||
|
"problem_title": first_submission.problem.title,
|
||||||
|
"submission_count": len(submissions),
|
||||||
|
"best_score": best_score,
|
||||||
|
"best_grade": best_grade,
|
||||||
|
"latest_submission_time": latest_time.isoformat() if latest_time else None,
|
||||||
|
"avg_score": round(avg_score, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
flowcharts_data.append(merged_item)
|
||||||
|
|
||||||
|
# 按最新提交时间排序
|
||||||
|
flowcharts_data.sort(
|
||||||
|
key=lambda x: x["latest_submission_time"] or "", reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
result.update(
|
result.update(
|
||||||
{
|
{
|
||||||
"solved": solved,
|
"solved": solved,
|
||||||
|
"flowcharts": flowcharts_data,
|
||||||
"grade": self._calculate_average_grade(solved),
|
"grade": self._calculate_average_grade(solved),
|
||||||
"tags": self._calculate_top_tags(problems.values()),
|
"tags": self._calculate_top_tags(problems.values()),
|
||||||
"difficulty": self._calculate_difficulty_distribution(
|
"difficulty": self._calculate_difficulty_distribution(
|
||||||
@@ -236,38 +307,38 @@ class AIDetailDataAPI(APIView):
|
|||||||
def _calculate_average_grade(self, solved):
|
def _calculate_average_grade(self, solved):
|
||||||
"""
|
"""
|
||||||
计算平均等级,使用加权平均方法
|
计算平均等级,使用加权平均方法
|
||||||
|
|
||||||
等级权重:S=4, A=3, B=2, C=1
|
等级权重:S=4, A=3, B=2, C=1
|
||||||
计算加权平均后,根据阈值确定最终等级
|
计算加权平均后,根据阈值确定最终等级
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
solved: 已解决的题目列表,每个包含grade字段
|
solved: 已解决的题目列表,每个包含grade字段
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
平均等级字符串 ("S", "A", "B", "C")
|
平均等级字符串 ("S", "A", "B", "C")
|
||||||
"""
|
"""
|
||||||
if not solved:
|
if not solved:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# 等级权重映射
|
# 等级权重映射
|
||||||
grade_weights = {"S": 4, "A": 3, "B": 2, "C": 1}
|
grade_weights = {"S": 4, "A": 3, "B": 2, "C": 1}
|
||||||
|
|
||||||
# 计算加权总分
|
# 计算加权总分
|
||||||
total_weight = 0
|
total_weight = 0
|
||||||
total_score = 0
|
total_score = 0
|
||||||
|
|
||||||
for s in solved:
|
for s in solved:
|
||||||
grade = s["grade"]
|
grade = s["grade"]
|
||||||
if grade in grade_weights:
|
if grade in grade_weights:
|
||||||
total_score += grade_weights[grade]
|
total_score += grade_weights[grade]
|
||||||
total_weight += 1
|
total_weight += 1
|
||||||
|
|
||||||
if total_weight == 0:
|
if total_weight == 0:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# 计算平均权重
|
# 计算平均权重
|
||||||
average_weight = total_score / total_weight
|
average_weight = total_score / total_weight
|
||||||
|
|
||||||
# 根据平均权重确定等级
|
# 根据平均权重确定等级
|
||||||
# S级: 3.5-4.0, A级: 2.5-3.5, B级: 1.5-2.5, C级: 1.0-1.5
|
# S级: 3.5-4.0, A级: 2.5-3.5, B级: 1.5-2.5, C级: 1.0-1.5
|
||||||
if average_weight >= 3.5:
|
if average_weight >= 3.5:
|
||||||
@@ -395,28 +466,28 @@ class AIDurationDataAPI(APIView):
|
|||||||
def _calculate_period_grade(self, user_first_ac, by_problem, user_id):
|
def _calculate_period_grade(self, user_first_ac, by_problem, user_id):
|
||||||
"""
|
"""
|
||||||
计算时间段内的平均等级,使用加权平均方法
|
计算时间段内的平均等级,使用加权平均方法
|
||||||
|
|
||||||
等级权重:S=4, A=3, B=2, C=1
|
等级权重:S=4, A=3, B=2, C=1
|
||||||
计算加权平均后,根据阈值确定最终等级
|
计算加权平均后,根据阈值确定最终等级
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_first_ac: 用户首次AC的提交记录
|
user_first_ac: 用户首次AC的提交记录
|
||||||
by_problem: 按题目分组的排名数据
|
by_problem: 按题目分组的排名数据
|
||||||
user_id: 用户ID
|
user_id: 用户ID
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
平均等级字符串 ("S", "A", "B", "C")
|
平均等级字符串 ("S", "A", "B", "C")
|
||||||
"""
|
"""
|
||||||
if not user_first_ac:
|
if not user_first_ac:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# 等级权重映射
|
# 等级权重映射
|
||||||
grade_weights = {"S": 4, "A": 3, "B": 2, "C": 1}
|
grade_weights = {"S": 4, "A": 3, "B": 2, "C": 1}
|
||||||
|
|
||||||
# 计算加权总分
|
# 计算加权总分
|
||||||
total_weight = 0
|
total_weight = 0
|
||||||
total_score = 0
|
total_score = 0
|
||||||
|
|
||||||
for item in user_first_ac:
|
for item in user_first_ac:
|
||||||
ranking_list = by_problem.get(item["problem_id"], [])
|
ranking_list = by_problem.get(item["problem_id"], [])
|
||||||
rank = next(
|
rank = next(
|
||||||
@@ -432,13 +503,13 @@ class AIDurationDataAPI(APIView):
|
|||||||
if grade in grade_weights:
|
if grade in grade_weights:
|
||||||
total_score += grade_weights[grade]
|
total_score += grade_weights[grade]
|
||||||
total_weight += 1
|
total_weight += 1
|
||||||
|
|
||||||
if total_weight == 0:
|
if total_weight == 0:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# 计算平均权重
|
# 计算平均权重
|
||||||
average_weight = total_score / total_weight
|
average_weight = total_score / total_weight
|
||||||
|
|
||||||
# 根据平均权重确定等级
|
# 根据平均权重确定等级
|
||||||
# S级: 3.5-4.0, A级: 2.5-3.5, B级: 1.5-2.5, C级: 1.0-1.5
|
# S级: 3.5-4.0, A级: 2.5-3.5, B级: 1.5-2.5, C级: 1.0-1.5
|
||||||
if average_weight >= 3.5:
|
if average_weight >= 3.5:
|
||||||
@@ -572,9 +643,10 @@ class AIHeatmapDataAPI(APIView):
|
|||||||
submission_count = submission_dict.get(day_date, 0)
|
submission_count = submission_dict.get(day_date, 0)
|
||||||
heatmap_data.append(
|
heatmap_data.append(
|
||||||
{
|
{
|
||||||
"timestamp": int(datetime.combine(
|
"timestamp": int(
|
||||||
day_date, datetime.min.time()
|
datetime.combine(day_date, datetime.min.time()).timestamp()
|
||||||
).timestamp() * 1000),
|
* 1000
|
||||||
|
),
|
||||||
"value": submission_count,
|
"value": submission_count,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,14 +41,15 @@ class FlowchartSubmissionListSerializer(serializers.ModelSerializer):
|
|||||||
"""用于列表显示的简化序列化器"""
|
"""用于列表显示的简化序列化器"""
|
||||||
|
|
||||||
username = serializers.CharField(source="user.username")
|
username = serializers.CharField(source="user.username")
|
||||||
|
problem = serializers.CharField(source="problem._id")
|
||||||
problem_title = serializers.CharField(source="problem.title")
|
problem_title = serializers.CharField(source="problem.title")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FlowchartSubmission
|
model = FlowchartSubmission
|
||||||
fields = [
|
fields = [
|
||||||
"id",
|
"id",
|
||||||
"username",
|
"username",
|
||||||
"problem_title",
|
"problem_title",
|
||||||
|
"problem",
|
||||||
"status",
|
"status",
|
||||||
"create_time",
|
"create_time",
|
||||||
"ai_score",
|
"ai_score",
|
||||||
@@ -58,3 +59,33 @@ class FlowchartSubmissionListSerializer(serializers.ModelSerializer):
|
|||||||
"processing_time",
|
"processing_time",
|
||||||
"evaluation_time",
|
"evaluation_time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FlowchartSubmissionSummarySerializer(serializers.ModelSerializer):
|
||||||
|
"""用于AI详情页面的极简序列化器,只包含必要字段"""
|
||||||
|
|
||||||
|
problem_title = serializers.CharField(source="problem.title")
|
||||||
|
problem__id = serializers.CharField(source="problem._id")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FlowchartSubmission
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"problem__id",
|
||||||
|
"problem_title",
|
||||||
|
"ai_score",
|
||||||
|
"ai_grade",
|
||||||
|
"create_time",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FlowchartSubmissionMergedSerializer(serializers.Serializer):
|
||||||
|
"""合并后的流程图提交序列化器"""
|
||||||
|
|
||||||
|
problem__id = serializers.CharField()
|
||||||
|
problem_title = serializers.CharField()
|
||||||
|
submission_count = serializers.IntegerField()
|
||||||
|
best_score = serializers.FloatField()
|
||||||
|
best_grade = serializers.CharField()
|
||||||
|
latest_submission_time = serializers.DateTimeField()
|
||||||
|
avg_score = serializers.FloatField()
|
||||||
|
|||||||
@@ -65,32 +65,32 @@ class FlowchartSubmissionAPI(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class FlowchartSubmissionListAPI(APIView):
|
class FlowchartSubmissionListAPI(APIView):
|
||||||
@login_required
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""获取流程图提交列表"""
|
"""获取流程图提交列表"""
|
||||||
user_id = request.GET.get("user_id")
|
username = request.GET.get("username")
|
||||||
problem_id = request.GET.get("problem_id")
|
problem_id = request.GET.get("problem_id")
|
||||||
offset = int(request.GET.get("offset", 0))
|
myself = request.GET.get("myself")
|
||||||
limit = int(request.GET.get("limit", 20))
|
|
||||||
|
|
||||||
queryset = FlowchartSubmission.objects.select_related("user", "problem")
|
queryset = FlowchartSubmission.objects.select_related("user", "problem")
|
||||||
|
|
||||||
# 权限过滤
|
|
||||||
if not request.user.is_admin_role():
|
|
||||||
queryset = queryset.filter(user=request.user)
|
|
||||||
|
|
||||||
# 其他过滤条件
|
|
||||||
if user_id:
|
|
||||||
queryset = queryset.filter(user_id=user_id)
|
|
||||||
if problem_id:
|
if problem_id:
|
||||||
queryset = queryset.filter(problem_id=problem_id)
|
try:
|
||||||
|
problem = Problem.objects.get(
|
||||||
|
_id=problem_id, contest_id__isnull=True, visible=True
|
||||||
|
)
|
||||||
|
except Problem.DoesNotExist:
|
||||||
|
return self.error("Problem doesn't exist")
|
||||||
|
queryset = queryset.filter(problem=problem)
|
||||||
|
if myself and myself == "1":
|
||||||
|
queryset = queryset.filter(user=request.user)
|
||||||
|
if username:
|
||||||
|
queryset = queryset.filter(user__username__icontains=username)
|
||||||
|
|
||||||
total = queryset.count()
|
data = self.paginate_data(request, queryset)
|
||||||
submissions = queryset[offset : offset + limit]
|
data["results"] = FlowchartSubmissionListSerializer(
|
||||||
|
data["results"], many=True
|
||||||
serializer = FlowchartSubmissionListSerializer(submissions, many=True)
|
).data
|
||||||
|
return self.success(data)
|
||||||
return self.success({"results": serializer.data, "total": total})
|
|
||||||
|
|
||||||
|
|
||||||
class FlowchartSubmissionRetryAPI(APIView):
|
class FlowchartSubmissionRetryAPI(APIView):
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class SubmissionSafeModelSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class SubmissionListSerializer(serializers.ModelSerializer):
|
class SubmissionListSerializer(serializers.ModelSerializer):
|
||||||
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")
|
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")
|
||||||
|
problem_title = serializers.CharField(source="problem.title")
|
||||||
show_link = serializers.SerializerMethodField()
|
show_link = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user