Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
402 lines
11 KiB
JavaScript
402 lines
11 KiB
JavaScript
// 使用Intl.Segmenter对中文文本进行分词
|
||
function segmentText(text) {
|
||
const segmenter = new Intl.Segmenter('zh-Hans', {
|
||
granularity: 'word',
|
||
}); // 使用中文简体语言
|
||
const segments = [];
|
||
for (let segment of segmenter.segment(text.toLowerCase())) {
|
||
// 转为小写后进行分词
|
||
segments.push(segment.segment);
|
||
}
|
||
return segments;
|
||
}
|
||
|
||
// 语气 停用次
|
||
const stopWords = [
|
||
'的',
|
||
'了',
|
||
'啊',
|
||
'哦',
|
||
'/',
|
||
'、',
|
||
' ',
|
||
'',
|
||
'-',
|
||
'(',
|
||
')',
|
||
'(',
|
||
')',
|
||
'+',
|
||
'=',
|
||
'~',
|
||
'!',
|
||
'<',
|
||
'>',
|
||
'?',
|
||
'[',
|
||
']',
|
||
'{',
|
||
'}',
|
||
];
|
||
|
||
function cleanKeywords(arr) {
|
||
return arr.filter((word) => word && !stopWords.includes(word));
|
||
}
|
||
|
||
function calculateMatchScore(source, target) {
|
||
const sourceSet = new Set(cleanKeywords(source));
|
||
const targetSet = new Set(cleanKeywords(target));
|
||
let matchCount = 0;
|
||
for (let word of sourceSet) {
|
||
if (targetSet.has(word)) {
|
||
matchCount++;
|
||
}
|
||
}
|
||
|
||
// 匹配度 = source中匹配到的词 / source总词数
|
||
return matchCount / sourceSet.size;
|
||
}
|
||
|
||
class CsimilarityJobs {
|
||
config = {
|
||
thresholdVal: 0.69,
|
||
titleSimilarityWeight: 0.4,
|
||
salaryMatchWeight: 0.2,
|
||
areaMatchWeight: 0.2,
|
||
educationMatchWeight: 0.2,
|
||
experiencenMatchWeight: 0.1,
|
||
};
|
||
userTitle = ['Java', 'C', '全栈工程师'];
|
||
userSalaryMin = 10000;
|
||
userSalaryMax = 15000;
|
||
userArea = 0; // 用户指定的区域(例如:市南区)
|
||
userEducation = 4; // 用户学历(假设4为本科)
|
||
userExperience = 2; // 用户工作经验
|
||
|
||
jobTitle = '';
|
||
jobMinSalary = 10000;
|
||
jobMaxSalary = 15000;
|
||
jobLocationAreaCode = 0;
|
||
jobEducation = 4;
|
||
jobExperience = 2;
|
||
jobCategory = '';
|
||
// 系统
|
||
log = false;
|
||
|
||
constructor() {}
|
||
|
||
setUserInfo(resume) {
|
||
this.userTitle = resume.jobTitle;
|
||
this.userSalaryMax = Number(resume.salaryMax);
|
||
this.userSalaryMin = Number(resume.salaryMin);
|
||
this.userArea = Number(resume.area);
|
||
this.userEducation = resume.education;
|
||
this.userExperience = this.getUserExperience(Number(resume.age));
|
||
}
|
||
|
||
setJobInfo(jobInfo) {
|
||
this.jobTitle = jobInfo.jobTitle;
|
||
this.jobMinSalary = jobInfo.minSalary;
|
||
this.jobMaxSalary = jobInfo.maxSalary;
|
||
this.jobLocationAreaCode = jobInfo.jobLocationAreaCode;
|
||
this.jobEducation = jobInfo.education;
|
||
this.jobExperience = jobInfo.experience;
|
||
this.jobCategory = jobInfo.jobCategory;
|
||
}
|
||
|
||
calculationMatchingDegreeJob(resume) {
|
||
// 计算职位标题相似度
|
||
// const titleSimilarity = stringSimilarity.compareTwoStrings(this.userTitle, job.jobTitle);
|
||
let jobT = null;
|
||
if (this.jobCategory) {
|
||
jobT = this.calculateBestJobCategoryMatch(
|
||
resume.jobTitle || resume.jobTitleString || [],
|
||
this.jobCategory,
|
||
);
|
||
} else {
|
||
jobT = this.calculateBestJobMatch(
|
||
resume.jobTitle || resume.jobTitleString || [],
|
||
this.jobTitle,
|
||
);
|
||
}
|
||
const { bestMatchJobTitle, maxSimilarity } = jobT;
|
||
|
||
// 计算薪资匹配度
|
||
const salaryMatch = this.calculateSalaryMatch(
|
||
Number(resume.salaryMin),
|
||
Number(resume.salaryMax),
|
||
this.jobMinSalary,
|
||
this.jobMaxSalary,
|
||
);
|
||
|
||
// 计算区域匹配度
|
||
const areaMatch = this.calculateAreaMatch(Number(resume.area), this.jobLocationAreaCode);
|
||
|
||
// 计算学历匹配度
|
||
const educationMatch = this.calculateEducationMatch(resume.education, this.jobEducation);
|
||
|
||
// 计算工作经验匹配度
|
||
// const experiencenMatch = this.calculateExperienceMatch2(this.userExperience, job.experience);
|
||
|
||
// 综合匹配度 = 0.4 * 职位相似度 + 0.2 * 薪资匹配度 + 0.1 * 区域匹配度 + 0.2 * 学历匹配度 + 0.1 * 工作经验匹配度
|
||
const overallMatch =
|
||
this.config.titleSimilarityWeight * maxSimilarity +
|
||
this.config.salaryMatchWeight * salaryMatch +
|
||
this.config.areaMatchWeight * areaMatch +
|
||
this.config.educationMatchWeight * educationMatch;
|
||
// console.log(`Job ${job.jobTitle}工作经验匹配度: ${experiencenMatch}`);
|
||
if (this.log) {
|
||
console.log(
|
||
`Job ${job.jobTitle} 标题相似度 ${maxSimilarity} 薪资匹配度: ${salaryMatch}学历匹配度: ${educationMatch} 区域匹配度: ${areaMatch} 综合匹配度: ${overallMatch.toFixed(2)}`,
|
||
);
|
||
}
|
||
|
||
// 设置阈值进行岗位匹配判断
|
||
const threshold = this.config.thresholdVal;
|
||
return {
|
||
overallMatch: overallMatch.toFixed(2) * 100 + '%',
|
||
data: resume,
|
||
maxSimilarity,
|
||
salaryMatch,
|
||
educationMatch,
|
||
areaMatch,
|
||
};
|
||
}
|
||
|
||
calculationMatchingDegree(job) {
|
||
// 计算职位标题相似度
|
||
// console.log(this.userTitle, job.jobTitle)
|
||
// const titleSimilarity = stringSimilarity.compareTwoStrings(this.userTitle, job.jobTitle);
|
||
let jobT = null;
|
||
if (job.jobCategory) {
|
||
jobT = this.calculateBestJobCategoryMatch(this.userTitle, job.jobCategory);
|
||
} else {
|
||
jobT = this.calculateBestJobMatch(this.userTitle, job.jobTitle);
|
||
}
|
||
const { bestMatchJobTitle, maxSimilarity } = jobT;
|
||
// 计算薪资匹配度
|
||
const salaryMatch = this.calculateSalaryMatch(
|
||
this.userSalaryMin,
|
||
this.userSalaryMax,
|
||
job.minSalary,
|
||
job.maxSalary,
|
||
);
|
||
|
||
// 计算区域匹配度
|
||
const areaMatch = this.calculateAreaMatch(this.userArea, job.jobLocationAreaCode);
|
||
|
||
// 计算学历匹配度
|
||
const educationMatch = this.calculateEducationMatch(this.userEducation, job.education);
|
||
|
||
// 计算工作经验匹配度
|
||
// const experiencenMatch = this.calculateExperienceMatch2(this.userExperience, job.experience);
|
||
|
||
// 综合匹配度 = 0.4 * 职位相似度 + 0.2 * 薪资匹配度 + 0.1 * 区域匹配度 + 0.2 * 学历匹配度 + 0.1 * 工作经验匹配度
|
||
const overallMatch =
|
||
this.config.titleSimilarityWeight * maxSimilarity +
|
||
this.config.salaryMatchWeight * salaryMatch +
|
||
this.config.areaMatchWeight * areaMatch +
|
||
this.config.educationMatchWeight * educationMatch;
|
||
// console.log(`Job ${job.jobTitle}工作经验匹配度: ${experiencenMatch}`);
|
||
if (this.log) {
|
||
console.log(
|
||
`Job ${job.jobTitle} 标题相似度 ${maxSimilarity} 薪资匹配度: ${salaryMatch}学历匹配度: ${educationMatch} 区域匹配度: ${areaMatch} 综合匹配度: ${overallMatch.toFixed(2)}`,
|
||
);
|
||
}
|
||
|
||
// 设置阈值进行岗位匹配判断
|
||
const threshold = this.config.thresholdVal;
|
||
if (overallMatch > threshold) {
|
||
return {
|
||
overallMatch: overallMatch.toFixed(2) * 100 + '%',
|
||
data: job,
|
||
maxSimilarity,
|
||
salaryMatch,
|
||
educationMatch,
|
||
areaMatch,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 根据用户年龄推算工作经验年限区间
|
||
getUserExperience(age) {
|
||
if ((age = 0)) {
|
||
// 30以下
|
||
return {
|
||
min: 0,
|
||
max: 5,
|
||
};
|
||
} else if (age <= 1) {
|
||
// 40以下
|
||
return {
|
||
min: 5,
|
||
max: 10,
|
||
};
|
||
} else if (age <= 2) {
|
||
// 50以下
|
||
return {
|
||
min: 10,
|
||
max: 20,
|
||
};
|
||
} else {
|
||
// 50以上
|
||
return {
|
||
min: 20,
|
||
max: 40,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 计算经验匹配度
|
||
calculateExperienceMatch2(userExperience, jobExperience) {
|
||
const jobExperienceRange = this.mapJobExperience(jobExperience);
|
||
|
||
if (
|
||
userExperience.min <= jobExperienceRange.max &&
|
||
userExperience.max >= jobExperienceRange.min
|
||
) {
|
||
return 1;
|
||
}
|
||
|
||
if (
|
||
(userExperience.min <= jobExperienceRange.max &&
|
||
userExperience.max > jobExperienceRange.min) ||
|
||
(userExperience.max >= jobExperienceRange.min && userExperience.min < jobExperienceRange.max)
|
||
) {
|
||
return 0.5; // 部分匹配
|
||
}
|
||
return 0; // 不匹配
|
||
}
|
||
|
||
// 映射岗位经验要求到工作经验年限区间
|
||
mapJobExperience(jobExperience) {
|
||
const experienceMapping = {
|
||
1: {
|
||
min: 0,
|
||
max: 0,
|
||
},
|
||
2: {
|
||
min: 0,
|
||
max: 1,
|
||
},
|
||
3: {
|
||
min: 0,
|
||
max: 1,
|
||
},
|
||
4: {
|
||
min: 1,
|
||
max: 3,
|
||
},
|
||
5: {
|
||
min: 3,
|
||
max: 5,
|
||
},
|
||
6: {
|
||
min: 5,
|
||
max: 10,
|
||
},
|
||
7: {
|
||
min: 10,
|
||
max: 20,
|
||
},
|
||
8: {
|
||
min: 0,
|
||
max: 40,
|
||
},
|
||
};
|
||
return experienceMapping[jobExperience];
|
||
}
|
||
|
||
// 计算工作经验匹配度
|
||
calculateExperiencenMatch(userExperience, jobExperience) {
|
||
if (userExperience === jobExperience) {
|
||
return 1;
|
||
} else if (userExperience > jobExperience) {
|
||
return 0.75;
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
calculateSalaryMatch(userMin, userMax, jobMin, jobMax) {
|
||
const isMinMatch = userMin >= jobMin && userMin <= jobMax;
|
||
const isMaxMatch = userMax >= jobMin && userMax <= jobMax;
|
||
|
||
if (isMinMatch || isMaxMatch) {
|
||
return 1;
|
||
}
|
||
|
||
const minDifference = Math.abs(userMin - jobMin);
|
||
const maxDifference = Math.abs(userMax - jobMax);
|
||
|
||
if (minDifference > 3000 && maxDifference > 3000) {
|
||
return 0;
|
||
}
|
||
|
||
return 0.5; // 部分匹配
|
||
}
|
||
|
||
// 计算区域匹配度
|
||
calculateAreaMatch(userArea, jobArea) {
|
||
return userArea === jobArea ? 1 : 0.5;
|
||
}
|
||
|
||
calculateBestJobCategoryMatch(userJobTitles, jobTitle) {
|
||
let maxSimilarity = 0;
|
||
let bestMatchJobTitle = '';
|
||
for (let i = 0; i < userJobTitles.length; i++) {
|
||
let userTitle = userJobTitles[i];
|
||
if (userTitle === jobTitle) {
|
||
maxSimilarity = 1;
|
||
bestMatchJobTitle = userTitle;
|
||
break;
|
||
}
|
||
}
|
||
return {
|
||
bestMatchJobTitle,
|
||
maxSimilarity,
|
||
};
|
||
}
|
||
|
||
// 计算职位匹配度
|
||
calculateBestJobMatch(userJobTitles, jobTitle) {
|
||
let maxSimilarity = 0;
|
||
let bestMatchJobTitle = '';
|
||
|
||
userJobTitles.forEach((userTitle) => {
|
||
const userSegments = segmentText(userTitle);
|
||
const jobSegments = segmentText(jobTitle);
|
||
// 比较分词的交集,计算匹配度
|
||
// const intersection = userSegments.filter(segment => jobSegments.includes(segment));
|
||
// const similarity = intersection.length / userSegments.length; // 计算匹配度
|
||
// 计算匹配度
|
||
const similarity = calculateMatchScore(userSegments, jobSegments);
|
||
// 记录匹配度最高的职位
|
||
if (similarity > maxSimilarity) {
|
||
maxSimilarity = similarity;
|
||
bestMatchJobTitle = userTitle;
|
||
}
|
||
});
|
||
|
||
return {
|
||
bestMatchJobTitle,
|
||
maxSimilarity,
|
||
};
|
||
}
|
||
|
||
// 计算学历匹配度
|
||
calculateEducationMatch(userEducation, jobEducation) {
|
||
if (userEducation === jobEducation) {
|
||
return 1;
|
||
} else if (userEducation > jobEducation) {
|
||
return 1;
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
const similarityJobs = new CsimilarityJobs();
|
||
|
||
export default similarityJobs;
|