Files
work_dhd_back_end/app/service/MatchService.php

621 lines
20 KiB
PHP
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.

<?php
declare (strict_types = 1);
namespace app\service;
/**
* 岗位简历匹配度计算服务
* 采用硬性条件一票否决 + 软性条件加分机制
*/
class MatchService
{
/**
* 计算岗位和简历的匹配度
* @param array $position 岗位信息
* @param array $resume 简历信息
* @return int 匹配度分数0-100分硬性条件不满足返回0分
*/
public function calculateMatchScore(array $position, array $resume): int
{
// 第一步:检查硬性条件(一票否决)
$hardCheck = $this->checkHardRequirements($position, $resume);
if (!$hardCheck['passed']) {
return 0; // 硬性条件不满足直接返回0分
}
// 第二步计算软性条件匹配度100分制
$softCheck = $this->calculateSoftRequirements($position, $resume);
return $softCheck['score'];
}
/**
* 检查硬性条件(一票否决机制)
* @param array $position 岗位信息
* @param array $resume 简历信息
* @return array
*/
private function checkHardRequirements(array $position, array $resume): array
{
$positionRequire = $position['position_require'] ?? [];
$result = [
'passed' => true,
'rejection_reasons' => [],
'details' => []
];
// 1. 学历要求(硬性)
if (!empty($positionRequire['学历要求'])) {
$educationCheck = $this->checkEducation($positionRequire['学历要求'], $resume);
$result['details']['学历要求'] = $educationCheck;
if (!$educationCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $educationCheck['reason'];
}
}
// 2. 学位要求(硬性)
if (!empty($positionRequire['学位要求'])) {
$degreeCheck = $this->checkDegree($positionRequire['学位要求'], $resume);
$result['details']['学位要求'] = $degreeCheck;
if (!$degreeCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $degreeCheck['reason'];
}
}
// 3. 年龄要求(硬性)
if (!empty($positionRequire['年龄要求'])) {
$ageCheck = $this->checkAge($positionRequire['年龄要求'], $resume);
$result['details']['年龄要求'] = $ageCheck;
if (!$ageCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $ageCheck['reason'];
}
}
// 4. 专业要求(硬性)
if (!empty($positionRequire['专业(学科)类别'])) {
$majorCheck = $this->checkMajor($positionRequire['专业(学科)类别'], $resume);
$result['details']['专业要求'] = $majorCheck;
if (!$majorCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $majorCheck['reason'];
}
}
// 5. 性别要求(硬性,如果明确要求)
$otherConditions = $positionRequire['其他资格条件'] ?? '';
if (preg_match('/适合(男|女)性/u', $otherConditions, $matches)) {
$genderCheck = $this->checkGender($matches[1], $resume);
$result['details']['性别要求'] = $genderCheck;
if (!$genderCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $genderCheck['reason'];
}
}
return $result;
}
/**
* 计算软性条件匹配度100分制
* @param array $position 岗位信息
* @param array $resume 简历信息
* @return array
*/
private function calculateSoftRequirements(array $position, array $resume): array
{
$positionRequire = $position['position_require'] ?? [];
$score = 0;
$details = [];
$maxScore = 100;
// 1. 专业匹配度40分- 即使专业类别符合,也可以根据专业相关性打分
$majorScore = $this->scoreMajorMatch($positionRequire['专业(学科)类别'] ?? '', $resume);
$score += $majorScore['score'];
$details['专业匹配度'] = $majorScore;
// 2. 学历层次匹配度20分- 超过要求的学历可以加分
$educationScore = $this->scoreEducationLevel($positionRequire['学历要求'] ?? '', $resume);
$score += $educationScore['score'];
$details['学历层次匹配度'] = $educationScore;
// 3. 专业资格条件20分- 竞赛获奖等
$qualificationScore = $this->scoreQualification($positionRequire['专业资格条件'] ?? '', $resume);
$score += $qualificationScore['score'];
$details['专业资格条件'] = $qualificationScore;
// 4. 基层工作经历10分
$workExpScore = $this->scoreWorkExperience($resume);
$score += $workExpScore['score'];
$details['基层工作经历'] = $workExpScore;
// 5. 其他条件匹配10分- 如政治面貌、特殊身份等
$otherScore = $this->scoreOtherConditions($positionRequire['其他资格条件'] ?? '', $resume);
$score += $otherScore['score'];
$details['其他条件'] = $otherScore;
return [
'score' => min(100, $score),
'max_score' => $maxScore,
'details' => $details
];
}
// ==================== 硬性条件检查方法 ====================
/**
* 检查学历要求(硬性)
*/
private function checkEducation(string $requirement, array $resume): array
{
$educations = $resume['education'] ?? [];
if (empty($educations)) {
return [
'passed' => false,
'reason' => '未提供学历信息',
'required' => $requirement,
'actual' => null
];
}
$highestEducation = $this->getHighestEducation($educations);
if (empty($highestEducation)) {
return [
'passed' => false,
'reason' => '无法识别最高学历',
'required' => $requirement,
'actual' => null
];
}
$educationLevel = $highestEducation['education_level'] ?? '';
$educationLevels = [
'普通本科' => 3,
'硕士研究生' => 4,
'博士研究生' => 5,
];
$requireLevel = $this->parseEducationRequire($requirement);
$actualLevel = $educationLevels[$educationLevel] ?? 0;
if ($actualLevel >= $requireLevel) {
return [
'passed' => true,
'required' => $requirement,
'actual' => $educationLevel
];
}
return [
'passed' => false,
'reason' => "学历不符合要求:需要{$requirement},实际为{$educationLevel}",
'required' => $requirement,
'actual' => $educationLevel
];
}
/**
* 检查学位要求(硬性)
*/
private function checkDegree(string $requirement, array $resume): array
{
$educations = $resume['education'] ?? [];
if (empty($educations)) {
return [
'passed' => false,
'reason' => '未提供学位信息',
'required' => $requirement,
'actual' => null
];
}
$highestEducation = $this->getHighestEducation($educations);
$degree = $highestEducation['degree'] ?? '';
$degreeLevels = [
'学士' => 1,
'硕士' => 2,
'博士' => 3,
];
$requireLevel = $this->parseDegreeRequire($requirement);
$actualLevel = $degreeLevels[$degree] ?? 0;
if ($actualLevel >= $requireLevel) {
return [
'passed' => true,
'required' => $requirement,
'actual' => $degree
];
}
return [
'passed' => false,
'reason' => "学位不符合要求:需要{$requirement},实际为{$degree}",
'required' => $requirement,
'actual' => $degree
];
}
/**
* 检查年龄要求(硬性)
*/
private function checkAge(string $requirement, array $resume): array
{
$birthDate = $resume['birth_date'] ?? '';
if (empty($birthDate)) {
return [
'passed' => false,
'reason' => '未提供出生日期',
'required' => $requirement,
'actual' => null
];
}
$age = $this->calculateAge($birthDate);
if (preg_match('/(\d+)周岁以上.*?(\d+)周岁以下/u', $requirement, $matches)) {
$minAge = (int)$matches[1];
$maxAge = (int)$matches[2];
if ($age >= $minAge && $age <= $maxAge) {
return [
'passed' => true,
'required' => $requirement,
'actual' => "{$age}"
];
}
return [
'passed' => false,
'reason' => "年龄不符合要求:需要{$minAge}-{$maxAge}岁,实际为{$age}",
'required' => $requirement,
'actual' => "{$age}"
];
}
return [
'passed' => false,
'reason' => '无法解析年龄要求格式',
'required' => $requirement,
'actual' => "{$age}"
];
}
/**
* 检查专业要求(硬性)
*/
private function checkMajor(string $requirement, array $resume): array
{
$educations = $resume['education'] ?? [];
if (empty($educations)) {
return [
'passed' => false,
'reason' => '未提供专业信息',
'required' => $requirement,
'actual' => null
];
}
$matchedMajors = [];
foreach ($educations as $education) {
$majorName = $education['majors_name'] ?? '';
if ($this->isMajorCategoryMatch($majorName, $requirement)) {
$matchedMajors[] = $majorName;
}
}
if (!empty($matchedMajors)) {
return [
'passed' => true,
'required' => $requirement,
'actual' => implode('、', $matchedMajors)
];
}
$actualMajors = array_map(function($edu) {
return $edu['majors_name'] ?? '';
}, $educations);
$actualMajors = array_filter($actualMajors);
return [
'passed' => false,
'reason' => "专业不符合要求:需要{$requirement},实际为" . implode('、', $actualMajors),
'required' => $requirement,
'actual' => implode('、', $actualMajors)
];
}
/**
* 检查性别要求(硬性)
*/
private function checkGender(string $requireGender, array $resume): array
{
$gender = $resume['gender'] ?? '';
if ($gender === $requireGender) {
return [
'passed' => true,
'required' => $requireGender,
'actual' => $gender
];
}
return [
'passed' => false,
'reason' => "性别不符合要求:需要{$requireGender},实际为{$gender}",
'required' => $requireGender,
'actual' => $gender
];
}
// ==================== 软性条件评分方法 ====================
/**
* 专业匹配度评分40分
*/
private function scoreMajorMatch(string $requirement, array $resume): array
{
if (empty($requirement)) {
return ['score' => 40, 'reason' => '无专业要求'];
}
$educations = $resume['education'] ?? [];
if (empty($educations)) {
return ['score' => 0, 'reason' => '无专业信息'];
}
$maxScore = 0;
foreach ($educations as $education) {
$majorName = $education['majors_name'] ?? '';
$score = $this->calculateMajorRelevanceScore($majorName, $requirement);
$maxScore = max($maxScore, $score);
}
return [
'score' => min(40, $maxScore),
'max_score' => 40,
'reason' => $maxScore >= 40 ? '专业高度匹配' : '专业部分匹配'
];
}
/**
* 学历层次匹配度评分20分
*/
private function scoreEducationLevel(string $requirement, array $resume): array
{
$educations = $resume['education'] ?? [];
if (empty($educations)) {
return ['score' => 0, 'reason' => '无学历信息'];
}
$highestEducation = $this->getHighestEducation($educations);
$educationLevel = $highestEducation['education_level'] ?? '';
$educationLevels = [
'普通本科' => 3,
'硕士研究生' => 4,
'博士研究生' => 5,
];
$requireLevel = $this->parseEducationRequire($requirement);
$actualLevel = $educationLevels[$educationLevel] ?? 0;
// 刚好满足要求15分超过一级20分超过两级及以上20分
if ($actualLevel == $requireLevel) {
return ['score' => 15, 'max_score' => 20, 'reason' => '刚好满足要求'];
} elseif ($actualLevel > $requireLevel) {
$exceed = $actualLevel - $requireLevel;
return [
'score' => 15 + min(5, $exceed * 5),
'max_score' => 20,
'reason' => "超过要求{$exceed}"
];
}
return ['score' => 0, 'reason' => '未达到要求'];
}
/**
* 专业资格条件评分20分
*/
private function scoreQualification(string $requirement, array $resume): array
{
if (empty($requirement)) {
return [
'score' => 20,
'max_score' => 20,
'reason' => '无专业资格要求'
];
}
// 这里可以根据简历中的证书、获奖等信息来评分
// 由于当前简历数据结构中没有这些字段,暂时返回基础分数
// 实际应用中需要扩展简历数据结构
return [
'score' => 0,
'max_score' => 20,
'reason' => '未提供专业资格证明材料(需扩展简历数据结构)'
];
}
/**
* 基层工作经历评分10分
*/
private function scoreWorkExperience(array $resume): array
{
$workExperience = $resume['work_experience'] ?? '';
if (empty($workExperience) || strpos($workExperience, '无') !== false) {
return [
'score' => 0,
'max_score' => 10,
'reason' => '无基层工作经历'
];
}
// 可以根据工作年限进一步细化评分
return [
'score' => 10,
'max_score' => 10,
'reason' => '有基层工作经历'
];
}
/**
* 其他条件评分10分
*/
private function scoreOtherConditions(string $requirement, array $resume): array
{
if (empty($requirement)) {
return ['score' => 10, 'reason' => '无其他条件要求'];
}
$score = 0;
$maxScore = 10;
// 政治面貌等可以根据岗位要求评分
// 当前暂不实现,返回基础分数
return [
'score' => 10,
'max_score' => $maxScore,
'reason' => '其他条件匹配(需根据具体岗位要求细化)'
];
}
// ==================== 辅助方法 ====================
/**
* 计算年龄
*/
private function calculateAge(string $birthDate): int
{
$birthTimestamp = strtotime($birthDate);
$currentTimestamp = time();
$age = date('Y', $currentTimestamp) - date('Y', $birthTimestamp);
if (date('md', $currentTimestamp) < date('md', $birthTimestamp)) {
$age--;
}
return $age;
}
/**
* 获取最高学历
*/
private function getHighestEducation(array $educations): ?array
{
if (empty($educations)) {
return null;
}
$educationLevels = [
'普通本科' => 3,
'硕士研究生' => 4,
'博士研究生' => 5,
];
$highest = null;
$highestLevel = 0;
foreach ($educations as $education) {
$level = $educationLevels[$education['education_level'] ?? ''] ?? 0;
if ($level > $highestLevel) {
$highestLevel = $level;
$highest = $education;
}
}
return $highest;
}
/**
* 解析学历要求
*/
private function parseEducationRequire(string $require): int
{
if (strpos($require, '本科') !== false) {
return 3;
}
if (strpos($require, '硕士') !== false) {
return 4;
}
if (strpos($require, '博士') !== false) {
return 5;
}
return 0;
}
/**
* 解析学位要求
*/
private function parseDegreeRequire(string $require): int
{
if (strpos($require, '学士') !== false) {
return 1;
}
if (strpos($require, '硕士') !== false) {
return 2;
}
if (strpos($require, '博士') !== false) {
return 3;
}
return 0;
}
/**
* 判断专业类别是否匹配(硬性检查)
*/
private function isMajorCategoryMatch(string $majorName, string $majorRequire): bool
{
if (empty($majorName) || empty($majorRequire)) {
return false;
}
$requireCategories = explode('', $majorRequire);
foreach ($requireCategories as $category) {
$category = trim($category);
// 计算机科学与技术类
if (strpos($category, '计算机') !== false) {
$computerKeywords = ['计算机', '软件', '网络', '信息', '数据', '人工智能', '大数据', '网络安全', '信息安全'];
foreach ($computerKeywords as $keyword) {
if (strpos($majorName, $keyword) !== false) {
return true;
}
}
}
// 电气、电子及自动化类
if (strpos($category, '电气') !== false || strpos($category, '电子') !== false || strpos($category, '自动化') !== false) {
$electronicsKeywords = ['电气', '电子', '自动化', '通信', '电信', '电子信息'];
foreach ($electronicsKeywords as $keyword) {
if (strpos($majorName, $keyword) !== false) {
return true;
}
}
}
}
return false;
}
/**
* 计算专业相关性分数(软性评分)
*/
private function calculateMajorRelevanceScore(string $majorName, string $majorRequire): int
{
if ($this->isMajorCategoryMatch($majorName, $majorRequire)) {
return 40; // 完全匹配
}
// 可以根据专业相似度进一步细化评分
// 这里简单返回0分
return 0;
}
}