v3: Detailed scoring system
This commit is contained in:
parent
f2151ec2b6
commit
b28bb7682b
349
index.html
349
index.html
@ -338,7 +338,7 @@ async function extractTextFromDOCX(file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Анализ резюме
|
// Анализ резюме
|
||||||
function analyzeResume(text, fileName) {
|
function analyzeResume(text, fileName, jobRequirements) {
|
||||||
const textLower = text.toLowerCase();
|
const textLower = text.toLowerCase();
|
||||||
|
|
||||||
// Извлечение данных
|
// Извлечение данных
|
||||||
@ -363,15 +363,16 @@ function analyzeResume(text, fileName) {
|
|||||||
const hardSkills = extractHardSkills(text);
|
const hardSkills = extractHardSkills(text);
|
||||||
const softSkills = extractSoftSkills(text);
|
const softSkills = extractSoftSkills(text);
|
||||||
|
|
||||||
// Расчет Score
|
// Расчет детализированного Score
|
||||||
const score = calculateMatchScore(hardSkills, experience, level);
|
const score = calculateDetailedScore(hardSkills, softSkills, experience, level, education, jobRequirements, text);
|
||||||
|
|
||||||
// Определение статуса
|
// Определение статуса на основе нового шкалы
|
||||||
let status = 'Weak Match';
|
let status = 'No Hire';
|
||||||
if (score >= 90) status = 'Top Candidate';
|
if (score.total >= 95) status = 'Exceptional Candidate';
|
||||||
else if (score >= 80) status = 'Strong Match';
|
else if (score.total >= 90) status = 'Strong Hire';
|
||||||
else if (score >= 70) status = 'Good Match';
|
else if (score.total >= 80) status = 'Hire';
|
||||||
else if (score >= 60) status = 'Potential Match';
|
else if (score.total >= 70) status = 'Consider';
|
||||||
|
else if (score.total >= 60) status = 'Weak Match';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@ -484,30 +485,184 @@ function extractEducation(text) {
|
|||||||
return 'Не указано';
|
return 'Не указано';
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateMatchScore(hardSkills, experience, level) {
|
function calculateDetailedScore(hardSkills, softSkills, experience, level, education, jobRequirements, text) {
|
||||||
// Базовый балл
|
const textLower = text.toLowerCase();
|
||||||
let score = 50;
|
|
||||||
|
|
||||||
// Балл за навыки (35%)
|
// 1. Профессиональный опыт — 30 баллов
|
||||||
const skillScore = Math.min(35, hardSkills.length * 5);
|
let experienceScore = 0;
|
||||||
score += skillScore - 25; // Корректировка
|
const experienceYears = parseInt(experience) || 0;
|
||||||
|
|
||||||
// Балл за уровень (25%)
|
// Оценка релевантного опыта
|
||||||
const levelScores = { 'Junior': 10, 'Middle': 20, 'Senior': 25, 'Lead': 25, 'Head': 25 };
|
if (experienceYears >= 5) experienceScore = 25;
|
||||||
score += (levelScores[level] || 15) - 15;
|
else if (experienceYears >= 3) experienceScore = 20;
|
||||||
|
else if (experienceYears >= 1) experienceScore = 15;
|
||||||
|
else experienceScore = 5;
|
||||||
|
|
||||||
// Балл за опыт (25%)
|
// Проверка опыта в аналогичной должности
|
||||||
if (experience.includes('5') || experience.includes('6') || experience.includes('7') || experience.includes('8') || experience.includes('9') || experience.includes('10')) {
|
const positionKeywords = ['разработчик', 'developer', 'менеджер', 'manager', 'аналитик', 'analyst', 'дизайнер', 'designer'];
|
||||||
score += 10;
|
const hasRelevantPosition = positionKeywords.some(keyword => textLower.includes(keyword));
|
||||||
} else if (experience.includes('3') || experience.includes('4')) {
|
if (hasRelevantPosition) experienceScore = Math.min(30, experienceScore + 5);
|
||||||
score += 5;
|
|
||||||
|
// Проверка опыта в аналогичной отрасли (если указана отрасль)
|
||||||
|
if (jobRequirements.department) {
|
||||||
|
const industryKeywords = jobRequirements.department.toLowerCase().split(' ');
|
||||||
|
const hasIndustryExperience = industryKeywords.some(keyword => textLower.includes(keyword));
|
||||||
|
if (hasIndustryExperience) experienceScore = Math.min(30, experienceScore + 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Случайная вариация для демо
|
// Сложность проектов (по keywords)
|
||||||
score += Math.floor(Math.random() * 10) - 5;
|
const complexKeywords = ['lead', 'руководитель', 'senior', 'старший', 'управление', 'management'];
|
||||||
|
const hasComplexProjects = complexKeywords.some(keyword => textLower.includes(keyword));
|
||||||
|
if (hasComplexProjects) experienceScore = Math.min(30, experienceScore + 5);
|
||||||
|
|
||||||
// Ограничение 0-100
|
experienceScore = Math.min(30, experienceScore);
|
||||||
return Math.max(0, Math.min(100, score));
|
|
||||||
|
// 2. Hard Skills — 25 баллов
|
||||||
|
let hardSkillsScore = 0;
|
||||||
|
const requiredSkills = jobRequirements.requiredSkills.map(s => s.toLowerCase());
|
||||||
|
const foundRequiredSkills = requiredSkills.filter(skill => textLower.includes(skill));
|
||||||
|
const requiredPercentage = requiredSkills.length > 0 ? (foundRequiredSkills.length / requiredSkills.length) * 100 : 100;
|
||||||
|
|
||||||
|
if (requiredPercentage >= 100) hardSkillsScore = 25;
|
||||||
|
else if (requiredPercentage >= 80) hardSkillsScore = 20;
|
||||||
|
else if (requiredPercentage >= 60) hardSkillsScore = 15;
|
||||||
|
else if (requiredPercentage >= 40) hardSkillsScore = 10;
|
||||||
|
else hardSkillsScore = 5;
|
||||||
|
|
||||||
|
// Бонус за наличие желательных навыков
|
||||||
|
const preferredSkills = jobRequirements.preferredSkills.map(s => s.toLowerCase());
|
||||||
|
const foundPreferredSkills = preferredSkills.filter(skill => textLower.includes(skill));
|
||||||
|
hardSkillsScore = Math.min(25, hardSkillsScore + foundPreferredSkills.length * 2);
|
||||||
|
|
||||||
|
// 3. Соответствие должности — 15 баллов
|
||||||
|
let positionScore = 0;
|
||||||
|
const levelOrder = ['Junior', 'Middle', 'Senior', 'Lead', 'Head'];
|
||||||
|
const candidateLevelIndex = levelOrder.indexOf(level);
|
||||||
|
const jobLevelIndex = levelOrder.indexOf(jobRequirements.level);
|
||||||
|
|
||||||
|
if (candidateLevelIndex === jobLevelIndex) positionScore = 15;
|
||||||
|
else if (candidateLevelIndex === jobLevelIndex - 1) positionScore = 10;
|
||||||
|
else if (candidateLevelIndex === jobLevelIndex - 2) positionScore = 5;
|
||||||
|
else if (candidateLevelIndex > jobLevelIndex) positionScore = 12; // Higher level than required
|
||||||
|
else positionScore = 3;
|
||||||
|
|
||||||
|
// Проверка обязанностей по описанию вакансии
|
||||||
|
if (jobRequirements.description) {
|
||||||
|
const jobKeywords = jobRequirements.description.toLowerCase().split(' ').filter(word => word.length > 3);
|
||||||
|
const matchingJobKeywords = jobKeywords.filter(keyword => textLower.includes(keyword));
|
||||||
|
const jobMatchPercentage = jobKeywords.length > 0 ? (matchingJobKeywords.length / jobKeywords.length) * 100 : 0;
|
||||||
|
positionScore = Math.min(15, positionScore + Math.floor(jobMatchPercentage / 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Отраслевой опыт — 10 баллов
|
||||||
|
let industryScore = 0;
|
||||||
|
const industryKeywords = ['fintech', 'banking', 'retail', 'e-commerce', 'healthcare', 'education', 'telecom', 'gaming'];
|
||||||
|
const jobIndustry = jobRequirements.description.toLowerCase();
|
||||||
|
|
||||||
|
for (const industry of industryKeywords) {
|
||||||
|
if (jobIndustry.includes(industry)) {
|
||||||
|
if (textLower.includes(industry)) {
|
||||||
|
industryScore = 10;
|
||||||
|
} else if (textLower.includes('банк') || textLower.includes('финанс')) {
|
||||||
|
industryScore = 7;
|
||||||
|
} else if (textLower.includes('ритейл') || textLower.includes('магазин')) {
|
||||||
|
industryScore = 5;
|
||||||
|
} else {
|
||||||
|
industryScore = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Образование и сертификаты — 5 баллов
|
||||||
|
let educationScore = 0;
|
||||||
|
if (education === 'Высшее образование') {
|
||||||
|
educationScore = 5;
|
||||||
|
} else if (education === 'Среднее профессиональное') {
|
||||||
|
educationScore = 3;
|
||||||
|
} else {
|
||||||
|
educationScore = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка сертификатов
|
||||||
|
const certKeywords = ['сертификат', 'certificate', 'certification', 'диплом', 'degree'];
|
||||||
|
const hasCerts = certKeywords.some(keyword => textLower.includes(keyword));
|
||||||
|
if (hasCerts) educationScore = Math.min(5, educationScore + 1);
|
||||||
|
|
||||||
|
// 6. Soft Skills — 5 баллов
|
||||||
|
let softSkillsScore = 0;
|
||||||
|
const softSkillKeywords = ['коммуникация', 'лидерство', 'управление', 'команда', 'переговоры', 'communication', 'leadership', 'team', 'management'];
|
||||||
|
const foundSoftSkills = softSkillKeywords.filter(skill => textLower.includes(skill));
|
||||||
|
|
||||||
|
if (foundSoftSkills.length >= 3) softSkillsScore = 5;
|
||||||
|
else if (foundSoftSkills.length >= 2) softSkillsScore = 4;
|
||||||
|
else if (foundSoftSkills.length >= 1) softSkillsScore = 3;
|
||||||
|
else softSkillsScore = 1;
|
||||||
|
|
||||||
|
// 7. Стабильность карьеры — 5 баллов
|
||||||
|
let stabilityScore = 3; // По умолчанию средняя стабильность
|
||||||
|
|
||||||
|
// Поиск мест работы
|
||||||
|
const workKeywords = ['работал', 'работаю', 'занимался', 'experience', 'work'];
|
||||||
|
const workMatches = textLower.split('\n').filter(line => workKeywords.some(keyword => line.includes(keyword)));
|
||||||
|
|
||||||
|
if (workMatches.length > 0) {
|
||||||
|
// Анализ длительности
|
||||||
|
const durationKeywords = ['года', 'лет', 'месяцев', 'год', ' year', ' years'];
|
||||||
|
const hasLongDuration = workMatches.some(match => durationKeywords.some(keyword => match.includes(keyword)));
|
||||||
|
|
||||||
|
if (hasLongDuration) {
|
||||||
|
stabilityScore = 5;
|
||||||
|
} else if (workMatches.length <= 2) {
|
||||||
|
stabilityScore = 4;
|
||||||
|
} else if (workMatches.length <= 3) {
|
||||||
|
stabilityScore = 3;
|
||||||
|
} else {
|
||||||
|
stabilityScore = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Дополнительные преимущества — 5 баллов
|
||||||
|
let bonusScore = 0;
|
||||||
|
const bonusKeywords = ['английский', 'english', 'international', 'международный', 'награда', 'award', 'публикация', 'publication', 'выступление', 'presentation'];
|
||||||
|
const foundBonus = bonusKeywords.filter(keyword => textLower.includes(keyword));
|
||||||
|
|
||||||
|
bonusScore = Math.min(5, foundBonus.length * 2);
|
||||||
|
|
||||||
|
// Итоговый балл
|
||||||
|
const totalScore = experienceScore + hardSkillsScore + positionScore + industryScore + educationScore + softSkillsScore + stabilityScore + bonusScore;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: Math.min(100, totalScore),
|
||||||
|
experience: experienceScore,
|
||||||
|
hardSkills: hardSkillsScore,
|
||||||
|
position: positionScore,
|
||||||
|
industry: industryScore,
|
||||||
|
education: educationScore,
|
||||||
|
softSkills: softSkillsScore,
|
||||||
|
stability: stabilityScore,
|
||||||
|
bonus: bonusScore
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение требований вакансии из формы
|
||||||
|
function getJobRequirements() {
|
||||||
|
const requiredSkillsText = document.getElementById('requiredSkills').value;
|
||||||
|
const preferredSkillsText = document.getElementById('preferredSkills').value;
|
||||||
|
const stopFactorsText = document.getElementById('stopFactors').value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: document.getElementById('jobTitle').value,
|
||||||
|
company: document.getElementById('company').value,
|
||||||
|
department: document.getElementById('department').value,
|
||||||
|
city: document.getElementById('city').value,
|
||||||
|
format: document.querySelector('input[name="format"]:checked')?.value || 'hybrid',
|
||||||
|
level: document.getElementById('level').value,
|
||||||
|
description: document.getElementById('jobDescription').value,
|
||||||
|
requiredSkills: requiredSkillsText.split('\n').map(s => s.trim()).filter(s => s),
|
||||||
|
preferredSkills: preferredSkillsText.split('\n').map(s => s.trim()).filter(s => s),
|
||||||
|
stopFactors: stopFactorsText.split('\n').map(s => s.trim()).filter(s => s)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Анализ всех резюме
|
// Анализ всех резюме
|
||||||
@ -519,12 +674,13 @@ async function analyzeAllResumes() {
|
|||||||
analyzeBtn.disabled = true;
|
analyzeBtn.disabled = true;
|
||||||
|
|
||||||
analyzedCandidates = [];
|
analyzedCandidates = [];
|
||||||
|
const jobRequirements = getJobRequirements();
|
||||||
|
|
||||||
for (const file of uploadedFiles) {
|
for (const file of uploadedFiles) {
|
||||||
try {
|
try {
|
||||||
const text = await extractText(file);
|
const text = await extractText(file);
|
||||||
if (text) {
|
if (text) {
|
||||||
const candidate = analyzeResume(text, file.name);
|
const candidate = analyzeResume(text, file.name, jobRequirements);
|
||||||
analyzedCandidates.push(candidate);
|
analyzedCandidates.push(candidate);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -533,7 +689,7 @@ async function analyzeAllResumes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Сортировка по Score
|
// Сортировка по Score
|
||||||
analyzedCandidates.sort((a, b) => b.score - a.score);
|
analyzedCandidates.sort((a, b) => b.score.total - a.score.total);
|
||||||
|
|
||||||
// Обновление UI
|
// Обновление UI
|
||||||
updateResultsUI();
|
updateResultsUI();
|
||||||
@ -557,18 +713,18 @@ function updateResultsUI() {
|
|||||||
resultsSummary.style.display = 'block';
|
resultsSummary.style.display = 'block';
|
||||||
noResults.style.display = 'none';
|
noResults.style.display = 'none';
|
||||||
|
|
||||||
// Статистика
|
// Статистика (обновлено под новые статусы)
|
||||||
document.getElementById('totalCandidates').textContent = analyzedCandidates.length;
|
document.getElementById('totalCandidates').textContent = analyzedCandidates.length;
|
||||||
document.getElementById('topCandidates').textContent = analyzedCandidates.filter(c => c.status === 'Top Candidate').length;
|
document.getElementById('topCandidates').textContent = analyzedCandidates.filter(c => c.status === 'Exceptional Candidate' || c.status === 'Strong Hire').length;
|
||||||
document.getElementById('strongMatches').textContent = analyzedCandidates.filter(c => c.status === 'Strong Match').length;
|
document.getElementById('strongMatches').textContent = analyzedCandidates.filter(c => c.status === 'Hire').length;
|
||||||
document.getElementById('weakMatches').textContent = analyzedCandidates.filter(c => c.status === 'Weak Match').length;
|
document.getElementById('weakMatches').textContent = analyzedCandidates.filter(c => c.status === 'Weak Match' || c.status === 'No Hire').length;
|
||||||
|
|
||||||
// Таблица
|
// Таблица
|
||||||
tbody.innerHTML = analyzedCandidates.map((candidate, index) => `
|
tbody.innerHTML = analyzedCandidates.map((candidate, index) => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${index + 1}</td>
|
<td>${index + 1}</td>
|
||||||
<td><strong>${candidate.name}</strong><br><small>${candidate.city} • ${candidate.lastPosition}</small></td>
|
<td><strong>${candidate.name}</strong><br><small>${candidate.city} • ${candidate.lastPosition}</small></td>
|
||||||
<td><span class="score-badge ${candidate.score >= 80 ? 'high' : candidate.score >= 60 ? 'medium' : 'low'}">${candidate.score}%</span></td>
|
<td><span class="score-badge ${candidate.score.total >= 80 ? 'high' : candidate.score.total >= 60 ? 'medium' : 'low'}">${candidate.score.total}%</span></td>
|
||||||
<td>${candidate.level}</td>
|
<td>${candidate.level}</td>
|
||||||
<td>${candidate.experience}</td>
|
<td>${candidate.experience}</td>
|
||||||
<td>${candidate.status}</td>
|
<td>${candidate.status}</td>
|
||||||
@ -596,10 +752,87 @@ function showCandidateModal(index) {
|
|||||||
<div>
|
<div>
|
||||||
<div class="section-title">Match Score</div>
|
<div class="section-title">Match Score</div>
|
||||||
<div style="text-align:center;padding:20px">
|
<div style="text-align:center;padding:20px">
|
||||||
<div style="font-size:48px;font-weight:800;color:var(--cyan)">${candidate.score}%</div>
|
<div style="font-size:48px;font-weight:800;color:var(--cyan)">${candidate.score.total}%</div>
|
||||||
<div class="progress-bar" style="margin-top:16px">
|
<div class="progress-bar" style="margin-top:16px">
|
||||||
<div class="progress-fill" style="width:${candidate.score}%"></div>
|
<div class="progress-fill" style="width:${candidate.score.total}%"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top:8px;font-weight:600;color:var(--gray-500)">${candidate.status}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Детализация баллов</div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px">
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Профессиональный опыт</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.experience}/30</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.experience/30)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Hard Skills</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.hardSkills}/25</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.hardSkills/25)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Соответствие должности</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.position}/15</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.position/15)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Отраслевой опыт</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.industry}/10</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.industry/10)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Образование и сертификаты</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.education}/5</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.education/5)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Soft Skills</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.softSkills}/5</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.softSkills/5)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Стабильность карьеры</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.stability}/5</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.stability/5)*100}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:8px">
|
||||||
|
<span>Дополнительные преимущества</span>
|
||||||
|
<span style="font-weight:700">${candidate.score.bonus}/5</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${(candidate.score.bonus/5)*100}%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -612,21 +845,20 @@ function showCandidateModal(index) {
|
|||||||
|
|
||||||
<div class="section-title">Слабые стороны</div>
|
<div class="section-title">Слабые стороны</div>
|
||||||
<ul style="margin-bottom:24px">
|
<ul style="margin-bottom:24px">
|
||||||
<li>Ограниченный набор технологий</li>
|
${candidate.score.hardSkills < 15 ? '<li>Недостаточно технических навыков</li>' : ''}
|
||||||
<li>Недостаточно информации в резюме</li>
|
${candidate.score.experience < 20 ? '<li>Ограниченный профессиональный опыт</li>' : ''}
|
||||||
<li>Отсутствие портфолио</li>
|
${candidate.score.position < 10 ? '<li>Несоответствие уровня позиции</li>' : ''}
|
||||||
<li>Нет опыта в смежных областях</li>
|
${candidate.score.industry < 5 ? '<li>Нет отраслевого опыта</li>' : ''}
|
||||||
<li>Слабое описание достижений</li>
|
${candidate.score.stability < 3 ? '<li>Частая смена мест работы</li>' : ''}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="section-title">GAP-анализ</div>
|
<div class="section-title">GAP-анализ</div>
|
||||||
<p style="margin-bottom:24px">Требуется дополнительная проверка следующих компетенций:</p>
|
<p style="margin-bottom:24px">Требуется дополнительная проверка следующих компетенций:</p>
|
||||||
<ul style="margin-bottom:24px">
|
<ul style="margin-bottom:24px">
|
||||||
<li>Глубокое знание конкретных технологий</li>
|
${candidate.score.hardSkills < 20 ? '<li>Глубокое знание конкретных технологий</li>' : ''}
|
||||||
<li>Опыт работы в команде</li>
|
${candidate.score.softSkills < 4 ? '<li>Коммуникативные и лидерские навыки</li>' : ''}
|
||||||
<li>Понимание бизнес-процессов</li>
|
${candidate.score.industry < 8 ? '<li>Понимание бизнес-процессов отрасли</li>' : ''}
|
||||||
<li>Коммуникативные навыки</li>
|
${candidate.score.position < 12 ? '<li>Соответствие обязанностям должности</li>' : ''}
|
||||||
<li>Мотивация и карьерные цели</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="section-title">Вопросы для интервью</div>
|
<div class="section-title">Вопросы для интервью</div>
|
||||||
@ -639,12 +871,20 @@ function showCandidateModal(index) {
|
|||||||
<li>Где видите себя через 5 лет?</li>
|
<li>Где видите себя через 5 лет?</li>
|
||||||
<li>Как справляетесь со стрессом?</li>
|
<li>Как справляетесь со стрессом?</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
<h4 style="margin-top:16px">Профессиональные вопросы:</h4>
|
||||||
|
<ol>
|
||||||
|
<li>Опишите ваш опыт работы с ключевыми технологиями</li>
|
||||||
|
<li>Расскажите о самом сложном проекте</li>
|
||||||
|
<li>Как вы подходите к решению задач?</li>
|
||||||
|
<li>Опишите ваш опыт работы в команде</li>
|
||||||
|
<li>Как вы следите за трендами в отрасли?</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-title">Рекомендация</div>
|
<div class="section-title">Рекомендация</div>
|
||||||
<div style="padding:16px;background:var(--cyan-50);border-radius:8px;border-left:4px solid var(--cyan)">
|
<div style="padding:16px;background:var(--cyan-50);border-radius:8px;border-left:4px solid var(--cyan)">
|
||||||
<strong>${candidate.score >= 80 ? 'Hire' : candidate.score >= 60 ? 'Hire with Reservations' : 'No Hire'}</strong>
|
<strong>${candidate.status}</strong>
|
||||||
<p style="margin-top:8px">Кандидат ${candidate.score >= 80 ? 'соответствует' : 'частично соответствует'} требованиям вакансии. ${candidate.score >= 80 ? 'Рекомендуется к интервью.' : 'Требуется дополнительная проверка компетенций.'}</p>
|
<p style="margin-top:8px">${candidate.score.total >= 90 ? 'Кандидат полностью соответствует требованиям. Рекомендуется к немедленному интервью.' : candidate.score.total >= 80 ? 'Кандидат соответствует требованиям. Рекомендуется к интервью.' : candidate.score.total >= 70 ? 'Кандидат частично соответствует. Рекомендуется рассмотреть после уточнения деталей.' : 'Кандидат не соответствует основным требованиям. Требуется дополнительная оценка.'}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -677,13 +917,22 @@ function generateHRReport() {
|
|||||||
|
|
||||||
top10.forEach((candidate, index) => {
|
top10.forEach((candidate, index) => {
|
||||||
report += `${index + 1}. ${candidate.name}\n`;
|
report += `${index + 1}. ${candidate.name}\n`;
|
||||||
report += ` Match Score: ${candidate.score}%\n`;
|
report += ` Match Score: ${candidate.score.total}/100\n`;
|
||||||
report += ` Уровень: ${candidate.level}\n`;
|
report += ` Уровень: ${candidate.level}\n`;
|
||||||
report += ` Опыт: ${candidate.experience}\n`;
|
report += ` Опыт: ${candidate.experience}\n`;
|
||||||
report += ` Статус: ${candidate.status}\n`;
|
report += ` Статус: ${candidate.status}\n`;
|
||||||
report += ` Город: ${candidate.city}\n`;
|
report += ` Город: ${candidate.city}\n`;
|
||||||
report += ` Последняя должность: ${candidate.lastPosition}\n`;
|
report += ` Последняя должность: ${candidate.lastPosition}\n`;
|
||||||
report += ` Рекомендация: ${candidate.score >= 80 ? 'Strong Hire' : candidate.score >= 60 ? 'Hire' : 'No Hire'}\n\n`;
|
report += ` Детализация:\n`;
|
||||||
|
report += ` - Профессиональный опыт: ${candidate.score.experience}/30\n`;
|
||||||
|
report += ` - Hard Skills: ${candidate.score.hardSkills}/25\n`;
|
||||||
|
report += ` - Соответствие должности: ${candidate.score.position}/15\n`;
|
||||||
|
report += ` - Отраслевой опыт: ${candidate.score.industry}/10\n`;
|
||||||
|
report += ` - Образование: ${candidate.score.education}/5\n`;
|
||||||
|
report += ` - Soft Skills: ${candidate.score.softSkills}/5\n`;
|
||||||
|
report += ` - Стабильность карьеры: ${candidate.score.stability}/5\n`;
|
||||||
|
report += ` - Дополнительные преимущества: ${candidate.score.bonus}/5\n`;
|
||||||
|
report += ` Рекомендация: ${candidate.status}\n\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Скачивание отчета
|
// Скачивание отчета
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user