import { Injectable, NotFoundException, BadRequestException, } from '@nestjs/common'; import { PrismaService } from '../../prisma/prisma.service'; import { SetAwardDto } from './dto/set-award.dto'; import { BatchSetAwardsDto } from './dto/batch-set-awards.dto'; import { QueryResultsDto } from './dto/query-results.dto'; @Injectable() export class ResultsService { constructor(private prisma: PrismaService) {} /** * 计算比赛所有作品的最终得分 */ async calculateAllFinalScores(contestId: number) { // 验证比赛是否存在 const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, include: { reviewRule: true, }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } // 获取所有有效作品 const works = await this.prisma.contestWork.findMany({ where: { contestId, validState: 1, isLatest: true, }, include: { scores: { where: { validState: 1 }, }, }, }); if (works.length === 0) { return { message: '没有需要计算的作品', calculatedCount: 0, }; } const calculationRule = contest.reviewRule?.calculationRule || 'average'; let calculatedCount = 0; // 获取评委权重(用于加权平均) const judges = await this.prisma.contestJudge.findMany({ where: { contestId, validState: 1, }, }); const judgeWeights = new Map( judges.map((j) => [j.judgeId, Number(j.weight || 1)]), ); for (const work of works) { if (work.scores.length === 0) { continue; } const scores = work.scores.map((s) => Number(s.totalScore)); let finalScore = 0; switch (calculationRule) { case 'average': finalScore = scores.reduce((a, b) => a + b, 0) / scores.length; break; case 'max': finalScore = Math.max(...scores); break; case 'min': finalScore = Math.min(...scores); break; case 'weighted': let totalWeight = 0; let weightedSum = 0; work.scores.forEach((score) => { const weight = judgeWeights.get(score.judgeId) || 1; weightedSum += Number(score.totalScore) * weight; totalWeight += weight; }); finalScore = totalWeight > 0 ? weightedSum / totalWeight : 0; break; default: finalScore = scores.reduce((a, b) => a + b, 0) / scores.length; } await this.prisma.contestWork.update({ where: { id: work.id }, data: { finalScore: Number(finalScore.toFixed(2)), }, }); calculatedCount++; } return { message: `成功计算 ${calculatedCount} 件作品的最终得分`, calculatedCount, calculationRule, }; } /** * 计算排名 */ async calculateRankings(contestId: number) { // 验证比赛是否存在 const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } // 获取所有有最终得分的作品,按得分降序排列 const works = await this.prisma.contestWork.findMany({ where: { contestId, validState: 1, isLatest: true, finalScore: { not: null }, }, orderBy: { finalScore: 'desc', }, }); if (works.length === 0) { return { message: '没有需要排名的作品,请先计算最终得分', rankedCount: 0, }; } // 计算排名(同分同名次) let currentRank = 1; let previousScore: number | null = null; let sameScoreCount = 0; for (let i = 0; i < works.length; i++) { const work = works[i]; const currentScore = Number(work.finalScore); if (previousScore !== null && currentScore === previousScore) { // 同分,保持相同排名 sameScoreCount++; } else { // 不同分,更新排名(跳过同分数量) currentRank = i + 1; sameScoreCount = 0; } await this.prisma.contestWork.update({ where: { id: work.id }, data: { rank: currentRank, }, }); previousScore = currentScore; } return { message: `成功计算 ${works.length} 件作品的排名`, rankedCount: works.length, }; } /** * 设置单个作品奖项 */ async setAward(workId: number, setAwardDto: SetAwardDto) { const work = await this.prisma.contestWork.findUnique({ where: { id: workId }, }); if (!work) { throw new NotFoundException('作品不存在'); } return this.prisma.contestWork.update({ where: { id: workId }, data: { awardLevel: setAwardDto.awardLevel, awardName: setAwardDto.awardName, certificateUrl: setAwardDto.certificateUrl, status: 'awarded', }, include: { registration: { include: { user: { select: { id: true, username: true, nickname: true, }, }, team: { select: { id: true, teamName: true, }, }, }, }, }, }); } /** * 批量设置奖项 */ async batchSetAwards(contestId: number, batchSetAwardsDto: BatchSetAwardsDto) { // 验证比赛是否存在 const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } const results = []; for (const award of batchSetAwardsDto.awards) { const work = await this.prisma.contestWork.findFirst({ where: { id: award.workId, contestId, validState: 1, }, }); if (!work) { results.push({ workId: award.workId, success: false, error: '作品不存在', }); continue; } await this.prisma.contestWork.update({ where: { id: award.workId }, data: { awardLevel: award.awardLevel, awardName: award.awardName, status: 'awarded', }, }); results.push({ workId: award.workId, success: true, }); } return { message: `批量设置完成`, total: batchSetAwardsDto.awards.length, successCount: results.filter((r) => r.success).length, failedCount: results.filter((r) => !r.success).length, results, }; } /** * 根据排名自动设置奖项 */ async autoSetAwards( contestId: number, awardConfig: { first?: number; second?: number; third?: number; excellent?: number; }, ) { const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } // 获取已排名的作品 const works = await this.prisma.contestWork.findMany({ where: { contestId, validState: 1, isLatest: true, rank: { not: null }, }, orderBy: { rank: 'asc', }, }); if (works.length === 0) { throw new BadRequestException('没有已排名的作品,请先计算排名'); } const firstCount = awardConfig.first || 0; const secondCount = awardConfig.second || 0; const thirdCount = awardConfig.third || 0; const excellentCount = awardConfig.excellent || 0; let assignedCount = 0; const awards: { workId: number; awardLevel: string; awardName: string }[] = []; for (let i = 0; i < works.length; i++) { const work = works[i]; let awardLevel: string | null = null; let awardName: string | null = null; if (i < firstCount) { awardLevel = 'first'; awardName = '一等奖'; } else if (i < firstCount + secondCount) { awardLevel = 'second'; awardName = '二等奖'; } else if (i < firstCount + secondCount + thirdCount) { awardLevel = 'third'; awardName = '三等奖'; } else if (i < firstCount + secondCount + thirdCount + excellentCount) { awardLevel = 'excellent'; awardName = '优秀奖'; } if (awardLevel) { await this.prisma.contestWork.update({ where: { id: work.id }, data: { awardLevel, awardName, status: 'awarded', }, }); awards.push({ workId: work.id, awardLevel, awardName, }); assignedCount++; } } return { message: `自动设置奖项完成`, assignedCount, awards, }; } /** * 发布赛果 */ async publishResults(contestId: number) { const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } if (contest.resultState === 'published') { throw new BadRequestException('赛果已发布'); } // 检查是否有已排名的作品 const rankedWorks = await this.prisma.contestWork.count({ where: { contestId, validState: 1, isLatest: true, rank: { not: null }, }, }); if (rankedWorks === 0) { throw new BadRequestException('没有已排名的作品,请先计算排名'); } // 更新比赛状态 const updatedContest = await this.prisma.contest.update({ where: { id: contestId }, data: { resultState: 'published', resultPublishTime: new Date(), status: 'finished', }, }); return { message: '赛果发布成功', contest: updatedContest, publishTime: updatedContest.resultPublishTime, }; } /** * 撤回发布 */ async unpublishResults(contestId: number) { const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } if (contest.resultState !== 'published') { throw new BadRequestException('赛果未发布'); } const updatedContest = await this.prisma.contest.update({ where: { id: contestId }, data: { resultState: 'unpublished', resultPublishTime: null, }, }); return { message: '已撤回赛果发布', contest: updatedContest, }; } /** * 获取比赛结果列表(作品列表) */ async getResults(contestId: number, queryDto: QueryResultsDto) { const { page = 1, pageSize = 10, workNo, accountNo } = queryDto; const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, include: { reviewRule: true, }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } const skip = (page - 1) * pageSize; // 构建查询条件 const where: any = { contestId, validState: 1, isLatest: true, }; // 作品编号搜索 if (workNo) { where.workNo = { contains: workNo }; } // 报名账号搜索(需要关联查询) if (accountNo) { where.registration = { user: { username: { contains: accountNo }, }, }; } const [works, total] = await Promise.all([ this.prisma.contestWork.findMany({ where, include: { registration: { include: { user: { include: { tenant: { select: { id: true, name: true, }, }, student: { include: { class: { include: { grade: { select: { id: true, name: true, }, }, }, }, }, }, }, }, team: { select: { id: true, teamName: true, }, }, teachers: { include: { user: { select: { id: true, username: true, nickname: true, }, }, }, }, }, }, }, orderBy: [ { finalScore: 'desc' }, ], skip, take: pageSize, }), this.prisma.contestWork.count({ where }), ]); return { contest: { id: contest.id, contestName: contest.contestName, resultState: contest.resultState, resultPublishTime: contest.resultPublishTime, }, list: works, total, page, pageSize, }; } /** * 获取比赛结果统计摘要 */ async getResultsSummary(contestId: number) { const contest = await this.prisma.contest.findUnique({ where: { id: contestId }, }); if (!contest) { throw new NotFoundException('比赛不存在'); } // 获取总作品数 const totalWorks = await this.prisma.contestWork.count({ where: { contestId, validState: 1, isLatest: true, }, }); // 获取已计分作品数 const scoredWorks = await this.prisma.contestWork.count({ where: { contestId, validState: 1, isLatest: true, finalScore: { not: null }, }, }); // 获取已排名作品数 const rankedWorks = await this.prisma.contestWork.count({ where: { contestId, validState: 1, isLatest: true, rank: { not: null }, }, }); // 获取已设奖作品数 const awardedWorks = await this.prisma.contestWork.count({ where: { contestId, validState: 1, isLatest: true, awardLevel: { not: null }, }, }); // 奖项分布 const awardStats = await this.prisma.contestWork.groupBy({ by: ['awardLevel'], where: { contestId, validState: 1, isLatest: true, awardLevel: { not: null }, }, _count: { id: true, }, }); const awardDistribution: Record = { first: 0, second: 0, third: 0, excellent: 0, }; awardStats.forEach((stat) => { if (stat.awardLevel) { awardDistribution[stat.awardLevel] = stat._count.id; } }); // 获取分数统计 const scoreStats = await this.prisma.contestWork.aggregate({ where: { contestId, validState: 1, isLatest: true, finalScore: { not: null }, }, _avg: { finalScore: true }, _max: { finalScore: true }, _min: { finalScore: true }, }); return { contest: { id: contest.id, contestName: contest.contestName, resultState: contest.resultState, resultPublishTime: contest.resultPublishTime, }, summary: { totalWorks, scoredWorks, rankedWorks, awardedWorks, unscoredWorks: totalWorks - scoredWorks, }, awardDistribution, scoreStats: { avgScore: scoreStats._avg.finalScore ? Number(scoreStats._avg.finalScore).toFixed(2) : null, maxScore: scoreStats._max.finalScore ? Number(scoreStats._max.finalScore).toFixed(2) : null, minScore: scoreStats._min.finalScore ? Number(scoreStats._min.finalScore).toFixed(2) : null, }, }; } }