diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/controller/AnswerController.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/controller/AnswerController.java index c984f10..ba550f5 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/controller/AnswerController.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/controller/AnswerController.java @@ -1,7 +1,7 @@ package io.wisoft.prepair.prepair_api.interview.answer.controller; -import io.wisoft.prepair.prepair_api.interview.answer.dto.AnswerRequest; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResponse; +import io.wisoft.prepair.prepair_api.interview.answer.dto.request.AnswerRequest; +import io.wisoft.prepair.prepair_api.interview.answer.dto.response.FeedbackResponse; import io.wisoft.prepair.prepair_api.common.response.ApiResponse; import io.wisoft.prepair.prepair_api.interview.answer.service.AnswerService; import io.wisoft.prepair.prepair_api.interview.answer.service.VideoAnswerSseService; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResult.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResult.java deleted file mode 100644 index 6d175f5..0000000 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResult.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; - -public record FinalFeedbackResult( - String finalFeedback -) { -} \ No newline at end of file diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerSubmitResult.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/AnswerSubmitResult.java similarity index 72% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerSubmitResult.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/AnswerSubmitResult.java index fddaad3..d4a793c 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerSubmitResult.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/AnswerSubmitResult.java @@ -1,4 +1,4 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewFeedback; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/CombinedFeedbackResult.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/CombinedFeedbackResult.java similarity index 56% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/CombinedFeedbackResult.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/CombinedFeedbackResult.java index 38e2c08..f34fb67 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/CombinedFeedbackResult.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/CombinedFeedbackResult.java @@ -1,7 +1,7 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; public record CombinedFeedbackResult( String combineFeedback, int score ) { -} \ No newline at end of file +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackDetail.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackDetail.java similarity index 62% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackDetail.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackDetail.java index 44290c4..1ea4305 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackDetail.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackDetail.java @@ -1,4 +1,4 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; public record FeedbackDetail( String good, diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResult.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackResult.java similarity index 66% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResult.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackResult.java index 6b4fa18..7b1bbda 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResult.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FeedbackResult.java @@ -1,4 +1,4 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; public record FeedbackResult( String good, diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackInput.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackInput.java new file mode 100644 index 0000000..a67d370 --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackInput.java @@ -0,0 +1,11 @@ +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; + +import java.util.UUID; + +public record FinalFeedbackInput( + UUID questionId, + String question, + String combinedFeedback, + int score +) { +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackResult.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackResult.java new file mode 100644 index 0000000..71c91ba --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/internal/FinalFeedbackResult.java @@ -0,0 +1,6 @@ +package io.wisoft.prepair.prepair_api.interview.answer.dto.internal; + +public record FinalFeedbackResult( + String finalFeedback +) { +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerRequest.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/request/AnswerRequest.java similarity index 63% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerRequest.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/request/AnswerRequest.java index 80d46d2..22aa7f8 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/AnswerRequest.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/request/AnswerRequest.java @@ -1,4 +1,4 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.request; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResponse.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FeedbackResponse.java similarity index 83% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResponse.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FeedbackResponse.java index 90a5cce..8f8a3b4 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FeedbackResponse.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FeedbackResponse.java @@ -1,5 +1,6 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.response; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackDetail; import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewFeedback; import io.wisoft.prepair.prepair_api.interview.answer.entity.FeedbackType; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResponse.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FinalFeedbackResponse.java similarity index 86% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResponse.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FinalFeedbackResponse.java index 958062b..d350919 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/FinalFeedbackResponse.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/dto/response/FinalFeedbackResponse.java @@ -1,4 +1,4 @@ -package io.wisoft.prepair.prepair_api.interview.answer.dto; +package io.wisoft.prepair.prepair_api.interview.answer.dto.response; import java.util.List; import java.util.UUID; @@ -18,4 +18,4 @@ public record QuestionFeedback( String videoFeedback ) { } -} \ No newline at end of file +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedHandler.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedHandler.java deleted file mode 100644 index 550dced..0000000 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedHandler.java +++ /dev/null @@ -1,236 +0,0 @@ -package io.wisoft.prepair.prepair_api.interview.answer.event; - -import io.wisoft.prepair.prepair_api.interview.answer.dto.CombinedFeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FinalFeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FinalFeedbackResponse; -import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewAnswer; -import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewFeedback; -import io.wisoft.prepair.prepair_api.interview.answer.entity.FeedbackType; -import io.wisoft.prepair.prepair_api.interview.answer.repository.AnswerRepository; -import io.wisoft.prepair.prepair_api.interview.answer.repository.FeedbackRepository; -import io.wisoft.prepair.prepair_api.interview.answer.service.AnswerPersistenceService; -import io.wisoft.prepair.prepair_api.interview.answer.service.FeedbackGenerator; -import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; -import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository; -import io.wisoft.prepair.prepair_api.interview.session.entity.InterviewSession; -import io.wisoft.prepair.prepair_api.interview.session.service.SessionPersistenceService; -import io.wisoft.prepair.prepair_api.common.support.SseEmitterManager; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -@Slf4j -@Component -@RequiredArgsConstructor -public class AllAnalysisCompletedHandler { - - private final FeedbackRepository feedbackRepository; - private final FeedbackGenerator feedbackGenerator; - private final AnswerPersistenceService answerPersistenceService; - private final AnswerRepository answerRepository; - private final QuestionRepository questionRepository; - private final SessionPersistenceService sessionPersistenceService; - private final SseEmitterManager sseEmitterManager; - - @Async("videoTaskExecutor") - @EventListener - public void handle(AllAnalysisCompletedEvent event) { - UUID answerId = event.answerId(); - - if (event.hasFailed()) { - log.error("[종합평가] 분석 실패로 종합평가 생략 - answerId: {}", answerId); - failSession(answerId, "분석 중 오류가 발생했습니다."); - return; - } - - try { - Optional feedbacksOpt = findAnalysisFeedbacks(answerId); - if (feedbacksOpt.isEmpty()) { - failSession(answerId, "분석 결과가 누락되어 종합 평가를 생성할 수 없습니다."); - return; - } - - InterviewAnswer answer = answerRepository.findByIdWithQuestionAndSession(answerId).orElse(null); - if (answer == null) return; - - saveCombinedFeedback(answerId, answer, feedbacksOpt.get()); - tryGenerateFinalFeedback(answer); - - } catch (Exception e) { - log.error("[종합평가] 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e); - failSession(answerId, "종합 평가 생성 중 오류가 발생했습니다."); - } - } - - private Optional findAnalysisFeedbacks(UUID answerId) { - List feedbacks = feedbackRepository.findByInterviewAnswerId(answerId); - - InterviewFeedback stt = feedbacks.stream() - .filter(f -> f.getFeedbackType() == FeedbackType.STT) - .findFirst() - .orElse(null); - - InterviewFeedback video = feedbacks.stream() - .filter(f -> f.getFeedbackType() == FeedbackType.VIDEO) - .findFirst() - .orElse(null); - - if (stt == null || video == null) { - log.error("[종합평가] STT 또는 Video 피드백 없음 - answerId: {}", answerId); - return Optional.empty(); - } - - return Optional.of(new AnalysisFeedbacks(stt, video)); - } - - private void saveCombinedFeedback(UUID answerId, InterviewAnswer answer, AnalysisFeedbacks feedbacks) { - String question = answer.getInterviewQuestion().getQuestion(); - - CombinedFeedbackResult result = feedbackGenerator.generateCombined( - question, - feedbacks.stt().getFeedback(), - feedbacks.video().getFeedback() - ); - - answerPersistenceService.saveCombinedFeedback(answerId, result); - log.info("[종합평가] 완료 - answerId: {}, score: {}", answerId, result.score()); - } - - private void tryGenerateFinalFeedback(InterviewAnswer answer) { - InterviewSession session = answer.getInterviewQuestion().getInterviewSession(); - if (session == null) { - log.warn("[최종평가] 세션 없음 - answerId: {}", answer.getId()); - return; - } - - UUID sessionId = session.getId(); - if (!isFinalFeedbackReady(sessionId, session.getTotalQuestionCount())) return; - - List questions = questionRepository.findByInterviewSessionId(sessionId); - - Map answerMap = answerRepository.findBySessionId(sessionId).stream() - .collect(Collectors.toMap(a -> a.getInterviewQuestion().getId(), a -> a)); - - Map> feedbackMap = feedbackRepository.findAllBySessionId(sessionId).stream() - .collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId())); - - FinalFeedbackData data = buildFinalData(questions, answerMap, feedbackMap); - FinalFeedbackResult finalResult = feedbackGenerator.generateFinal(data.promptInput()); - - completeSession(session, data, finalResult); - } - - private boolean isFinalFeedbackReady(UUID sessionId, int totalQuestionCount) { - long combinedCount = feedbackRepository.countBySessionIdAndFeedbackType(sessionId, FeedbackType.COMBINED); - - if (combinedCount < totalQuestionCount) { - log.info("[최종평가] 아직 모든 질문 완료되지 않음 - sessionId: {}, {}/{}", sessionId, combinedCount, totalQuestionCount); - return false; - } - return true; - } - - private FinalFeedbackData buildFinalData( - List questions, - Map answerMap, - Map> feedbackMap - ) { - StringBuilder promptInput = new StringBuilder(); - List questionFeedbacks = new ArrayList<>(); - int totalScore = 0; - - for (InterviewQuestion q : questions) { - InterviewAnswer ans = answerMap.get(q.getId()); - if (ans == null) continue; - - List answerFeedbacks = feedbackMap.getOrDefault(ans.getId(), List.of()); - - InterviewFeedback combined = answerFeedbacks.stream() - .filter(f -> f.getFeedbackType() == FeedbackType.COMBINED) - .findFirst() - .orElse(null); - if (combined == null) continue; - - String sttFeedbackStr = answerFeedbacks.stream() - .filter(f -> f.getFeedbackType() == FeedbackType.STT) - .findFirst() - .map(InterviewFeedback::getFeedback) - .orElse(null); - - String videoFeedbackStr = answerFeedbacks.stream() - .filter(f -> f.getFeedbackType() == FeedbackType.VIDEO) - .findFirst() - .map(InterviewFeedback::getFeedback) - .orElse(null); - - promptInput.append("질문: ").append(q.getQuestion()).append("\n"); - promptInput.append("종합 평가: ").append(combined.getFeedback()).append("\n"); - promptInput.append("점수: ").append(combined.getScore()).append("\n\n"); - - questionFeedbacks.add(new FinalFeedbackResponse.QuestionFeedback( - q.getId(), - q.getQuestion(), - combined.getScore(), - combined.getFeedback(), - sttFeedbackStr, - videoFeedbackStr - )); - - totalScore += combined.getScore(); - } - - int finalScore = questionFeedbacks.isEmpty() ? 0 : totalScore / questionFeedbacks.size(); - return new FinalFeedbackData(promptInput.toString(), questionFeedbacks, finalScore); - } - - private void completeSession(InterviewSession session, FinalFeedbackData data, FinalFeedbackResult finalResult) { - UUID sessionId = session.getId(); - - sessionPersistenceService.saveCompletedSession(sessionId, data.finalScore(), finalResult.finalFeedback()); - - FinalFeedbackResponse response = new FinalFeedbackResponse( - sessionId, - data.finalScore(), - finalResult.finalFeedback(), - data.questionFeedbacks() - ); - - sseEmitterManager.send(sessionId, "final-complete", response); - sseEmitterManager.complete(sessionId); - - log.info("[최종평가] 완료 - sessionId: {}, finalScore: {}", sessionId, data.finalScore()); - } - - private void failSession(UUID answerId, String message) { - InterviewAnswer answer = answerRepository.findByIdWithQuestionAndSession(answerId).orElse(null); - if (answer == null || answer.getInterviewQuestion().getInterviewSession() == null) return; - - InterviewSession session = answer.getInterviewQuestion().getInterviewSession(); - sessionPersistenceService.saveFailedSession(session.getId()); - - sseEmitterManager.send(session.getId(), "analysis-failed", Map.of("message", message)); - sseEmitterManager.complete(session.getId()); - } - - private record AnalysisFeedbacks( - InterviewFeedback stt, - InterviewFeedback video - ) { - } - - private record FinalFeedbackData( - String promptInput, - List questionFeedbacks, - int finalScore - ) { - } -} \ No newline at end of file diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedEvent.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedEvent.java similarity index 53% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedEvent.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedEvent.java index c9d9484..c9f6fcd 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AllAnalysisCompletedEvent.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedEvent.java @@ -2,5 +2,5 @@ import java.util.UUID; -public record AllAnalysisCompletedEvent(UUID answerId, boolean hasFailed) { +public record AnalysisCompletedEvent(UUID answerId, boolean hasFailed) { } diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedHandler.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedHandler.java new file mode 100644 index 0000000..4f3f3b0 --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletedHandler.java @@ -0,0 +1,37 @@ +package io.wisoft.prepair.prepair_api.interview.answer.event; + +import io.wisoft.prepair.prepair_api.interview.answer.service.AnalysisCompletionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AnalysisCompletedHandler { + + private final AnalysisCompletionService analysisCompletionService; + + @Async("videoTaskExecutor") + @EventListener + public void handle(AnalysisCompletedEvent event) { + UUID answerId = event.answerId(); + + if (event.hasFailed()) { + log.error("[종합평가] 분석 실패로 종합평가 생략 - answerId: {}", answerId); + analysisCompletionService.failSession(answerId, "분석 중 오류가 발생했습니다."); + return; + } + + try { + analysisCompletionService.processAnalysisCompletion(answerId); + } catch (Exception e) { + log.error("[종합평가] 실패 - answerId: {}, error: {}", answerId, e.getMessage(), e); + analysisCompletionService.failSession(answerId, "종합 평가 생성 중 오류가 발생했습니다."); + } + } +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletionTracker.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletionTracker.java index c389c08..6e7a765 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletionTracker.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/AnalysisCompletionTracker.java @@ -54,7 +54,7 @@ private void finishAnalysis(UUID answerId, boolean failed) { int analysisCount = state.analysisCount.incrementAndGet(); if (analysisCount == ANALYSIS_TASKS) { eventPublisher.publishEvent( - new AllAnalysisCompletedEvent(answerId, state.analysisFailed.get()) + new AnalysisCompletedEvent(answerId, state.analysisFailed.get()) ); } finishTask(answerId); diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/VideoAnswerProcessor.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/VideoAnswerProcessor.java index ae8ac01..848a644 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/VideoAnswerProcessor.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/event/VideoAnswerProcessor.java @@ -3,8 +3,8 @@ import io.wisoft.prepair.prepair_api.common.exception.BusinessException; import io.wisoft.prepair.prepair_api.common.exception.ErrorCode; import io.wisoft.prepair.prepair_api.external.storage.S3FileStorage; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackDetail; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackDetail; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackResult; import io.wisoft.prepair.prepair_api.interview.answer.entity.FeedbackType; import io.wisoft.prepair.prepair_api.interview.answer.service.AnswerPersistenceService; import io.wisoft.prepair.prepair_api.interview.answer.service.FeedbackGenerator; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnalysisCompletionService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnalysisCompletionService.java new file mode 100644 index 0000000..5369a30 --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnalysisCompletionService.java @@ -0,0 +1,222 @@ +package io.wisoft.prepair.prepair_api.interview.answer.service; + +import io.wisoft.prepair.prepair_api.common.exception.BusinessException; +import io.wisoft.prepair.prepair_api.common.exception.ErrorCode; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.CombinedFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FinalFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.response.FinalFeedbackResponse; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FinalFeedbackInput; +import io.wisoft.prepair.prepair_api.interview.answer.entity.FeedbackType; +import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewAnswer; +import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewFeedback; +import io.wisoft.prepair.prepair_api.interview.answer.repository.AnswerRepository; +import io.wisoft.prepair.prepair_api.interview.answer.repository.FeedbackRepository; +import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; +import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository; +import io.wisoft.prepair.prepair_api.interview.session.entity.InterviewSession; +import io.wisoft.prepair.prepair_api.interview.session.notifier.SessionCompletionNotifier; +import io.wisoft.prepair.prepair_api.interview.session.service.SessionPersistenceService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AnalysisCompletionService { + + private final AnswerRepository answerRepository; + private final FeedbackRepository feedbackRepository; + private final QuestionRepository questionRepository; + private final FeedbackGenerator feedbackGenerator; + private final AnswerPersistenceService answerPersistenceService; + private final SessionPersistenceService sessionPersistenceService; + private final SessionCompletionNotifier completionNotifier; + + public void processAnalysisCompletion(UUID answerId) { + List feedbacks = feedbackRepository.findByInterviewAnswerId(answerId); + + InterviewFeedback stt = findRequiredFeedback(feedbacks, FeedbackType.STT); + InterviewFeedback video = findRequiredFeedback(feedbacks, FeedbackType.VIDEO); + + InterviewAnswer answer = answerRepository.findByIdWithQuestionAndSession(answerId) + .orElseThrow(() -> new BusinessException(ErrorCode.ANSWER_NOT_FOUND)); + + CombinedFeedbackResult result = feedbackGenerator.generateCombined( + answer.getInterviewQuestion().getQuestion(), + stt.getFeedback(), + video.getFeedback() + ); + + answerPersistenceService.saveCombinedFeedback(answerId, result); + log.info("[종합평가] 완료 - answerId: {}, score: {}", answerId, result.score()); + completeSessionIfReady(answer); + } + + private void completeSessionIfReady(InterviewAnswer answer) { + InterviewSession session = getSession(answer); + if (session == null) { + log.warn("[최종평가] 세션 없음 - answerId: {}", answer.getId()); + return; + } + + if (!isFinalFeedbackReady(session)) { + return; + } + + UUID sessionId = session.getId(); + FinalFeedbackData data = buildFinalData(sessionId); + FinalFeedbackResult finalResult = feedbackGenerator.generateFinal(data.toInputs()); + + completeSession(sessionId, data, finalResult); + } + + + public void failSession(UUID answerId, String message) { + InterviewAnswer answer = answerRepository.findByIdWithQuestionAndSession(answerId).orElse(null); + if (answer == null) { + log.warn("[세션실패] 답변 없음 - answerId: {}", answerId); + return; + } + + InterviewSession session = getSession(answer); + if (session == null) { + log.warn("[세션실패] 세션 없음 - answerId: {}", answerId); + return; + } + + sessionPersistenceService.saveFailedSession(session.getId()); + completionNotifier.notifyFailure(session.getId(), message); + } + + private InterviewSession getSession(InterviewAnswer answer) { + return answer.getInterviewQuestion().getInterviewSession(); + } + + private InterviewFeedback findRequiredFeedback(List feedbacks, FeedbackType type) { + return feedbacks.stream() + .filter(f -> f.getFeedbackType() == type) + .findFirst() + .orElseThrow(() -> new BusinessException(ErrorCode.INTERNAL_ERROR)); + } + + private boolean isFinalFeedbackReady(InterviewSession session) { + UUID sessionId = session.getId(); + long combinedCount = feedbackRepository.countBySessionIdAndFeedbackType(sessionId, FeedbackType.COMBINED); + + if (combinedCount < session.getTotalQuestionCount()) { + log.info( + "[최종평가] 아직 모든 질문 완료되지 않음 - sessionId: {}, {}/{}", + sessionId, + combinedCount, + session.getTotalQuestionCount() + ); + return false; + } + return true; + } + + private void completeSession( + UUID sessionId, + FinalFeedbackData data, + FinalFeedbackResult finalResult + ) { + sessionPersistenceService.saveCompletedSession(sessionId, data.finalScore(), finalResult.finalFeedback()); + + FinalFeedbackResponse response = new FinalFeedbackResponse( + sessionId, + data.finalScore(), + finalResult.finalFeedback(), + data.questionFeedbacks() + ); + + completionNotifier.notifyComplete(sessionId, response); + + log.info("[최종평가] 완료 - sessionId: {}, finalScore: {}", sessionId, data.finalScore()); + } + + private FinalFeedbackData buildFinalData(UUID sessionId) { + List questions = questionRepository.findByInterviewSessionId(sessionId); + Map answerMap = getAnswerMap(sessionId); + Map> feedbackMap = getFeedbackMap(sessionId); + + List questionFeedbacks = new ArrayList<>(); + int totalScore = 0; + + for (InterviewQuestion question : questions) { + InterviewAnswer answer = answerMap.get(question.getId()); + if (answer == null) continue; + + List answerFeedbacks = feedbackMap.getOrDefault(answer.getId(), List.of()); + InterviewFeedback combined = findOptionalFeedback(answerFeedbacks, FeedbackType.COMBINED); + + if (combined == null) continue; + + questionFeedbacks.add(new FinalFeedbackResponse.QuestionFeedback( + question.getId(), + question.getQuestion(), + combined.getScore(), + combined.getFeedback(), + getFeedbackText(answerFeedbacks, FeedbackType.STT), + getFeedbackText(answerFeedbacks, FeedbackType.VIDEO) + )); + + totalScore += combined.getScore(); + } + + int finalScore = calculateAverageScore(totalScore, questionFeedbacks.size()); + return new FinalFeedbackData(questionFeedbacks, finalScore); + } + + private Map getAnswerMap(UUID sessionId) { + return answerRepository.findBySessionId(sessionId).stream() + .collect(Collectors.toMap(a -> a.getInterviewQuestion().getId(), a -> a)); + } + + private Map> getFeedbackMap(UUID sessionId) { + return feedbackRepository.findAllBySessionId(sessionId).stream() + .collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId())); + } + + private InterviewFeedback findOptionalFeedback(List feedbacks, FeedbackType type) { + return feedbacks.stream() + .filter(f -> f.getFeedbackType() == type) + .findFirst() + .orElse(null); + } + + private String getFeedbackText(List feedbacks, FeedbackType type) { + InterviewFeedback feedback = findOptionalFeedback(feedbacks, type); + return feedback == null ? null : feedback.getFeedback(); + } + + private int calculateAverageScore(int totalScore, int count) { + if (count == 0) { + return 0; + } + + return totalScore / count; + } + + private record FinalFeedbackData( + List questionFeedbacks, + int finalScore + ) { + List toInputs() { + return questionFeedbacks.stream() + .map(qf -> new FinalFeedbackInput( + qf.questionId(), + qf.question(), + qf.combinedFeedback(), + qf.combinedScore() + )) + .toList(); + } + } +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerPersistenceService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerPersistenceService.java index 9f0f622..9d4d96c 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerPersistenceService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerPersistenceService.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.wisoft.prepair.prepair_api.interview.answer.dto.AnswerSubmitResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.CombinedFeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackDetail; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.AnswerSubmitResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.CombinedFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackDetail; import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewAnswer; import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewFeedback; import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerService.java index 8545f73..f2076e1 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/AnswerService.java @@ -1,9 +1,9 @@ package io.wisoft.prepair.prepair_api.interview.answer.service; -import io.wisoft.prepair.prepair_api.interview.answer.dto.AnswerSubmitResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackDetail; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResponse; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.AnswerSubmitResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackDetail; +import io.wisoft.prepair.prepair_api.interview.answer.dto.response.FeedbackResponse; import io.wisoft.prepair.prepair_api.interview.answer.entity.InterviewAnswer; import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; import io.wisoft.prepair.prepair_api.external.member.MemberServiceClient; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/FeedbackGenerator.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/FeedbackGenerator.java index 0a588dc..78bb41c 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/FeedbackGenerator.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/FeedbackGenerator.java @@ -2,24 +2,27 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.wisoft.prepair.prepair_api.interview.answer.dto.CombinedFeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResult; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FinalFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.CombinedFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FinalFeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FinalFeedbackInput; import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; import io.wisoft.prepair.prepair_api.external.openai.OpenAiClient; import io.wisoft.prepair.prepair_api.common.exception.BusinessException; import io.wisoft.prepair.prepair_api.common.exception.ErrorCode; -import io.wisoft.prepair.prepair_api.interview.prompt.PromptBuilder; +import io.wisoft.prepair.prepair_api.interview.prompt.FeedbackPromptBuilder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.List; + @Slf4j @Component @RequiredArgsConstructor public class FeedbackGenerator { - private final PromptBuilder promptBuilder; + private final FeedbackPromptBuilder promptBuilder; private final ObjectMapper objectMapper; private final OpenAiClient openAiClient; @@ -51,8 +54,8 @@ public CombinedFeedbackResult generateCombined(final String question, final Stri } } - public FinalFeedbackResult generateFinal(final String questionsAndFeedbacks) { - String prompt = promptBuilder.buildFinalFeedbackPrompt(questionsAndFeedbacks); + public FinalFeedbackResult generateFinal(final List inputs) { + String prompt = promptBuilder.buildFinalFeedbackPrompt(inputs); String raw = openAiClient.generateText(prompt); try { diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/VideoFrameAnalysisService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/VideoFrameAnalysisService.java index a3d56bc..6ce2010 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/VideoFrameAnalysisService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/answer/service/VideoFrameAnalysisService.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.wisoft.prepair.prepair_api.interview.answer.dto.FeedbackResult; +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FeedbackResult; import io.wisoft.prepair.prepair_api.external.openai.OpenAiClient; import io.wisoft.prepair.prepair_api.common.exception.BusinessException; import io.wisoft.prepair.prepair_api.common.exception.ErrorCode; diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/FeedbackPromptBuilder.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/FeedbackPromptBuilder.java new file mode 100644 index 0000000..93f1389 --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/FeedbackPromptBuilder.java @@ -0,0 +1,89 @@ +package io.wisoft.prepair.prepair_api.interview.prompt; + +import io.wisoft.prepair.prepair_api.interview.answer.dto.internal.FinalFeedbackInput; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class FeedbackPromptBuilder { + + public String buildFeedbackPrompt(String question, String questionTag, String answer) { + return """ + 너는 %s 관련 직무 분야의 면접 전문가야. 질문의 맥락을 먼저 파악한 후, 해당 분야의 기준에 맞춰 답변을 평가해줘. + + 규칙: + 1. good: 답변에서 잘한 점을 작성해줘. + 2. improvement: 부족한 부분을 구체적으로 작성해줘. + 3. recommendation: 개선 방향을 제안해줘. + 4. score: 0~100 사이 정수로 점수를 매겨줘. + 5. 답변이 질문과 무관하거나 내용이 없는 경우, score는 0~10점으로 매기고 good은 "없음"으로 작성해줘. + 6. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. + {\ + "good": "잘한 점", + "improvement": "부족한 점", + "recommendation": "개선 방향", + "score": 85 + } + + 질문: %s + 답변: %s + """.formatted(questionTag, question, answer); + } + + public String buildCombinedFeedbackPrompt(String question, String sttFeedback, String videoFeedback) { + return """ + 너는 면접 평가 전문가야. 아래 면접 질문에 대한 음성 분석(STT) 피드백과 영상 분석(Video) 피드백을 종합하여 한 줄 종합 평가를 작성해줘. + + 규칙: + 1. 음성 분석과 영상 분석의 내용을 모두 고려하여 종합적으로 평가해줘. + 2. combineFeedback: 한 줄로 종합 평가를 작성해줘. + 3. score: 0~100 사이 정수로 종합 점수를 매겨줘. + 4. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. + { + "combineFeedback": "한 줄 종합 평가", + "score": 85 + } + + 면접 질문: %s + + 음성 분석 피드백: + %s + + 영상 분석 피드백: + %s + """.formatted(question, sttFeedback, videoFeedback); + } + + public String buildFinalFeedbackPrompt(List inputs) { + String questionsAndFeedbacks = inputs.stream() + .map(input -> """ + 질문: %s + 종합 평가: %s + 점수: %d + + """.formatted( + input.question(), + input.combinedFeedback(), + input.score() + )) + .collect(Collectors.joining()); + + return """ + 너는 면접 평가 전문가야. 아래는 한 면접 세션에서 진행된 모든 질문과 각 질문에 대한 종합 평가 결과야. + 이를 바탕으로 면접자의 전체적인 면접 수행에 대한 최종 평가 요약을 작성해줘. + + 규칙: + 1. 전체 질문에 대한 답변 경향, 강점, 약점을 종합적으로 분석해줘. + 2. finalFeedback: 2-3문장으로 최종 평가 요약을 작성해줘. + 3. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. + { + "finalFeedback": "최종 평가 요약" + } + + 면접 질문 및 종합 평가 결과: + %s + """.formatted(questionsAndFeedbacks); + } +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/PromptBuilder.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/QuestionPromptBuilder.java similarity index 53% rename from src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/PromptBuilder.java rename to src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/QuestionPromptBuilder.java index 7d5d048..f57a9f6 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/PromptBuilder.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/QuestionPromptBuilder.java @@ -6,7 +6,7 @@ import java.util.List; @Component -public class PromptBuilder { +public class QuestionPromptBuilder { public String buildDailyQuestionPrompt(String job, List previousQuestions) { StringBuilder prompt = new StringBuilder(); @@ -34,11 +34,11 @@ public String buildDailyQuestionPrompt(String job, List previ } public String buildCompanyQuestionPrompt(String jobPostingContent) { - return """ + return """ 너는 채용공고 내용을 분석하여 실제 그 기업의 면접을 진행하는 면접관이야. - + 아래 채용공고 정보를 바탕으로 지원자에게 물어볼 면접 질문 5개를 만들고, 관련 키워드 태그 2-3개를 추출해줘. - + 규칙: 1. 채용공고의 기술 스택, 자격 요건, 담당 업무를 반드시 반영해줘. 2. 반드시 아래 JSON 형식으로만 응답하고, 다른 텍스트는 포함하지 마. @@ -65,13 +65,12 @@ public String buildCompanyQuestionPrompt(String jobPostingContent) { "tags": ["태그1", "태그2", "태그3"] } ] - + 채용공고 정보: %s """.formatted(jobPostingContent); } - public String buildVideoQuestionPrompt(String job, int count) { return """ 너는 %s 직무의 실제 면접을 진행하는 면접관이야. @@ -91,70 +90,4 @@ public String buildVideoQuestionPrompt(String job, int count) { ] """.formatted(job, count); } - - public String buildFeedbackPrompt(String question, String questionTag, String answer) { - - return """ - 너는 %s 관련 직무 분야의 면접 전문가야. 질문의 맥락을 먼저 파악한 후, 해당 분야의 기준에 맞춰 답변을 평가해줘. - - 규칙: - 1. good: 답변에서 잘한 점을 작성해줘. - 2. improvement: 부족한 부분을 구체적으로 작성해줘. - 3. recommendation: 개선 방향을 제안해줘. - 4. score: 0~100 사이 정수로 점수를 매겨줘. - 5. 답변이 질문과 무관하거나 내용이 없는 경우, score는 0~10점으로 매기고 good은 "없음"으로 작성해줘. - 6. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. - {\ - "good": "잘한 점", - "improvement": "부족한 점", - "recommendation": "개선 방향", - "score": 85 - } - - 질문: %s - 답변: %s - """.formatted(questionTag, question, answer); - } - - public String buildCombinedFeedbackPrompt(String question, String sttFeedback, String videoFeedback) { - return """ - 너는 면접 평가 전문가야. 아래 면접 질문에 대한 음성 분석(STT) 피드백과 영상 분석(Video) 피드백을 종합하여 한 줄 종합 평가를 작성해줘. - - 규칙: - 1. 음성 분석과 영상 분석의 내용을 모두 고려하여 종합적으로 평가해줘. - 2. combineFeedback: 한 줄로 종합 평가를 작성해줘. - 3. score: 0~100 사이 정수로 종합 점수를 매겨줘. - 4. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. - { - "combineFeedback": "한 줄 종합 평가", - "score": 85 - } - - 면접 질문: %s - - 음성 분석 피드백: - %s - - 영상 분석 피드백: - %s - """.formatted(question, sttFeedback, videoFeedback); - } - - public String buildFinalFeedbackPrompt(String questionsAndFeedbacks) { - return """ - 너는 면접 평가 전문가야. 아래는 한 면접 세션에서 진행된 모든 질문과 각 질문에 대한 종합 평가 결과야. - 이를 바탕으로 면접자의 전체적인 면접 수행에 대한 최종 평가 요약을 작성해줘. - - 규칙: - 1. 전체 질문에 대한 답변 경향, 강점, 약점을 종합적으로 분석해줘. - 2. finalFeedback: 2-3문장으로 최종 평가 요약을 작성해줘. - 3. 반드시 아래 JSON 형식으로만 응답하고, 마크다운(```json 등)은 포함하지 마. - { - "finalFeedback": "최종 평가 요약" - } - - 면접 질문 및 종합 평가 결과: - %s - """.formatted(questionsAndFeedbacks); - } } diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/VideoAnalysisPromptBuilder.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/VideoAnalysisPromptBuilder.java index aea3fed..3a2c42a 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/VideoAnalysisPromptBuilder.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/prompt/VideoAnalysisPromptBuilder.java @@ -32,4 +32,6 @@ public String buildVisionPrompt() { } """; } + + } diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/QuestionService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/QuestionService.java index 9fe0588..b084ac9 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/QuestionService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/QuestionService.java @@ -15,7 +15,7 @@ import io.wisoft.prepair.prepair_api.external.member.dto.MemberSchedulerInfo; import io.wisoft.prepair.prepair_api.external.openai.OpenAiClient; import io.wisoft.prepair.prepair_api.external.openai.dto.QuestionWithTags; -import io.wisoft.prepair.prepair_api.interview.prompt.PromptBuilder; +import io.wisoft.prepair.prepair_api.interview.prompt.QuestionPromptBuilder; import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository; import io.wisoft.prepair.prepair_api.interview.jobposting.service.JobPostingService; @@ -39,7 +39,7 @@ public class QuestionService { private final QuestionRepository questionRepository; private final MemberServiceClient memberServiceClient; private final OpenAiClient openAiClient; - private final PromptBuilder promptBuilder; + private final QuestionPromptBuilder promptBuilder; public List getQuestions(UUID memberId, QuestionType type) { return questionRepository.findByMemberIdAndQuestionTypeOrderByCreatedAtDesc(memberId, type) diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/TodayQuestionService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/TodayQuestionService.java index 5a15e91..1fc0b0d 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/TodayQuestionService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/service/TodayQuestionService.java @@ -4,7 +4,7 @@ import io.wisoft.prepair.prepair_api.external.member.dto.MemberSchedulerInfo; import io.wisoft.prepair.prepair_api.external.openai.OpenAiClient; import io.wisoft.prepair.prepair_api.external.openai.dto.QuestionWithTags; -import io.wisoft.prepair.prepair_api.interview.prompt.PromptBuilder; +import io.wisoft.prepair.prepair_api.interview.prompt.QuestionPromptBuilder; import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository; import lombok.RequiredArgsConstructor; @@ -26,7 +26,7 @@ public class TodayQuestionService { private final QuestionRepository questionRepository; private final MemberServiceClient memberServiceClient; private final OpenAiClient openAiClient; - private final PromptBuilder promptBuilder; + private final QuestionPromptBuilder promptBuilder; public void sendTodayQuestions() { List members = memberServiceClient.getMembers(); diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/notifier/SessionCompletionNotifier.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/notifier/SessionCompletionNotifier.java new file mode 100644 index 0000000..b744a9a --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/notifier/SessionCompletionNotifier.java @@ -0,0 +1,29 @@ +package io.wisoft.prepair.prepair_api.interview.session.notifier; + +import io.wisoft.prepair.prepair_api.common.support.SseEmitterManager; +import io.wisoft.prepair.prepair_api.interview.answer.dto.response.FinalFeedbackResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class SessionCompletionNotifier { + + private static final String FINAL_COMPLETE_EVENT = "final-complete"; + private static final String ANALYSIS_FAILED_EVENT = "analysis-failed"; + + private final SseEmitterManager sseEmitterManager; + + public void notifyComplete(UUID sessionId, FinalFeedbackResponse response) { + sseEmitterManager.send(sessionId, FINAL_COMPLETE_EVENT, response); + sseEmitterManager.complete(sessionId); + } + + public void notifyFailure(UUID sessionId, String message) { + sseEmitterManager.send(sessionId, ANALYSIS_FAILED_EVENT, Map.of("message", message)); + sseEmitterManager.complete(sessionId); + } +}