history-dataset-from-csv-smoke.js 4.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const { buildRecords, readCsv } = require('./history-dataset-from-csv');
  7. const { auditRecords, loadRecords } = require('./historical-dataset-audit');
  8. function main() {
  9. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-history-from-csv-'));
  10. const input = path.join(dir, 'history.csv');
  11. const output = path.join(dir, 'history-dataset');
  12. fs.writeFileSync(input, withBom([
  13. 'brief编号,项目名称,类目,客户原始Brief,历史人工补号量基线,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因',
  14. 'dha-2026,DHA历史Brief,母婴,母婴DHA真实历史Brief,含预算和参考账号,6,https://www.xiaohongshu.com/user/profile/ref-dha,小红书,营养师妈妈,https://example.com/dha/a,可直接发客户,客户选中,',
  15. 'dha-2026,DHA历史Brief,母婴,母婴DHA真实历史Brief,含预算和参考账号,6,https://www.xiaohongshu.com/user/profile/ref-dha,小红书,泛生活号,https://example.com/dha/b,跑偏,客户拒绝,内容方向偏离DHA',
  16. 'skin-2026,修护历史Brief,护肤,敏感肌修护真实历史Brief,含禁投项,4,https://www.xiaohongshu.com/explore/ref-skin,小红书,敏感肌成分党,https://example.com/skin/a,商务复核,客户选中,',
  17. 'skin-2026,修护历史Brief,护肤,敏感肌修护真实历史Brief,含禁投项,4,https://www.xiaohongshu.com/explore/ref-skin,小红书,硬广导购号,https://example.com/skin/b,调性不符,客户拒绝,硬广感太强',
  18. 'home-2026,家清历史Brief,家清,家清清洁真实历史Brief,含人群和场景,5,https://www.douyin.com/user/ref-home,抖音,家居清洁实验室,https://example.com/home/a,可直接发客户,客户选中,',
  19. 'home-2026,家清历史Brief,家清,家清清洁真实历史Brief,含人群和场景,5,https://www.douyin.com/user/ref-home,抖音,装修设计号,https://example.com/home/b,跑偏,客户拒绝,内容重装修设计'
  20. ].join('\n')), 'utf8');
  21. const records = buildRecords(readCsv(input));
  22. assert(records.length === 3, 'should group rows into three history briefs');
  23. assert(records[0].manualFinalList.length === 2, 'should keep two manual list rows per brief');
  24. assert(records[0].customerFeedback.length === 2, 'should keep customer feedback rows');
  25. assert(records[0].referenceLinks.length === 1, 'should dedupe reference links per brief');
  26. const result = spawnSync(process.execPath, [
  27. path.join(__dirname, 'history-dataset-from-csv.js'),
  28. '--input',
  29. input,
  30. '--output',
  31. output
  32. ], { cwd: path.resolve(__dirname, '..'), encoding: 'utf8' });
  33. if (result.status !== 0) {
  34. process.stderr.write(result.stderr || result.stdout || '');
  35. throw new Error('history-dataset-from-csv failed');
  36. }
  37. assert(fs.existsSync(path.join(output, 'dha-2026.json')), 'should write per-brief json file');
  38. const summary = auditRecords({ inputPath: output, records: loadRecords(output), minBriefs: 3 });
  39. assert(summary.acceptance.readyForLongRun, 'converted dataset should be ready for long run');
  40. assert(summary.acceptance.readyForCustomerEffectProof, 'converted dataset should be ready for customer effect proof');
  41. assert(summary.withManualSupplementBaseline === 3, 'converted dataset should include manual supplement baselines');
  42. const missingInput = path.join(dir, 'history-missing.csv');
  43. fs.writeFileSync(missingInput, withBom([
  44. 'brief编号,项目名称,类目,客户原始Brief,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因',
  45. 'missing-2026,缺基线Brief,护肤,缺人工补号量基线,https://example.com/ref,小红书,敏感肌记录者,https://example.com/missing/a,可直接发客户,客户选中,'
  46. ].join('\n')), 'utf8');
  47. const missingRecords = buildRecords(readCsv(missingInput));
  48. const missingSummary = auditRecords({
  49. inputPath: missingInput,
  50. records: missingRecords.map((record, index) => ({ file: `csv-row-${index}.json`, record })),
  51. minBriefs: 1
  52. });
  53. assert(missingSummary.acceptance.readyForLongRun, 'missing baseline should still allow basic long-run readiness');
  54. assert(!missingSummary.acceptance.readyForCustomerEffectProof, 'missing baseline should block customer effect proof');
  55. console.log(JSON.stringify({
  56. ok: true,
  57. input,
  58. output,
  59. briefCount: records.length,
  60. readyForCustomerEffectProof: summary.acceptance.readyForCustomerEffectProof
  61. }, null, 2));
  62. }
  63. function withBom(text) {
  64. return `\uFEFF${text}`;
  65. }
  66. function assert(condition, message) {
  67. if (!condition) throw new Error(message);
  68. }
  69. if (require.main === module) main();