ty-produktovyy-dizayner-i-se/matcher.js

246 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Matcher - логика сопоставления резюме и вакансии
*/
function extractRequirements(vacancyText) {
if (!vacancyText || typeof vacancyText !== 'string') {
return { criteria: [], error: 'Введите текст вакансии' };
}
const result = window.aiAdapter.generate('extractCriteria', { vacancyText });
return result;
}
function matchResumeToCriteria(criteria, resumeText) {
if (!criteria || criteria.length === 0) {
return { results: [], coverage: 0, error: 'Список критериев пуст' };
}
if (!resumeText || typeof resumeText !== 'string') {
return { results: [], coverage: 0, error: 'Введите текст резюме' };
}
const result = window.aiAdapter.generate('matchResume', { criteria, resumeText });
return result;
}
function updateCriterionStatus(results, criterionId, newStatus, note) {
if (!results || !Array.isArray(results)) {
return results;
}
return results.map(result => {
if (result.criterionId === criterionId) {
return {
...result,
status: newStatus,
note: note || result.note
};
}
return result;
});
}
function addCustomCriterion(criteria, label, required, weight, synonyms) {
const newCriterion = {
id: `custom-${Date.now()}`,
label: label.trim(),
required: required !== false,
weight: Math.max(1, Math.min(5, parseInt(weight, 10) || 3)),
synonyms: (synonyms || []).filter(s => s.trim().length > 0),
category: 'custom'
};
return [...criteria, newCriterion];
}
function removeCriterion(criteria, criterionId) {
return criteria.filter(c => c.id !== criterionId);
}
function updateCriterion(criteria, criterionId, updates) {
return criteria.map(c => {
if (c.id === criterionId) {
return { ...c, ...updates };
}
return c;
});
}
function generateInterviewQuestions(matchResults) {
if (!matchResults || !matchResults.results || matchResults.results.length === 0) {
return { questions: [] };
}
const unmatched = matchResults.results.filter(
r => r.status === 'clarify' || r.status === 'notfound'
);
if (unmatched.length === 0) {
return { questions: [] };
}
const result = window.aiAdapter.generate('generateQuestions', {
unmatchedCriteria: unmatched
});
return result;
}
function calculateCoverage(results) {
if (!results || results.length === 0) {
return 0;
}
const totalWeight = results.reduce((sum, r) => sum + (r.weight || 0), 0);
if (totalWeight === 0) {
return 0;
}
const confirmedWeight = results
.filter(r => r.status === 'confirmed')
.reduce((sum, r) => sum + (r.weight || 0), 0);
return Math.round((confirmedWeight / totalWeight) * 100);
}
function getStatusStats(results) {
if (!results || !Array.isArray(results)) {
return { confirmed: 0, clarify: 0, notfound: 0, total: 0 };
}
return {
confirmed: results.filter(r => r.status === 'confirmed').length,
clarify: results.filter(r => r.status === 'clarify').length,
notfound: results.filter(r => r.status === 'notfound').length,
total: results.length
};
}
function exportSummary(vacancyText, resumeText, criteria, matchResults, questions) {
const coverage = calculateCoverage(matchResults?.results || []);
const stats = getStatusStats(matchResults?.results || []);
const summary = {
exportedAt: new Date().toISOString(),
vacancy: {
title: 'Вакансия',
criteriaCount: criteria?.length || 0
},
coverage: {
percentage: coverage,
confirmed: stats.confirmed,
toClarify: stats.clarify,
notFound: stats.notfound,
total: stats.total
},
results: matchResults?.results?.map(r => ({
requirement: r.label,
type: r.required ? 'Обязательное' : 'Желательное',
weight: r.weight,
status: getStatusLabel(r.status),
evidence: r.evidence?.[0] || 'Нет данных'
})) || [],
interviewQuestions: questions?.questions?.map(q => ({
topic: q.label,
question: q.question,
followUp: q.followUp
})) || []
};
return summary;
}
function getStatusLabel(status) {
const labels = {
confirmed: 'Подтверждено',
clarify: 'Уточнить',
notfound: 'Не найдено'
};
return labels[status] || status;
}
function downloadJSON(data, filename) {
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function downloadText(text, filename) {
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function formatExportText(summary) {
let text = 'HR CANDIDATE MATCHER — СВОДКА\n';
text += '=' .repeat(50) + '\n\n';
text += `Дата экспорта: ${new Date(summary.exportedAt).toLocaleString('ru-RU')}\n\n`;
text += 'ПОКРЫТИЕ ТРЕБОВАНИЙ\n';
text += '-'.repeat(30) + '\n';
text += `Общее покрытие: ${summary.coverage.percentage}%\n`;
text += `Подтверждено: ${summary.coverage.confirmed}\n`;
text += `Требует уточнения: ${summary.coverage.toClarify}\n`;
text += `Не найдено: ${summary.coverage.notFound}\n`;
text += `Всего критериев: ${summary.coverage.total}\n\n`;
text += 'РЕЗУЛЬТАТЫ СОПОСТАВЛЕНИЯ\n';
text += '-'.repeat(30) + '\n';
summary.results.forEach((r, i) => {
text += `${i + 1}. ${r.requirement}\n`;
text += ` Тип: ${r.type}, Вес: ${r.weight}\n`;
text += ` Статус: ${r.status}\n`;
text += ` Доказательство: ${r.evidence}\n\n`;
});
if (summary.interviewQuestions.length > 0) {
text += 'ВОПРОСЫ ДЛЯ ИНТЕРВЬЮ\n';
text += '-'.repeat(30) + '\n';
summary.interviewQuestions.forEach((q, i) => {
text += `${i + 1}. Тема: ${q.topic}\n`;
text += ` Вопрос: ${q.question}\n`;
if (q.followUp && q.followUp.length > 0) {
text += ` Дополнительные: ${q.followUp.join('; ')}\n`;
}
text += '\n';
});
}
text += '=' .repeat(50) + '\n';
text += 'Создано с помощью HR Candidate Matcher\n';
return text;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
extractRequirements,
matchResumeToCriteria,
updateCriterionStatus,
addCustomCriterion,
removeCriterion,
updateCriterion,
generateInterviewQuestions,
calculateCoverage,
getStatusStats,
exportSummary,
downloadJSON,
downloadText,
formatExportText
};
}