This commit is contained in:
@@ -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']
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user