1ㅊㅏ완료
This commit is contained in:
152
backend/api/report/summary/regenerate-ai.post.ts
Normal file
152
backend/api/report/summary/regenerate-ai.post.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { defineEventHandler, createError, getCookie } from 'h3'
|
||||
import { query, queryOne, execute } from '../../../utils/db'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY
|
||||
})
|
||||
|
||||
/**
|
||||
* 기존 취합 보고서에 AI 요약 일괄 생성
|
||||
* POST /api/report/summary/regenerate-ai
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const userId = getCookie(event, 'user_id')
|
||||
if (!userId) {
|
||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
||||
}
|
||||
|
||||
// AI 요약이 없는 취합 보고서 조회
|
||||
const summaries = await query<any>(`
|
||||
SELECT s.summary_id, s.project_id, s.report_year, s.report_week,
|
||||
p.project_name
|
||||
FROM wr_aggregated_report_summary s
|
||||
JOIN wr_project_info p ON s.project_id = p.project_id
|
||||
WHERE s.ai_work_summary IS NULL OR s.ai_plan_summary IS NULL
|
||||
ORDER BY s.summary_id
|
||||
`, [])
|
||||
|
||||
console.log(`AI 요약 생성 대상: ${summaries.length}건`)
|
||||
|
||||
let successCount = 0
|
||||
let errorCount = 0
|
||||
|
||||
for (const summary of summaries) {
|
||||
try {
|
||||
// 해당 프로젝트/주차의 Task 조회
|
||||
const tasks = await query<any>(`
|
||||
SELECT
|
||||
t.task_type,
|
||||
t.task_description,
|
||||
t.task_hours,
|
||||
t.is_completed,
|
||||
e.employee_name as author_name
|
||||
FROM wr_weekly_report r
|
||||
JOIN wr_weekly_report_task t ON r.report_id = t.report_id
|
||||
JOIN wr_employee_info e ON r.author_id = e.employee_id
|
||||
WHERE t.project_id = $1
|
||||
AND r.report_year = $2
|
||||
AND r.report_week = $3
|
||||
AND r.report_status IN ('SUBMITTED', 'AGGREGATED')
|
||||
ORDER BY t.task_type, e.employee_name
|
||||
`, [summary.project_id, summary.report_year, summary.report_week])
|
||||
|
||||
if (tasks.length === 0) {
|
||||
console.log(`Skip ${summary.summary_id}: no tasks`)
|
||||
continue
|
||||
}
|
||||
|
||||
// AI 요약 생성
|
||||
const { workSummary, planSummary } = await generateAISummary(
|
||||
tasks,
|
||||
summary.project_name,
|
||||
summary.report_year,
|
||||
summary.report_week
|
||||
)
|
||||
|
||||
// 업데이트
|
||||
await execute(`
|
||||
UPDATE wr_aggregated_report_summary
|
||||
SET ai_work_summary = $1,
|
||||
ai_plan_summary = $2,
|
||||
ai_summary_at = NOW()
|
||||
WHERE summary_id = $3
|
||||
`, [workSummary, planSummary, summary.summary_id])
|
||||
|
||||
successCount++
|
||||
console.log(`Generated AI summary for ${summary.project_name} (${summary.report_year}-W${summary.report_week})`)
|
||||
|
||||
} catch (e: any) {
|
||||
console.error(`Error for summary ${summary.summary_id}:`, e.message)
|
||||
errorCount++
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
total: summaries.length,
|
||||
successCount,
|
||||
errorCount
|
||||
}
|
||||
})
|
||||
|
||||
async function generateAISummary(tasks: any[], projectName: string, year: number, week: number) {
|
||||
const workTasks = tasks.filter(t => t.task_type === 'WORK')
|
||||
const planTasks = tasks.filter(t => t.task_type === 'PLAN')
|
||||
|
||||
const workPrompt = `주간보고 취합 전문가입니다. 아래 금주 실적을 간결하게 요약해주세요.
|
||||
|
||||
## 프로젝트: ${projectName}
|
||||
## 기간: ${year}년 ${week}주차
|
||||
|
||||
## 금주 실적 (${workTasks.length}건)
|
||||
${workTasks.map(t => `- [${t.author_name}] ${t.is_completed ? '완료' : '진행중'} | ${t.task_description} (${t.task_hours}h)`).join('\n') || '없음'}
|
||||
|
||||
## 요약 규칙
|
||||
1. 완료된 작업과 진행 중인 작업을 구분하여 핵심만 요약
|
||||
2. 동일/유사한 작업은 하나로 통합
|
||||
3. 담당자 이름은 생략하고 내용 위주로 작성
|
||||
4. 3~5줄 이내로 간결하게
|
||||
5. 마크다운 리스트 형식으로 작성`
|
||||
|
||||
const planPrompt = `주간보고 취합 전문가입니다. 아래 차주 계획을 간결하게 요약해주세요.
|
||||
|
||||
## 프로젝트: ${projectName}
|
||||
## 기간: ${year}년 ${week+1}주차 계획
|
||||
|
||||
## 차주 계획 (${planTasks.length}건)
|
||||
${planTasks.map(t => `- [${t.author_name}] ${t.task_description} (${t.task_hours}h)`).join('\n') || '없음'}
|
||||
|
||||
## 요약 규칙
|
||||
1. 주요 계획을 우선순위에 따라 요약
|
||||
2. 동일/유사한 작업은 하나로 통합
|
||||
3. 담당자 이름은 생략하고 내용 위주로 작성
|
||||
4. 2~4줄 이내로 간결하게
|
||||
5. 마크다운 리스트 형식으로 작성`
|
||||
|
||||
const [workRes, planRes] = await Promise.all([
|
||||
openai.chat.completions.create({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{ role: 'system', content: '주간보고 취합 전문가입니다. 간결하게 요약합니다.' },
|
||||
{ role: 'user', content: workPrompt }
|
||||
],
|
||||
temperature: 0.3,
|
||||
max_tokens: 500
|
||||
}),
|
||||
openai.chat.completions.create({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{ role: 'system', content: '주간보고 취합 전문가입니다. 간결하게 요약합니다.' },
|
||||
{ role: 'user', content: planPrompt }
|
||||
],
|
||||
temperature: 0.3,
|
||||
max_tokens: 500
|
||||
})
|
||||
])
|
||||
|
||||
return {
|
||||
workSummary: workRes.choices[0]?.message?.content || '요약 없음',
|
||||
planSummary: planRes.choices[0]?.message?.content || '요약 없음'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user