301 lines
12 KiB
TypeScript
301 lines
12 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
||
import { EvaluationService } from '@/lib/services/database';
|
||
|
||
export async function GET(
|
||
request: NextRequest,
|
||
{ params }: { params: { id: string } }
|
||
) {
|
||
try {
|
||
const evaluationId = parseInt(params.id);
|
||
|
||
if (isNaN(evaluationId)) {
|
||
return NextResponse.json(
|
||
{ success: false, error: '無效的評審ID' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
console.log(`📊 開始獲取評審詳細數據: ID=${evaluationId}`);
|
||
|
||
// 獲取評審詳細數據
|
||
const evaluationWithDetails = await EvaluationService.findWithDetails(evaluationId);
|
||
|
||
if (!evaluationWithDetails) {
|
||
return NextResponse.json(
|
||
{ success: false, error: '找不到指定的評審記錄' },
|
||
{ status: 404 }
|
||
);
|
||
}
|
||
|
||
console.log(`✅ 成功獲取評審數據: 專案=${evaluationWithDetails.project?.title}`);
|
||
|
||
// 處理反饋數據,按類型分組
|
||
const feedbackByType = {
|
||
criteria: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'criteria') || [],
|
||
strength: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'strength') || [],
|
||
improvement: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'improvement') || [],
|
||
overall: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'overall') || []
|
||
};
|
||
|
||
// 為每個評分標準獲取對應的 AI 評語、優點和改進建議
|
||
const criteriaWithFeedback = evaluationWithDetails.scores?.map(score => {
|
||
const criteriaId = score.criteria_item_id;
|
||
const criteriaName = score.criteria_item_name || '未知項目';
|
||
|
||
// 獲取該評分標準的 AI 評語
|
||
const criteriaFeedback = feedbackByType.criteria
|
||
.filter(f => f.criteria_item_id === criteriaId)
|
||
.map(f => f.content)
|
||
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
|
||
|
||
// 獲取該評分標準的優點
|
||
const strengths = feedbackByType.strength
|
||
.filter(f => f.criteria_item_id === criteriaId)
|
||
.map(f => f.content)
|
||
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
|
||
|
||
// 獲取該評分標準的改進建議
|
||
const improvements = feedbackByType.improvement
|
||
.filter(f => f.criteria_item_id === criteriaId)
|
||
.map(f => f.content)
|
||
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
|
||
|
||
return {
|
||
name: criteriaName,
|
||
score: Number(score.score) || 0,
|
||
maxScore: Number(score.max_score) || 10,
|
||
weight: Number(score.weight) || 1,
|
||
weightedScore: Number(score.weighted_score) || 0,
|
||
percentage: Number(score.percentage) || 0,
|
||
feedback: criteriaFeedback[0] || '無評語', // 使用第一個評語
|
||
strengths: strengths,
|
||
improvements: improvements
|
||
};
|
||
}) || [];
|
||
|
||
// 生成圖表數據
|
||
const chartData = {
|
||
// 各項目得分 - 使用 evaluation_scores 的 score 資料
|
||
barChart: criteriaWithFeedback.map(criteria => ({
|
||
name: criteria.name,
|
||
score: criteria.score
|
||
})),
|
||
|
||
// 權重分布 - 使用 evaluation_scores 的 weighted_score 資料
|
||
pieChart: criteriaWithFeedback.map(criteria => ({
|
||
name: criteria.name,
|
||
value: criteria.weightedScore,
|
||
weight: criteria.weight
|
||
})),
|
||
|
||
// 能力雷達圖 - 使用 evaluation_scores 的 score 資料
|
||
radarChart: criteriaWithFeedback.map(criteria => ({
|
||
subject: criteria.name,
|
||
score: criteria.score,
|
||
fullMark: criteria.maxScore
|
||
}))
|
||
};
|
||
|
||
// 轉換數據格式以符合前端需求
|
||
const formattedData = {
|
||
projectTitle: evaluationWithDetails.project?.title || '未知專案',
|
||
overallScore: Number(evaluationWithDetails.overall_score) || 0,
|
||
totalPossible: Number(evaluationWithDetails.max_possible_score) || 100,
|
||
grade: evaluationWithDetails.grade || 'N/A',
|
||
performanceStatus: evaluationWithDetails.performance_status || 'N/A',
|
||
recommendedStars: evaluationWithDetails.recommended_stars || 0,
|
||
analysisDate: evaluationWithDetails.created_at ? new Date(evaluationWithDetails.created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
|
||
criteria: criteriaWithFeedback,
|
||
overview: {
|
||
excellentItems: evaluationWithDetails.excellent_items || 0,
|
||
improvementItems: evaluationWithDetails.improvement_items || 0,
|
||
overallPerformance: Number(evaluationWithDetails.overall_score) || 0
|
||
},
|
||
detailedAnalysis: {
|
||
summary: feedbackByType.overall[0]?.content || '無詳細分析',
|
||
keyFindings: feedbackByType.overall.map(f => f.content).filter((content, index, arr) => arr.indexOf(content) === index) // 去重
|
||
},
|
||
improvementSuggestions: {
|
||
// 整體改進建議 - evaluation_feedback 的第一筆 overall
|
||
overallSuggestions: feedbackByType.overall[0]?.content || '無改進建議',
|
||
|
||
// 繼續保持的優勢 - evaluation_feedback 的 feedback_type = strength 且 criteria_item_id 是 null 的全部資料
|
||
maintainStrengths: feedbackByType.strength
|
||
.filter(f => f.criteria_item_id === null) // 只取 criteria_item_id 是 null 的
|
||
.map(f => {
|
||
const content = f.content || '無描述';
|
||
const colonIndex = content.indexOf(':');
|
||
const title = colonIndex > -1 ? content.substring(0, colonIndex + 1) : '系統優勢:';
|
||
const description = colonIndex > -1 ? content.substring(colonIndex + 1).trim() : content;
|
||
|
||
return {
|
||
title: title,
|
||
description: description
|
||
};
|
||
}),
|
||
|
||
// 重點改進方向 - evaluation_feedback 的 feedback_type = improvement 且 criteria_item_id 是 null 的全部資料,但不能有最後 3 筆數據,也不能是第一筆
|
||
keyImprovements: (() => {
|
||
const allImprovementData = feedbackByType.improvement
|
||
.filter(f => f.criteria_item_id === null); // 只取 criteria_item_id 是 null 的
|
||
|
||
// 如果數據不足5筆,則不進行過濾,直接使用所有數據
|
||
const improvementData = allImprovementData.length >= 5
|
||
? allImprovementData.slice(1, -3) // 排除第一筆和最後3筆
|
||
: allImprovementData; // 數據不足時使用所有數據
|
||
|
||
// 分組處理 - 先收集所有組,然後合併相同標題的組
|
||
const tempGroups = [];
|
||
let currentGroup = null;
|
||
|
||
for (const item of improvementData) {
|
||
const content = item.content || '';
|
||
const colonIndex = content.indexOf(':');
|
||
|
||
if (colonIndex > -1) {
|
||
// 有冒號的情況
|
||
const title = content.substring(0, colonIndex + 1);
|
||
const remainingContent = content.substring(colonIndex + 1).trim();
|
||
|
||
// 檢查是否包含小標題 "建議:"
|
||
const suggestionIndex = remainingContent.indexOf('建議:');
|
||
|
||
if (suggestionIndex > -1) {
|
||
// 如果有小標題,開始新的 group
|
||
if (currentGroup) {
|
||
tempGroups.push(currentGroup);
|
||
}
|
||
|
||
currentGroup = {
|
||
title: title,
|
||
subtitle: '建議:',
|
||
suggestions: []
|
||
};
|
||
|
||
// 處理建議後面的內容
|
||
const suggestionsText = remainingContent.substring(suggestionIndex + 3).trim();
|
||
const suggestions = suggestionsText.split('\n')
|
||
.map(s => s.trim())
|
||
.filter(s => s.startsWith('•'))
|
||
.map(s => s.substring(1).trim());
|
||
|
||
currentGroup.suggestions = suggestions;
|
||
} else if (remainingContent.startsWith('•')) {
|
||
// 如果是條列資料,添加到當前 group
|
||
if (currentGroup) {
|
||
const suggestion = remainingContent.substring(1).trim();
|
||
currentGroup.suggestions.push(suggestion);
|
||
}
|
||
} else {
|
||
// 如果是普通描述,創建簡單的 group
|
||
if (currentGroup) {
|
||
tempGroups.push(currentGroup);
|
||
}
|
||
|
||
currentGroup = {
|
||
title: title,
|
||
subtitle: '',
|
||
suggestions: [remainingContent]
|
||
};
|
||
}
|
||
} else {
|
||
// 沒有冒號的情況,創建簡單的 group
|
||
if (currentGroup) {
|
||
tempGroups.push(currentGroup);
|
||
}
|
||
|
||
// 檢查內容是否以 • 開頭
|
||
if (content.startsWith('•')) {
|
||
// 如果是條列資料,添加到當前 group
|
||
if (currentGroup) {
|
||
const suggestion = content.substring(1).trim();
|
||
currentGroup.suggestions.push(suggestion);
|
||
} else {
|
||
// 如果沒有當前 group,創建一個
|
||
currentGroup = {
|
||
title: '改進方向:',
|
||
subtitle: '',
|
||
suggestions: [content.substring(1).trim()]
|
||
};
|
||
}
|
||
} else {
|
||
// 如果是普通描述,創建新的 group,將內容作為描述而不是條列建議
|
||
currentGroup = {
|
||
title: '改進方向:',
|
||
subtitle: '',
|
||
description: content,
|
||
suggestions: []
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加最後一個 group
|
||
if (currentGroup) {
|
||
tempGroups.push(currentGroup);
|
||
}
|
||
|
||
// 合併相同標題的組
|
||
const groups = [];
|
||
const groupMap = new Map();
|
||
|
||
for (const group of tempGroups) {
|
||
if (groupMap.has(group.title)) {
|
||
// 合併到現有組
|
||
const existingGroup = groupMap.get(group.title);
|
||
if (group.suggestions && group.suggestions.length > 0) {
|
||
// 合併建議並去重
|
||
const allSuggestions = [...(existingGroup.suggestions || []), ...group.suggestions];
|
||
existingGroup.suggestions = [...new Set(allSuggestions)]; // 使用 Set 去重
|
||
}
|
||
if (group.description) {
|
||
existingGroup.description = group.description;
|
||
}
|
||
} else {
|
||
// 創建新組
|
||
groupMap.set(group.title, { ...group });
|
||
}
|
||
}
|
||
|
||
// 將合併後的組添加到結果數組
|
||
for (const group of groupMap.values()) {
|
||
groups.push(group);
|
||
}
|
||
|
||
return groups;
|
||
})(),
|
||
|
||
// 下一步行動計劃 - evaluation_feedback 的 feedback_type = improvement 且 criteria_item_id 是 null 的最後 3 筆數據
|
||
actionPlan: feedbackByType.improvement
|
||
.filter(f => f.criteria_item_id === null) // 只取 criteria_item_id 是 null 的
|
||
.slice(-3) // 取最後3筆
|
||
.map((f, index) => {
|
||
const content = f.content || '無描述';
|
||
const colonIndex = content.indexOf(':');
|
||
const phase = colonIndex > -1 ? content.substring(0, colonIndex + 1) : `階段 ${index + 1}:`;
|
||
const description = colonIndex > -1 ? content.substring(colonIndex + 1).trim() : content;
|
||
|
||
return {
|
||
phase: phase,
|
||
description: description
|
||
};
|
||
})
|
||
},
|
||
chartData: chartData
|
||
};
|
||
|
||
console.log(`📋 評審數據格式化完成: 專案=${formattedData.projectTitle}, 分數=${formattedData.overallScore}`);
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
data: formattedData
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('❌ 獲取評審詳細數據失敗:', error);
|
||
return NextResponse.json(
|
||
{ success: false, error: '獲取評審詳細數據失敗' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
} |