Files
zhaopin/skill/scripts/daily-report.js
T
mzl92 e0d9c4e434 初始提交: 智联简历筛选系统v10(含Hermes每日自动化层)
- 硬性预筛门槛: 外贸经验必须(仅工作经历匹配), 培养类拦截, 毕业2020-2026, 年龄<=35
- 七维评分+动态阈值+打招呼纪律
- Hermes每日自动化: daily-runner(09:30-18:00)/daily-report/enforce-cutoff/stop-tasks
- v10.4: 每累计200人自动刷新页面
- 修正: CDP端口9222(反向隧道), 通知只发私聊(防串台), hr2任务禁用
- WSL端: cdp-tunnel.sh 浏览器工人守护
2026-08-26 22:55:08 +08:00

125 lines
5.0 KiB
JavaScript
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.
#!/usr/bin/env node
/**
* 每日筛选汇总生成器 v1.0(2026-08-26)
* 从 recruitment.db 查询当日数据,生成 Markdown 日报。
* 用法:node daily-report.js [YYYY-MM-DD] [--db /path/to.db]
* 输出:Markdown 文本到 stdout(cron 任务用 hermes send -f 发送)
*/
const Database = require('better-sqlite3');
const path = require('path');
const args = process.argv.slice(2);
const dateArg = args.find(a => /^\d{4}-\d{2}-\d{2}$/.test(a));
const dbArgIdx = args.indexOf('--db');
const DB_PATH = dbArgIdx >= 0 ? args[dbArgIdx + 1] : '/opt/data/recruitment-data/recruitment-live.db';
const day = dateArg || new Date().toISOString().slice(0, 10);
const dayStart = `${day}T00:00`;
const dayEnd = `${day}T23:59`;
const db = new Database(DB_PATH, { readonly: true });
// ---------- 数据查询 ----------
const rows = db.prepare(
`SELECT account, name, age, education, experience, school, score, score_level, role_tag,
greeted, greeted_at, notes
FROM candidates WHERE scraped_at >= ? AND scraped_at <= ?`
).all(dayStart, dayEnd);
// 账号聚合
const byAcct = {};
for (const r of rows) {
const a = byAcct[r.account || '未知'] || (byAcct[r.account || '未知'] = { n: 0, rec: 0, wait: 0, irr: 0, greeted: 0 });
a.n++;
if (r.score_level === '推荐') a.rec++;
else if (r.score_level === '待定') a.wait++;
else a.irr++;
if (r.greeted) a.greeted++;
}
const total = { n: rows.length, rec: 0, wait: 0, irr: 0, greeted: 0 };
for (const a of Object.values(byAcct)) { total.rec += a.rec; total.wait += a.wait; total.irr += a.irr; total.greeted += a.greeted; }
// 打招呼明细(当日实际打过招呼的人)
const greetedRows = rows.filter(r => r.greeted && r.greeted_at && r.greeted_at.startsWith(day))
.sort((x, y) => (y.score || 0) - (x.score || 0));
// 历史累计
const histTotal = db.prepare('SELECT COUNT(*) c FROM candidates').get().c;
// 最近一次聊天剩余(从最近 scrape_log 无记录,需运行时传入;此处用环境变量或显示待补)
const chatLeft = process.env.CHAT_LEFT_HR1 ? `hr1 剩 ${process.env.CHAT_LEFT_HR1}` : 'hr1 剩 ?';
const chatLeft2 = process.env.CHAT_LEFT_HR2 ? `hr2 剩 ${process.env.CHAT_LEFT_HR2}` : 'hr2 剩 ?';
// ---------- Markdown 渲染 ----------
const WEEK = ['日', '一', '二', '三', '四', '五', '六'];
const wk = WEEK[new Date(day + 'T12:00:00Z').getUTCDay()];
const ACCT_META = {
hr1: { label: 'hr1(Chrome)', chatLeft: () => process.env.CHAT_LEFT_HR1 ? `剩 ${process.env.CHAT_LEFT_HR1} 次` : '—', status: () => process.env.HR1_STATUS || '✅ 在线' },
hr2: { label: 'hr2(Edge)', chatLeft: () => process.env.CHAT_LEFT_HR2 ? `剩 ${process.env.CHAT_LEFT_HR2} 次` : '—', status: () => process.env.HR2_STATUS || '✅ 在线' },
};
const md = [];
md.push(`# 📊 智联简历筛选日报`);
md.push('');
md.push(`**${day} 周${wk}** · 岗位:二手车出口外贸业务员`);
md.push('---');
md.push('');
// 总览
md.push(`## ■ 今日总览`);
md.push('');
md.push(`| 指标 | hr1 Chrome | hr2 Edge | 合计 |`);
md.push(`|:---|---:|---:|---:|`);
md.push(`| 新增筛选 | ${byAcct.hr1?.n || 0} | ${byAcct.hr2?.n || 0} | **${total.n}** |`);
md.push(`| 打招呼 | ${byAcct.hr1?.greeted || 0} | ${byAcct.hr2?.greeted || 0} | **${total.greeted}** |`);
md.push('');
// 分账号小节
for (const acct of ['hr1', 'hr2']) {
const meta = ACCT_META[acct];
const v = byAcct[acct];
md.push(`## ■ ${meta.label}`);
md.push('');
if (!v || v.n === 0) {
md.push(`> 今日无筛选数据 · ${meta.status()}`);
md.push('');
continue;
}
const p = (n) => ((n / v.n) * 100).toFixed(1) + '%';
md.push(`| 结果 | 人数 | 占比 |`);
md.push(`|:---|---:|---:|`);
md.push(`| ✅ 推荐 | ${v.rec} | ${p(v.rec)} |`);
md.push(`| ⏳ 待定 | ${v.wait} | ${p(v.wait)} |`);
md.push(`| ⛔ 拦截 | ${v.irr} | ${p(v.irr)} |`);
md.push('');
const gr = greetedRows.filter(g => g.account === acct);
if (gr.length) {
md.push(`**打招呼画像(${gr.length} 人)**`);
md.push('');
md.push(`| 姓名 | 年龄 | 学历 | 经验 | 评分 | 级别 |`);
md.push(`|:---|---:|:---:|---:|---:|:---:|`);
const top = gr.slice(0, 5);
for (const g of top) {
const expRaw = (g.experience || '').toString();
const exp = /应届生|在校生/.test(expRaw) ? '应届' : (expRaw.match(/\d+年/) || [])[0] || ((expRaw.match(/^\d+$/)?.[0] || '') + '年' || '—');
md.push(`| ${g.name} | ${g.age || '—'} | ${g.education || '—'} | ${exp} | ${g.score || '—'} | ${g.score_level} |`);
}
if (gr.length > 5) md.push(`| …共 ${gr.length} 人 | | | | | |`);
md.push('');
}
}
md.push(`## ■ 资源与状态`);
md.push('');
md.push(`| 账号 | 聊天剩余 | 状态 |`);
md.push(`|:---|---:|:---|`);
md.push(`| hr1 Chrome | ${ACCT_META.hr1.chatLeft()} | ${ACCT_META.hr1.status()} |`);
md.push(`| hr2 Edge | ${ACCT_META.hr2.chatLeft()} | ${ACCT_META.hr2.status()} |`);
md.push('');
md.push(`- 历史累计:${histTotal.toLocaleString()} 人`);
console.log(md.join('\n'));
db.close();