算法
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 .models import Competition, CompetitionEnrollment, Project, Score, ScoreDimension, Comment, ProjectFile
from shop.models import WeChatUser from shop.models import WeChatUser
from shop.sms_utils import send_sms 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 from ai_services.services import AliyunTingwuService
logger = logging.getLogger(__name__) 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() 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 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 = { data = {
'id': project.id, 'id': project.id,
'title': project.title, 'title': project.title,
@@ -342,7 +417,14 @@ def project_detail_api(request, project_id):
'current_comment': current_comment, 'current_comment': current_comment,
'ai_result': ai_data, 'ai_result': ai_data,
'audio_url': audio_url, '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 # 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 --> <!-- Loaded via JS -->
</div> </div>
</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> </div>
<!-- Right Column: Grading --> <!-- Right Column: Grading -->
@@ -601,6 +636,18 @@ async function viewProject(id) {
).join('') : '<div class="text-center text-gray-400 py-4 text-sm">暂无历史评语</div>'; ).join('') : '<div class="text-center text-gray-400 py-4 text-sm">暂无历史评语</div>';
document.getElementById('modalHistoryComments').innerHTML = historyHtml; 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 // Render Score Inputs
const dimensionsHtml = data.dimensions.map(d => ` 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}"> <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}">