#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const HISTORY_HEADER = [ 'brief编号', '项目名称', '类目', '客户原始Brief', '历史人工补号量基线', '参考账号或视频', '平台', '博主名称', '主页链接', '人工复核标签', '客户选择', '拒绝原因' ]; const REVIEW_HEADER = [ 'brief编号', '策略', '排名', '平台', '博主名称', '综合分', 'brief匹配分', '参考风格分', '主页证据分', '视觉质感分', '调性一致分', '证据加分', '证据风险扣分', '推荐理由', '风险提示', '主页链接', '人工复核标签', '客户选择', '归因类型', '反馈原因', '本轮人工补号量' ]; function main() { const args = parseArgs(process.argv.slice(2)); const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `data-intake-pack-${Date.now()}`)); fs.mkdirSync(output, { recursive: true }); const historyCsv = path.join(output, 'history-data-template.csv'); const reviewCsv = path.join(output, 'manual-review-template.csv'); const readme = path.join(output, 'README.md'); writeCsv(historyCsv, [ HISTORY_HEADER, [ 'brief-001', '示例项目', '母婴', '填写客户原始 Brief,不要写 token、报价明细或密钥。', '6', 'https://example.com/reference-video', '小红书', '示例博主', 'https://example.com/profile', '可直接发客户', '客户选中', '' ] ]); writeCsv(reviewCsv, [ REVIEW_HEADER, [ 'brief-001', 'result-first-broad', '1', '小红书', '示例博主', '86', '82', '78', '80', '79', '81', '4', '0', '命中 Brief 人群和参考风格,可给商务复核。', '需确认报价有效期、档期和近 30 天竞品合作。', 'https://example.com/profile', '商务复核', '待客户反馈', '', '', '3' ] ]); fs.writeFileSync(readme, withBom(renderReadme()), 'utf8'); console.log(JSON.stringify({ output, files: { historyCsv, reviewCsv, readme } }, null, 2)); } function renderReadme() { return [ '# 提号真实数据收集包', '', '用途:把商务历史 Brief、人工名单、客户选择和本轮复核结果整理成可审计数据。', '', '## 文件', '', '- `history-data-template.csv`:整理 5-10 个真实历史 Brief,用于 `history:from-csv` 和 `history:audit`。', '- `manual-review-template.csv`:整理本轮 AI 产出的候选名单和商务/客户复核,用于 `review:metrics` 和 `customer-effect:audit`。', '', '## 填写规则', '', '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token 或客户敏感报价明细。', '- `历史人工补号量基线` 必须填写,才能证明人工补号量减少 50%。', '- `客户选择` 建议填写 `客户选中`、`客户拒绝` 或 `待客户反馈`。', '- 负样本必须填写 `归因类型`,例如 `需求解析错`、`隐性规则漏`、`召回关键词错`、`主页证据不足`、`视频证据误判`、`排序权重错`、`输出解释错`。', '', '## 字段填写验收表', '', '### 历史数据表', '', '| 字段 | 必填规则 | 常见退回原因 |', '| --- | --- | --- |', '| brief编号 | 同一项目保持稳定编号。 | 空值、多个项目混用同一编号。 |', '| 客户原始Brief | 填客户原始需求,至少能看出类目、人群、平台、预算或内容方向。 | 只写“见附件”“种草合作”或少于 20 字。 |', '| 历史人工补号量基线 | 填数字,表示历史同类需求人工额外补号数量。 | 空值、写“不知道”、写区间。 |', '| 参考账号或视频 | 填真实主页链接或视频链接。 | example.com、截图占位、只写账号昵称无链接。 |', '| 博主名称/主页链接 | 填人工最终名单里的真实博主和主页。 | 只有博主名、主页缺失、候选占位。 |', '| 客户选择 | 填 `客户选中` 或 `客户拒绝`。 | 一直保留 `待客户反馈` 时不能进入客户效果审计。 |', '| 拒绝原因 | 客户拒绝样本必须填写。 | 拒绝样本为空,无法归因优化。 |', '', '### 人工复核表', '', '| 字段 | 必填规则 | 常见退回原因 |', '| --- | --- | --- |', '| 人工复核标签 | 每个候选都要填,例如 `可直接发客户`、`商务复核`、`跑偏`、`调性不符`。 | 空值或只写“看情况”。 |', '| 归因类型 | 负样本必须填固定归因类型。 | 跑偏、调性不符、硬性违约但没有归因。 |', '| 反馈原因 | 写具体原因,能让下一轮优化规则或权重。 | 只写“不合适”。 |', '| 客户选择 | 已有客户反馈时填 `客户选中` 或 `客户拒绝`。 | 全部待反馈时只能做复核指标,不能做客户效果。 |', '| 本轮人工补号量 | 填数字,表示本轮 AI 后仍需人工补号数量。 | 空值或非数字,无法计算人工补号减少率。 |', '', '## 先跑预审', '', '商务/投放填完后先跑:', '', '```powershell', 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', '```', '', '预审 `failureCount=0` 后,再进入 history、review 和 customer-effect 审计。', '', '## 审计命令', '', '```powershell', 'npm run history:from-csv -- --input --output ', 'npm run history:audit -- --input --output <历史审计输出目录> --strict', 'npm run review:metrics -- --input --output <复核指标输出目录> --strict', 'npm run customer-effect:audit -- --review-csv --history-audit --output <客户效果输出目录> --strict', 'npm run evidence:index -- --output outputs\\evidence-index-latest', '```', '', '## 边界', '', '- 模板生成不代表业务效果达标。', '- 只有真实客户选择、真实历史补号基线和本轮补号量都通过审计,才能宣称客户效果证明完成。' ].join('\n'); } function writeCsv(file, rows) { fs.writeFileSync(file, withBom(rows.map(row => row.map(csvCell).join(',')).join('\n')), 'utf8'); } function csvCell(value) { const text = String(value ?? ''); return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text; } function parseArgs(argv) { const args = {}; for (let index = 0; index < argv.length; index += 1) { const raw = argv[index]; if (!raw.startsWith('--')) continue; const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase()); const next = argv[index + 1]; if (!next || next.startsWith('--')) args[key] = true; else { args[key] = next; index += 1; } } return args; } function withBom(text) { return `\uFEFF${text}`; } if (require.main === module) main(); module.exports = { HISTORY_HEADER, REVIEW_HEADER, renderReadme };