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 @@ -2,14 +2,15 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jobdri.jobdri_api.domain.analysis.dto.external.llm.CandidateReviewResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.external.llm.AnalysisLlmResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.MissingKeywordResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.MissingKeywordSource;
import com.jobdri.jobdri_api.domain.analysis.type.QuestionAnalysisStatus;
import com.jobdri.jobdri_api.domain.analysis.service.core.AnalysisResultConstants;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationAnalysisCommand;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationCandidateReviewDecision;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationCandidateReviewSnapshot;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationGeneratedResult;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationLlmSnapshot;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationMissingKeyword;
import com.jobdri.jobdri_api.domain.evaluation.analysis.model.EvaluationQuestionAnalysis;
import com.jobdri.jobdri_api.domain.evaluation.analysis.policy.EvaluationAnalysisPolicyConstants;
import com.jobdri.jobdri_api.domain.evaluation.analysis.port.EvaluationAnalysisGenerator;
import com.jobdri.jobdri_api.domain.evaluation.analysis.sanitization.EvaluationSanitizationService;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -26,7 +27,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Service
Expand Down Expand Up @@ -136,17 +136,17 @@ private EvaluationAnalysisResult analyzeCase(EvaluationAnalysisCase evaluationCa
evaluationCase.question(),
evaluationCase.answer()
));
AnalysisLlmResponse llmResponse = generatedResult.response();
EvaluationLlmSnapshot llmResponse = generatedResult.responseSnapshot();

int jobFit = validateScore("jobFit", llmResponse == null ? null : llmResponse.jobFit());
int impact = validateScore("impact", llmResponse == null ? null : llmResponse.impact());
int completeness = validateScore("completeness", llmResponse == null ? null : llmResponse.completeness());
List<MissingKeywordResponse> missingKeywords = buildMissingKeywords(evaluationCase, llmResponse);
List<EvaluationMissingKeyword> missingKeywords = buildMissingKeywords(evaluationCase, llmResponse);
List<EvaluationQuestionAnalysisResult> questionAnalyses = buildQuestionAnalyses(evaluationCase, llmResponse);
log.debug(
"Evaluation serialized missing keyword flow. caseId={}, candidateMissingKeywordCount={}, finalMissingKeywordCount={}, evaluationSerializedMissingKeywordCount={}",
evaluationCase.caseId(),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().missingKeywordCandidates()),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().missingKeywordCandidates()),
size(llmResponse == null ? null : llmResponse.missingKeywords()),
missingKeywords.size()
);
Expand All @@ -167,21 +167,21 @@ private EvaluationAnalysisResult analyzeCase(EvaluationAnalysisCase evaluationCa
normalizeFeedback(llmResponse.feedback()),
writeJson(missingKeywords),
writeJson(questionAnalyses),
writeJson(llmResponse),
writeJson(generatedResult.rawCandidateResponse()),
writeJson(generatedResult.sanitizedCandidateResponse()),
writeJson(generatedResult.candidateReviewResponse()),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().analysisCandidates()),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().analysisCandidates()),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().strengthCandidates()),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().missingKeywordCandidates()),
acceptedDecisionCount(generatedResult.candidateReviewResponse()),
rejectedDecisionCount(generatedResult.candidateReviewResponse()),
rejectionCodeCounts(generatedResult.candidateReviewResponse()),
generatedResult.rawLlmResponseJson(),
generatedResult.rawCandidateResponseJson(),
generatedResult.sanitizedCandidateResponseJson(),
generatedResult.candidateReviewResponseJson(),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().analysisCandidates()),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().analysisCandidates()),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().strengthCandidates()),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().missingKeywordCandidates()),
acceptedDecisionCount(generatedResult.candidateReviewSnapshot()),
rejectedDecisionCount(generatedResult.candidateReviewSnapshot()),
rejectionCodeCounts(generatedResult.candidateReviewSnapshot()),
questionAnalyses.size(),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().strengthCandidates()),
llmResponse.keyStrengths() == null ? 0 : llmResponse.keyStrengths().size(),
size(generatedResult.sanitizedCandidateResponse() == null ? null : generatedResult.sanitizedCandidateResponse().missingKeywordCandidates()),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().strengthCandidates()),
llmResponse.keyStrengthQuotes() == null ? 0 : llmResponse.keyStrengthQuotes().size(),
size(generatedResult.sanitizedCandidateSnapshot() == null ? null : generatedResult.sanitizedCandidateSnapshot().missingKeywordCandidates()),
missingKeywords.size(),
generatedResult.candidateCallLatencyMs(),
generatedResult.finalCallLatencyMs(),
Expand All @@ -199,34 +199,30 @@ private EvaluationAnalysisResult analyzeCase(EvaluationAnalysisCase evaluationCa
);
}

private List<MissingKeywordResponse> buildMissingKeywords(
private List<EvaluationMissingKeyword> buildMissingKeywords(
EvaluationAnalysisCase evaluationCase,
AnalysisLlmResponse llmResponse
EvaluationLlmSnapshot llmResponse
) {
if (llmResponse == null || llmResponse.missingKeywords() == null) {
return List.of();
}

List<MissingKeywordResponse> result = new ArrayList<>();
List<EvaluationMissingKeyword> result = new ArrayList<>();
Set<String> seenKeywords = new HashSet<>();

for (AnalysisLlmResponse.MissingKeywordItem item : llmResponse.missingKeywords()) {
for (EvaluationMissingKeyword item : llmResponse.missingKeywords()) {
if (item == null || !StringUtils.hasText(item.keyword())) {
continue;
}

String keyword = item.keyword().trim();
if (keyword.length() > AnalysisResultConstants.MAX_MISSING_KEYWORD_LENGTH) {
if (keyword.length() > EvaluationAnalysisPolicyConstants.MAX_MISSING_KEYWORD_LENGTH) {
continue;
}

Optional<MissingKeywordSource> source = MissingKeywordSource.from(item.source());
if (source.isEmpty()) {
continue;
}
if (!evaluationSanitizationService.isValidMissingKeyword(
keyword,
source.get(),
item.source(),
evaluationCase.mainTasks(),
evaluationCase.qualifications()
)) {
Expand All @@ -244,8 +240,8 @@ private List<MissingKeywordResponse> buildMissingKeywords(
continue;
}

result.add(new MissingKeywordResponse(keyword, source.get()));
if (result.size() >= AnalysisResultConstants.MAX_MISSING_KEYWORDS) {
result.add(new EvaluationMissingKeyword(keyword, item.source()));
if (result.size() >= EvaluationAnalysisPolicyConstants.MAX_MISSING_KEYWORDS) {
break;
}
}
Expand All @@ -254,7 +250,7 @@ private List<MissingKeywordResponse> buildMissingKeywords(

private List<EvaluationQuestionAnalysisResult> buildQuestionAnalyses(
EvaluationAnalysisCase evaluationCase,
AnalysisLlmResponse llmResponse
EvaluationLlmSnapshot llmResponse
) {
if (llmResponse == null || llmResponse.questionAnalyses() == null) {
return List.of();
Expand All @@ -272,7 +268,7 @@ private List<EvaluationQuestionAnalysisResult> buildQuestionAnalyses(
Set<Long> fabricatedQuestionIds = new HashSet<>();
Set<String> keyStrengthQuotes = normalizedKeyStrengthQuotes(llmResponse);

for (AnalysisLlmResponse.QuestionAnalysisItem item : llmResponse.questionAnalyses()) {
for (EvaluationQuestionAnalysis item : llmResponse.questionAnalyses()) {
if (item == null || item.questionId() == null || !StringUtils.hasText(item.sentence())) {
continue;
}
Expand All @@ -297,7 +293,7 @@ private List<EvaluationQuestionAnalysisResult> buildQuestionAnalyses(
}

int currentCount = analysisCountByQuestionId.getOrDefault(item.questionId(), 0);
if (currentCount >= AnalysisResultConstants.MAX_ANALYSES_PER_QUESTION) {
if (currentCount >= EvaluationAnalysisPolicyConstants.MAX_ANALYSES_PER_QUESTION) {
continue;
}

Expand Down Expand Up @@ -339,30 +335,30 @@ private List<EvaluationQuestionAnalysisResult> buildQuestionAnalyses(
return result;
}

private Set<String> normalizedKeyStrengthQuotes(AnalysisLlmResponse llmResponse) {
if (llmResponse == null || llmResponse.keyStrengths() == null) {
private Set<String> normalizedKeyStrengthQuotes(EvaluationLlmSnapshot llmResponse) {
if (llmResponse == null || llmResponse.keyStrengthQuotes() == null) {
return Set.of();
}
return llmResponse.keyStrengths().stream()
.filter(item -> item != null && StringUtils.hasText(item.quote()))
.map(item -> normalizeKeyword(item.quote()))
return llmResponse.keyStrengthQuotes().stream()
.filter(StringUtils::hasText)
.map(this::normalizeKeyword)
.collect(java.util.stream.Collectors.toSet());
}

private int validateScore(String fieldName, Integer score) {
if (score == null
|| score < AnalysisResultConstants.MIN_SCORE
|| score > AnalysisResultConstants.MAX_SCORE) {
|| score < EvaluationAnalysisPolicyConstants.MIN_SCORE
|| score > EvaluationAnalysisPolicyConstants.MAX_SCORE) {
throw new IllegalArgumentException("자소서 분석 AI 응답의 " + fieldName + " 점수 범위가 올바르지 않습니다.");
}
return score;
}

private int calculateScore(int jobFit, int impact, int completeness) {
return (int) Math.round(
jobFit * AnalysisResultConstants.JOB_FIT_WEIGHT
+ impact * AnalysisResultConstants.IMPACT_WEIGHT
+ completeness * AnalysisResultConstants.COMPLETENESS_WEIGHT
jobFit * EvaluationAnalysisPolicyConstants.JOB_FIT_WEIGHT
+ impact * EvaluationAnalysisPolicyConstants.IMPACT_WEIGHT
+ completeness * EvaluationAnalysisPolicyConstants.COMPLETENESS_WEIGHT
);
}

Expand Down Expand Up @@ -399,7 +395,7 @@ private Integer size(List<?> values) {
return values == null ? null : values.size();
}

private Integer acceptedDecisionCount(CandidateReviewResponse reviewResponse) {
private Integer acceptedDecisionCount(EvaluationCandidateReviewSnapshot reviewResponse) {
if (reviewResponse == null || reviewResponse.decisions() == null) {
return null;
}
Expand All @@ -408,7 +404,7 @@ private Integer acceptedDecisionCount(CandidateReviewResponse reviewResponse) {
.count();
}

private Integer rejectedDecisionCount(CandidateReviewResponse reviewResponse) {
private Integer rejectedDecisionCount(EvaluationCandidateReviewSnapshot reviewResponse) {
if (reviewResponse == null || reviewResponse.decisions() == null) {
return null;
}
Expand All @@ -417,16 +413,16 @@ private Integer rejectedDecisionCount(CandidateReviewResponse reviewResponse) {
.count();
}

private String rejectionCodeCounts(CandidateReviewResponse reviewResponse) {
private String rejectionCodeCounts(EvaluationCandidateReviewSnapshot reviewResponse) {
if (reviewResponse == null || reviewResponse.decisions() == null) {
return "";
}
Map<String, Long> counts = reviewResponse.decisions().stream()
.filter(decision -> decision != null
&& decision.rejectionCode() != null
&& decision.rejectionCode() != CandidateReviewResponse.RejectionCode.NONE)
&& StringUtils.hasText(decision.rejectionCode())
&& !"NONE".equals(decision.rejectionCode()))
.collect(java.util.stream.Collectors.groupingBy(
decision -> decision.rejectionCode().name(),
EvaluationCandidateReviewDecision::rejectionCode,
java.util.stream.Collectors.counting()
));
return writeJson(counts);
Expand Down
Loading
Loading