diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/llm/AnalysisLlmResponse.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/llm/AnalysisLlmResponse.java index 0cb253e..d8a77d8 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/llm/AnalysisLlmResponse.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/llm/AnalysisLlmResponse.java @@ -13,6 +13,7 @@ public record AnalysisLlmResponse( public record QuestionAnalysisItem( Long questionId, String sentence, + String status, String reason, String improvement ) { diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionAnalysisResponse.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionAnalysisResponse.java index c4c6f97..92a9e92 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionAnalysisResponse.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/response/QuestionAnalysisResponse.java @@ -1,10 +1,12 @@ package com.jobdri.jobdri_api.domain.analysis.dto.response; import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysis; +import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysisStatus; public record QuestionAnalysisResponse( Long questionAnalysisId, String sentence, + String status, String reason, String improvement, int start, @@ -14,10 +16,18 @@ public static QuestionAnalysisResponse from(QuestionAnalysis questionAnalysis) { return new QuestionAnalysisResponse( questionAnalysis.getId(), questionAnalysis.getSentence(), + statusValue(questionAnalysis.getStatus()), questionAnalysis.getReason(), questionAnalysis.getImprovement(), questionAnalysis.getStart(), questionAnalysis.getEnd() ); } + + private static String statusValue(QuestionAnalysisStatus status) { + if (status == null) { + return QuestionAnalysisStatus.MENTIONED.name().toLowerCase(); + } + return status.name().toLowerCase(); + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java index 5641065..ab393b8 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysis.java @@ -32,6 +32,11 @@ public class QuestionAnalysis { @Column(nullable = false, columnDefinition = "TEXT") private String improvement; + @Enumerated(EnumType.STRING) + @Column(nullable = false, columnDefinition = "varchar(255) default 'MENTIONED'") + @Builder.Default + private QuestionAnalysisStatus status = QuestionAnalysisStatus.MENTIONED; + @Column(name = "start_index", nullable = false) private int start; @@ -44,6 +49,7 @@ public static QuestionAnalysis create( String sentence, String reason, String improvement, + QuestionAnalysisStatus status, int start, int end ) { @@ -53,6 +59,7 @@ public static QuestionAnalysis create( .sentence(sentence) .reason(reason) .improvement(improvement) + .status(status) .start(start) .end(end) .build(); diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysisStatus.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysisStatus.java new file mode 100644 index 0000000..2c99de5 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/QuestionAnalysisStatus.java @@ -0,0 +1,8 @@ +package com.jobdri.jobdri_api.domain.analysis.entity; + +public enum QuestionAnalysisStatus { + PROVEN, + MENTIONED, + MISSING, + FABRICATED +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisAiClient.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisAiClient.java index 54afd62..3318d87 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisAiClient.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisAiClient.java @@ -79,6 +79,7 @@ private String buildPrompt(JobPosting jobPosting, List questions) { { "questionId": 1, "sentence": "자소서 답변 안에 실제 존재하는 정확한 부분 문자열", + "status": "mentioned", "reason": "문제 이유", "improvement": "개선 예시 문장" } @@ -131,6 +132,7 @@ private String buildPrompt(JobPosting jobPosting, List questions) { [중요 규칙] - JSON 외 텍스트, 마크다운, 코드블럭을 출력하지 않는다. - questionAnalyses의 questionId는 입력된 questionId 중 하나만 사용한다. + - questionAnalyses의 status는 proven, mentioned, missing, fabricated 중 하나만 사용한다. - sentence는 answer에 포함된 정확한 substring만 사용한다. - start/end index는 출력하지 않는다. 서버가 Java에서 계산한다. - 원문 매칭이 불확실하면 questionAnalyses에 포함하지 않는다. diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java index 96c8dbc..a787cda 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisService.java @@ -7,6 +7,7 @@ import com.jobdri.jobdri_api.domain.analysis.entity.Analysis; import com.jobdri.jobdri_api.domain.analysis.entity.Question; import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysis; +import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysisStatus; import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository; import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository; @@ -137,6 +138,7 @@ private List buildQuestionAnalyses( sentence, defaultString(item.reason()), defaultString(item.improvement()), + normalizeStatus(item.status()), start, start + sentence.length() )); @@ -199,4 +201,16 @@ private String normalizeFeedback(String feedback) { private String defaultString(String value) { return value == null ? "" : value; } + + private QuestionAnalysisStatus normalizeStatus(String status) { + if (!StringUtils.hasText(status)) { + return QuestionAnalysisStatus.MENTIONED; + } + + try { + return QuestionAnalysisStatus.valueOf(status.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + return QuestionAnalysisStatus.MENTIONED; + } + } } diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java index 64ea3e6..fb9852b 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java @@ -94,6 +94,7 @@ void analyzeSavesAnalysis() { new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), "Spring Boot API를 개발했습니다.", + "mentioned", "성과 지표가 없어 구체성이 약합니다.", "Spring Boot API를 개발해 응답 시간을 20% 개선했습니다." ) @@ -109,6 +110,7 @@ void analyzeSavesAnalysis() { assertThat(response.completeness()).isEqualTo(80); assertThat(response.questions()).hasSize(1); assertThat(response.questions().get(0).analyses()).hasSize(1); + assertThat(response.questions().get(0).analyses().get(0).status()).isEqualTo("mentioned"); assertThat(response.questions().get(0).analyses().get(0).start()).isEqualTo(0); assertThat(response.questions().get(0).analyses().get(0).end()) .isEqualTo("Spring Boot API를 개발했습니다.".length()); @@ -159,6 +161,7 @@ void analyzeSkipsSentenceNotInAnswer() { new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), "답변에 없는 문장입니다.", + "fabricated", "원문에 없습니다.", "원문 기반 문장으로 개선해야 합니다." ) @@ -188,6 +191,7 @@ void analyzeReplacesExistingAnalysis() { List.of(new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), "가입 완료율을 개선했습니다.", + "mentioned", "수치가 부족합니다.", "가입 완료율을 12% 개선했습니다." )) @@ -201,6 +205,7 @@ void analyzeReplacesExistingAnalysis() { List.of(new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), "API 응답 속도를 개선했습니다.", + "proven", "성과 기준이 더 필요합니다.", "API 응답 속도를 300ms 단축했습니다." )) @@ -212,6 +217,7 @@ void analyzeReplacesExistingAnalysis() { assertThat(second.analysisId()).isNotEqualTo(first.analysisId()); assertThat(second.score()).isEqualTo(88); assertThat(second.feedback()).isEqualTo("두 번째 분석"); + assertThat(second.questions().get(0).analyses().get(0).status()).isEqualTo("proven"); assertThat(analysisRepository.findByMockApplyId(mockApply.getId()).orElseThrow().getScore()).isEqualTo(88); assertThat(questionAnalysisRepository.findAllByAnalysisId(second.analysisId())).hasSize(1); assertThat(questionAnalysisRepository.findAllByAnalysisId(first.analysisId())).isEmpty(); @@ -232,6 +238,7 @@ void getAnalysis() { List.of(new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), "서비스 개선 경험이 있습니다.", + "mentioned", "구체성이 조금 부족합니다.", "서비스 개선 경험으로 전환율을 10% 높였습니다." ))