diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/EvaluationAnalysisBatchService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/EvaluationAnalysisBatchService.java index 59bad6f3..baaf19a0 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/EvaluationAnalysisBatchService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/evaluation/EvaluationAnalysisBatchService.java @@ -279,7 +279,10 @@ private List buildQuestionAnalyses( continue; } if (status == QuestionAnalysisStatus.FABRICATED - && !AnalysisSanitizationRules.hasFabricatedDirectConflictReason(item.reason())) { + && !AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + item.sentence(), + item.reason() + )) { continue; } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/ai/AnalysisAiClient.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/ai/AnalysisAiClient.java index 37d8628f..dd248b9b 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/ai/AnalysisAiClient.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/ai/AnalysisAiClient.java @@ -1297,7 +1297,10 @@ private List sanitizeAnalysisCandid continue; } if (status == QuestionAnalysisStatus.FABRICATED - && !AnalysisSanitizationRules.hasFabricatedDirectConflictReason(candidate.reasonBasis())) { + && !AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + candidate.sentence(), + candidate.reasonBasis() + )) { continue; } if (!StringUtils.hasText(candidate.candidateId())) { @@ -1818,7 +1821,10 @@ private List buildAcceptedQuestionAnal continue; } if (status == QuestionAnalysisStatus.FABRICATED - && !AnalysisSanitizationRules.hasFabricatedDirectConflictReason(decision.reason())) { + && !AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + candidate.sentence(), + decision.reason() + )) { continue; } String answer = answerByQuestionId.get(candidate.questionId()); @@ -2114,7 +2120,10 @@ private CandidateReviewResponse.CandidateDecision validateAcceptedDecision( return null; } if (status == QuestionAnalysisStatus.FABRICATED - && !AnalysisSanitizationRules.hasFabricatedDirectConflictReason(decision.reason())) { + && !AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + candidate.sentence(), + decision.reason() + )) { return null; } String improvement = AnalysisSanitizationRules.normalizeImprovement( diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisService.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisService.java index 62e8da66..7f279a68 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisService.java @@ -493,7 +493,10 @@ private List buildQuestionAnalyses( continue; } if (status == QuestionAnalysisStatus.FABRICATED - && !AnalysisSanitizationRules.hasFabricatedDirectConflictReason(item.reason())) { + && !AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + item.sentence(), + item.reason() + )) { continue; } int currentCount = analysisCountByQuestionId.getOrDefault(question.getId(), 0); diff --git a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRules.java b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRules.java index 60c7b24b..810e59f1 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRules.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRules.java @@ -26,7 +26,8 @@ public final class AnalysisSanitizationRules { "구체적으로 서술합니다", "명확히 설명합니다", "명확히 서술합니다", "추가해 보", "추가하면 좋", "설명해 보", "구체적으로 작성", "구체적으로 설명", "강조하겠", "추가하겠", "명확히 작성", "작성할 수 있", "설명할 수 있", - "보완하겠", "드러내겠", "제시하겠" + "보완하겠", "드러내겠", "제시하겠", "추가할 수 있", "수정할 수 있", + "수정하는 방향", "강조하는 방향" }; private static final String[] CONTRADICTORY_PROVEN_REASON_TERMS = { "근거가 부족", "성과가 부족", "수치가 부족", "구체성이 부족", "보완이 필요", @@ -185,6 +186,17 @@ public static boolean hasFabricatedDirectConflictReason(String reason) { return containsTeamAndIndividualProjectConflict(normalized); } + public static boolean hasFabricatedDirectConflictEvidence(String sentence, String reason) { + if (!StringUtils.hasText(reason)) { + return false; + } + if (hasFabricatedDirectConflictReason(reason)) { + return true; + } + return StringUtils.hasText(sentence) + && hasFabricatedDirectConflictReason(sentence + " " + reason); + } + private static boolean containsTeamAndIndividualProjectConflict(String normalizedReason) { boolean teamClaim = normalizedReason.contains(normalize("팀 프로젝트")) || normalizedReason.contains(normalize("팀으로 진행")); diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisServiceTest.java index 9d089d03..7739f4c9 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisServiceTest.java @@ -643,7 +643,11 @@ void analyzeAllowsEmptyQuestionAnalyses() { void analyzeKeepsTwoValidQuestionAnalyses() { User user = saveUser("analysis-two-valid-items@example.com"); MockApply mockApply = saveMockApply(user); - Question question = saveQuestion(mockApply, "성과 경험", "첫 번째 문장입니다. 두 번째 문장입니다."); + Question question = saveQuestion( + mockApply, + "성과 경험", + "첫 번째 문장입니다. 모든 과정을 혼자 수행한 개인 프로젝트였습니다." + ); when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse( 80, 80, @@ -659,9 +663,9 @@ void analyzeKeepsTwoValidQuestionAnalyses() { ), new AnalysisLlmResponse.QuestionAnalysisItem( question.getId(), - "두 번째 문장입니다.", + "모든 과정을 혼자 수행한 개인 프로젝트였습니다.", "fabricated", - "팀 프로젝트로 진행했으나 개인 프로젝트라고 주장함", + "실제 프로젝트는 총 5명이 함께 진행한 팀 프로젝트였습니다.", null ) ) diff --git a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRulesTest.java b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRulesTest.java index 3862f301..dabe5eb4 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRulesTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/analysis/service/sanitization/AnalysisSanitizationRulesTest.java @@ -49,6 +49,14 @@ void detectsFabricatedReasonByConflictMeaning() { assertThat(AnalysisSanitizationRules.hasFabricatedDirectConflictReason( "팀 프로젝트와 개인의 역할이 혼재되어 있습니다." )).isFalse(); + assertThat(AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + "모든 과정을 혼자 수행한 개인 프로젝트였습니다.", + "실제 프로젝트는 총 5명이 함께 진행한 팀 프로젝트였습니다." + )).isTrue(); + assertThat(AnalysisSanitizationRules.hasFabricatedDirectConflictEvidence( + "팀 프로젝트에 참여했습니다.", + "콘텐츠 제작 역할을 담당했습니다." + )).isFalse(); } @Test @@ -80,6 +88,18 @@ void removesUnsafeImprovement() { "입사 후 API 운영 안정화에 기여하겠습니다.", false )).isEmpty(); + assertThat(AnalysisSanitizationRules.normalizeImprovement( + "API 응답 속도를 개선했습니다.", + answer, + "구체적인 성과 수치를 추가하면 좋습니다.", + false + )).isEmpty(); + assertThat(AnalysisSanitizationRules.normalizeImprovement( + "API 응답 속도를 개선했습니다.", + answer, + "성과가 드러나도록 수정할 수 있습니다.", + false + )).isEmpty(); } @Test