1ㅊㅏ완료
This commit is contained in:
169
backend/api/admin/parse-image.post.ts
Normal file
169
backend/api/admin/parse-image.post.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
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
|
||||
}))
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user