import { query } from './database'; // AI 評分結果的 TypeScript 介面 export interface AIEvaluationResult { projectTitle: string; overallScore: number; totalPossible: number; grade: string; performanceStatus: string; recommendedStars: number; analysisDate: string; criteria: { name: string; score: number; maxScore: number; weight: number; weightedScore: number; feedback: string; strengths: string[]; improvements: string[]; }[]; overview: { excellentItems: number; improvementItems: number; overallPerformance: number; }; detailedAnalysis: { summary: string; keyFindings: string[]; }; improvementSuggestions: { overallSuggestions: string; maintainStrengths: { title: string; description: string; }[]; keyImprovements: { title: string; description: string; suggestions: string[]; }[]; actionPlan: { phase: string; description: string; }[]; }; } // 評分項目名稱對應到 criteria_items 表的 ID const criteriaNameToId: Record = { "應用實務性": 52, "創新性": 53, "成效與效益": 54, "擴散與可複用性": 55, "簡報與表達": 56 // 注意:資料庫中的名稱末尾有零寬度空格,但這裡使用清理後的名稱 }; export class EvaluationUploadService { /** * 上傳 AI 評分結果到資料庫 * @param projectId 專案 ID * @param aiResult AI 評分結果 * @param analysisDuration 分析耗時(秒) * @param aiModelVersion AI 模型版本 * @returns 創建的 evaluation ID */ static async uploadEvaluationResult( projectId: number, aiResult: AIEvaluationResult, analysisDuration?: number, aiModelVersion: string = 'gemini-1.5-pro' ): Promise { try { // 1. 創建 evaluations 記錄 const evaluationId = await this.createEvaluationRecord( projectId, aiResult, analysisDuration, aiModelVersion ); // 2. 創建 evaluation_scores 記錄 await this.createEvaluationScores(evaluationId, aiResult); // 3. 創建 evaluation_feedback 記錄 await this.createEvaluationFeedback(evaluationId, aiResult); return evaluationId; } catch (error) { console.error('上傳評分結果時發生錯誤:', error); throw error; } } /** * 創建 evaluations 記錄 */ private static async createEvaluationRecord( projectId: number, aiResult: AIEvaluationResult, analysisDuration?: number, aiModelVersion: string = 'gemini-1.5-pro' ): Promise { const sql = ` INSERT INTO evaluations (project_id, overall_score, max_possible_score, grade, analysis_duration, ai_model_version, status, error_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ projectId, aiResult.overallScore, aiResult.totalPossible, aiResult.grade, analysisDuration, aiModelVersion, 'completed', null ]); return result.insertId; } /** * 創建 evaluation_scores 記錄 */ private static async createEvaluationScores( evaluationId: number, aiResult: AIEvaluationResult ): Promise { const sql = ` INSERT INTO evaluation_scores (evaluation_id, criteria_item_id, score, max_score, weight, weighted_score, percentage) VALUES (?, ?, ?, ?, ?, ?, ?) `; for (const criteria of aiResult.criteria) { const criteriaItemId = criteriaNameToId[criteria.name]; if (!criteriaItemId) { console.warn(`找不到評分項目 "${criteria.name}" 的 ID`); continue; } const percentage = (criteria.score / criteria.maxScore) * 100; await query(sql, [ evaluationId, criteriaItemId, criteria.score, criteria.maxScore, criteria.weight, criteria.weightedScore, percentage ]); } } /** * 創建 evaluation_feedback 記錄 */ private static async createEvaluationFeedback( evaluationId: number, aiResult: AIEvaluationResult ): Promise { const sql = ` INSERT INTO evaluation_feedback (evaluation_id, criteria_item_id, feedback_type, content, sort_order) VALUES (?, ?, ?, ?, ?) `; let sortOrder = 1; // 1. 整體反饋 await query(sql, [ evaluationId, null, 'overall', aiResult.detailedAnalysis.summary, sortOrder++ ]); // 2. 各項標準的反饋 for (const criteria of aiResult.criteria) { // 清理名稱中的特殊字符,包括零寬度空格 const cleanName = criteria.name.replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); const criteriaItemId = criteriaNameToId[cleanName]; console.log(`🔍 處理 criteria: "${criteria.name}" -> 清理後: "${cleanName}" -> ID: ${criteriaItemId}`); if (!criteriaItemId) { console.warn(`⚠️ 找不到 criteria_item_id for: "${cleanName}"`); continue; } // 標準反饋 await query(sql, [ evaluationId, criteriaItemId, 'criteria', criteria.feedback, sortOrder++ ]); // 優點反饋 for (const strength of criteria.strengths) { await query(sql, [ evaluationId, criteriaItemId, 'strength', strength, sortOrder++ ]); } // 改進建議反饋 for (const improvement of criteria.improvements) { await query(sql, [ evaluationId, criteriaItemId, 'improvement', improvement, sortOrder++ ]); } } // 3. 改進建議的整體建議 await query(sql, [ evaluationId, null, 'improvement', aiResult.improvementSuggestions.overallSuggestions, sortOrder++ ]); // 4. 保持優勢 for (const strength of aiResult.improvementSuggestions.maintainStrengths) { await query(sql, [ evaluationId, null, 'strength', `${strength.title}: ${strength.description}`, sortOrder++ ]); } // 5. 關鍵改進建議 for (const improvement of aiResult.improvementSuggestions.keyImprovements) { await query(sql, [ evaluationId, null, 'improvement', `${improvement.title}: ${improvement.description}`, sortOrder++ ]); } // 6. 行動計劃 for (const plan of aiResult.improvementSuggestions.actionPlan) { await query(sql, [ evaluationId, null, 'improvement', `${plan.phase}: ${plan.description}`, sortOrder++ ]); } } /** * 更新 criteria_item_id 對應關係 * @param newMapping 新的對應關係 */ static updateCriteriaMapping(newMapping: Record): void { Object.assign(criteriaNameToId, newMapping); } /** * 獲取當前的 criteria_item_id 對應關係 */ static getCriteriaMapping(): Record { return { ...criteriaNameToId }; } } // 預設的 AI 評分結果(用於測試) export const defaultAIEvaluationResult: AIEvaluationResult = { "projectTitle": "ITBU_人咧 PeoplePing 智能出勤系統", "overallScore": 78.5, "totalPossible": 100, "grade": "A-", "performanceStatus": "表現良好", "recommendedStars": 4, "analysisDate": "2024-10-27", "criteria": [ { "name": "應用實務性", "score": 8, "maxScore": 10, "weight": 30, "weightedScore": 24, "feedback": "系統設計貼近實際需求,解決了主管和員工的痛點,具備可行性。", "strengths": [ "明確指出解決了主管無法即時掌握團隊出勤狀況和員工查詢流程繁瑣的問題", "提供了 Dify Chat Flow 和 Web Dashboard 兩個使用者介面", "簡潔明瞭地說明了系統架構和流程" ], "improvements": [ "可以加入更多實際案例或數據,例如導入前的數據和導入後的對比數據", "更詳細地說明系統的安全性及數據隱私保護措施" ] }, { "name": "創新性", "score": 6, "maxScore": 10, "weight": 15, "weightedScore": 9, "feedback": "利用 Dify Chat Flow 整合自然語言處理,提升使用者體驗,但整體創新性不算高。", "strengths": [ "結合 Dify Chat Flow 和 Web Dashboard,提供了多樣化的使用方式", "利用自然語言處理,讓使用者能以更自然的方式進行出勤登記" ], "improvements": [ "可以探索更多創新應用,例如結合生物識別技術、位置服務等", "進一步提升自然語言理解能力,處理更多複雜的出勤場景" ] }, { "name": "成效與效益", "score": 9, "maxScore": 10, "weight": 25, "weightedScore": 22.5, "feedback": "量化了系統帶來的效率提升,數據明確,具有說服力。", "strengths": [ "使用數據明確地呈現了系統帶來的效率提升,例如出勤登錄時間縮短 83%,月報彙整時間縮減 99%", "系統錯誤率控制在 1% 以下,展現了系統的穩定性" ], "improvements": [ "可以更詳細地說明數據的計算方法和資料來源" ] }, { "name": "擴散與可複用性", "score": 7, "maxScore": 10, "weight": 20, "weightedScore": 14, "feedback": "系統架構清晰,模組化設計良好,具備一定程度的可複用性。", "strengths": [ "系統架構清晰,各模組功能明確", "後續優化與應用擴展規劃合理,展現了系統的擴展性" ], "improvements": [ "可以更詳細地說明系統的模組化設計,以及如何方便地複製到其他部門" ] }, { "name": "簡報與表達", "score": 8, "maxScore": 10, "weight": 10, "weightedScore": 8, "feedback": "簡報內容結構清晰,表達流暢,但部分幻燈片內容略顯簡潔。", "strengths": [ "簡報目錄清晰,結構合理", "圖表使用恰當,數據呈現清晰" ], "improvements": [ "某些幻燈片可以添加更多圖表或說明,使內容更豐富完整", "幻燈片 5 的「人咧~聊天流程展示」需要更具體的內容" ] } ], "overview": { "excellentItems": 1, "improvementItems": 0, "overallPerformance": 78.5 }, "detailedAnalysis": { "summary": "整體而言,此專案具備良好的應用實務性、成效與效益,以及擴散與可複用性。創新性方面尚有提升空間,簡報表達也需進一步完善。", "keyFindings": [ "系統有效解決了實際問題,並量化了效益提升", "系統架構清晰,具備一定程度的可複用性", "創新性方面仍有提升空間,可以探索更多創新應用" ] }, "improvementSuggestions": { "overallSuggestions": "整體而言,專案表現良好,建議著重提升創新性,並補充更多數據和案例,以增强說服力。", "maintainStrengths": [ { "title": "數據驅動的效益呈現", "description": "有效地利用數據量化了系統帶來的效率提升,這部分是專案的優勢,應該持續保持。" } ], "keyImprovements": [ { "title": "提升創新性", "description": "探索更多創新應用,例如結合生物識別技術、位置服務等,提升系統的競爭力。", "suggestions": [ "研究市場上最新的技術,尋求更多創新點", "考慮與其他系統整合,擴展系統功能" ] }, { "title": "完善數據說明和案例", "description": "提供更詳細的數據計算方法和資料來源,並添加更多實際案例,以增强簡報的說服力。", "suggestions": [ "提供導入前後的數據對比", "加入更多使用者反饋和成功案例" ] } ], "actionPlan": [ { "phase": "短期目標(1-2週)", "description": "完善數據說明和案例,補充更多細節。" }, { "phase": "中期目標(1個月)", "description": "研究新的技術和應用場景,探討系統的創新升級。" }, { "phase": "長期目標(3個月)", "description": "完成系統的創新升級,並將其應用到更多部門。" } ] } };