Files
zhaopin/scripts/daily-report.js
T

119 lines
5.2 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.3(2026-09-15 密集型改版)
* 从 recruitment.db 查询当日数据,生成 Markdown 日报。
* 用法:node daily-report.js [YYYY-MM-DD] [--db /path/to.db]
* 输出:Markdown 文本到 stdout(cron 任务用 hermes send -f 发送)
*
* v1.3 改版:表格密集化——多行小表合并为单行紧凑格式,
* 减少行数、去掉冗余表头,一屏读完。
*/
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;
// ---------- Markdown 渲染 ----------
const WEEK = ['日', '一', '二', '三', '四', '五', '六'];
const wk = WEEK[new Date(day + 'T12:00:00Z').getUTCDay()];
const ACCT_META = {
hr1: { label: 'hr1(Chrome)', short: 'hr1·Chrome', chatLeft: () => process.env.CHAT_LEFT_HR1 || '—', status: () => process.env.HR1_STATUS || '✅ 在线' },
hr2: { label: 'hr2(Edge)', short: 'hr2·Edge', chatLeft: () => process.env.CHAT_LEFT_HR2 || '—', status: () => process.env.HR2_STATUS || '✅ 在线' },
};
// ---------- 开工判断(2026-09-02 用户定稿)----------
// 未开工 = 当日无任何筛选数据。两个都没开工 → 输出空串(不发日报);
// 只有一个开工 → 日报只包含该账号,未开工的整块剔除。
const activeAccts = ['hr1', 'hr2'].filter(a => (byAcct[a]?.n || 0) > 0);
const md = [];
if (activeAccts.length > 0) {
md.push(`# 📊 智联简历筛选日报`);
md.push('');
md.push(`**${day} 周${wk}** · 二手车出口外贸业务员`);
md.push('');
// ===== 密集型总览:一张表装下所有账号的核心数字 =====
md.push(`## ■ 总览`);
md.push('');
if (activeAccts.length === 2) {
md.push(`| 账号 | 筛选 | 打招呼 | 推荐 | 待定 | 拦截 | 聊天剩余 | 状态 |`);
md.push(`|:--|--:|--:|--:|--:|--:|--:|:--|`);
for (const a of activeAccts) {
const v = byAcct[a];
const p = (n) => v.n ? Math.round(n / v.n * 100) + '%' : '0%';
md.push(`| ${ACCT_META[a].short} | ${v.n} | ${v.greeted} | ${v.rec} | ${v.wait} | ${v.irr}(${p(v.irr)}) | ${ACCT_META[a].chatLeft()} | ${ACCT_META[a].status()} |`);
}
md.push(`| **合计** | **${total.n}** | **${total.greeted}** | **${total.rec}** | **${total.wait}** | **${total.irr}** | | |`);
} else {
const a = activeAccts[0];
const v = byAcct[a];
const p = (n) => v.n ? Math.round(n / v.n * 100) + '%' : '0%';
md.push(`| 账号 | 筛选 | 打招呼 | 推荐 | 待定 | 拦截 | 聊天剩余 | 状态 |`);
md.push(`|:--|--:|--:|--:|--:|--:|--:|:--|`);
md.push(`| ${ACCT_META[a].short} | ${v.n} | ${v.greeted} | ${v.rec} | ${v.wait} | ${v.irr}(${p(v.irr)}) | ${ACCT_META[a].chatLeft()} | ${ACCT_META[a].status()} |`);
}
md.push('');
md.push(`> 历史累计 ${histTotal.toLocaleString()} 人`);
md.push('');
// ===== 打招呼画像(密集:每账号一张表,最多5行,信息列合并) =====
for (const acct of activeAccts) {
const gr = greetedRows.filter(g => g.account === acct);
if (!gr.length) continue;
md.push(`## ■ ${ACCT_META[acct].short} 打招呼 ${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} | ${(g.notes || '').split(';').slice(0, 2).join(' ').slice(0, 24)} |`);
}
if (gr.length > 5) md.push(`| …共 ${gr.length} 人 | | | |`);
md.push('');
}
} // end if (activeAccts.length > 0)
console.log(md.join('\n'));
db.close();