1
This commit is contained in:
@@ -655,71 +655,6 @@ class JobFilterService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤职位列表(基于文本匹配)
|
||||
* @param {Array} jobs - 职位列表
|
||||
* @param {object} filterRules - 过滤规则
|
||||
* @param {object} resumeInfo - 简历信息(可选)
|
||||
* @param {number} jobTypeId - 职位类型ID(可选)
|
||||
* @param {object} options - 选项
|
||||
* @param {boolean} options.autoSave - 是否自动保存评分结果到数据库(默认false)
|
||||
* @returns {Promise<Array>} 过滤后的职位列表(带匹配分数)
|
||||
*/
|
||||
async filterJobs(jobs, filterRules = {}, resumeInfo = {}, jobTypeId = null, options = {}) {
|
||||
const {
|
||||
minScore = 60, // 最低匹配分数
|
||||
excludeOutsourcing = true, // 是否排除外包
|
||||
excludeKeywords = [] // 额外排除关键词
|
||||
} = filterRules;
|
||||
|
||||
const { autoSave = false } = options;
|
||||
|
||||
// 获取职位类型配置
|
||||
const { excludeKeywords: typeExcludeKeywords } = await this.getJobTypeConfig(jobTypeId);
|
||||
const allExcludeKeywords = [...typeExcludeKeywords, ...excludeKeywords];
|
||||
|
||||
const results = [];
|
||||
for (const job of jobs) {
|
||||
const jobData = job.toJSON ? job.toJSON() : job;
|
||||
|
||||
// 分析匹配度(如果 autoSave 为 true 且 job 有 id,则自动保存)
|
||||
const analysisOptions = autoSave && jobData.id ? {
|
||||
jobPostingId: jobData.id,
|
||||
autoSave: true
|
||||
} : {};
|
||||
|
||||
const analysis = await this.analyzeJobMatch(jobData, resumeInfo, jobTypeId, analysisOptions);
|
||||
|
||||
results.push({
|
||||
...jobData,
|
||||
matchScore: analysis.overallScore,
|
||||
matchAnalysis: analysis
|
||||
});
|
||||
}
|
||||
|
||||
return results
|
||||
.filter(job => {
|
||||
// 1. 最低分数过滤
|
||||
if (job.matchScore < minScore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 外包过滤
|
||||
if (excludeOutsourcing && job.matchAnalysis.isOutsourcing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 排除关键词过滤
|
||||
const jobText = this.buildJobText(job);
|
||||
if (allExcludeKeywords.some(kw => jobText.includes(kw.toLowerCase()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => b.matchScore - a.matchScore); // 按匹配分数降序排序
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据自定义权重配置计算职位评分
|
||||
* @param {Object} jobData - 职位数据
|
||||
|
||||
@@ -4,7 +4,6 @@ const jobFilterEngine = require('../services/jobFilterEngine');
|
||||
const command = require('../core/command');
|
||||
const config = require('../infrastructure/config');
|
||||
const db = require('../../dbProxy');
|
||||
const { jobFilterService } = require('../../job/services');
|
||||
|
||||
/**
|
||||
* 自动投递处理器
|
||||
@@ -105,13 +104,13 @@ class DeliverHandler extends BaseHandler {
|
||||
// 9. 过滤已投递的公司
|
||||
const recentCompanies = await this.getRecentDeliveredCompanies(sn_code, 30);
|
||||
|
||||
// 10. 过滤、评分、排序职位
|
||||
const filteredJobs = await this.filterAndScoreJobs(
|
||||
// 10. 过滤 + 评分 + 按 60 分阈值筛(入口在 jobFilterEngine,便于阅读)
|
||||
const filteredJobs = await jobFilterEngine.filterAndScoreJobsForDeliver(
|
||||
pendingJobs,
|
||||
filterConfig,
|
||||
resume,
|
||||
accountConfig,
|
||||
jobTypeConfig,
|
||||
filterConfig,
|
||||
recentCompanies
|
||||
);
|
||||
|
||||
@@ -338,72 +337,6 @@ class DeliverHandler extends BaseHandler {
|
||||
return new Set(recentApplies.map(apply => apply.companyName).filter(Boolean));
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤和评分职位
|
||||
*/
|
||||
async filterAndScoreJobs(jobs, resume, accountConfig, jobTypeConfig, filterConfig, recentCompanies) {
|
||||
const scored = [];
|
||||
const jobDesc = (j) => `${j.companyName || '?'} / ${j.jobTitle || '?'}`;
|
||||
|
||||
console.log(`[自动投递-过滤] 开始过滤与评分,待处理职位数: ${jobs.length}`);
|
||||
|
||||
for (const job of jobs) {
|
||||
// 1. 过滤近期已投递的公司
|
||||
if (job.companyName && recentCompanies.has(job.companyName)) {
|
||||
console.log(`[自动投递-过滤] 步骤1-已投递公司 剔除: ${jobDesc(job)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 使用 jobFilterEngine 过滤和评分
|
||||
const filtered = await jobFilterEngine.filterJobs([job], filterConfig, resume);
|
||||
if (filtered.length === 0) {
|
||||
console.log(`[自动投递-过滤] 步骤2-jobFilterEngine 剔除: ${jobDesc(job)} (不满足过滤条件)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. 使用原有的评分系统(job_filter_service)计算详细分数
|
||||
const scoreResult = jobFilterService.calculateJobScoreWithWeights(
|
||||
job,
|
||||
resume,
|
||||
accountConfig,
|
||||
jobTypeConfig,
|
||||
accountConfig.is_salary_priority || []
|
||||
);
|
||||
|
||||
// 4. 计算关键词奖励
|
||||
const KeywordMatcher = require('../utils/keywordMatcher');
|
||||
const keywordBonus = KeywordMatcher.calculateBonus(
|
||||
`${job.jobTitle} ${job.companyName} ${job.jobDescription || ''}`,
|
||||
filterConfig.filter_keywords,
|
||||
{ baseScore: 5, maxBonus: 20 }
|
||||
);
|
||||
|
||||
const finalScore = scoreResult.totalScore + keywordBonus.score;
|
||||
|
||||
// 5. 只保留评分 >= 60 的职位
|
||||
if (finalScore < 60) {
|
||||
console.log(`[自动投递-过滤] 步骤5-评分不足(>=60) 剔除: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)} (基础=${scoreResult.totalScore?.toFixed(1)}, 关键词奖励=${keywordBonus.score})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
scored.push({
|
||||
...job,
|
||||
matchScore: finalScore,
|
||||
scoreDetails: {
|
||||
...scoreResult.scores,
|
||||
keywordBonus: keywordBonus.score
|
||||
}
|
||||
});
|
||||
console.log(`[自动投递-过滤] 通过: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)}`);
|
||||
}
|
||||
|
||||
// 按评分降序排序
|
||||
scored.sort((a, b) => b.matchScore - a.matchScore);
|
||||
|
||||
console.log(`[自动投递-过滤] 结束: 原始=${jobs.length}, 通过=${scored.length}, 剔除=${jobs.length - scored.length}`);
|
||||
return scored;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建投递指令
|
||||
*/
|
||||
|
||||
@@ -63,7 +63,11 @@ class ConfigManager {
|
||||
page_count: 3, // 搜索页数
|
||||
keywords: [], // 搜索关键词
|
||||
exclude_keywords: [], // 排除关键词
|
||||
time_range: null // 时间范围
|
||||
time_range: null, // 时间范围
|
||||
city: '', // 城市
|
||||
salary_range: '', // 薪资范围
|
||||
experience: '', // 经验
|
||||
education: '' // 学历
|
||||
};
|
||||
|
||||
return this.parseConfig(searchConfig, defaultConfig);
|
||||
|
||||
@@ -3,18 +3,19 @@ const KeywordMatcher = require('../utils/keywordMatcher');
|
||||
const db = require('../../dbProxy');
|
||||
|
||||
/**
|
||||
* 职位过滤引擎
|
||||
* 综合处理职位的过滤、评分和排序
|
||||
* 职位过滤引擎(schedule 自动投递用)
|
||||
* 本文件集中:过滤(薪资/关键词/活跃度/去重)+ 评分(权重分+关键词奖励)+ 按分数阈值筛。
|
||||
* 自动投递只需调 filterAndScoreJobsForDeliver 一个方法。
|
||||
*/
|
||||
class JobFilterEngine {
|
||||
/**
|
||||
* 过滤职位列表
|
||||
* 过滤职位列表(薪资 → 关键词 → 活跃度 → 去重)
|
||||
* @param {Array} jobs - 职位列表
|
||||
* @param {object} config - 过滤配置
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @param {object} resumeInfo - 简历信息(未使用,兼容签名)
|
||||
* @returns {Promise<Array>} 过滤后的职位列表
|
||||
*/
|
||||
async filterJobs(jobs, config, resumeInfo = {}) {
|
||||
async filterJobs(jobs, config) {
|
||||
if (!jobs || jobs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -61,6 +62,59 @@ class JobFilterEngine {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动投递用:过滤 + 评分 + 按 60 分阈值筛,一次调用完成(便于阅读与维护)
|
||||
*/
|
||||
async filterAndScoreJobsForDeliver(jobs, filterConfig, resume, accountConfig, jobTypeConfig, recentCompanies) {
|
||||
const scored = [];
|
||||
const jobDesc = (j) => `${j.companyName || '?'} / ${j.jobTitle || '?'}`;
|
||||
const { jobFilterService } = require('../../job/services');
|
||||
|
||||
console.log(`[jobFilterEngine] filterAndScoreJobsForDeliver 开始,待处理: ${jobs.length}`);
|
||||
|
||||
for (const job of jobs) {
|
||||
if (job.companyName && recentCompanies.has(job.companyName)) {
|
||||
console.log(`[jobFilterEngine] 已投递公司 剔除: ${jobDesc(job)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const filtered = await this.filterJobs([job], filterConfig, resume);
|
||||
if (filtered.length === 0) {
|
||||
console.log(`[jobFilterEngine] 过滤条件不通过 剔除: ${jobDesc(job)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const scoreResult = jobFilterService.calculateJobScoreWithWeights(
|
||||
job,
|
||||
resume,
|
||||
accountConfig,
|
||||
jobTypeConfig,
|
||||
accountConfig.is_salary_priority || []
|
||||
);
|
||||
const keywordBonus = KeywordMatcher.calculateBonus(
|
||||
`${job.jobTitle || ''} ${job.companyName || ''} ${job.jobDescription || ''}`,
|
||||
filterConfig.filter_keywords || [],
|
||||
{ baseScore: 5, maxBonus: 20 }
|
||||
);
|
||||
const finalScore = scoreResult.totalScore + keywordBonus.score;
|
||||
|
||||
if (finalScore < 60) {
|
||||
console.log(`[jobFilterEngine] 评分不足(>=60) 剔除: ${jobDesc(job)} 总分=${finalScore.toFixed(1)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
scored.push({
|
||||
...job,
|
||||
matchScore: finalScore,
|
||||
scoreDetails: { ...scoreResult.scores, keywordBonus: keywordBonus.score }
|
||||
});
|
||||
}
|
||||
|
||||
scored.sort((a, b) => b.matchScore - a.matchScore);
|
||||
console.log(`[jobFilterEngine] filterAndScoreJobsForDeliver 结束: 原始${jobs.length} 通过${scored.length}`);
|
||||
return scored;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按薪资过滤
|
||||
* @param {Array} jobs - 职位列表
|
||||
@@ -99,14 +153,6 @@ class JobFilterEngine {
|
||||
return KeywordMatcher.filterJobs(jobs, {
|
||||
excludeKeywords: exclude_keywords,
|
||||
filterKeywords: filter_keywords
|
||||
}, (job) => {
|
||||
// 组合职位名称、描述、技能要求等
|
||||
return [
|
||||
job.name || job.jobName || '',
|
||||
job.description || job.jobDescription || '',
|
||||
job.skills || '',
|
||||
job.welfare || ''
|
||||
].join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -376,39 +422,6 @@ class JobFilterEngine {
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 综合处理:过滤 + 评分 + 排序
|
||||
* @param {Array} jobs - 职位列表
|
||||
* @param {object} config - 过滤配置
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @param {object} options - 选项
|
||||
* @returns {Promise<Array>} 处理后的职位列表
|
||||
*/
|
||||
async process(jobs, config, resumeInfo = {}, options = {}) {
|
||||
const {
|
||||
maxCount = 10, // 最大返回数量
|
||||
sortBy = 'score' // 排序方式
|
||||
} = options;
|
||||
|
||||
// 1. 过滤
|
||||
let filtered = await this.filterJobs(jobs, config, resumeInfo);
|
||||
|
||||
console.log(`[职位过滤] 原始: ${jobs.length} 个,过滤后: ${filtered.length} 个`);
|
||||
|
||||
// 2. 评分
|
||||
const scored = this.scoreJobs(filtered, resumeInfo, config);
|
||||
|
||||
// 3. 排序
|
||||
const sorted = this.sortJobs(scored, sortBy);
|
||||
|
||||
// 4. 截取
|
||||
const result = sorted.slice(0, maxCount);
|
||||
|
||||
console.log(`[职位过滤] 最终返回: ${result.length} 个职位`);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
||||
@@ -2,6 +2,7 @@ const BaseTask = require('./baseTask');
|
||||
const db = require('../../dbProxy');
|
||||
const config = require('../infrastructure/config');
|
||||
const authorizationService = require('../../../services/authorization_service');
|
||||
const ConfigManager = require('../services/configManager');
|
||||
|
||||
/**
|
||||
* 自动投递任务
|
||||
@@ -54,7 +55,7 @@ class AutoDeliverTask extends BaseTask {
|
||||
}
|
||||
|
||||
// 3. 获取投递配置
|
||||
const deliverConfig = this.parseDeliverConfig(account.deliver_config);
|
||||
const deliverConfig = ConfigManager.parseDeliverConfig(account.deliver_config);
|
||||
|
||||
// 4. 检查日投递限制
|
||||
const dailyLimit = config.platformDailyLimits[account.platform_type] || 50;
|
||||
@@ -108,30 +109,6 @@ class AutoDeliverTask extends BaseTask {
|
||||
return account ? account.toJSON() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析投递配置
|
||||
*/
|
||||
parseDeliverConfig(deliver_config) {
|
||||
if (typeof deliver_config === 'string') {
|
||||
try {
|
||||
deliver_config = JSON.parse(deliver_config);
|
||||
} catch (e) {
|
||||
deliver_config = {};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deliver_interval: deliver_config?.deliver_interval || 30,
|
||||
min_salary: deliver_config?.min_salary || 0,
|
||||
max_salary: deliver_config?.max_salary || 0,
|
||||
page_count: deliver_config?.page_count || 3,
|
||||
max_deliver: deliver_config?.max_deliver || 10,
|
||||
filter_keywords: deliver_config?.filter_keywords || [],
|
||||
exclude_keywords: deliver_config?.exclude_keywords || [],
|
||||
time_range: deliver_config?.time_range || null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已投递数量
|
||||
*/
|
||||
@@ -217,7 +194,7 @@ class AutoDeliverTask extends BaseTask {
|
||||
}
|
||||
|
||||
// 3. 获取投递配置
|
||||
const deliverConfig = this.parseDeliverConfig(account.deliver_config);
|
||||
const deliverConfig = ConfigManager.parseDeliverConfig(account.deliver_config);
|
||||
|
||||
// 4. 检查时间范围
|
||||
if (deliverConfig.time_range) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const BaseTask = require('./baseTask');
|
||||
const db = require('../../dbProxy');
|
||||
const config = require('../infrastructure/config');
|
||||
const ConfigManager = require('../services/configManager');
|
||||
|
||||
/**
|
||||
* 自动搜索职位任务
|
||||
@@ -53,7 +54,7 @@ class AutoSearchTask extends BaseTask {
|
||||
}
|
||||
|
||||
// 2. 获取搜索配置
|
||||
const searchConfig = this.parseSearchConfig(account.search_config);
|
||||
const searchConfig = ConfigManager.parseSearchConfig(account.search_config);
|
||||
|
||||
// 3. 检查日搜索限制
|
||||
const dailyLimit = config.dailyLimits.maxSearch || 20;
|
||||
@@ -93,29 +94,6 @@ class AutoSearchTask extends BaseTask {
|
||||
return account ? account.toJSON() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析搜索配置
|
||||
*/
|
||||
parseSearchConfig(search_config) {
|
||||
if (typeof search_config === 'string') {
|
||||
try {
|
||||
search_config = JSON.parse(search_config);
|
||||
} catch (e) {
|
||||
search_config = {};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
search_interval: search_config?.search_interval || 60,
|
||||
page_count: search_config?.page_count || 3,
|
||||
city: search_config?.city || '',
|
||||
salary_range: search_config?.salary_range || '',
|
||||
experience: search_config?.experience || '',
|
||||
education: search_config?.education || '',
|
||||
time_range: search_config?.time_range || null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已搜索数量
|
||||
*/
|
||||
@@ -162,7 +140,7 @@ class AutoSearchTask extends BaseTask {
|
||||
}
|
||||
|
||||
// 3. 获取搜索配置
|
||||
const searchConfig = this.parseSearchConfig(account.search_config);
|
||||
const searchConfig = ConfigManager.parseSearchConfig(account.search_config);
|
||||
|
||||
// 4. 检查时间范围
|
||||
if (searchConfig.time_range) {
|
||||
|
||||
@@ -199,11 +199,13 @@ class KeywordMatcher {
|
||||
* @param {Function} textExtractor - 文本提取函数 (job) => string
|
||||
* @returns {Array} 匹配通过的职位(带匹配信息)
|
||||
*/
|
||||
static filterJobs(jobs, config, textExtractor = (job) => `${job.name || ''} ${job.description || ''}`) {
|
||||
static filterJobs(jobs, config) {
|
||||
if (!jobs || jobs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const textExtractor=(job) => `${job.jobTitle || ''} ${job.companyIndustry || ''}`;
|
||||
|
||||
const filtered = [];
|
||||
|
||||
for (const job of jobs) {
|
||||
|
||||
Reference in New Issue
Block a user