151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import { query } from '../../utils/db'
|
|
import { callOpenAI } from '../../utils/openai'
|
|
import { requireAuth } from '../../utils/session'
|
|
|
|
interface ParsedTask {
|
|
description: string
|
|
hours: number
|
|
isCompleted?: boolean
|
|
}
|
|
|
|
interface ParsedProject {
|
|
projectName: string
|
|
matchedProjectId: number | null
|
|
workTasks: ParsedTask[]
|
|
planTasks: ParsedTask[]
|
|
}
|
|
|
|
interface ParsedResult {
|
|
projects: ParsedProject[]
|
|
issueDescription: string | null
|
|
vacationDescription: string | null
|
|
remarkDescription: string | null
|
|
}
|
|
|
|
/**
|
|
* 개인 주간보고 텍스트 분석 (OpenAI)
|
|
* POST /api/ai/parse-my-report
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const userId = await requireAuth(event)
|
|
|
|
const body = await readBody<{ rawText: string }>(event)
|
|
|
|
if (!body.rawText || body.rawText.trim().length < 5) {
|
|
throw createError({ statusCode: 400, message: '분석할 텍스트를 입력해주세요.' })
|
|
}
|
|
|
|
// 기존 프로젝트 목록 조회
|
|
const projects = await query<any>(`
|
|
SELECT project_id, project_code, project_name
|
|
FROM wr_project_info
|
|
WHERE project_status = 'IN_PROGRESS'
|
|
`)
|
|
|
|
// 프로젝트 목록을 ID 포함해서 전달
|
|
const projectList = projects.map(p => `[ID:${p.project_id}] ${p.project_code}: ${p.project_name}`).join('\n')
|
|
|
|
// OpenAI 분석
|
|
const prompt = buildMyReportPrompt(body.rawText, projectList)
|
|
const aiResponse = await callOpenAI(prompt, true)
|
|
|
|
let parsed: ParsedResult
|
|
try {
|
|
parsed = JSON.parse(aiResponse)
|
|
} catch (e) {
|
|
throw createError({ statusCode: 500, message: 'AI 응답 파싱 실패' })
|
|
}
|
|
|
|
// 프로젝트 매칭
|
|
if (parsed.projects) {
|
|
for (const proj of parsed.projects) {
|
|
if (!proj.matchedProjectId && proj.projectName) {
|
|
const matched = projects.find((p: any) =>
|
|
p.project_name.toLowerCase().includes(proj.projectName.toLowerCase()) ||
|
|
proj.projectName.toLowerCase().includes(p.project_name.toLowerCase()) ||
|
|
p.project_code.toLowerCase() === proj.projectName.toLowerCase()
|
|
)
|
|
if (matched) {
|
|
proj.matchedProjectId = matched.project_id
|
|
proj.projectName = matched.project_name
|
|
}
|
|
}
|
|
|
|
// workTasks 기본값
|
|
proj.workTasks = (proj.workTasks || []).map((t: any) => ({
|
|
description: t.description || '',
|
|
hours: t.hours || 0,
|
|
isCompleted: t.isCompleted !== false
|
|
}))
|
|
|
|
// planTasks 기본값
|
|
proj.planTasks = (proj.planTasks || []).map((t: any) => ({
|
|
description: t.description || '',
|
|
hours: t.hours || 0
|
|
}))
|
|
}
|
|
|
|
// 내용 없는 프로젝트 제외 (workTasks, planTasks 모두 비어있으면 제외)
|
|
parsed.projects = parsed.projects.filter((proj: any) =>
|
|
(proj.workTasks && proj.workTasks.length > 0) ||
|
|
(proj.planTasks && proj.planTasks.length > 0)
|
|
)
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
parsed,
|
|
projects
|
|
}
|
|
})
|
|
|
|
function buildMyReportPrompt(rawText: string, projectList: string): any[] {
|
|
return [
|
|
{
|
|
role: 'system',
|
|
content: `당신은 주간보고 내용을 분석하는 AI입니다.
|
|
사용자가 입력한 텍스트를 분석하여 프로젝트별 업무 내용을 추출해주세요.
|
|
|
|
현재 등록된 프로젝트 목록 (형식: [ID:숫자] 코드: 이름):
|
|
${projectList}
|
|
|
|
⚠️ 중요: 입력 텍스트에서 추출한 프로젝트명과 위 목록을 비교하여 가장 유사한 프로젝트의 ID를 matchedProjectId에 반환하세요.
|
|
- 유사도 판단: 키워드 일치, 약어, 부분 문자열 등 고려
|
|
- 예: "한우 유전체" → "보은 한우 온라인 유전체 분석" 매칭 가능
|
|
- 예: "HEIS" → "보건환경연구원 HEIS" 매칭 가능
|
|
- 매칭되는 프로젝트가 없으면 matchedProjectId는 null
|
|
|
|
응답은 반드시 아래 JSON 형식으로만 출력하세요:
|
|
{
|
|
"projects": [
|
|
{
|
|
"projectName": "입력에서 추출한 원본 프로젝트명",
|
|
"matchedProjectId": 5,
|
|
"workTasks": [
|
|
{"description": "작업내용", "hours": 8, "isCompleted": true}
|
|
],
|
|
"planTasks": [
|
|
{"description": "계획내용", "hours": 8}
|
|
]
|
|
}
|
|
],
|
|
"issueDescription": "이슈/리스크 내용 또는 null",
|
|
"vacationDescription": "휴가일정 내용 또는 null",
|
|
"remarkDescription": "기타사항 내용 또는 null"
|
|
}
|
|
|
|
규칙:
|
|
1. projectName은 입력 텍스트에서 추출한 원본 그대로
|
|
2. matchedProjectId는 위 프로젝트 목록에서 가장 유사한 프로젝트의 ID (숫자)
|
|
3. "금주 실적", "이번주", "완료" 등은 workTasks로 분류
|
|
4. "차주 계획", "다음주", "예정" 등은 planTasks로 분류
|
|
5. 시간이 명시되지 않은 경우 hours는 0으로
|
|
6. JSON 외의 텍스트는 절대 출력하지 마세요`
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: rawText
|
|
}
|
|
]
|
|
}
|