精简 Problem 模型:移除 rule_type / total_score / io_mode

系统只用 ACM 模式且题目固定标准 IO,这三列已无实际用途:
- rule_type / total_score:OI 专用,删除后 judge/dispatcher 的 OI 分支全部塌缩为 ACM
- io_mode:判题改为固定发送 Standard IO 常量;languages.py 的 seccomp_rule
  保留 dict 形态但改用字面量 key,不改判题机 wire 契约
- prompt 字段保留(预留给未来 AI 分析)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 11:40:38 -06:00
parent 4743b0398a
commit 9535b64a4b
11 changed files with 58 additions and 140 deletions

View File

@@ -13,7 +13,7 @@ from account.models import User
from conf.models import JudgeServer
from contest.models import ACMContestRank
from options.options import SysOptions
from problem.models import Problem, ProblemRuleType
from problem.models import Problem
from problem.utils import parse_problem_template
from submission.models import JudgeStatus, Submission, is_accepted
from utils.cache import cache
@@ -114,22 +114,6 @@ class JudgeDispatcher(DispatcherBase):
self.submission.statistic_info["time_cost"] = max([x["cpu_time"] for x in resp_data])
self.submission.statistic_info["memory_cost"] = max([x["memory"] for x in resp_data])
# sum up the score in OI mode
if self.problem.rule_type == ProblemRuleType.OI:
score = 0
try:
for i in range(len(resp_data)):
if resp_data[i]["result"] == JudgeStatus.ACCEPTED:
resp_data[i]["score"] = self.problem.test_case_score[i]["score"]
score += resp_data[i]["score"]
else:
resp_data[i]["score"] = 0
except IndexError:
logger.error(f"Index Error raised when summing up the score in problem {self.problem.id}")
self.submission.statistic_info["score"] = 0
return
self.submission.statistic_info["score"] = score
def judge(self):
language = self.submission.language
sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0]
@@ -147,7 +131,7 @@ class JudgeDispatcher(DispatcherBase):
"max_memory": 1024 * 1024 * self.problem.memory_limit,
"test_case_id": self.problem.test_case_id,
"output": False,
"io_mode": self.problem.io_mode,
"io_mode": {"io_mode": "Standard IO", "input": "input.txt", "output": "output.txt"},
}
with ChooseJudgeServer() as server:
@@ -188,14 +172,11 @@ class JudgeDispatcher(DispatcherBase):
self.submission.info = resp
self._compute_statistic_info(resp["data"])
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
# ACM模式下,多个测试点全部正确则AC否则取第一个错误的测试点的状态
# OI模式下, 若多个测试点全部正确则AC 若全部错误则取第一个错误测试点状态,否则为部分正确
# 多个测试点全部正确则AC否则取第一个错误的测试点的状态
if not error_test_case:
self.submission.result = JudgeStatus.ACCEPTED
elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
self.submission.result = error_test_case[0]["result"]
else:
self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED
self.submission.result = error_test_case[0]["result"]
if self.submission.result == JudgeStatus.ACCEPTED:
ast_rules = self.problem.ast_rules
@@ -252,7 +233,6 @@ class JudgeDispatcher(DispatcherBase):
problem.save(update_fields=["accepted_number", "statistic_info"])
profile = User.objects.select_for_update().get(id=self.submission.user_id).userprofile
if problem.rule_type == ProblemRuleType.ACM:
acm_problems_status = profile.acm_problems_status.get("problems", {})
if not is_accepted(acm_problems_status[problem_id]["status"]):
acm_problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
@@ -261,19 +241,6 @@ class JudgeDispatcher(DispatcherBase):
profile.acm_problems_status["problems"] = acm_problems_status
profile.save(update_fields=["accepted_number", "acm_problems_status"])
else:
oi_problems_status = profile.oi_problems_status.get("problems", {})
score = self.submission.statistic_info["score"]
if not is_accepted(oi_problems_status[problem_id]["status"]):
# minus last time score, add this tim score
profile.add_score(this_time_score=score, last_time_score=oi_problems_status[problem_id]["score"])
oi_problems_status[problem_id]["score"] = score
oi_problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
if is_accepted(self.submission.result):
profile.accepted_number += 1
profile.oi_problems_status["problems"] = oi_problems_status
profile.save(update_fields=["accepted_number", "oi_problems_status"])
def update_problem_status(self):
result = str(self.submission.result)
problem_id = str(self.problem.id)
@@ -292,7 +259,6 @@ class JudgeDispatcher(DispatcherBase):
user_profile = user.userprofile
user_profile.submission_number = F("submission_number") + 1
profile_status = JudgeStatus.ACCEPTED if is_accepted(self.submission.result) else self.submission.result
if problem.rule_type == ProblemRuleType.ACM:
acm_problems_status = user_profile.acm_problems_status.get("problems", {})
if problem_id not in acm_problems_status:
acm_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id}
@@ -305,24 +271,6 @@ class JudgeDispatcher(DispatcherBase):
user_profile.acm_problems_status["problems"] = acm_problems_status
user_profile.save(update_fields=["submission_number", "accepted_number", "acm_problems_status"])
else:
oi_problems_status = user_profile.oi_problems_status.get("problems", {})
score = self.submission.statistic_info["score"]
if problem_id not in oi_problems_status:
user_profile.add_score(score)
oi_problems_status[problem_id] = {"status": profile_status, "_id": self.problem._id, "score": score}
if is_accepted(self.submission.result):
user_profile.accepted_number += 1
elif not is_accepted(oi_problems_status[problem_id]["status"]):
# minus last time score, add this time score
user_profile.add_score(this_time_score=score, last_time_score=oi_problems_status[problem_id]["score"])
oi_problems_status[problem_id]["score"] = score
oi_problems_status[problem_id]["status"] = profile_status
if is_accepted(self.submission.result):
user_profile.accepted_number += 1
user_profile.oi_problems_status["problems"] = oi_problems_status
user_profile.save(update_fields=["submission_number", "accepted_number", "oi_problems_status"])
def update_contest_problem_status(self):
with transaction.atomic():
user = User.objects.select_for_update().get(id=self.submission.user_id)

View File

@@ -1,5 +1,3 @@
from problem.models import ProblemIOMode
default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"]
_c_lang_config = {
@@ -27,7 +25,7 @@ int main() {
"max_memory": 256 * 1024 * 1024,
"compile_command": "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c17 {src_path} -lm -o {exe_path}",
},
"run": {"command": "{exe_path}", "seccomp_rule": {ProblemIOMode.STANDARD: "c_cpp", ProblemIOMode.FILE: "c_cpp_file_io"}, "env": default_env},
"run": {"command": "{exe_path}", "seccomp_rule": {"Standard IO": "c_cpp", "File IO": "c_cpp_file_io"}, "env": default_env},
}
@@ -56,7 +54,7 @@ int main() {
"max_memory": 1024 * 1024 * 1024,
"compile_command": "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++20 {src_path} -lm -o {exe_path}",
},
"run": {"command": "{exe_path}", "seccomp_rule": {ProblemIOMode.STANDARD: "c_cpp", ProblemIOMode.FILE: "c_cpp_file_io"}, "env": default_env},
"run": {"command": "{exe_path}", "seccomp_rule": {"Standard IO": "c_cpp", "File IO": "c_cpp_file_io"}, "env": default_env},
}

View File

@@ -88,7 +88,7 @@ class SQLJudgeDispatcher(JudgeDispatcher):
raise SQLProblemConfigError("测试点不是 SQL 类型,请重新上传 SQL 测试点压缩包")
init_scripts = []
# 按 "1","2",… 自然序遍历,保证与 test_case_score 的下标对应OI 计分依赖顺序)
# 按 "1","2",… 自然序遍历,保证测试点顺序稳定
for key in sorted(info["test_cases"].keys(), key=natural_sort_key):
input_name = info["test_cases"][key]["input_name"]
try:

View File

@@ -1,7 +1,6 @@
# Generated by Django 5.2.3 on 2025-06-14 08:51
import django.db.models.deletion
import problem.models
import utils.models
from django.conf import settings
from django.db import migrations, models
@@ -47,7 +46,7 @@ class Migration(migrations.Migration):
('last_update_time', models.DateTimeField(auto_now=True, null=True)),
('time_limit', models.IntegerField()),
('memory_limit', models.IntegerField()),
('io_mode', models.JSONField(default=problem.models._default_io_mode)),
('io_mode', models.JSONField(default=dict)),
('spj', models.BooleanField(default=False)),
('spj_language', models.TextField(null=True)),
('spj_code', models.TextField(null=True)),

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.4 on 2026-07-04 17:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('problem', '0012_problem_sql_display'),
]
operations = [
migrations.RemoveField(
model_name='problem',
name='io_mode',
),
migrations.RemoveField(
model_name='problem',
name='rule_type',
),
migrations.RemoveField(
model_name='problem',
name='total_score',
),
]

View File

@@ -13,24 +13,6 @@ class ProblemTag(models.Model):
db_table = "problem_tag"
class ProblemRuleType(models.TextChoices):
ACM = "ACM", "ACM"
OI = "OI", "OI"
class ProblemIOMode(models.TextChoices):
STANDARD = "Standard IO", "Standard IO"
FILE = "File IO", "File IO"
def _default_io_mode():
return {
"io_mode": ProblemIOMode.STANDARD,
"input": "input.txt",
"output": "output.txt",
}
class Problem(models.Model):
# display ID
_id = models.TextField(db_index=True)
@@ -58,18 +40,14 @@ class Problem(models.Model):
time_limit = models.IntegerField()
# MB
memory_limit = models.IntegerField()
# io mode
io_mode = models.JSONField(default=_default_io_mode)
rule_type = models.TextField(choices=ProblemRuleType.choices)
visible = models.BooleanField(default=True, db_default=True)
difficulty = models.TextField(choices=Difficulty.choices)
tags = models.ManyToManyField(ProblemTag)
source = models.TextField(null=True)
# 预留:题目考察知识点,供未来 AI 分析使用(当前未接线)
prompt = models.TextField(null=True)
# [{language: "python", code: "..."}]
answers = models.JSONField(null=True)
# for OI mode
total_score = models.IntegerField(default=0, db_default=0)
submission_number = models.BigIntegerField(default=0, db_default=0)
accepted_number = models.BigIntegerField(default=0, db_default=0)
# {JudgeStatus.ACCEPTED: 3, JudgeStatus.WRONG_ANSWER: 11}, the number means count

View File

@@ -1,5 +1,3 @@
import re
from django import forms
from utils.api import UsernameSerializer, serializers
@@ -9,7 +7,7 @@ from utils.serializers import (
LanguageNameMultiChoiceField,
)
from .models import Problem, ProblemIOMode, ProblemRuleType, ProblemTag
from .models import Problem, ProblemTag
from .utils import parse_problem_template
@@ -48,20 +46,6 @@ class SQLTestCasePreviewSerializer(serializers.Serializer):
mode = serializers.ChoiceField(choices=["query", "modify"])
class ProblemIOModeSerializer(serializers.Serializer):
io_mode = serializers.ChoiceField(choices=ProblemIOMode.choices)
input = serializers.CharField()
output = serializers.CharField()
def validate(self, attrs):
if attrs["input"] == attrs["output"]:
raise serializers.ValidationError("Invalid io mode")
for item in (attrs["input"], attrs["output"]):
if not re.match("^[a-zA-Z0-9.]+$", item):
raise serializers.ValidationError("Invalid io file name format")
return attrs
class CreateOrEditProblemSerializer(serializers.Serializer):
_id = serializers.CharField(max_length=32, allow_blank=True, allow_null=True)
title = serializers.CharField(max_length=1024)
@@ -75,8 +59,6 @@ class CreateOrEditProblemSerializer(serializers.Serializer):
memory_limit = serializers.IntegerField(min_value=1, max_value=1024)
languages = LanguageNameMultiChoiceField()
template = serializers.DictField(child=serializers.CharField(min_length=1))
rule_type = serializers.ChoiceField(choices=ProblemRuleType.choices)
io_mode = ProblemIOModeSerializer()
visible = serializers.BooleanField()
difficulty = serializers.ChoiceField(choices=Difficulty.choices)
tags = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=False)

View File

@@ -19,7 +19,7 @@ from utils.api import APIError, APIView, CSRFExemptAPIView, validate_serializer
from utils.openai import get_ai_client
from utils.shortcuts import natural_sort_key, rand_str
from ..models import Problem, ProblemRuleType, ProblemTag
from ..models import Problem, ProblemTag
from ..serializers import (
AddContestProblemSerializer,
ContestProblemMakePublicSerializer,
@@ -195,14 +195,6 @@ class TestCaseAPI(CSRFExemptAPIView, TestCaseZipProcessor):
class ProblemBase(APIView):
def common_checks(self, request):
data = request.data
if data["rule_type"] == ProblemRuleType.OI:
total_score = 0
for item in data["test_case_score"]:
if item["score"] <= 0:
return "Invalid score"
else:
total_score += item["score"]
data["total_score"] = total_score
data["languages"] = list(data["languages"])
# SQL 题校验:.sql 测试点与 .in/.out 沙箱判题互斥SQL 必须是唯一语言

View File

@@ -57,7 +57,7 @@ class SubmissionModelSerializer(serializers.ModelSerializer):
fields = "__all__"
# 不显示submission info的serializer, 用于ACM rule_type
# 不显示submission info的serializer, 用于非管理员查看他人提交
class SubmissionSafeModelSerializer(serializers.ModelSerializer):
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")

View File

@@ -11,7 +11,7 @@ from judge.tasks import judge_task
from options.options import SysOptions
# from judge.dispatcher import JudgeDispatcher
from problem.models import Problem, ProblemRuleType
from problem.models import Problem
from utils.api import APIView, AsyncAPIView, validate_serializer
from utils.cache import cache
from utils.captcha import Captcha
@@ -116,10 +116,7 @@ class SubmissionAPI(AsyncAPIView):
if not submission.check_user_permission(request.user):
return self.error("No permission for this submission")
if (
submission.problem.rule_type == ProblemRuleType.OI
or request.user.is_admin_role()
):
if request.user.is_admin_role():
submission_data = await self.async_serialize_data(SubmissionModelSerializer, submission)
else:
submission_data = await self.async_serialize_data(SubmissionSafeModelSerializer, submission)

View File

@@ -12,7 +12,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
django.setup()
from django.conf import settings
from account.models import User, UserProfile, AdminType, ProblemPermission
from problem.models import Problem, ProblemTag, ProblemRuleType
from problem.models import Problem, ProblemTag
from utils.constants import Difficulty
admin_type_map = {0: AdminType.REGULAR_USER, 1: AdminType.STUDENT_ADMIN, 2: AdminType.SUPER_ADMIN}
@@ -154,7 +154,6 @@ def import_problems():
print("%s test_case files don't exist, omitted" % data["title"])
continue
data["test_case_score"] = test_case_score
data["rule_type"] = ProblemRuleType.ACM
data["template"] = {}
data.pop("total_submit_number")
data.pop("total_accepted_number")