算法
All checks were successful
Deploy to Server / deploy (push) Successful in 28s

This commit is contained in:
jeremygan2021
2026-03-20 13:13:07 +08:00
parent 02335d26c2
commit 98db4d6f75
2 changed files with 111 additions and 14 deletions

View File

@@ -7,7 +7,12 @@ class ScoreDimensionInline(admin.TabularInline):
model = ScoreDimension model = ScoreDimension
extra = 1 extra = 1
tab = True tab = True
fields = ('name', 'description', 'weight', 'max_score', 'is_public', 'is_peer_review', 'order') fields = ('name', 'description', 'weight', 'max_score', 'formula_type', 'formula', 'is_public', 'is_peer_review', 'order')
@admin.display(description="算式预览")
def formula_preview(self, obj):
preview = obj.get_formula_preview()
return preview if preview else "-"
class ProjectFileInline(admin.TabularInline): class ProjectFileInline(admin.TabularInline):
model = ProjectFile model = ProjectFile
@@ -32,6 +37,10 @@ class CompetitionAdmin(ModelAdmin):
('时间和状态', { ('时间和状态', {
'fields': ('start_time', 'end_time', 'status', 'project_visibility', 'allow_contestant_grading', 'is_active') 'fields': ('start_time', 'end_time', 'status', 'project_visibility', 'allow_contestant_grading', 'is_active')
}), }),
('评分计算设置', {
'fields': ('score_calculation_type', 'custom_score_formula'),
'description': '配置得分计算方式:默认模式使用(维度分数×权重)求和后取评委平均;自定义算式使用下方公式直接计算最终得分。变量格式: dimension_维度ID如 dimension_1, dimension_2'
}),
) )
actions = ['make_published', 'make_ended'] actions = ['make_published', 'make_ended']

View File

@@ -37,6 +37,14 @@ class Competition(models.Model):
allow_contestant_grading = models.BooleanField(default=False, verbose_name="允许选手互评") allow_contestant_grading = models.BooleanField(default=False, verbose_name="允许选手互评")
SCORE_CALCULATION_CHOICES = (
('default', '默认加权平均'),
('custom', '自定义算式'),
)
score_calculation_type = models.CharField(max_length=20, choices=SCORE_CALCULATION_CHOICES, default='default', verbose_name="得分计算方式")
custom_score_formula = models.CharField(max_length=1000, blank=True, verbose_name="自定义得分算式", help_text="如使用自定义算式,将使用此公式计算最终得分。变量格式: dimension_维度ID如 dimension_1, dimension_2")
is_active = models.BooleanField(default=True, verbose_name="是否启用") is_active = models.BooleanField(default=True, verbose_name="是否启用")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
@@ -88,12 +96,20 @@ class ScoreDimension(models.Model):
""" """
评分维度配置 评分维度配置
""" """
FORMULA_TYPE_CHOICES = (
('weight', '权重模式'),
('formula', '自定义算式'),
)
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='score_dimensions', verbose_name="所属比赛") competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='score_dimensions', verbose_name="所属比赛")
name = models.CharField(max_length=100, verbose_name="维度名称") name = models.CharField(max_length=100, verbose_name="维度名称")
description = models.TextField(verbose_name="维度说明", blank=True) description = models.TextField(verbose_name="维度说明", blank=True)
weight = models.DecimalField(max_digits=6, decimal_places=4, default=1.0000, verbose_name="权重", help_text="例如 0.3000 表示 30%") weight = models.DecimalField(max_digits=6, decimal_places=4, default=1.0000, verbose_name="权重", help_text="例如 0.3000 表示 30%")
max_score = models.IntegerField(default=100, verbose_name="满分值") max_score = models.IntegerField(default=100, verbose_name="满分值")
formula_type = models.CharField(max_length=20, choices=FORMULA_TYPE_CHOICES, default='weight', verbose_name="算式类型")
formula = models.CharField(max_length=500, blank=True, verbose_name="自定义算式", help_text="使用维度ID作为变量如: dimension_1 * 0.3 + dimension_2 * 0.5 + dimension_3 * 0.2")
is_public = models.BooleanField(default=True, verbose_name="是否公开给评委", help_text="如果关闭评委端将看不到此评分维度通常用于AI自动评分") is_public = models.BooleanField(default=True, verbose_name="是否公开给评委", help_text="如果关闭评委端将看不到此评分维度通常用于AI自动评分")
is_peer_review = models.BooleanField(default=False, verbose_name="是否用于选手互评", help_text="如果开启,此评分维度仅在选手互评时可见,评委和嘉宾看不到") is_peer_review = models.BooleanField(default=False, verbose_name="是否用于选手互评", help_text="如果开启,此评分维度仅在选手互评时可见,评委和嘉宾看不到")
@@ -107,6 +123,20 @@ class ScoreDimension(models.Model):
def __str__(self): def __str__(self):
return f"{self.competition.title} - {self.name}" return f"{self.competition.title} - {self.name}"
def get_formula_preview(self):
"""
获取算式预览显示维度名称而非ID
"""
if not self.formula or self.formula_type != 'formula':
return None
dimension_map = {d.id: d.name for d in self.competition.score_dimensions.all()}
result = self.formula
for dim_id, dim_name in dimension_map.items():
result = result.replace(f'dimension_{dim_id}', f'[{dim_name}]')
return result
class Project(models.Model): class Project(models.Model):
@@ -147,39 +177,97 @@ class Project(models.Model):
def calculate_score(self): def calculate_score(self):
""" """
计算项目得分 计算项目得分
计算公式: 支持两种模式:
1. 默认加权平均:每个评委的得分 = sum(维度分数 × 维度权重),然后所有评委取平均
2. 自定义算式:使用比赛级别的 custom_score_formula 计算最终得分
自定义算式变量格式:
- dimension_X: 第X个维度的平均分所有评委对该维度的平均分
- 也可以在算式中直接使用维度ID
"""
scores = self.scores.all()
if not scores.exists():
self.final_score = 0
self.save()
return 0
competition = self.competition
if competition.score_calculation_type == 'custom' and competition.custom_score_formula:
return self._calculate_custom_score(scores, competition.custom_score_formula)
return self._calculate_default_score(scores)
def _calculate_default_score(self, scores):
"""
默认加权平均模式
1. 获取所有评委对该项目的打分 1. 获取所有评委对该项目的打分
2. 每个评委的得分 = sum(维度分数 × 维度权重) 2. 每个评委的得分 = sum(维度分数 × 维度权重)
3. 项目最终得分 = 所有评委得分的平均值 3. 项目最终得分 = 所有评委得分的平均值
""" """
# 获取所有评分
scores = self.scores.all()
if not scores.exists():
return 0
# 找出所有参与评分的评委
judges = set(score.judge for score in scores) judges = set(score.judge for score in scores)
if not judges: if not judges:
return 0 return 0
total_weighted_score = 0 total_weighted_score = 0
for judge in judges: for judge in judges:
judge_score = 0 judge_score = 0
# 获取该评委对该项目的所有维度打分
judge_scores = scores.filter(judge=judge) judge_scores = scores.filter(judge=judge)
for score in judge_scores: for score in judge_scores:
# 直接用原始分数乘以权重相加 judge_score += float(score.score) * float(score.dimension.weight)
judge_score += score.score * score.dimension.weight
total_weighted_score += judge_score total_weighted_score += judge_score
# 平均分
avg_score = total_weighted_score / len(judges) avg_score = total_weighted_score / len(judges)
self.final_score = avg_score self.final_score = round(avg_score, 2)
self.save() self.save()
return avg_score return avg_score
def _calculate_custom_score(self, scores, formula):
"""
自定义算式模式
使用比赛配置的自定义算式计算得分
"""
dimension_scores = {}
dimensions = self.competition.score_dimensions.all()
for dimension in dimensions:
dim_scores = scores.filter(dimension=dimension)
if dim_scores.exists():
avg = sum(float(s.score) for s in dim_scores) / dim_scores.count()
dimension_scores[f'dimension_{dimension.id}'] = avg
if not dimension_scores:
self.final_score = 0
self.save()
return 0
try:
result = eval(formula, {"__builtins__": {}}, dimension_scores)
final_score = float(result)
self.final_score = round(final_score, 2)
self.save()
return self.final_score
except Exception as e:
print(f"算式计算错误: {e}, formula: {formula}, values: {dimension_scores}")
return self._calculate_default_score(scores)
def calculate_judge_score(self, judge):
"""
计算指定评委对该项目的得分
用于显示评委个人评分
"""
scores = self.scores.filter(judge=judge)
if not scores.exists():
return 0
total = 0
for score in scores:
total += float(score.score) * float(score.dimension.weight)
return round(total, 2)
class ProjectFile(models.Model): class ProjectFile(models.Model):