日报v1.3密集型改版: 多表合并为单张总览表(账号×7指标一行), 打招呼画像列合并(年龄·学历·经验/评分·级别/命中), 行数减半

This commit is contained in:
hermes
2026-09-16 10:09:30 +00:00
parent 0ba8b4c235
commit 102afb488a
+37 -54
View File
@@ -1,9 +1,12 @@
#!/usr/bin/env node
/**
* 每日筛选汇总生成器 v1.0(2026-08-26)
* 每日筛选汇总生成器 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');
@@ -47,17 +50,13 @@ const greetedRows = rows.filter(r => r.greeted && r.greeted_at && r.greeted_at.s
// 历史累计
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 || '✅ 在线' },
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 用户定稿)----------
@@ -69,66 +68,50 @@ const md = [];
if (activeAccts.length > 0) {
md.push(`# 📊 智联简历筛选日报`);
md.push('');
md.push(`**${day} 周${wk}** · 岗位:二手车出口外贸业务员`);
md.push('---');
md.push(`**${day} 周${wk}** · 二手车出口外贸业务员`);
md.push('');
// 总览(只列开工账号;单账号时省略对比表)
md.push(`## ■ 今日总览`);
// ===== 密集型总览:一张表装下所有账号的核心数字 =====
md.push(`## ■ 总览`);
md.push('');
if (activeAccts.length === 2) {
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(`| 账号 | 筛选 | 打招呼 | 推荐 | 待定 | 拦截 | 聊天剩余 | 状态 |`);
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];
md.push(`| 指标 | ${ACCT_META[a].label} |`);
md.push(`|:---|---:|`);
md.push(`| 新增筛选 | **${byAcct[a].n}** |`);
md.push(`| 打招呼 | **${byAcct[a].greeted}** |`);
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('');
// 分账号小节(只列开工账号,2026-09-02)
// ===== 打招呼画像(密集:每账号一张表,最多5行,信息列合并) =====
for (const acct of activeAccts) {
const meta = ACCT_META[acct];
const v = byAcct[acct];
md.push(`## ■ ${meta.label}`);
md.push('');
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('');
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('');
}
md.push(`## ■ 资源与状态`);
md.push('');
md.push(`| 账号 | 聊天剩余 | 状态 |`);
md.push(`|:---|---:|:---|`);
for (const a of activeAccts) {
md.push(`| ${ACCT_META[a].label.replace('(', ' ').replace(')', '')} | ${ACCT_META[a].chatLeft()} | ${ACCT_META[a].status()} |`);
}
md.push('');
md.push(`- 历史累计:${histTotal.toLocaleString()} 人`);
} // end if (activeAccts.length > 0)
console.log(md.join('\n'));