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

This commit is contained in:
jeremygan2021
2026-03-20 13:20:09 +08:00
parent 98db4d6f75
commit 0d7ba5d87c
3 changed files with 169 additions and 2 deletions

View File

@@ -18,7 +18,7 @@ import uuid
from .models import Competition, CompetitionEnrollment, Project, Score, ScoreDimension, Comment, ProjectFile
from shop.models import WeChatUser
from shop.sms_utils import send_sms
from ai_services.models import TranscriptionTask
from ai_services.models import TranscriptionTask, AIEvaluation
from ai_services.services import AliyunTingwuService
logger = logging.getLogger(__name__)
@@ -332,6 +332,81 @@ def project_detail_api(request, project_id):
latest_task_any = TranscriptionTask.objects.filter(project=project).order_by('-created_at').first()
audio_url = latest_task_any.file_url if latest_task_any else None
# 计算各类评分(仅对评委和嘉宾可见)
judge_score_avg = None
peer_score_avg = None
ai_score_avg = None
final_score = float(project.final_score) if project.final_score else 0
if role in ['judge', 'guest']:
# 评委评分:所有评委的平均分(不包括选手互评维度)
judge_dimensions = ScoreDimension.objects.filter(
competition=project.competition,
is_public=True,
is_peer_review=False
)
judge_enrollments = CompetitionEnrollment.objects.filter(
competition=project.competition,
role='judge'
)
if judge_dimensions.exists() and judge_enrollments.exists():
judge_total = 0
judge_count = 0
for judge_enrollment in judge_enrollments:
judge_project_scores = Score.objects.filter(
project=project,
judge=judge_enrollment,
dimension__in=judge_dimensions
)
if judge_project_scores.exists():
judge_score = sum(
float(s.score) * float(s.dimension.weight)
for s in judge_project_scores
)
judge_total += judge_score
judge_count += 1
if judge_count > 0:
judge_score_avg = round(judge_total / judge_count, 2)
# 选手互评分:所有选手的平均分(仅互评维度)
peer_dimensions = ScoreDimension.objects.filter(
competition=project.competition,
is_peer_review=True
)
peer_enrollments = CompetitionEnrollment.objects.filter(
competition=project.competition,
role='contestant'
)
if peer_dimensions.exists() and peer_enrollments.exists():
peer_total = 0
peer_count = 0
for peer_enrollment in peer_enrollments:
peer_project_scores = Score.objects.filter(
project=project,
judge=peer_enrollment,
dimension__in=peer_dimensions
)
if peer_project_scores.exists():
peer_score = sum(
float(s.score) * float(s.dimension.weight)
for s in peer_project_scores
)
peer_total += peer_score
peer_count += 1
if peer_count > 0:
peer_score_avg = round(peer_total / peer_count, 2)
# AI评分来自AIEvaluation的平均分
ai_evaluations = AIEvaluation.objects.filter(
task__project=project,
task__status='COMPLETED'
)
ai_scores = [e.score for e in ai_evaluations if e.score is not None]
if ai_scores:
ai_score_avg = round(sum(ai_scores) / len(ai_scores), 2)
data = {
'id': project.id,
'title': project.title,
@@ -342,7 +417,14 @@ def project_detail_api(request, project_id):
'current_comment': current_comment,
'ai_result': ai_data,
'audio_url': audio_url,
'can_grade': role == 'judge' or (role == 'contestant' and project.contestant.user != user) # Contestant can grade others if allowed
'can_grade': role == 'judge' or (role == 'contestant' and project.contestant.user != user),
# 评分细项(仅评委和嘉宾可见)
'score_details': {
'judge_score': judge_score_avg,
'peer_score': peer_score_avg,
'ai_score': ai_score_avg,
'final_score': final_score
} if role in ['judge', 'guest'] else None
}
# Specifically for guest: can_grade is False

View File

@@ -0,0 +1,38 @@
# Generated by Django 6.0.1 on 2026-03-20 05:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('competition', '0006_add_peer_review_field'),
]
operations = [
migrations.AddField(
model_name='competition',
name='custom_score_formula',
field=models.CharField(blank=True, help_text='如使用自定义算式,将使用此公式计算最终得分。变量格式: dimension_维度ID如 dimension_1, dimension_2', max_length=1000, verbose_name='自定义得分算式'),
),
migrations.AddField(
model_name='competition',
name='score_calculation_type',
field=models.CharField(choices=[('default', '默认加权平均'), ('custom', '自定义算式')], default='default', max_length=20, verbose_name='得分计算方式'),
),
migrations.AddField(
model_name='scoredimension',
name='formula',
field=models.CharField(blank=True, help_text='使用维度ID作为变量如: dimension_1 * 0.3 + dimension_2 * 0.5 + dimension_3 * 0.2', max_length=500, verbose_name='自定义算式'),
),
migrations.AddField(
model_name='scoredimension',
name='formula_type',
field=models.CharField(choices=[('weight', '权重模式'), ('formula', '自定义算式')], default='weight', max_length=20, verbose_name='算式类型'),
),
migrations.AlterField(
model_name='scoredimension',
name='weight',
field=models.DecimalField(decimal_places=4, default=1.0, help_text='例如 0.3000 表示 30%', max_digits=6, verbose_name='权重'),
),
]

View File

@@ -149,6 +149,41 @@
<!-- Loaded via JS -->
</div>
</div>
<!-- 评分细项(仅评委和嘉宾可见) -->
<div id="scoreDetailsSection" style="display: none;" class="bg-gradient-to-br from-gray-50 to-blue-50 rounded-xl p-5 border border-gray-200 shadow-sm">
<h4 class="text-lg font-bold text-gray-900 mb-4 flex items-center"><i class="fas fa-chart-bar mr-2 text-indigo-500"></i>评分明细</h4>
<div class="grid grid-cols-2 gap-4">
<div class="bg-white rounded-lg p-3 border border-gray-100 shadow-sm">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-semibold text-gray-500 uppercase">评委评分</span>
<i class="fas fa-user-tie text-blue-400 text-sm"></i>
</div>
<span id="judgeScoreValue" class="text-2xl font-bold text-blue-600">--</span>
</div>
<div class="bg-white rounded-lg p-3 border border-gray-100 shadow-sm">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-semibold text-gray-500 uppercase">选手互评分</span>
<i class="fas fa-users text-green-400 text-sm"></i>
</div>
<span id="peerScoreValue" class="text-2xl font-bold text-green-600">--</span>
</div>
<div class="bg-white rounded-lg p-3 border border-gray-100 shadow-sm">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-semibold text-gray-500 uppercase">AI评分</span>
<i class="fas fa-robot text-purple-400 text-sm"></i>
</div>
<span id="aiScoreValue" class="text-2xl font-bold text-purple-600">--</span>
</div>
<div class="bg-white rounded-lg p-3 border border-gray-100 shadow-sm">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-semibold text-gray-500 uppercase">最终平均分</span>
<i class="fas fa-star text-yellow-400 text-sm"></i>
</div>
<span id="finalScoreValue" class="text-2xl font-bold text-yellow-600">--</span>
</div>
</div>
</div>
</div>
<!-- Right Column: Grading -->
@@ -601,6 +636,18 @@ async function viewProject(id) {
).join('') : '<div class="text-center text-gray-400 py-4 text-sm">暂无历史评语</div>';
document.getElementById('modalHistoryComments').innerHTML = historyHtml;
// 渲染评分细项(仅评委和嘉宾可见)
const scoreDetailsSection = document.getElementById('scoreDetailsSection');
if (data.score_details) {
scoreDetailsSection.style.display = 'block';
document.getElementById('judgeScoreValue').innerText = data.score_details.judge_score !== null ? data.score_details.judge_score : '--';
document.getElementById('peerScoreValue').innerText = data.score_details.peer_score !== null ? data.score_details.peer_score : '--';
document.getElementById('aiScoreValue').innerText = data.score_details.ai_score !== null ? data.score_details.ai_score : '--';
document.getElementById('finalScoreValue').innerText = data.score_details.final_score !== null ? data.score_details.final_score : '--';
} else {
scoreDetailsSection.style.display = 'none';
}
// Render Score Inputs
const dimensionsHtml = data.dimensions.map(d => `
<div class="bg-white p-3 rounded-lg border border-gray-200" data-dimension-id="${d.id}" data-max-score="${d.max_score}" data-weight="${d.weight}">