Files
zhaopin/scripts/test-gate.js
T

76 lines
4.5 KiB
JavaScript

// test-gate.js — 用真实数据库回测 v10 硬性预筛门槛
// 用法: node test-gate.js [db路径]
// 验证点:
// 1) 历史已打招呼的人,现在有多少会被新门槛拦截(拦截率)
// 2) 被拦截者的原因分布(无外贸信号 / 毕业年份超窗 / 年龄)
// 3) 需求方点名的无关职业(售前/证券/保险/金融/售后)是否被拦
// 4) 老标准下"推荐"的人是否仍被放行(不能误杀优质候选)
'use strict';
const path = require('path');
function tryRequire(names) { for (const n of names) { try { return require(n); } catch (_) {} } throw new Error('需要 better-sqlite3'); }
const Database = tryRequire([process.env.SQLITE3_PATH, path.join(__dirname, 'node_modules', 'better-sqlite3'), 'better-sqlite3']);
const { hardGate } = require('./scoring');
const dbPath = process.argv[2] || path.join(__dirname, '..', '..', 'data', 'recruitment.db');
const db = new Database(dbPath, { readonly: true });
const rows = db.prepare(`
SELECT name, age, education, experience, raw_text, education_detail, work_detail, cert_detail, score_level
FROM candidates WHERE greeted = 1`).all();
console.log(`历史已打招呼: ${rows.length} 人,开始回测 v10 门槛...\n`);
let pass = 0;
const blocked = { 'no-experience': [], 'trainee-only': [], 'grad-year': [], 'age': [] };
for (const r of rows) {
const g = hardGate({
rawText: r.raw_text, workDetail: r.work_detail, educationDetail: r.education_detail,
certDetail: r.cert_detail, age: r.age, education: r.education, experience: r.experience,
});
if (g.pass) pass++;
else if (g.reason.startsWith('grad-year')) blocked['grad-year'].push({ r, reason: g.reason });
else if (g.reason.startsWith('age')) blocked['age'].push({ r, reason: g.reason });
else if (g.reason.startsWith('trainee-only')) blocked['trainee-only'].push({ r, reason: g.reason });
else blocked['no-experience'].push({ r, reason: g.reason });
}
const total = rows.length;
console.log(`== 总体 ==`);
console.log(`放行 ${pass} 人 (${(pass / total * 100).toFixed(1)}%) | 拦截 ${total - pass} 人 (${((total - pass) / total * 100).toFixed(1)}%)`);
console.log(`拦截原因: 无外贸经验 ${blocked['no-experience'].length} | 培养类(仅专业/证书) ${blocked['trainee-only'].length} | 毕业年份超窗 ${blocked['grad-year'].length} | 年龄>35 ${blocked['age'].length}`);
// 需求方点名的无关职业,在历史已打招呼中的分布及拦截情况
const IRRELEVANT_JOBS = ['售前', '证券', '保险', '金融', '售后', '投资顾问', '银行', '理财'];
console.log(`\n== 需求方点名的无关职业(历史已打招呼中的命中情况)==`);
for (const job of IRRELEVANT_JOBS) {
const hits = rows.filter(r => (r.work_detail || '') .includes(job));
if (!hits.length) continue;
const wouldBlock = hits.filter(h => !hardGate({
rawText: h.raw_text, workDetail: h.work_detail, educationDetail: h.education_detail,
certDetail: h.cert_detail, age: h.age, education: h.education, experience: h.experience,
}).pass);
console.log(`"${job}": 命中 ${hits.length} 人 → 新门槛拦截 ${wouldBlock.length} 人${wouldBlock.length < hits.length ? `(漏放 ${hits.length - wouldBlock.length},多因简历含外贸关键词)` : ' ✅'}`);
}
// 无信号被拦者抽样(看职业构成)
console.log(`\n== "无外贸经验"被拦者抽样 20 人 ==`);
for (const { r } of blocked['no-experience'].slice(0, 20)) {
const title = (r.work_detail || r.raw_text || '').split('\n').map(s => s.trim()).find(s => s && s.length > 3 && !/^\d/.test(s)) || '?';
console.log(` ⛔ ${r.name} ${r.age || '?'}岁 ${r.education || ''} | ${(title || '').substring(0, 40)}`);
}
// 误杀检查:老标准"推荐"的人被拦了多少
const recs = db.prepare(`SELECT name, age, education, experience, raw_text, education_detail, work_detail, cert_detail FROM candidates WHERE score_level = '推荐'`).all();
let recBlocked = 0;
const recBlockedSamples = [];
for (const r of recs) {
const g = hardGate({
rawText: r.raw_text, workDetail: r.work_detail, educationDetail: r.education_detail,
certDetail: r.cert_detail, age: r.age, education: r.education, experience: r.experience,
});
if (!g.pass) { recBlocked++; if (recBlockedSamples.length < 8) recBlockedSamples.push(`${r.name}(${g.reason})`); }
}
console.log(`\n== 误杀检查 ==`);
console.log(`老标准"推荐" ${recs.length} 人 → 被新门槛拦截 ${recBlocked} 人`);
if (recBlockedSamples.length) console.log(`被拦样本: ${recBlockedSamples.join(' | ')}`);