Skip to content

Commit f4372bf

Browse files
committed
매칭률 수정, 새로운 서류 업데이트할 경우 반영되도록 수정
1 parent 1ac9de6 commit f4372bf

File tree

2 files changed

+54
-20
lines changed

2 files changed

+54
-20
lines changed

backend/core/views/job_planner/job_planner_view.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,14 +2120,24 @@ def _evaluate_with_llm(self, candidates, user_profile):
21202120
llm_client = openai.OpenAI(api_key=api_key)
21212121

21222122
def evaluate_one(candidate):
2123+
# 기술 매칭 점수: 객관적 계산 (40점 만점)
2124+
matched_count = candidate.get('matched_count', 0)
2125+
total_skills = candidate.get('total_skills', 1)
2126+
skill_score = round(min(matched_count / total_skills, 1.0) * 40) if total_skills > 0 else 0
2127+
21232128
detail_text = candidate.get('detail_text', '')
21242129
if not detail_text:
2125-
# 상세 텍스트 없으면 스킬 매칭 점수 그대로 사용
2130+
# 상세 텍스트 없으면 스킬 점수만으로 산출
2131+
candidate['skill_score'] = skill_score
2132+
candidate['experience_score'] = 0
2133+
candidate['project_score'] = 0
2134+
candidate['llm_score'] = skill_score
21262135
return candidate
21272136

21282137
prompt = f"""당신은 채용 적합도 평가 전문가입니다.
21292138
2130-
아래 [채용공고]와 [지원자 프로필]을 비교하여 적합도를 평가하세요.
2139+
아래 [채용공고]와 [지원자 프로필]을 비교하여 경력과 프로젝트 적합도만 평가하세요.
2140+
(기술 스택 일치도는 별도로 계산하므로 평가하지 마세요)
21312141
21322142
[채용공고]
21332143
회사: {candidate['company_name']}
@@ -2136,18 +2146,16 @@ def evaluate_one(candidate):
21362146
{detail_text}
21372147
21382148
[지원자 프로필]
2139-
보유 스킬: {', '.join(user_profile.get('skills', []))}
21402149
경력: {user_profile.get('experience_summary', '정보 없음')}
21412150
프로젝트: {user_profile.get('projects_summary', '정보 없음')}
21422151
핵심 성과: {user_profile.get('achievements_summary', '정보 없음')}
21432152
2144-
다음 기준으로 평가하세요:
2145-
1. 기술 스택 일치도 (스킬이 공고 요구사항과 얼마나 맞는지)
2146-
2. 경력 적합도 (경력 내용이 공고 업무와 관련 있는지)
2147-
3. 프로젝트 관련성 (수행한 프로젝트가 공고 업무에 도움이 되는지)
2153+
다음 기준별 배점으로 평가하세요:
2154+
1. 경력 적합도 (30점 만점): 경력 내용이 공고 업무와 관련 있는지
2155+
2. 프로젝트 관련성 (30점 만점): 수행한 프로젝트가 공고 업무에 도움이 되는지
21482156
21492157
반드시 아래 JSON 형식으로만 응답하세요:
2150-
{{"requirements_summary": "이 공고가 원하는 인재상/주요 요구사항을 2-3문장으로 요약 (공고 내용 기반)", "score": 0~100 정수, "reason": "이 지원자에게 적합한 이유를 구체적 근거 기반으로 2-3문장으로 작성"}}"""
2158+
{{"requirements_summary": "이 공고가 원하는 인재상/주요 요구사항을 2-3문장으로 요약 (공고 내용 기반)", "experience_score": 0~30 정수, "project_score": 0~30 정수, "reason": "이 지원자에게 적합한 이유를 구체적 근거 기반으로 2-3문장으로 작성"}}"""
21512159

21522160
try:
21532161
response = llm_client.chat.completions.create(
@@ -2158,14 +2166,23 @@ def evaluate_one(candidate):
21582166
max_tokens=600,
21592167
)
21602168
result = json.loads(response.choices[0].message.content)
2161-
candidate['llm_score'] = result.get('score', 0)
2169+
exp_score = min(result.get('experience_score', 0), 30)
2170+
proj_score = min(result.get('project_score', 0), 30)
2171+
total = skill_score + exp_score + proj_score
2172+
2173+
candidate['skill_score'] = skill_score
2174+
candidate['experience_score'] = exp_score
2175+
candidate['project_score'] = proj_score
2176+
candidate['llm_score'] = total
21622177
candidate['reason'] = result.get('reason', candidate.get('reason', ''))
21632178
candidate['requirements_summary'] = result.get('requirements_summary', '')
2164-
candidate['requirements_summary'] = result.get('requirements_summary', '')
2165-
print(f" 🤖 {candidate['company_name']} | LLM 적합도: {candidate['llm_score']}점")
2179+
print(f" 🤖 {candidate['company_name']} | 기술:{skill_score}/40(객관) 경력:{exp_score}/30 프로젝트:{proj_score}/30 = 총:{total}점")
21662180
except Exception as e:
21672181
print(f" ⚠️ LLM 평가 실패 ({candidate['company_name']}): {e}")
2168-
candidate['llm_score'] = int(candidate.get('match_rate', 0) * 100)
2182+
candidate['skill_score'] = skill_score
2183+
candidate['experience_score'] = 0
2184+
candidate['project_score'] = 0
2185+
candidate['llm_score'] = skill_score
21692186
return candidate
21702187

21712188
# 최대 5개 병렬 평가

frontend/src/features/job_planner/components/JobPlannerModal.vue

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -391,41 +391,41 @@
391391
<div class="upload-item">
392392
<span class="upload-label">이력서</span>
393393
<div class="upload-row">
394-
<label class="upload-btn" :class="{ uploaded: resumePdf }">
394+
<label class="upload-btn" :class="{ uploaded: resumeFileName }">
395395
{{ resumeFileName || '+ PDF 업로드' }}
396396
<input type="file" accept=".pdf" @change="handlePdfUpload($event, 'resume')" hidden>
397397
</label>
398-
<button v-if="resumePdf" class="upload-clear-btn" @click="clearPdf('resume')" title="삭제">×</button>
398+
<button v-if="resumeFileName" class="upload-clear-btn" @click="clearPdf('resume')" title="삭제">×</button>
399399
</div>
400400
</div>
401401
<div class="upload-item">
402402
<span class="upload-label">경력기술서</span>
403403
<div class="upload-row">
404-
<label class="upload-btn" :class="{ uploaded: careerDescPdf }">
404+
<label class="upload-btn" :class="{ uploaded: careerDescFileName }">
405405
{{ careerDescFileName || '+ PDF 업로드' }}
406406
<input type="file" accept=".pdf" @change="handlePdfUpload($event, 'career_description')" hidden>
407407
</label>
408-
<button v-if="careerDescPdf" class="upload-clear-btn" @click="clearPdf('career_description')" title="삭제">×</button>
408+
<button v-if="careerDescFileName" class="upload-clear-btn" @click="clearPdf('career_description')" title="삭제">×</button>
409409
</div>
410410
</div>
411411
<div class="upload-item">
412412
<span class="upload-label">자기소개서</span>
413413
<div class="upload-row">
414-
<label class="upload-btn" :class="{ uploaded: coverLetterPdf }">
414+
<label class="upload-btn" :class="{ uploaded: coverLetterFileName }">
415415
{{ coverLetterFileName || '+ PDF 업로드' }}
416416
<input type="file" accept=".pdf" @change="handlePdfUpload($event, 'cover_letter')" hidden>
417417
</label>
418-
<button v-if="coverLetterPdf" class="upload-clear-btn" @click="clearPdf('cover_letter')" title="삭제">×</button>
418+
<button v-if="coverLetterFileName" class="upload-clear-btn" @click="clearPdf('cover_letter')" title="삭제">×</button>
419419
</div>
420420
</div>
421421
<div class="upload-item">
422422
<span class="upload-label">포트폴리오</span>
423423
<div class="upload-row">
424-
<label class="upload-btn" :class="{ uploaded: portfolioPdf }">
424+
<label class="upload-btn" :class="{ uploaded: portfolioFileName }">
425425
{{ portfolioFileName || '+ PDF 업로드' }}
426426
<input type="file" accept=".pdf" @change="handlePdfUpload($event, 'portfolio')" hidden>
427427
</label>
428-
<button v-if="portfolioPdf" class="upload-clear-btn" @click="clearPdf('portfolio')" title="삭제">×</button>
428+
<button v-if="portfolioFileName" class="upload-clear-btn" @click="clearPdf('portfolio')" title="삭제">×</button>
429429
</div>
430430
</div>
431431
</div>
@@ -1258,14 +1258,31 @@ export default {
12581258
}
12591259
}
12601260
1261+
// 디버깅: 어떤 서류가 새로 파싱되고 어떤 서류가 기존 결과 사용인지 확인
1262+
console.log('[서류파싱] 새로 파싱할 서류:', {
1263+
resume: !!this.resumePdf,
1264+
cover_letter: !!this.coverLetterPdf,
1265+
portfolio: !!this.portfolioPdf,
1266+
career_description: !!this.careerDescPdf
1267+
});
1268+
console.log('[서류파싱] existing_doc_results 키:', Object.keys(payload.existing_doc_results || {}));
1269+
12611270
const response = await axios.post('/api/core/job-planner/parse-resume/', payload);
1271+
console.log('[서류파싱] 응답 _doc_results 키:', Object.keys(response.data._doc_results || {}));
1272+
12621273
this.applyProfileData(response.data);
12631274
this.documentParseSuccess = true;
12641275
// 개별 서류 파싱 결과 저장
12651276
if (response.data._doc_results) {
12661277
this.saveDocResultsToStorage(response.data._doc_results);
12671278
}
12681279
this.saveProfileToStorage();
1280+
// 파싱 완료 후 PDF 데이터 클리어 (다음 파싱 시 부분 업데이트가 정상 작동하도록)
1281+
this.resumePdf = null;
1282+
this.coverLetterPdf = null;
1283+
this.portfolioPdf = null;
1284+
this.careerDescPdf = null;
1285+
console.log('[서류파싱] PDF 데이터 클리어 완료');
12691286
} catch (error) {
12701287
console.error('서류 분석 실패:', error);
12711288
this.errorMessage = error.response?.data?.error || '서류 분석 중 오류가 발생했습니다.';

0 commit comments

Comments
 (0)