customer-effect-audit-smoke.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { buildMetrics, readCsv } = require('./review-metrics');
  6. const { buildCustomerEffectSummary } = require('./customer-effect-audit');
  7. function main() {
  8. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-customer-effect-'));
  9. const reviewCsv = path.join(dir, 'reviewed.csv');
  10. fs.writeFileSync(reviewCsv, withBom([
  11. 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型,本轮人工补号量',
  12. 'brief-a,baseline-live,1,小红书,基础召回A,80,80,40,60,70,70,0,0,基础召回,需复核,http://example.com/a1,商务复核,客户选中,,3',
  13. 'brief-a,baseline-live,2,小红书,基础召回B,79,79,40,60,70,70,0,0,基础召回,调性偏泛,http://example.com/a2,跑偏,,召回关键词错,3',
  14. 'brief-a,baseline-live,3,小红书,基础召回C,78,78,40,60,70,70,0,0,基础召回,需复核,http://example.com/a3,商务复核,客户选中,,3',
  15. 'brief-a,reference-account,4,小红书,参考召回A,90,90,88,85,85,88,2,0,参考相似,需复核,http://example.com/a4,可直接发客户,客户选中,,3',
  16. 'brief-a,reference-account,5,小红书,参考召回B,89,89,87,85,85,88,2,0,参考相似,需复核,http://example.com/a5,商务复核,客户选中,,3',
  17. 'brief-a,homepage-evidence,6,小红书,主页证据A,88,88,82,90,85,88,2,0,主页匹配,需复核,http://example.com/a6,商务复核,客户选中,,3',
  18. 'brief-a,homepage-evidence,7,小红书,主页证据B,87,87,82,89,85,88,2,0,主页匹配,需复核,http://example.com/a7,商务复核,客户选中,,3',
  19. 'brief-a,video-enhanced,8,小红书,视频证据A,86,86,84,88,85,88,2,0,视频证据匹配,需复核,http://example.com/a8,商务复核,客户选中,,3',
  20. 'brief-a,video-enhanced,9,小红书,视频证据B,85,85,84,88,85,88,2,0,视频证据匹配,需复核,http://example.com/a9,商务复核,,,3',
  21. 'brief-a,result-first,10,小红书,结果优先A,84,84,80,86,84,86,2,0,综合匹配,需复核,http://example.com/a10,商务复核,客户选中,,3'
  22. ].join('\n')), 'utf8');
  23. const rows = readCsv(reviewCsv);
  24. const reviewMetrics = buildMetrics({ inputPath: reviewCsv, rows });
  25. const passingHistory = {
  26. acceptance: { readyForCustomerEffectProof: true },
  27. items: [
  28. { id: 'brief-a', manualSupplementBaseline: 6 }
  29. ]
  30. };
  31. const passSummary = buildCustomerEffectSummary({
  32. reviewPath: reviewCsv,
  33. reviewRows: rows,
  34. reviewMetrics,
  35. history: passingHistory,
  36. historyAuditPath: path.join(dir, 'historical-dataset-audit.json')
  37. });
  38. assert(passSummary.customerSelectedRate === 0.8, 'customer selected rate should be 80%');
  39. assert(passSummary.referenceCustomerSelectedRate === 0.8333, 'reference selected rate should be 83%');
  40. assert(passSummary.manualSupplementReductionRate === 0.5, 'manual supplement reduction should be 50%');
  41. assert(passSummary.acceptance.customerSelectedRate30Pass, '30% selected-rate gate should pass');
  42. assert(passSummary.acceptance.customerSelectedRate50Pass, '50% selected-rate gate should pass');
  43. assert(passSummary.acceptance.referenceCustomerSelectedRate40Pass, 'reference selected-rate gate should pass');
  44. assert(passSummary.acceptance.manualSupplementReduction50Pass, 'manual supplement reduction gate should pass');
  45. assert(passSummary.acceptance.overallPass, 'complete customer effect proof should pass');
  46. const missingCurrentCsv = path.join(dir, 'missing-current.csv');
  47. fs.writeFileSync(missingCurrentCsv, withBom([
  48. 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型',
  49. 'brief-b,reference-account,1,小红书,参考召回B,90,90,88,85,85,88,2,0,参考相似,需复核,http://example.com/b1,可直接发客户,客户选中,'
  50. ].join('\n')), 'utf8');
  51. const missingCurrentRows = readCsv(missingCurrentCsv);
  52. const missingCurrentSummary = buildCustomerEffectSummary({
  53. reviewPath: missingCurrentCsv,
  54. reviewRows: missingCurrentRows,
  55. reviewMetrics: buildMetrics({ inputPath: missingCurrentCsv, rows: missingCurrentRows }),
  56. history: passingHistory,
  57. historyAuditPath: path.join(dir, 'historical-dataset-audit.json')
  58. });
  59. assert(!missingCurrentSummary.acceptance.currentManualSupplementAvailable, 'missing current manual supplement count should be pending');
  60. assert(!missingCurrentSummary.acceptance.overallPass, 'missing current manual supplement count should block overall pass');
  61. const missingBaselineSummary = buildCustomerEffectSummary({
  62. reviewPath: reviewCsv,
  63. reviewRows: rows,
  64. reviewMetrics,
  65. history: {
  66. acceptance: { readyForCustomerEffectProof: false },
  67. items: [{ id: 'brief-a', manualSupplementBaseline: null }]
  68. },
  69. historyAuditPath: path.join(dir, 'bad-history.json')
  70. });
  71. assert(!missingBaselineSummary.acceptance.manualSupplementBaselineAvailable, 'missing baseline should be detected');
  72. assert(!missingBaselineSummary.acceptance.historyReadyForCustomerEffectProof, 'bad history audit should block customer proof');
  73. assert(!missingBaselineSummary.acceptance.overallPass, 'missing baseline should block overall pass');
  74. console.log(JSON.stringify({
  75. ok: true,
  76. output: dir,
  77. customerSelectedRate: passSummary.customerSelectedRate,
  78. referenceCustomerSelectedRate: passSummary.referenceCustomerSelectedRate,
  79. manualSupplementReductionRate: passSummary.manualSupplementReductionRate
  80. }, null, 2));
  81. }
  82. function withBom(text) {
  83. return `\uFEFF${text}`;
  84. }
  85. function assert(condition, message) {
  86. if (!condition) throw new Error(message);
  87. }
  88. if (require.main === module) main();