Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private String buildPrompt(JobPosting jobPosting, List<Question> questions) {
"sentence": "자소서 답변 안에 실제 존재하는 정확한 부분 문자열",
"status": "mentioned",
"reason": "문제 이유",
"improvement": "개선 예시 문장"
"improvement": "사용자가 그대로 붙여 넣을 수 있는 완성된 개선 예시 문장"
}
]
}
Expand Down Expand Up @@ -134,6 +134,12 @@ private String buildPrompt(JobPosting jobPosting, List<Question> questions) {
- questionAnalyses의 questionId는 입력된 questionId 중 하나만 사용한다.
- questionAnalyses의 status는 proven, mentioned, missing, fabricated 중 하나만 사용한다.
- sentence는 answer에 포함된 정확한 substring만 사용한다.
- improvement는 첨삭 조언이 아니라 sentence를 대체할 수 있는 완성된 예시 문장이어야 한다.
- improvement에는 "하세요.", "해주세요.", "해야 합니다.", "필요합니다."로 끝나는 지시문을 쓰지 않는다.
- improvement에는 "추가하세요.", "보완하세요.", "수정해주세요.", "명확히 해야 합니다." 같은 첨삭 조언 표현을 쓰지 않는다.
- improvement는 반드시 한국어 평서문으로 작성하고, 가능하면 수치/성과/행동을 포함한다.
- 좋은 improvement 예: "저는 쿼리 실행 계획을 분석해 누락된 인덱스를 추가했고, 평균 응답 시간을 1.8초에서 0.6초로 단축했습니다."
- 나쁜 improvement 예: "성과 수치를 추가하여 문제 해결의 효과를 명확히 하세요."
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- start/end index는 출력하지 않는다. 서버가 Java에서 계산한다.
- 원문 매칭이 불확실하면 questionAnalyses에 포함하지 않는다.
""".formatted(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private List<QuestionAnalysis> buildQuestionAnalyses(
analysis,
sentence,
defaultString(item.reason()),
defaultString(item.improvement()),
normalizeImprovement(item.improvement()),
normalizeStatus(item.status()),
start,
start + sentence.length()
Expand Down Expand Up @@ -219,6 +219,32 @@ private String defaultString(String value) {
return value == null ? "" : value;
}

private String normalizeImprovement(String improvement) {
if (!StringUtils.hasText(improvement)) {
return "";
}

String normalized = improvement.trim();
if (isInstructionLikeImprovement(normalized)) {
return "";
}
return normalized;
}

private boolean isInstructionLikeImprovement(String improvement) {
return improvement.endsWith("하세요.")
|| improvement.endsWith("해주세요.")
|| improvement.endsWith("해 주세요.")
|| improvement.endsWith("하십시오.")
|| improvement.endsWith("해주십시오.")
|| improvement.endsWith("해 주십시오.")
|| improvement.endsWith("해야 합니다.")
|| improvement.endsWith("필요합니다.")
|| improvement.matches(".*[을를]\\s+(추가|보완|수정)하\\s*(세요|십시오).*")
|| improvement.matches(".*명확히\\s+(하\\s*(세요|십시오|야 합니다)|해\\s*(주세요|주십시오|야 합니다)).*")
|| improvement.matches("^(추가|보완|수정).*(하세요|해주세요|해 주세요|하십시오|해주십시오|해 주십시오)(\\.|$)");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private QuestionAnalysisStatus normalizeStatus(String status) {
if (!StringUtils.hasText(status)) {
return QuestionAnalysisStatus.MENTIONED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,127 @@ void analyzeSkipsSentenceNotInAnswer() {
assertThat(questionAnalysisRepository.findAllByAnalysisId(analysis.getId())).isEmpty();
}

@Test
@DisplayName("LLM improvement가 첨삭 지시문이면 빈 값으로 저장한다")
void analyzeNormalizesInstructionLikeImprovement() {
User user = saveUser("analysis-instruction-improvement@example.com");
MockApply mockApply = saveMockApply(user);
Question question = saveQuestion(mockApply, "문제 해결 경험", "저는 로그를 확인하고 쿼리 실행 계획을 분석했습니다.");
when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse(
64,
70,
55,
67,
"개선 예시 문장 검증입니다.",
List.of(new AnalysisLlmResponse.QuestionAnalysisItem(
question.getId(),
"저는 로그를 확인하고 쿼리 실행 계획을 분석했습니다.",
"mentioned",
"성과 수치가 부족합니다.",
"성과 수치를 추가하여 문제 해결의 효과를 명확히 하세요."
))
));

AnalysisResponse response = analysisService.analyze(user, mockApply.getId());

assertThat(response.questions().get(0).analyses()).hasSize(1);
assertThat(response.questions().get(0).analyses().get(0).improvement()).isEmpty();
}
Comment on lines +239 to +264
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Expand test coverage to include false positive scenarios.

The current test validates that instruction-like improvements are correctly filtered, but doesn't verify that legitimate declarative sentences are preserved. Given the contains() checks in isInstructionLikeImprovement(), you should add test cases to ensure valid improvement sentences aren't incorrectly normalized to empty strings.

🧪 Suggested additional test case
`@Test`
`@DisplayName`("완성된 평서문 improvement는 그대로 저장한다")
void analyzePreservesDeclarativeImprovement() {
    User user = saveUser("analysis-declarative-improvement@example.com");
    MockApply mockApply = saveMockApply(user);
    Question question = saveQuestion(mockApply, "문제 해결 경험", "저는 로그를 확인했습니다.");
    String validImprovement = "저는 로그를 분석하고 누락된 인덱스를 추가하여 응답 시간을 1.8초에서 0.6초로 단축했습니다.";
    when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse(
            70,
            75,
            65,
            70,
            "평서문 검증입니다.",
            List.of(new AnalysisLlmResponse.QuestionAnalysisItem(
                    question.getId(),
                    "저는 로그를 확인했습니다.",
                    "mentioned",
                    "성과가 부족합니다.",
                    validImprovement
            ))
    ));

    AnalysisResponse response = analysisService.analyze(user, mockApply.getId());

    assertThat(response.questions().get(0).analyses()).hasSize(1);
    assertThat(response.questions().get(0).analyses().get(0).improvement())
            .isEqualTo(validImprovement);
}

This test would currently fail due to the overly broad contains("추가하여") check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisServiceTest.java`
around lines 239 - 264, Add the new test method
analyzePreservesDeclarativeImprovement (as in the suggested snippet) to
AnalysisServiceTest to assert that a legitimate declarative improvement string
is preserved; then update the isInstructionLikeImprovement(...) logic (the
method currently using contains("추가하여") and similar checks) to be more
discriminating—e.g., detect imperative forms or explicit instruction markers
(use regex that matches sentences starting with imperative verbs or ending with
imperative/formal instruction endings like "하세요", "해라", "해주세요") instead of
blanket contains checks—so declarative sentences such as the provided
validImprovement are not normalized to empty.


@Test
@DisplayName("완성된 평서문 improvement는 그대로 저장한다")
void analyzePreservesDeclarativeImprovement() {
User user = saveUser("analysis-declarative-improvement@example.com");
MockApply mockApply = saveMockApply(user);
Question question = saveQuestion(mockApply, "문제 해결 경험", "저는 로그를 확인했습니다.");
String validImprovement = "저는 로그를 분석하고 누락된 인덱스를 추가하여 응답 시간을 1.8초에서 0.6초로 단축했습니다.";
when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse(
70,
75,
65,
70,
"평서문 검증입니다.",
List.of(new AnalysisLlmResponse.QuestionAnalysisItem(
question.getId(),
"저는 로그를 확인했습니다.",
"mentioned",
"성과가 부족합니다.",
validImprovement
))
));

AnalysisResponse response = analysisService.analyze(user, mockApply.getId());

assertThat(response.questions().get(0).analyses()).hasSize(1);
assertThat(response.questions().get(0).analyses().get(0).improvement())
.isEqualTo(validImprovement);
}

@Test
@DisplayName("띄어쓰기가 포함된 첨삭 지시문도 빈 값으로 저장한다")
void analyzeNormalizesSpacedInstructionLikeImprovement() {
User user = saveUser("analysis-spaced-instruction-improvement@example.com");
MockApply mockApply = saveMockApply(user);
Question question = saveQuestion(mockApply, "문제 해결 경험", "저는 로그를 확인했습니다.");
when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse(
64,
70,
55,
67,
"띄어쓰기 지시문 검증입니다.",
List.of(new AnalysisLlmResponse.QuestionAnalysisItem(
question.getId(),
"저는 로그를 확인했습니다.",
"mentioned",
"성과가 부족합니다.",
"성과 수치를 추가해 주세요."
))
));

AnalysisResponse response = analysisService.analyze(user, mockApply.getId());

assertThat(response.questions().get(0).analyses()).hasSize(1);
assertThat(response.questions().get(0).analyses().get(0).improvement()).isEmpty();
}

@Test
@DisplayName("하십시오와 해주십시오 형태의 첨삭 지시문도 빈 값으로 저장한다")
void analyzeNormalizesFormalInstructionLikeImprovement() {
User user = saveUser("analysis-formal-instruction-improvement@example.com");
MockApply mockApply = saveMockApply(user);
Question firstQuestion = saveQuestion(mockApply, "문제 해결 경험", "저는 로그를 확인했습니다.");
Question secondQuestion = saveQuestion(mockApply, "성과 경험", "저는 API 응답 속도를 개선했습니다.");
when(analysisAiClient.analyze(any(), any())).thenReturn(new AnalysisLlmResponse(
64,
70,
55,
67,
"격식체 지시문 검증입니다.",
List.of(
new AnalysisLlmResponse.QuestionAnalysisItem(
firstQuestion.getId(),
"저는 로그를 확인했습니다.",
"mentioned",
"성과가 부족합니다.",
"문장을 명확히 하십시오."
),
new AnalysisLlmResponse.QuestionAnalysisItem(
secondQuestion.getId(),
"저는 API 응답 속도를 개선했습니다.",
"mentioned",
"성과 수치가 부족합니다.",
"수정해주십시오."
)
)
));

AnalysisResponse response = analysisService.analyze(user, mockApply.getId());

assertThat(response.questions()).hasSize(2);
assertThat(response.questions().get(0).analyses().get(0).improvement()).isEmpty();
assertThat(response.questions().get(1).analyses().get(0).improvement()).isEmpty();
}

@Test
@DisplayName("재분석 시 기존 분석과 문항 분석을 새 결과로 교체한다")
void analyzeReplacesExistingAnalysis() {
Expand Down
Loading