Files
ai-showcase-platform/app/api/admin/scoring/route.ts

416 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =====================================================
// 評分管理 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { ScoringService } from '@/lib/services/database-service';
// 獲取競賽評分記錄
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const competitionId = searchParams.get('competitionId');
const judgeId = searchParams.get('judgeId');
const status = searchParams.get('status');
const search = searchParams.get('search');
if (!competitionId) {
return NextResponse.json({
success: false,
message: '缺少競賽ID',
error: 'competitionId 參數是必需的'
}, { status: 400 });
}
let scores;
if (judgeId) {
// 獲取特定評審的評分記錄
scores = await ScoringService.getJudgeScores(judgeId, competitionId);
} else {
// 獲取競賽的所有評分記錄
scores = await ScoringService.getCompetitionScores(competitionId);
}
// 狀態篩選
if (status && status !== 'all') {
if (status === 'completed') {
scores = scores.filter(score => score.total_score > 0);
} else if (status === 'pending') {
scores = scores.filter(score => !score.total_score || score.total_score === 0);
}
}
// 搜尋篩選
if (search) {
const searchLower = search.toLowerCase();
scores = scores.filter(score =>
score.judge_name?.toLowerCase().includes(searchLower) ||
score.app_name?.toLowerCase().includes(searchLower) ||
score.creator_name?.toLowerCase().includes(searchLower)
);
}
// 獲取評分統計
const stats = await ScoringService.getCompetitionScoreStats(competitionId);
// 處理評分數據,將 score_details 轉換為前端期望的格式
const processedScores = scores.map((score: any) => {
// 解析 score_details 字符串
let scoreDetails: Record<string, number> = {};
if (score.score_details) {
const details = score.score_details.split(',');
details.forEach((detail: string) => {
const [ruleName, scoreValue] = detail.split(':');
if (ruleName && scoreValue) {
scoreDetails[ruleName] = parseInt(scoreValue);
}
});
}
// 映射到前端期望的字段
return {
...score,
innovation_score: scoreDetails['創新性'] || scoreDetails['innovation'] || 0,
technical_score: scoreDetails['技術性'] || scoreDetails['technical'] || 0,
usability_score: scoreDetails['實用性'] || scoreDetails['usability'] || 0,
presentation_score: scoreDetails['展示效果'] || scoreDetails['presentation'] || 0,
impact_score: scoreDetails['影響力'] || scoreDetails['impact'] || 0,
};
});
return NextResponse.json({
success: true,
message: '評分記錄獲取成功',
data: {
scores: processedScores,
stats,
total: processedScores.length
}
});
} catch (error) {
console.error('獲取評分記錄失敗:', error);
return NextResponse.json({
success: false,
message: '獲取評分記錄失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}
// 處理評分的輔助函數
async function processScoringWithCompetitionId(participantId: string, judgeId: string, scores: any, comments: string, competitionId: string, isEdit: boolean = false, recordId: string | null = null) {
const rules = await ScoringService.getCompetitionRules(competitionId);
if (!rules || rules.length === 0) {
return NextResponse.json({
success: false,
message: '競賽評分規則未設置',
error: '請先設置競賽評分規則'
}, { status: 400 });
}
// 驗證評分格式(基於實際的競賽規則)
const providedScores = Object.keys(scores).filter(key => scores[key] > 0);
const invalidScores = providedScores.filter(score => scores[score] < 1 || scores[score] > 10);
if (invalidScores.length > 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: `無效的評分項目: ${invalidScores.join(', ')}`
}, { status: 400 });
}
if (providedScores.length === 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: '至少需要提供一個評分項目'
}, { status: 400 });
}
// 計算總分基於權重轉換為100分制
let totalScore = 0;
let totalWeight = 0;
rules.forEach((rule: any) => {
const score = scores[rule.name];
if (score && score > 0) {
totalScore += score * (rule.weight / 100);
totalWeight += rule.weight;
}
});
// 如果總權重為0使用平均分
if (totalWeight === 0) {
const validScores = Object.values(scores).filter(score => score > 0);
totalScore = validScores.length > 0 ? validScores.reduce((sum, score) => sum + score, 0) / validScores.length : 0;
}
// 轉換為100分制10分制 * 10 = 100分制
totalScore = totalScore * 10;
// 將自定義評分映射到標準字段
const validScoreData: any = {
judge_id: judgeId,
app_id: participantId,
competition_id: competitionId,
scores: scores, // 傳遞原始評分數據
total_score: totalScore,
comments: comments || null,
isEdit: isEdit || false,
recordId: recordId || null
};
// 按順序將自定義評分映射到標準字段
const standardFields = ['innovation_score', 'technical_score', 'usability_score', 'presentation_score', 'impact_score'];
const customScores = Object.entries(scores).filter(([key, value]) => value > 0);
customScores.forEach(([customKey, score], index) => {
if (index < standardFields.length) {
validScoreData[standardFields[index]] = score;
}
});
const result = await ScoringService.submitAppScore(validScoreData);
return NextResponse.json({
success: true,
message: '評分提交成功',
data: result
});
}
// 提交評分
export async function POST(request: NextRequest) {
try {
const body = await request.json();
console.log('🔍 API 接收到的請求數據:', JSON.stringify(body, null, 2));
const {
judgeId,
participantId,
participantType,
scores,
comments,
competitionId,
isEdit,
recordId
} = body;
console.log('🔍 解析後的參數:');
console.log('judgeId:', judgeId, typeof judgeId);
console.log('participantId:', participantId, typeof participantId);
console.log('participantType:', participantType, typeof participantType);
console.log('scores:', scores, typeof scores);
console.log('competitionId:', competitionId, typeof competitionId);
console.log('isEdit:', isEdit, typeof isEdit);
console.log('recordId:', recordId, typeof recordId);
// 驗證必填欄位
if (!judgeId || !participantId || !participantType || !scores) {
console.log('❌ 缺少必填欄位驗證失敗');
return NextResponse.json({
success: false,
message: '缺少必填欄位',
error: 'judgeId, participantId, participantType, scores 為必填欄位'
}, { status: 400 });
}
// 驗證評分類型
if (!['app', 'proposal', 'team'].includes(participantType)) {
return NextResponse.json({
success: false,
message: '無效的參賽者類型',
error: 'participantType 必須是 "app"、"proposal" 或 "team"'
}, { status: 400 });
}
let result;
if (participantType === 'app') {
// 獲取競賽規則來驗證評分格式
let finalCompetitionId = await ScoringService.getCompetitionIdByAppId(participantId);
if (!finalCompetitionId) {
// 如果找不到競賽關聯嘗試通過其他方式獲取競賽ID
console.log('⚠️ 找不到APP的競賽關聯嘗試其他方式...');
// 檢查是否有其他方式獲取競賽ID例如通過請求參數
if (competitionId) {
console.log('✅ 使用參數中的競賽ID:', competitionId);
finalCompetitionId = competitionId;
} else {
return NextResponse.json({
success: false,
message: '找不到對應的競賽',
error: 'APP未註冊到任何競賽中請先在競賽管理中將APP添加到競賽'
}, { status: 400 });
}
}
const rules = await ScoringService.getCompetitionRules(finalCompetitionId);
if (!rules || rules.length === 0) {
return NextResponse.json({
success: false,
message: '競賽評分規則未設置',
error: '請先設置競賽評分規則'
}, { status: 400 });
}
// 驗證評分格式(基於實際的競賽規則)
console.log('🔍 競賽規則:', rules);
console.log('🔍 提交的評分:', scores);
const providedScores = Object.keys(scores).filter(key => scores[key] > 0);
const invalidScores = providedScores.filter(score => scores[score] < 1 || scores[score] > 10);
console.log('🔍 提供的評分項目:', providedScores);
console.log('🔍 無效的評分項目:', invalidScores);
if (invalidScores.length > 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: `無效的評分項目: ${invalidScores.join(', ')}`
}, { status: 400 });
}
if (providedScores.length === 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: '至少需要提供一個評分項目'
}, { status: 400 });
}
// 驗證所有競賽規則都有對應的評分
const ruleNames = rules.map((rule: any) => rule.name);
const missingRules = ruleNames.filter(ruleName => !(ruleName in scores) || scores[ruleName] <= 0);
if (missingRules.length > 0) {
console.log('⚠️ 缺少評分的規則:', missingRules);
console.log('可用規則:', ruleNames);
console.log('提供的評分:', Object.keys(scores));
}
// 計算總分基於權重轉換為100分制
let totalScore = 0;
let totalWeight = 0;
rules.forEach((rule: any) => {
const score = scores[rule.name];
if (score && score > 0) {
totalScore += score * (rule.weight / 100);
totalWeight += rule.weight;
}
});
// 如果總權重為0使用平均分
if (totalWeight === 0) {
const validScores = Object.values(scores).filter(score => score > 0);
totalScore = validScores.length > 0 ? validScores.reduce((sum, score) => sum + score, 0) / validScores.length : 0;
}
// 轉換為100分制10分制 * 10 = 100分制
totalScore = totalScore * 10;
// 使用新的基於競賽規則的評分系統
const validScoreData = {
judge_id: judgeId,
app_id: participantId,
competition_id: finalCompetitionId,
scores: scores, // 直接使用原始評分數據
total_score: totalScore,
comments: comments || null,
isEdit: isEdit || false,
recordId: recordId || null
};
result = await ScoringService.submitAppScore(validScoreData);
} else if (participantType === 'proposal') {
// 驗證提案評分格式
const requiredScores = ['problem_identification_score', 'solution_feasibility_score', 'innovation_score', 'impact_score', 'presentation_score'];
const missingScores = requiredScores.filter(score => !(score in scores) || scores[score] < 1 || scores[score] > 10);
if (missingScores.length > 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: `缺少或無效的評分項目: ${missingScores.join(', ')}`
}, { status: 400 });
}
// 計算總分
const totalScore = (
scores.problem_identification_score +
scores.solution_feasibility_score +
scores.innovation_score +
scores.impact_score +
scores.presentation_score
) / 5;
result = await ScoringService.submitProposalScore({
judge_id: judgeId,
proposal_id: participantId,
problem_identification_score: scores.problem_identification_score,
solution_feasibility_score: scores.solution_feasibility_score,
innovation_score: scores.innovation_score,
impact_score: scores.impact_score,
presentation_score: scores.presentation_score,
total_score: totalScore,
comments: comments || null
});
} else if (participantType === 'team') {
// 驗證團隊評分格式
const requiredScores = ['innovation_score', 'technical_score', 'usability_score', 'presentation_score', 'impact_score'];
const missingScores = requiredScores.filter(score => !(score in scores) || scores[score] < 1 || scores[score] > 10);
if (missingScores.length > 0) {
return NextResponse.json({
success: false,
message: '評分格式無效',
error: `缺少或無效的評分項目: ${missingScores.join(', ')}`
}, { status: 400 });
}
// 計算總分
const totalScore = (
scores.innovation_score +
scores.technical_score +
scores.usability_score +
scores.presentation_score +
scores.impact_score
) / 5;
// 團隊評分使用應用評分表
result = await ScoringService.submitTeamScore({
judge_id: judgeId,
teamId: participantId,
innovation_score: scores.innovation_score,
technical_score: scores.technical_score,
usability_score: scores.usability_score,
presentation_score: scores.presentation_score,
impact_score: scores.impact_score,
total_score: totalScore,
comments: comments || null
});
}
return NextResponse.json({
success: true,
message: '評分提交成功',
data: result
});
} catch (error) {
console.error('提交評分失敗:', error);
return NextResponse.json({
success: false,
message: '提交評分失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}