style: ruff format 全仓库

行宽 180 下把历史遗留的折行表达式合并,无语义改动。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 04:45:26 -06:00
parent db104ac091
commit 3a9ab83ba5
56 changed files with 455 additions and 769 deletions

View File

@@ -1,6 +1,7 @@
"""
WebSocket consumers for flowchart evaluation updates
"""
import json
import logging
@@ -18,31 +19,25 @@ class FlowchartConsumer(AsyncWebsocketConsumer):
async def connect(self):
"""处理 WebSocket 连接"""
self.user = self.scope["user"]
# 只允许认证用户连接
if not self.user.is_authenticated:
await self.close()
return
# 使用用户 ID 作为组名,这样可以向特定用户推送消息
self.group_name = f"flowchart_user_{self.user.id}"
# 加入用户专属的组
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.channel_layer.group_add(self.group_name, self.channel_name)
await self.accept()
logger.info(f"Flowchart WebSocket connected: user_id={self.user.id}, channel={self.channel_name}")
async def disconnect(self, close_code):
"""处理 WebSocket 断开连接"""
if hasattr(self, 'group_name'):
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
if hasattr(self, "group_name"):
await self.channel_layer.group_discard(self.group_name, self.channel_name)
logger.info(f"Flowchart WebSocket disconnected: user_id={self.user.id}, close_code={close_code}")
async def receive(self, text_data):
@@ -53,13 +48,10 @@ class FlowchartConsumer(AsyncWebsocketConsumer):
try:
data = json.loads(text_data)
message_type = data.get("type")
if message_type == "ping":
# 响应心跳包
await self.send(text_data=json.dumps({
"type": "pong",
"timestamp": data.get("timestamp")
}))
await self.send(text_data=json.dumps({"type": "pong", "timestamp": data.get("timestamp")}))
elif message_type == "subscribe":
# 订阅特定流程图提交的更新
submission_id = data.get("submission_id")

View File

@@ -6,61 +6,59 @@ from utils.shortcuts import rand_str
User = get_user_model()
class FlowchartSubmissionStatus:
PENDING = 0 # 等待AI评分
PROCESSING = 1 # AI评分中
COMPLETED = 2 # 评分完成
FAILED = 3 # 评分失败
PENDING = 0 # 等待AI评分
PROCESSING = 1 # AI评分中
COMPLETED = 2 # 评分完成
FAILED = 3 # 评分失败
class FlowchartSubmission(models.Model):
"""流程图提交模型"""
id = models.TextField(default=rand_str, primary_key=True, db_index=True)
# 基础信息
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='flowchart_submissions')
problem = models.ForeignKey(Problem, on_delete=models.CASCADE, related_name='flowchart_submissions')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="flowchart_submissions")
problem = models.ForeignKey(Problem, on_delete=models.CASCADE, related_name="flowchart_submissions")
# 提交内容
mermaid_code = models.TextField() # Mermaid代码
flowchart_data = models.JSONField(default=dict) # 流程图元数据
# 状态信息
status = models.IntegerField(default=FlowchartSubmissionStatus.PENDING)
create_time = models.DateTimeField(auto_now_add=True)
# AI评分结果
ai_score = models.FloatField(null=True, blank=True) # AI评分 (0-100)
ai_grade = models.CharField(max_length=10, null=True, blank=True) # 等级 (S/A/B/C)
ai_feedback = models.TextField(null=True, blank=True) # AI反馈
ai_suggestions = models.TextField(null=True, blank=True) # AI建议
ai_criteria_details = models.JSONField(default=dict) # 详细评分标准
# 处理信息
ai_provider = models.CharField(max_length=50, default='deepseek')
ai_model = models.CharField(max_length=50, default='deepseek-v4-flash')
ai_provider = models.CharField(max_length=50, default="deepseek")
ai_model = models.CharField(max_length=50, default="deepseek-v4-flash")
processing_time = models.FloatField(null=True, blank=True) # AI处理耗时(秒)
evaluation_time = models.DateTimeField(null=True, blank=True) # 评分完成时间
evaluation_time = models.DateTimeField(null=True, blank=True) # 评分完成时间
class Meta:
db_table = 'flowchart_submission'
ordering = ['-create_time']
db_table = "flowchart_submission"
ordering = ["-create_time"]
indexes = [
models.Index(fields=['user', 'create_time'], name='flowchart_user_time_idx'),
models.Index(fields=['problem', 'create_time'], name='flowchart_problem_time_idx'),
models.Index(fields=['status'], name='flowchart_status_idx'),
models.Index(fields=["user", "create_time"], name="flowchart_user_time_idx"),
models.Index(fields=["problem", "create_time"], name="flowchart_problem_time_idx"),
models.Index(fields=["status"], name="flowchart_status_idx"),
]
def __str__(self):
return f"FlowchartSubmission {self.id}"
def check_user_permission(self, user, check_share=True):
"""检查用户权限"""
if (
self.user_id == user.id
or not user.is_regular_user()
or self.problem.created_by_id == user.id
):
if self.user_id == user.id or not user.is_regular_user() or self.problem.created_by_id == user.id:
return True
return False

View File

@@ -18,6 +18,7 @@ class CreateFlowchartSubmissionSerializer(serializers.Serializer):
def validate_flowchart_data(self, value):
import json
if len(json.dumps(value)) > 500 * 1024:
raise serializers.ValidationError("流程图数据过大")
return value
@@ -55,6 +56,7 @@ class FlowchartSubmissionListSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="user.username")
problem = serializers.CharField(source="problem._id")
problem_title = serializers.CharField(source="problem.title")
class Meta:
model = FlowchartSubmission
fields = [

View File

@@ -20,16 +20,16 @@ def evaluate_flowchart_task(submission_id):
submission = None
try:
submission = FlowchartSubmission.objects.get(id=submission_id)
# 更新状态为处理中
submission.status = FlowchartSubmissionStatus.PROCESSING
submission.save()
start_time = time.time()
# 使用固定评分标准
system_prompt = build_evaluation_prompt(submission.problem)
# 构建用户提示词,包含标准答案对比
user_prompt = f"""
请对以下Mermaid流程图进行评分
@@ -53,16 +53,13 @@ def evaluate_flowchart_task(submission_id):
user_prompt += f"\n设计提示:{submission.problem.flowchart_hint}\n"
user_prompt += "\n请按照评分标准进行详细评估并给出0-100的分数。\n"
# 调用AI进行评分
client = get_ai_client()
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}],
temperature=0,
extra_body={"thinking": {"type": "disabled"}},
)
@@ -74,30 +71,31 @@ def evaluate_flowchart_task(submission_id):
# 保存评分结果
with transaction.atomic():
submission.ai_score = score_data['score']
submission.ai_grade = score_data['grade']
submission.ai_feedback = score_data['feedback']
submission.ai_suggestions = score_data.get('suggestions', '')
submission.ai_criteria_details = score_data.get('criteria_details', {})
submission.ai_provider = 'deepseek'
submission.ai_model = 'deepseek-v4-flash'
submission.ai_score = score_data["score"]
submission.ai_grade = score_data["grade"]
submission.ai_feedback = score_data["feedback"]
submission.ai_suggestions = score_data.get("suggestions", "")
submission.ai_criteria_details = score_data.get("criteria_details", {})
submission.ai_provider = "deepseek"
submission.ai_model = "deepseek-v4-flash"
submission.processing_time = processing_time
submission.status = FlowchartSubmissionStatus.COMPLETED
submission.evaluation_time = timezone.now()
submission.save()
# 推送评分完成通知
from utils.websocket import push_flowchart_evaluation_update
push_flowchart_evaluation_update(
submission_id=str(submission.id),
user_id=submission.user_id,
data={
"type": "flowchart_evaluation_completed",
"score": score_data['score'],
"grade": score_data['grade'],
}
"score": score_data["score"],
"grade": score_data["grade"],
},
)
except Exception as e:
logger.exception("evaluate_flowchart_task failed for submission %s", submission_id)
if submission is not None:
@@ -105,6 +103,7 @@ def evaluate_flowchart_task(submission_id):
submission.save()
from utils.websocket import push_flowchart_evaluation_update
push_flowchart_evaluation_update(
submission_id=str(submission.id),
user_id=submission.user_id,
@@ -116,9 +115,10 @@ def evaluate_flowchart_task(submission_id):
)
raise e
def build_evaluation_prompt(problem):
"""构建AI评分提示词 - 使用固定标准"""
# 使用固定的评分标准
criteria_text = """
- 逻辑正确性 (权重: 1.0, 最高分: 40): 检查流程图的逻辑是否正确,包括条件判断、循环结构等
@@ -126,7 +126,7 @@ def build_evaluation_prompt(problem):
- 规范性 (权重: 0.6, 最高分: 20): 检查流程图符号使用是否规范是否符合标准不要评价节点ID
- 清晰度 (权重: 0.4, 最高分: 10): 评估流程图的整体布局和连线情况不要因节点ID扣分
"""
return f"""
你是一个专业的编程教学助手负责评估学生提交的Mermaid流程图。
@@ -166,15 +166,17 @@ def build_evaluation_prompt(problem):
}}
"""
def parse_ai_evaluation_response(ai_response):
"""解析AI评分响应解析失败时抛出异常由调用方处理"""
import re
# 优先匹配代码块中的 JSON避免贪婪匹配误抓 reasoning 段落
code_block = re.search(r'```(?:json)?\s*(\{[\s\S]*?\})\s*```', ai_response, re.DOTALL)
code_block = re.search(r"```(?:json)?\s*(\{[\s\S]*?\})\s*```", ai_response, re.DOTALL)
if code_block:
json_str = code_block.group(1)
else:
json_match = re.search(r'\{.*\}', ai_response, re.DOTALL)
json_match = re.search(r"\{.*\}", ai_response, re.DOTALL)
if not json_match:
raise ValueError("AI响应中未找到JSON数据")
json_str = json_match.group()

View File

@@ -9,9 +9,9 @@ from ..views.oj import (
)
urlpatterns = [
path('flowchart/submission', FlowchartSubmissionAPI.as_view()),
path('flowchart/submissions', FlowchartSubmissionListAPI.as_view()),
path('flowchart/submission/retry', FlowchartSubmissionRetryAPI.as_view()),
path('flowchart/submission/detail', FlowchartSubmissionDetailAPI.as_view()),
path('flowchart/submission/current', FlowchartSubmissionCurrentAPI.as_view()),
path("flowchart/submission", FlowchartSubmissionAPI.as_view()),
path("flowchart/submissions", FlowchartSubmissionListAPI.as_view()),
path("flowchart/submission/retry", FlowchartSubmissionRetryAPI.as_view()),
path("flowchart/submission/detail", FlowchartSubmissionDetailAPI.as_view()),
path("flowchart/submission/current", FlowchartSubmissionCurrentAPI.as_view()),
]

View File

@@ -1,2 +1 @@
# Create your views here.

View File

@@ -20,16 +20,44 @@ STOPWORDS = frozenset(
)
CUSTOM_WORDS = [
"循环结构", "条件判断", "判断条件", "结束条件", "循环条件",
"异常处理", "边界条件", "输入输出", "输入验证",
"开始结束", "结束节点", "开始节点", "判断节点",
"流程走向", "逻辑错误", "逻辑缺陷", "逻辑不清",
"缺少分支", "缺少步骤", "缺少判断", "缺少循环",
"死循环", "无限循环", "循环出口", "循环体",
"条件分支", "分支结构", "分支不全", "分支缺失",
"符号使用", "符号不规范", "连线混乱",
"变量初始化", "赋值操作", "累加操作",
"终止条件", "退出条件", "返回值",
"循环结构",
"条件判断",
"判断条件",
"结束条件",
"循环条件",
"异常处理",
"边界条件",
"输入输出",
"输入验证",
"开始结束",
"结束节点",
"开始节点",
"判断节点",
"流程走向",
"逻辑错误",
"逻辑缺陷",
"逻辑不清",
"缺少分支",
"缺少步骤",
"缺少判断",
"缺少循环",
"死循环",
"无限循环",
"循环出口",
"循环体",
"条件分支",
"分支结构",
"分支不全",
"分支缺失",
"符号使用",
"符号不规范",
"连线混乱",
"变量初始化",
"赋值操作",
"累加操作",
"终止条件",
"退出条件",
"返回值",
]
for _w in CUSTOM_WORDS:
@@ -38,7 +66,7 @@ for _w in CUSTOM_WORDS:
def get_real_name(username, class_name):
if class_name and username.startswith("ks"):
return username[len(f"ks{class_name}"):]
return username[len(f"ks{class_name}") :]
return username
@@ -63,9 +91,7 @@ class FlowchartStatisticsAPI(APIView):
problem_id = request.GET.get("problem_id")
if problem_id:
try:
problem = Problem.objects.get(
_id__iexact=problem_id, contest_id__isnull=True, visible=True
)
problem = Problem.objects.get(_id__iexact=problem_id, contest_id__isnull=True, visible=True)
except Problem.DoesNotExist:
return self.error("Problem doesn't exist")
submissions = submissions.filter(problem=problem)
@@ -85,23 +111,21 @@ class FlowchartStatisticsAPI(APIView):
total_count = submissions.count()
if total_count == 0:
return self.success({
"total_count": 0,
"avg_score": 0,
"grade_distribution": {},
"criteria_averages": {},
"person_count": len(all_users_dict),
"completed_count": 0,
"word_frequencies": [],
"data_unaccepted": [],
})
return self.success(
{
"total_count": 0,
"avg_score": 0,
"grade_distribution": {},
"criteria_averages": {},
"person_count": len(all_users_dict),
"completed_count": 0,
"word_frequencies": [],
"data_unaccepted": [],
}
)
# 1. Grade distribution
grade_counts = dict(
submissions.values_list("ai_grade")
.annotate(count=Count("id"))
.values_list("ai_grade", "count")
)
grade_counts = dict(submissions.values_list("ai_grade").annotate(count=Count("id")).values_list("ai_grade", "count"))
# 2. Average score
avg_score = submissions.aggregate(avg=Avg("ai_score"))["avg"] or 0
@@ -113,9 +137,7 @@ class FlowchartStatisticsAPI(APIView):
wordcloud_texts = []
for row in submissions.values_list(
"ai_criteria_details", "ai_feedback", "ai_suggestions"
).iterator():
for row in submissions.values_list("ai_criteria_details", "ai_feedback", "ai_suggestions").iterator():
details, feedback, suggestions = row
if details and isinstance(details, dict):
for key, val in details.items():
@@ -139,9 +161,7 @@ class FlowchartStatisticsAPI(APIView):
}
# 4. Completion stats
submitted_users = set(
submissions.values_list("user__username", flat=True).distinct()
)
submitted_users = set(submissions.values_list("user__username", flat=True).distinct())
completed_count = len(submitted_users)
# Unaccepted users
@@ -155,16 +175,18 @@ class FlowchartStatisticsAPI(APIView):
# 5. Word cloud from feedback + suggestions + criteria comments
word_freq = self._build_word_frequencies(wordcloud_texts)
return self.success({
"total_count": total_count,
"avg_score": round(avg_score, 1),
"grade_distribution": grade_counts,
"criteria_averages": criteria_averages,
"person_count": len(all_users_dict),
"completed_count": completed_count,
"word_frequencies": word_freq,
"data_unaccepted": unaccepted,
})
return self.success(
{
"total_count": total_count,
"avg_score": round(avg_score, 1),
"grade_distribution": grade_counts,
"criteria_averages": criteria_averages,
"person_count": len(all_users_dict),
"completed_count": completed_count,
"word_frequencies": word_freq,
"data_unaccepted": unaccepted,
}
)
@staticmethod
def _build_word_frequencies(texts, top_n=80):

View File

@@ -47,11 +47,7 @@ class FlowchartSubmissionAPI(AsyncAPIView):
return self.error("submission_id is required")
try:
submission = await (
FlowchartSubmission.objects.select_related("user", "problem")
.filter(id=submission_id)
.afirst()
)
submission = await FlowchartSubmission.objects.select_related("user", "problem").filter(id=submission_id).afirst()
if submission is None:
raise FlowchartSubmission.DoesNotExist
except FlowchartSubmission.DoesNotExist:
@@ -74,9 +70,7 @@ class FlowchartSubmissionListAPI(AsyncAPIView):
if problem_id:
try:
problem = await Problem.objects.aget(
_id__iexact=problem_id, contest_id__isnull=True, visible=True
)
problem = await Problem.objects.aget(_id__iexact=problem_id, contest_id__isnull=True, visible=True)
except Problem.DoesNotExist:
return self.error("Problem doesn't exist")
queryset = queryset.filter(problem=problem)
@@ -90,9 +84,7 @@ class FlowchartSubmissionListAPI(AsyncAPIView):
if request.GET.get("today") == "1":
now = timezone.now()
queryset = queryset.filter(
create_time__gte=now.replace(hour=0, minute=0, second=0, microsecond=0)
)
queryset = queryset.filter(create_time__gte=now.replace(hour=0, minute=0, second=0, microsecond=0))
grade = request.GET.get("grade")
if grade in ("S", "A", "B", "C"):
@@ -115,11 +107,7 @@ class FlowchartSubmissionRetryAPI(AsyncAPIView):
return self.error("submission_id is required")
try:
submission = await (
FlowchartSubmission.objects.select_related("problem")
.filter(id=submission_id)
.afirst()
)
submission = await FlowchartSubmission.objects.select_related("problem").filter(id=submission_id).afirst()
if submission is None:
raise FlowchartSubmission.DoesNotExist
except FlowchartSubmission.DoesNotExist:
@@ -187,7 +175,7 @@ class FlowchartSubmissionDetailAPI(AsyncAPIView):
else:
if page < 0 or page > count:
return self.error("Page out of range")
result = [s async for s in submissions[page - 1:page]]
result = [s async for s in submissions[page - 1 : page]]
submission = result[0]
data = await self.async_serialize_data(FlowchartSubmissionSerializer, submission)
return self.success({"submission": data, "count": count})