Files

301 lines
12 KiB
TypeScript
Raw Permalink 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.

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: Math.round(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 }
);
}
}