Files
weeklyreport/backend/api/admin/parse-image.post.ts
2026-01-05 02:00:13 +09:00

170 lines
5.0 KiB
TypeScript

import { query } from '../../utils/db'
import { callOpenAIVision, REPORT_PARSE_SYSTEM_PROMPT } from '../../utils/openai'
const ADMIN_EMAIL = 'coziny@gmail.com'
interface ParsedTask {
description: string
hours: number
}
interface ParsedProject {
projectName: string
workTasks: ParsedTask[]
planTasks: ParsedTask[]
}
interface ParsedReport {
employeeName: string
employeeEmail: string | null
projects: ParsedProject[]
issueDescription: string | null
vacationDescription: string | null
remarkDescription: string | null
}
interface ParsedResult {
reportYear: number
reportWeek: number
weekStartDate: string
weekEndDate: string
reports: ParsedReport[]
}
/**
* 이미지에서 주간보고 분석 (OpenAI Vision)
* POST /api/admin/parse-image
*/
export default defineEventHandler(async (event) => {
// 관리자 권한 체크
const userId = getCookie(event, 'user_id')
if (!userId) {
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
}
const currentUser = await query<any>(`
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
`, [userId])
if (!currentUser[0] || currentUser[0].employee_email !== ADMIN_EMAIL) {
throw createError({ statusCode: 403, message: '관리자만 사용할 수 있습니다.' })
}
const body = await readBody<{ images: string[] }>(event)
if (!body.images || body.images.length === 0) {
throw createError({ statusCode: 400, message: '분석할 이미지를 업로드해주세요.' })
}
if (body.images.length > 10) {
throw createError({ statusCode: 400, message: '이미지는 최대 10장까지 업로드 가능합니다.' })
}
// OpenAI Vision 분석
const aiResponse = await callOpenAIVision(REPORT_PARSE_SYSTEM_PROMPT, body.images)
let parsed: ParsedResult
try {
parsed = JSON.parse(aiResponse)
} catch (e) {
throw createError({ statusCode: 500, message: 'AI 응답 파싱 실패' })
}
// 주차 정보 기본값 설정 (AI가 파싱 못한 경우)
const now = new Date()
if (!parsed.reportYear) {
parsed.reportYear = now.getFullYear()
}
if (!parsed.reportWeek) {
// ISO 주차 계산
const startOfYear = new Date(now.getFullYear(), 0, 1)
const days = Math.floor((now.getTime() - startOfYear.getTime()) / (24 * 60 * 60 * 1000))
parsed.reportWeek = Math.ceil((days + startOfYear.getDay() + 1) / 7)
}
if (!parsed.weekStartDate || !parsed.weekEndDate) {
// 현재 주의 월요일~일요일 계산
const day = now.getDay()
const monday = new Date(now)
monday.setDate(now.getDate() - (day === 0 ? 6 : day - 1))
const sunday = new Date(monday)
sunday.setDate(monday.getDate() + 6)
parsed.weekStartDate = monday.toISOString().split('T')[0]
parsed.weekEndDate = sunday.toISOString().split('T')[0]
}
// 기존 직원 목록 조회
const employees = await query<any>(`
SELECT employee_id, employee_name, employee_email
FROM wr_employee_info
WHERE is_active = true
`)
// 기존 프로젝트 목록 조회
const projects = await query<any>(`
SELECT project_id, project_code, project_name
FROM wr_project_info
WHERE project_status != 'COMPLETED'
`)
// 직원 및 프로젝트 매칭
const matchedReports = parsed.reports.map(report => {
let matchedEmployee = null
if (report.employeeEmail) {
matchedEmployee = employees.find(
(e: any) => e.employee_email.toLowerCase() === report.employeeEmail?.toLowerCase()
)
}
if (!matchedEmployee) {
matchedEmployee = employees.find(
(e: any) => e.employee_name === report.employeeName
)
}
const matchedProjects = report.projects.map(proj => {
const existingProject = projects.find((p: any) =>
p.project_name.includes(proj.projectName) ||
proj.projectName.includes(p.project_name)
)
return {
...proj,
matchedProjectId: existingProject?.project_id || null,
matchedProjectCode: existingProject?.project_code || null,
matchedProjectName: existingProject?.project_name || null,
isNewProject: !existingProject
}
})
return {
...report,
matchedEmployeeId: matchedEmployee?.employee_id || null,
matchedEmployeeName: matchedEmployee?.employee_name || null,
matchedEmployeeEmail: matchedEmployee?.employee_email || null,
isEmployeeMatched: !!matchedEmployee,
isNewEmployee: !matchedEmployee && !!report.employeeEmail,
projects: matchedProjects
}
})
return {
success: true,
parsed: {
reportYear: parsed.reportYear,
reportWeek: parsed.reportWeek,
weekStartDate: parsed.weekStartDate,
weekEndDate: parsed.weekEndDate,
reports: matchedReports
},
employees: employees.map((e: any) => ({
employeeId: e.employee_id,
employeeName: e.employee_name,
employeeEmail: e.employee_email
})),
projects: projects.map((p: any) => ({
projectId: p.project_id,
projectCode: p.project_code,
projectName: p.project_name
}))
}
})