create-data-intake-pack.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const HISTORY_HEADER = [
  5. 'brief编号',
  6. '项目名称',
  7. '类目',
  8. '客户原始Brief',
  9. '历史人工补号量基线',
  10. '参考账号或视频',
  11. '平台',
  12. '博主名称',
  13. '主页链接',
  14. '人工复核标签',
  15. '客户选择',
  16. '拒绝原因'
  17. ];
  18. const REVIEW_HEADER = [
  19. 'brief编号',
  20. '策略',
  21. '排名',
  22. '平台',
  23. '博主名称',
  24. '综合分',
  25. 'brief匹配分',
  26. '参考风格分',
  27. '主页证据分',
  28. '视觉质感分',
  29. '调性一致分',
  30. '证据加分',
  31. '证据风险扣分',
  32. '推荐理由',
  33. '风险提示',
  34. '主页链接',
  35. '人工复核标签',
  36. '客户选择',
  37. '归因类型',
  38. '反馈原因',
  39. '本轮人工补号量'
  40. ];
  41. function main() {
  42. const args = parseArgs(process.argv.slice(2));
  43. const output = path.resolve(args.output || path.join(__dirname, '..', 'outputs', `data-intake-pack-${Date.now()}`));
  44. fs.mkdirSync(output, { recursive: true });
  45. const historyCsv = path.join(output, 'history-data-template.csv');
  46. const reviewCsv = path.join(output, 'manual-review-template.csv');
  47. const readme = path.join(output, 'README.md');
  48. writeCsv(historyCsv, [
  49. HISTORY_HEADER,
  50. [
  51. 'brief-001',
  52. '示例项目',
  53. '母婴',
  54. '填写客户原始 Brief,不要写 token、报价明细或密钥。',
  55. '6',
  56. 'https://example.com/reference-video',
  57. '小红书',
  58. '示例博主',
  59. 'https://example.com/profile',
  60. '可直接发客户',
  61. '客户选中',
  62. ''
  63. ]
  64. ]);
  65. writeCsv(reviewCsv, [
  66. REVIEW_HEADER,
  67. [
  68. 'brief-001',
  69. 'result-first-broad',
  70. '1',
  71. '小红书',
  72. '示例博主',
  73. '86',
  74. '82',
  75. '78',
  76. '80',
  77. '79',
  78. '81',
  79. '4',
  80. '0',
  81. '命中 Brief 人群和参考风格,可给商务复核。',
  82. '需确认报价有效期、档期和近 30 天竞品合作。',
  83. 'https://example.com/profile',
  84. '商务复核',
  85. '待客户反馈',
  86. '',
  87. '',
  88. '3'
  89. ]
  90. ]);
  91. fs.writeFileSync(readme, withBom(renderReadme()), 'utf8');
  92. console.log(JSON.stringify({ output, files: { historyCsv, reviewCsv, readme } }, null, 2));
  93. }
  94. function renderReadme() {
  95. return [
  96. '# 提号真实数据收集包',
  97. '',
  98. '用途:把商务历史 Brief、人工名单、客户选择和本轮复核结果整理成可审计数据。',
  99. '',
  100. '## 文件',
  101. '',
  102. '- `history-data-template.csv`:整理 5-10 个真实历史 Brief,用于 `history:from-csv` 和 `history:audit`。',
  103. '- `manual-review-template.csv`:整理本轮 AI 产出的候选名单和商务/客户复核,用于 `review:metrics` 和 `customer-effect:audit`。',
  104. '',
  105. '## 填写规则',
  106. '',
  107. '- 不要写 Parse sessionToken、模型 token、鉴权头、npm token 或客户敏感报价明细。',
  108. '- `历史人工补号量基线` 必须填写,才能证明人工补号量减少 50%。',
  109. '- `客户选择` 建议填写 `客户选中`、`客户拒绝` 或 `待客户反馈`。',
  110. '- 负样本必须填写 `归因类型`,例如 `需求解析错`、`隐性规则漏`、`召回关键词错`、`主页证据不足`、`视频证据误判`、`排序权重错`、`输出解释错`。',
  111. '',
  112. '## 字段填写验收表',
  113. '',
  114. '### 历史数据表',
  115. '',
  116. '| 字段 | 必填规则 | 常见退回原因 |',
  117. '| --- | --- | --- |',
  118. '| brief编号 | 同一项目保持稳定编号。 | 空值、多个项目混用同一编号。 |',
  119. '| 客户原始Brief | 填客户原始需求,至少能看出类目、人群、平台、预算或内容方向。 | 只写“见附件”“种草合作”或少于 20 字。 |',
  120. '| 历史人工补号量基线 | 填数字,表示历史同类需求人工额外补号数量。 | 空值、写“不知道”、写区间。 |',
  121. '| 参考账号或视频 | 填真实主页链接或视频链接。 | example.com、截图占位、只写账号昵称无链接。 |',
  122. '| 博主名称/主页链接 | 填人工最终名单里的真实博主和主页。 | 只有博主名、主页缺失、候选占位。 |',
  123. '| 客户选择 | 填 `客户选中` 或 `客户拒绝`。 | 一直保留 `待客户反馈` 时不能进入客户效果审计。 |',
  124. '| 拒绝原因 | 客户拒绝样本必须填写。 | 拒绝样本为空,无法归因优化。 |',
  125. '',
  126. '### 人工复核表',
  127. '',
  128. '| 字段 | 必填规则 | 常见退回原因 |',
  129. '| --- | --- | --- |',
  130. '| 人工复核标签 | 每个候选都要填,例如 `可直接发客户`、`商务复核`、`跑偏`、`调性不符`。 | 空值或只写“看情况”。 |',
  131. '| 归因类型 | 负样本必须填固定归因类型。 | 跑偏、调性不符、硬性违约但没有归因。 |',
  132. '| 反馈原因 | 写具体原因,能让下一轮优化规则或权重。 | 只写“不合适”。 |',
  133. '| 客户选择 | 已有客户反馈时填 `客户选中` 或 `客户拒绝`。 | 全部待反馈时只能做复核指标,不能做客户效果。 |',
  134. '| 本轮人工补号量 | 填数字,表示本轮 AI 后仍需人工补号数量。 | 空值或非数字,无法计算人工补号减少率。 |',
  135. '',
  136. '## 先跑预审',
  137. '',
  138. '商务/投放填完后先跑:',
  139. '',
  140. '```powershell',
  141. 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest',
  142. '```',
  143. '',
  144. '预审 `failureCount=0` 后,再进入 history、review 和 customer-effect 审计。',
  145. '',
  146. '## 审计命令',
  147. '',
  148. '```powershell',
  149. 'npm run history:from-csv -- --input <history-data-template.csv> --output <history-dataset目录>',
  150. 'npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict',
  151. 'npm run review:metrics -- --input <manual-review-template.csv> --output <复核指标输出目录> --strict',
  152. 'npm run customer-effect:audit -- --review-csv <manual-review-template.csv> --history-audit <historical-dataset-audit.json> --output <客户效果输出目录> --strict',
  153. 'npm run evidence:index -- --output outputs\\evidence-index-latest',
  154. '```',
  155. '',
  156. '## 边界',
  157. '',
  158. '- 模板生成不代表业务效果达标。',
  159. '- 只有真实客户选择、真实历史补号基线和本轮补号量都通过审计,才能宣称客户效果证明完成。'
  160. ].join('\n');
  161. }
  162. function writeCsv(file, rows) {
  163. fs.writeFileSync(file, withBom(rows.map(row => row.map(csvCell).join(',')).join('\n')), 'utf8');
  164. }
  165. function csvCell(value) {
  166. const text = String(value ?? '');
  167. return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
  168. }
  169. function parseArgs(argv) {
  170. const args = {};
  171. for (let index = 0; index < argv.length; index += 1) {
  172. const raw = argv[index];
  173. if (!raw.startsWith('--')) continue;
  174. const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
  175. const next = argv[index + 1];
  176. if (!next || next.startsWith('--')) args[key] = true;
  177. else {
  178. args[key] = next;
  179. index += 1;
  180. }
  181. }
  182. return args;
  183. }
  184. function withBom(text) {
  185. return `\uFEFF${text}`;
  186. }
  187. if (require.main === module) main();
  188. module.exports = {
  189. HISTORY_HEADER,
  190. REVIEW_HEADER,
  191. renderReadme
  192. };