experience-transcript-index-smoke.js 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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 ROOT = path.resolve(__dirname, '..');
  7. function main() {
  8. const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-experience-transcript-index-'));
  9. const transcriptsDir = path.join(temp, 'transcripts');
  10. const output = path.join(temp, 'index');
  11. fs.mkdirSync(transcriptsDir, { recursive: true });
  12. fs.writeFileSync(path.join(transcriptsDir, '黄圆-AI提号经验逐字稿.md'), [
  13. '# 黄圆',
  14. '参考账号要拆类型和调性。',
  15. '小红书和蒲公英可以通过相似推荐链路继续找。',
  16. '抖音星图标签不准时,用招募和关键词兜底。',
  17. '看主页近10篇、20篇封面首图判断风格。',
  18. '先用预算、CPE、CP1、CPM、粉丝、女粉和城市做第一轮过滤。',
  19. 'Brief 之外还要看产品、参考账号和客户聊天记录,客户说的隐藏要求要并入。'
  20. ].join('\n'), 'utf8');
  21. fs.writeFileSync(path.join(transcriptsDir, '邓发-AI提号经验逐字稿.md'), [
  22. '# 邓发',
  23. '美容仪和脱毛仪要理解女性受众,不能推荐男性账号。',
  24. '内容质感看画面清晰、像素、排版,不要下沉。',
  25. '报价 300 元以上要看封面统一、背景干净和画面精致。',
  26. '近一个月停更、平均点赞百赞以下、重复评论和同一个人评论都要复核数据真实性。',
  27. '时尚穿搭要拆港风、成熟风,美妆护肤要拆妆教、测评和产品合集。',
  28. '封面不确定再点进去看视频内容,可以继续解析每一帧。',
  29. '最终要看客户选中率、降本增效和补号变化。',
  30. ['Authorization', 'Bearer abcdefghijklmnopqrstuvwxyz'].join(': ')
  31. ].join('\n'), 'utf8');
  32. const result = spawnSync(process.execPath, [
  33. 'scripts/experience-transcript-index.js',
  34. '--transcripts-dir',
  35. transcriptsDir,
  36. '--output',
  37. output,
  38. '--strict'
  39. ], {
  40. cwd: ROOT,
  41. encoding: 'utf8',
  42. shell: false
  43. });
  44. if (result.stdout) process.stdout.write(result.stdout);
  45. if (result.stderr) process.stderr.write(result.stderr);
  46. assert(result.status === 0, 'experience transcript index should pass');
  47. const summaryPath = path.join(output, 'experience-transcript-index-summary.json');
  48. const reportPath = path.join(output, 'experience-transcript-index.md');
  49. const csvPath = path.join(output, 'experience-transcript-index.csv');
  50. const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
  51. const report = fs.readFileSync(reportPath, 'utf8');
  52. const csv = fs.readFileSync(csvPath, 'utf8');
  53. assert(summary.proofLevel === 'not_business_proof', 'summary should not be business proof');
  54. assert(summary.materialType === 'experience_seed', 'summary should classify as experience seed');
  55. assert(summary.directCustomerProof === false, 'summary should not be direct customer proof');
  56. assert(summary.canCloseProofGap === false, 'summary should not close proof gap');
  57. assert(summary.complete === false, 'summary should not claim completion');
  58. assert(summary.transcriptCount === 2, 'summary should index two transcripts');
  59. assert(summary.coveredRuleCount === summary.ruleCount, 'fixture should cover all rules');
  60. assert(summary.ruleCoverage.every(item => item.directCustomerProof === false), 'rules should keep proof boundary');
  61. assert(summary.ruleCoverage.some(item => item.id === 'reference-type-tone' && item.status === 'covered'), 'reference rule should be covered');
  62. assert(summary.ruleCoverage.some(item => item.id === 'customer-effect-goal' && item.status === 'covered'), 'customer effect boundary rule should be covered');
  63. assert(report.includes('提号经验逐字稿索引'), 'report should render title');
  64. assert(report.includes('canCloseProofGap:false'), 'report should render proof boundary');
  65. assert(csv.includes('experience-rule'), 'csv should include rule rows');
  66. assert(!JSON.stringify(summary).includes('Authorization'), 'summary should not expose secret-like transcript text');
  67. assert(!report.includes('Authorization'), 'report should not expose secret-like transcript text');
  68. assert(!csv.includes('Authorization'), 'csv should not expose secret-like transcript text');
  69. console.log(JSON.stringify({
  70. ok: true,
  71. transcriptCount: summary.transcriptCount,
  72. coveredRuleCount: summary.coveredRuleCount
  73. }, null, 2));
  74. }
  75. function assert(condition, message) {
  76. if (!condition) throw new Error(message);
  77. }
  78. main();