Files
work_dhd_back_end/app/service/MatchService.php
2026-01-06 15:38:14 +08:00

1152 lines
42 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;
use think\facade\Db;
/**
* 岗位简历匹配度计算服务
* 采用硬性条件一票否决 + 软性条件加分机制
*/
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 int $userId 用户ID
* @param int $page 页码从1开始
* @param int $pageSize 每页数量
* @param bool $filterZero 是否过滤0分岗位
* @return array
*/
public function batchMatchFromDb(int $userId, int $page = 1, int $pageSize = 20, bool $filterZero = false): array
{
// 1. 从数据库获取用户简历信息
$resume = $this->getUserResumeFromDb($userId);
if (empty($resume)) {
// 记录调试信息
error_log("获取用户简历失败 - 用户ID: {$userId}");
return [
'list' => [],
'pagination' => [
'page' => $page,
'page_size' => $pageSize,
'total' => 0,
'total_pages' => 0
],
'debug' => [
'error' => '用户不存在或简历信息为空',
'user_id' => $userId
]
];
}
// 2. 从数据库获取所有岗位
$filteredPositions = $this->filterPositionsFromDb($resume);
// 记录调试信息
error_log("获取到岗位数量: " . count($filteredPositions) . ", 用户ID: {$userId}");
if (empty($filteredPositions)) {
return [
'list' => [],
'pagination' => [
'page' => $page,
'page_size' => $pageSize,
'total' => 0,
'total_pages' => 0
],
'debug' => [
'error' => '未找到任何岗位',
'user_id' => $userId,
'positions_count' => 0
]
];
}
// 3. 计算匹配度
$results = [];
$zeroScoreCount = 0;
$firstZeroScoreReason = null; // 记录第一个0分岗位的拒绝原因
foreach ($filteredPositions as $position) {
try {
// 检查硬性条件,获取详细的拒绝原因
$hardCheck = $this->checkHardRequirements($position, $resume);
$score = 0;
$rejectionInfo = null;
if ($hardCheck['passed']) {
// 硬性条件通过,计算软性条件分数
$softCheck = $this->calculateSoftRequirements($position, $resume);
$score = $softCheck['score'];
} else {
// 硬性条件不通过,记录拒绝原因
$score = 0;
$rejectionInfo = [
'rejection_reasons' => $hardCheck['rejection_reasons'],
'details' => $hardCheck['details']
];
}
if ($score == 0) {
$zeroScoreCount++;
// 记录第一个0分岗位的拒绝原因
if ($firstZeroScoreReason === null && $rejectionInfo !== null) {
$firstZeroScoreReason = [
'position_id' => $position['id'] ?? 0,
'position_name' => $position['position_name'] ?? $position['name'] ?? '',
'rejection_reasons' => $rejectionInfo['rejection_reasons'],
'details' => $rejectionInfo['details']
];
}
}
if ($filterZero && $score == 0) {
continue; // 过滤0分岗位
}
$results[] = [
'position_id' => $position['id'] ?? 0,
'match_score' => $score,
'position' => $position
];
} catch (\Exception $e) {
// 如果计算出错,记录错误但继续处理其他岗位
error_log("计算匹配度失败 - 岗位ID: " . ($position['id'] ?? 'unknown') . ", 错误: " . $e->getMessage());
continue;
}
}
// 记录调试信息
error_log("匹配完成 - 总岗位数: " . count($filteredPositions) . ", 有效结果数: " . count($results) . ", 0分岗位数: {$zeroScoreCount}, filter_zero: " . ($filterZero ? 'true' : 'false'));
// 4. 按匹配度降序排序
usort($results, function($a, $b) {
return $b['match_score'] - $a['match_score'];
});
// 5. 分页
$total = count($results);
$totalPages = (int)ceil($total / $pageSize);
$offset = ($page - 1) * $pageSize;
$paginatedList = array_slice($results, $offset, $pageSize);
return [
'list' => $paginatedList,
'pagination' => [
'page' => $page,
'page_size' => $pageSize,
'total' => $total,
'total_pages' => $totalPages,
'has_more' => $page < $totalPages
],
'debug' => [
'total_positions' => count($filteredPositions),
'zero_score_count' => $zeroScoreCount,
'filter_zero' => $filterZero,
'result_count' => $total,
'resume_info' => [
'user_id' => $resume['user_id'] ?? 0,
'has_education' => !empty($resume['education']),
'education_count' => count($resume['education'] ?? []),
'birth_date' => $resume['birth_date'] ?? '',
'gender' => $resume['gender'] ?? '',
'education_details' => array_map(function($edu) {
return [
'education_level' => $edu['education_level'] ?? '',
'degree' => $edu['degree'] ?? '',
'majors_name' => $edu['majors_name'] ?? '',
'majors_code' => $edu['majors_code'] ?? '',
'majors_category' => $edu['majors_category'] ?? '',
'school_name' => $edu['school_name'] ?? '',
];
}, $resume['education'] ?? []),
'education_raw' => array_map(function($edu) {
// 返回原始数据的前几个字段用于调试(避免数据过大)
$raw = $edu['_raw'] ?? [];
// 只返回前15个字段
return array_slice($raw, 0, 15, true);
}, $resume['education'] ?? []),
],
'first_zero_reason' => $firstZeroScoreReason
]
];
}
/**
* 从数据库获取用户简历信息
* @param int $userId 用户ID
* @return array
*/
private function getUserResumeFromDb(int $userId): array
{
// 1. 获取用户基本信息t_user表
// 尝试不同的主键字段名
$user = null;
try {
$user = Db::name('t_user')->where('uid', $userId)->find();
} catch (\Exception $e) {
// ignore and fallback below
}
// 如果按 uid 没有查到,再按 id 尝试(即使没有异常也尝试,以防字段是 id
if (empty($user)) {
try {
$user = Db::name('t_user')->where('id', $userId)->find();
} catch (\Exception $e2) {
error_log("查询用户失败 - 用户ID: {$userId}, 错误: " . $e2->getMessage());
}
}
if (empty($user)) {
error_log("用户不存在 - 用户ID: {$userId}");
return [];
}
// 转换为数组(如果返回的是对象)
if (is_object($user)) {
$user = $user->toArray();
}
// 2. 获取用户简历信息t_user_curriculum_vitae表
// 尝试使用uid或user_id字段
$curriculumVitae = null;
try {
$curriculumVitae = Db::name('t_user_curriculum_vitae')
->where('uid', $userId)
->find();
} catch (\Exception $e) {
// ignore and fallback below
}
// 如果按 uid 没查到,再按 user_id 查一次
if (empty($curriculumVitae)) {
try {
$curriculumVitae = Db::name('t_user_curriculum_vitae')
->where('user_id', $userId)
->find();
} catch (\Exception $e2) {
// 如果都不存在curriculumVitae保持为null
}
}
// 转换为数组
if (is_object($curriculumVitae)) {
$curriculumVitae = $curriculumVitae->toArray();
} elseif ($curriculumVitae === null) {
$curriculumVitae = [];
}
// 3. 获取用户教育经历t_user_education表
// 尝试使用uid或user_id字段
$educations = [];
try {
$educations = Db::name('t_user_education')
->where('uid', $userId)
->order('education_level desc') // 按学历等级降序,最高学历在前
->select()
->toArray();
} catch (\Exception $e) {
// ignore and fallback below
}
// 如果按 uid 没查到,再按 user_id 查一次(即使没有异常也尝试,以防字段是 user_id
if (empty($educations)) {
try {
$educations = Db::name('t_user_education')
->where('user_id', $userId)
->order('education_level desc')
->select()
->toArray();
} catch (\Exception $e2) {
// 如果都不存在educations保持为空数组
}
}
// 3.1 如果教育经历里有专业代码预先加载专业表da_majors以便解析专业名称
$majorCodeMap = [];
$majorCodes = [];
foreach ($educations as $education) {
$codeFields = ['major_code', 'majors_code', 'code', 'major_id', 'majors_id', 'major_no'];
foreach ($codeFields as $field) {
if (!empty($education[$field])) {
$majorCodes[] = $education[$field];
}
}
}
$majorCodes = array_values(array_unique($majorCodes));
if (!empty($majorCodes)) {
try {
$majors = Db::name('da_majors')
->whereIn('code', $majorCodes)
->select()
->toArray();
foreach ($majors as $major) {
$code = $major['code'] ?? $major['major_code'] ?? null;
if (!$code) {
continue;
}
$majorCodeMap[$code] = [
'name' => $major['major_name'] ?? $major['name'] ?? $major['title'] ?? '',
'category' => $major['category_name'] ?? $major['category'] ?? $major['major_category'] ?? '',
];
}
} catch (\Exception $e) {
// da_majors 表不存在或字段不匹配时忽略
}
}
// 构建简历数据结构
$resume = [
'user_id' => $userId,
// 从用户表获取基本信息
'birth_date' => $user['birth_date'] ?? $curriculumVitae['birth_date'] ?? '',
'gender' => $user['gender'] ?? $curriculumVitae['gender'] ?? '',
'ethnicity' => $user['ethnicity'] ?? $curriculumVitae['ethnicity'] ?? '',
'political_status' => $user['political_status'] ?? $curriculumVitae['political_status'] ?? '',
// 从简历表获取工作经历等信息
'work_experience' => $curriculumVitae['work_experience'] ?? $user['work_experience'] ?? '',
// 教育经历从t_user_education表获取
'education' => []
];
// 处理教育经历数据,确保字段名正确
foreach ($educations as $education) {
// 尝试多种可能的专业字段名
$majorName = '';
$possibleMajorFields = [
'majors_name', 'major_name', 'major', 'major_field',
'specialty', 'profession', 'discipline', 'subject',
'专业名称', '专业', '学科专业', '专业类别'
];
foreach ($possibleMajorFields as $field) {
if (!empty($education[$field])) {
$majorName = $education[$field];
break;
}
}
// 如果有专业代码但没有名称,尝试从 da_majors 映射获取
if (empty($majorName)) {
$codeFields = ['major_code', 'majors_code', 'code', 'major_id', 'majors_id', 'major_no'];
foreach ($codeFields as $field) {
if (!empty($education[$field]) && isset($majorCodeMap[$education[$field]])) {
$majorName = $majorCodeMap[$education[$field]]['name'] ?? '';
$education['major_category'] = $majorCodeMap[$education[$field]]['category'] ?? '';
break;
}
}
}
// 保存专业代码,便于后续调试
$majorCode = '';
foreach (['major_code', 'majors_code', 'code', 'major_id', 'majors_id', 'major_no'] as $field) {
if (!empty($education[$field])) {
$majorCode = $education[$field];
break;
}
}
$resume['education'][] = [
'education_level' => $education['education_level'] ?? $education['education'] ?? $education['学历'] ?? '',
'degree' => $education['degree'] ?? $education['学位'] ?? '',
'majors_name' => $majorName,
'majors_code' => $majorCode,
'majors_category' => $education['major_category'] ?? $education['专业类别'] ?? '',
'school_name' => $education['school_name'] ?? $education['school'] ?? $education['学校名称'] ?? '',
'graduation_date' => $education['graduation_date'] ?? $education['毕业时间'] ?? '',
// 保存原始数据用于调试
'_raw' => $education
];
}
return $resume;
}
/**
* 从数据库获取所有岗位(用于匹配)
* @param array $resume 简历信息(用于可选的快速过滤)
* @return array
*/
private function filterPositionsFromDb(array $resume): array
{
// 获取所有未删除的岗位no_notice_position表
// 注意:根据用户要求,需要匹配所有岗位,所以这里不做严格过滤
// 只排除已删除的岗位,其他过滤在详细匹配时进行
$query = Db::name('no_notice_position');
// 排除已删除的岗位如果表中有deleted_at字段
// 使用where条件如果字段不存在SQL会报错但我们可以通过查询表结构来判断
// 为了安全,先尝试查询,如果失败则查询所有记录
try {
// 尝试添加deleted_at条件
$query->whereNull('deleted_at');
$positions = $query->select()->toArray();
error_log("从no_notice_position表获取到 " . count($positions) . " 个岗位已排除deleted_at");
} catch (\Exception $e) {
// 如果字段不存在或其他错误,查询所有记录
try {
$query = Db::name('no_notice_position');
$positions = $query->select()->toArray();
error_log("从no_notice_position表获取到 " . count($positions) . " 个岗位无deleted_at字段");
} catch (\Exception $e2) {
error_log("查询岗位失败: " . $e2->getMessage());
$positions = [];
}
}
// 解析JSON字段构建position_require结构
foreach ($positions as &$position) {
// 处理position_other_require字段可能是JSON格式
$otherRequire = [];
if (!empty($position['position_other_require'])) {
if (is_string($position['position_other_require'])) {
$otherRequire = json_decode($position['position_other_require'], true) ?: [];
} else {
$otherRequire = $position['position_other_require'];
}
}
// 构建统一的position_require结构
$position['position_require'] = [
'学历要求' => $position['education_require'] ?? '',
'学位要求' => $position['degree_require'] ?? '',
'年龄要求' => $position['age_require'] ?? '',
'性别要求' => $position['sex_require'] ?? '',
'专业(学科)类别' => $otherRequire['专业(学科)类别'] ?? '',
'专业-本科' => $otherRequire['专业-本科'] ?? '',
'专业-硕士' => $otherRequire['专业-硕士'] ?? '',
'其他资格条件' => $otherRequire['其他资格条件'] ?? '',
'专业资格条件' => $otherRequire['专业资格条件'] ?? '',
];
}
return $positions;
}
/**
* 检查硬性条件(一票否决机制)
* @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. 年龄要求(硬性)
$ageRequire = $positionRequire['年龄要求'] ?? '';
if (!empty($ageRequire) && trim($ageRequire) !== '' && trim($ageRequire) !== '无' && trim($ageRequire) !== '不限制') {
$ageCheck = $this->checkAge($ageRequire, $resume);
$result['details']['年龄要求'] = $ageCheck;
if (!$ageCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $ageCheck['reason'];
}
}
// 4. 专业要求(硬性)
// 支持多种专业字段格式:专业(学科)类别、专业-本科、专业-硕士
$majorRequire = $this->getMajorRequirement($positionRequire, $resume);
if (!empty($majorRequire)) {
$majorCheck = $this->checkMajor($majorRequire, $resume);
$result['details']['专业要求'] = $majorCheck;
if (!$majorCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $majorCheck['reason'];
}
}
// 5. 性别要求(硬性,如果明确要求)
// 优先检查 sex_require 字段
$sexRequire = $positionRequire['性别要求'] ?? '';
if (!empty($sexRequire) && $sexRequire !== '不限制' && $sexRequire !== '无') {
$genderCheck = $this->checkGender($sexRequire, $resume);
$result['details']['性别要求'] = $genderCheck;
if (!$genderCheck['passed']) {
$result['passed'] = false;
$result['rejection_reasons'][] = $genderCheck['reason'];
}
}
// 如果 sex_require 字段没有明确要求,再检查其他资格条件
if (empty($sexRequire) || $sexRequire === '不限制' || $sexRequire === '无') {
$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,
'本科' => 3,
'大学本科' => 3,
'本科学历' => 3,
'硕士研究生' => 4,
'硕士' => 4,
'研究生' => 4,
'博士研究生' => 5,
'博士' => 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
{
// 如果年龄要求为空或"无",直接通过
$requirement = trim($requirement);
if (empty($requirement) || $requirement === '无' || $requirement === '不限制') {
return [
'passed' => true,
'required' => $requirement ?: '无要求',
'actual' => '无要求'
];
}
$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'] ??
$education['major_name'] ??
$education['major'] ??
$education['major_field'] ??
$education['specialty'] ??
$education['profession'] ??
$education['majors_category'] ?? '';
if (!empty($majorName) && $this->isMajorCategoryMatch($majorName, $requirement)) {
$matchedMajors[] = $majorName;
}
}
if (!empty($matchedMajors)) {
return [
'passed' => true,
'required' => $requirement,
'actual' => implode('、', $matchedMajors)
];
}
// 收集所有专业名称用于错误信息
$actualMajors = [];
foreach ($educations as $edu) {
$majorName = $edu['majors_name'] ??
$edu['major_name'] ??
$edu['major'] ??
$edu['major_field'] ??
$edu['specialty'] ??
$edu['profession'] ??
$edu['majors_category'] ?? '';
if (!empty($majorName)) {
$actualMajors[] = $majorName;
}
}
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,
'本科' => 3,
'大学本科' => 3,
'本科学历' => 3,
'硕士研究生' => 4,
'硕士' => 4,
'研究生' => 4,
'博士研究生' => 5,
'博士' => 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,
'本科' => 3,
'大学本科' => 3,
'本科学历' => 3,
'硕士研究生' => 4,
'硕士' => 4,
'研究生' => 4,
'博士研究生' => 5,
'博士' => 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 getMajorRequirement(array $positionRequire, array $resume): string
{
// 优先使用 专业(学科)类别
if (!empty($positionRequire['专业(学科)类别'])) {
return $positionRequire['专业(学科)类别'];
}
// 根据最高学历选择对应的专业要求
$educations = $resume['education'] ?? [];
if (!empty($educations)) {
$highestEducation = $this->getHighestEducation($educations);
$educationLevel = $highestEducation['education_level'] ?? '';
// 判断学历等级
$isUndergraduate = in_array($educationLevel, ['普通本科', '本科', '大学本科', '本科学历']);
$isGraduate = in_array($educationLevel, ['硕士研究生', '硕士', '研究生', '博士研究生', '博士']);
if ($isUndergraduate && !empty($positionRequire['专业-本科'])) {
return $positionRequire['专业-本科'];
}
if ($isGraduate && !empty($positionRequire['专业-硕士'])) {
return $positionRequire['专业-硕士'];
}
// 如果没有对应学历的专业要求,尝试使用另一个
if ($isGraduate && !empty($positionRequire['专业-本科'])) {
return $positionRequire['专业-本科'];
}
}
return '';
}
/**
* 判断专业类别是否匹配(硬性检查)
*/
private function isMajorCategoryMatch(string $majorName, string $majorRequire): bool
{
if (empty($majorName) || empty($majorRequire)) {
return false;
}
// 支持多种分隔符:,、、
$requireCategories = preg_split('/[,、,]/u', $majorRequire);
foreach ($requireCategories as $category) {
$category = trim($category);
// 直接匹配专业名称
if ($majorName === $category || strpos($majorName, $category) !== false || strpos($category, $majorName) !== false) {
return true;
}
// 计算机科学与技术类
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;
}
}
}
// 教育学类
if (strpos($category, '教育') !== false) {
if (strpos($majorName, '教育') !== false) {
return true;
}
}
// 心理学类
if (strpos($category, '心理') !== false) {
if (strpos($majorName, '心理') !== false) {
return true;
}
}
}
return false;
}
/**
* 计算专业相关性分数(软性评分)
*/
private function calculateMajorRelevanceScore(string $majorName, string $majorRequire): int
{
if ($this->isMajorCategoryMatch($majorName, $majorRequire)) {
return 40; // 完全匹配
}
// 可以根据专业相似度进一步细化评分
// 这里简单返回0分
return 0;
}
}