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

166 lines
5.6 KiB
JavaScript
Raw 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.

/* HR Candidate Matcher — Business Logic */
/* global window */
(function () {
'use strict';
var APP = window.HRMatcher = window.HRMatcher || {};
APP.logic = {
validateInput: function (vacancyText, resumeText) {
var errors = {};
var valid = true;
if (!vacancyText || vacancyText.trim().length < 20) {
errors.vacancy = 'Введите текст вакансии (минимум 20 символов)';
valid = false;
}
if (!resumeText || resumeText.trim().length < 20) {
errors.resume = 'Введите текст резюме (минимум 20 символов)';
valid = false;
}
return { valid: valid, errors: errors };
},
validateCriteria: function (criteria) {
if (!criteria || criteria.length === 0) {
return { valid: false, message: 'Добавьте хотя бы один критерий' };
}
for (var i = 0; i < criteria.length; i++) {
var c = criteria[i];
if (!c.label || c.label.trim().length < 2) {
return { valid: false, message: 'Укажите название для критерия №' + (i + 1) };
}
if (c.weight < 1 || c.weight > 5 || isNaN(c.weight)) {
return { valid: false, message: 'Вес критерия «' + c.label + '» должен быть от 1 до 5' };
}
}
return { valid: true };
},
runMatch: function (criteria, resumeText) {
var results = [];
for (var i = 0; i < criteria.length; i++) {
var result = APP.aiAdapter.matchCriterion(criteria[i], resumeText);
results.push(result);
}
return results;
},
getFilteredResults: function (matchResults, filter) {
if (!filter || filter === 'all') return matchResults;
return matchResults.filter(function (r) { return r.status === filter; });
},
getCriterionById: function (criteria, id) {
for (var i = 0; i < criteria.length; i++) {
if (criteria[i].id === id) return criteria[i];
}
return null;
},
getUnconfirmedCriteria: function (criteria, matchResults) {
var resultMap = {};
matchResults.forEach(function (r) { resultMap[r.criterionId] = r; });
return criteria.filter(function (c) {
var r = resultMap[c.id];
return !r || r.status !== APP.STATUS.CONFIRMED;
});
},
generateInterviewData: function (criteria, matchResults) {
var resultMap = {};
matchResults.forEach(function (r) { resultMap[r.criterionId] = r; });
var sections = [];
var unconfirmed = this.getUnconfirmedCriteria(criteria, matchResults);
unconfirmed.forEach(function (c) {
var result = resultMap[c.id];
var status = result ? result.status : APP.STATUS.NOT_FOUND;
var questions = APP.aiAdapter.generateQuestions(c, result);
sections.push({
criterion: c,
status: status,
questions: questions
});
});
return sections;
},
exportSummary: function (vacancy, criteria, matchResults) {
var lines = [];
var now = new Date();
lines.push('=== Сводка по кандидату ===');
lines.push('Дата: ' + now.toLocaleDateString('ru-KZ'));
lines.push('Позиция: ' + (vacancy.title || 'Не указана'));
lines.push('');
var coverage = APP.aiAdapter.computeCoverage(criteria, matchResults);
lines.push('Покрытие указанных требований: ' + coverage + '%');
lines.push('');
var resultMap = {};
matchResults.forEach(function (r) { resultMap[r.criterionId] = r; });
var statusLabels = {};
statusLabels[APP.STATUS.CONFIRMED] = 'Подтверждено';
statusLabels[APP.STATUS.NOT_FOUND] = 'Не найдено';
statusLabels[APP.STATUS.UNCLEAR] = 'Уточнить';
lines.push('--- Критерии ---');
criteria.forEach(function (c) {
var result = resultMap[c.id];
var statusText = result ? statusLabels[result.status] || '—' : '—';
var typeText = c.required ? 'Обязательное' : 'Желательное';
lines.push('• ' + c.label + ' [' + typeText + ', вес ' + c.weight + '] — ' + statusText);
if (result && result.note) {
lines.push(' Примечание: ' + result.note);
}
});
lines.push('');
lines.push('--- Вопросы для интервью ---');
var interviewData = this.generateInterviewData(criteria, matchResults);
var qNum = 1;
interviewData.forEach(function (section) {
lines.push('');
lines.push('▸ ' + section.criterion.label + ' (' + (statusLabels[section.status] || '—') + ')');
section.questions.forEach(function (q) {
lines.push(' ' + qNum + '. ' + q);
qNum++;
});
});
lines.push('');
lines.push('---');
lines.push('Данные обезличены. Сводка подготовлена в демонстрационном режиме.');
lines.push('Персональные данные кандидата не включены.');
return lines.join('\n');
},
formatNumber: function (num) {
try {
return new Intl.NumberFormat('ru-KZ').format(num);
} catch (e) {
return String(num);
}
},
formatCurrency: function (amount) {
try {
return new Intl.NumberFormat('ru-KZ', {
style: 'currency',
currency: 'KZT',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(amount);
} catch (e) {
return amount + ' ₸';
}
}
};
})();