feedback-loop-closure-smoke.js 4.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { runTihaoSourcing } = require('../mcp/src/tools/tihao-brief-sourcing-run');
  6. const { readFeedbackCsv } = require('./import-feedback-memory');
  7. const { readMemory, applyFeedbackRecordsToMemory } = require('../mcp/src/features/tihao-sourcing/preference-memory');
  8. const { buildFeedbackLoopClosureSummary } = require('./feedback-loop-closure-audit');
  9. async function main() {
  10. const out = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-feedback-loop-closure-'));
  11. const memoryPath = path.join(out, 'memory.json');
  12. const feedbackPath = path.join(out, 'feedback.csv');
  13. const resultPath = path.join(out, 'second-round', 'tihao-sourcing-result.json');
  14. const blockedNames = [
  15. '618母婴好物清单',
  16. '精致生活小鹿',
  17. '办公室健康零食测评'
  18. ];
  19. fs.writeFileSync(feedbackPath, [
  20. 'brief编号,平台,博主名称,主页链接,人工复核标签,反馈原因,归因类型,影响范围,是否升级为团队规则,下一轮动作',
  21. 'flower-skincare,小红书,618母婴好物清单,https://www.xiaohongshu.com/user/profile/xhs-dha-003,跑偏,护肤 Brief 不应优先推荐母婴清单号,需求解析,客户品牌级,否,剔除账号',
  22. 'flower-skincare,小红书,精致生活小鹿,https://www.xiaohongshu.com/user/profile/xhs-beauty-002,主页质感不符,封面下沉且主页质感不符,主页证据,团队规则,是,剔除账号',
  23. 'flower-skincare,小红书,办公室健康零食测评,https://www.xiaohongshu.com/user/profile/xhs-food-001,主页质感不符,封面下沉且主页质感不符,主页证据,团队规则,是,剔除账号',
  24. 'flower-skincare,小红书,下沉硬广一号,https://www.xiaohongshu.com/user/profile/xhs-low-001,主页质感不符,封面下沉且主页质感不符,主页证据,团队规则,是,剔除账号'
  25. ].join('\n'), 'utf8');
  26. const memory = applyFeedbackRecordsToMemory(readMemory(memoryPath), readFeedbackCsv(feedbackPath));
  27. fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2), 'utf8');
  28. const result = await runTihaoSourcing({
  29. collectionMode: 'sample',
  30. briefText: [
  31. '客户名称:花漾肌研',
  32. '平台与人数:小红书 6 人,抖音 2 人',
  33. '粉丝量范围:2万-50万',
  34. '预算范围:5000-18000',
  35. '偏好真实体验、素人感、成分拆解、敏感肌修护,不要硬广。'
  36. ].join('\n'),
  37. memory: memoryPath,
  38. output: path.join(out, 'second-round')
  39. });
  40. assert(result.status === 'ok', 'second round sourcing should return ok');
  41. assert(fs.existsSync(resultPath), 'second round result json should exist');
  42. const summary = buildFeedbackLoopClosureSummary({
  43. feedbackPath,
  44. memoryPath,
  45. resultPath,
  46. mode: 'sample',
  47. collectionMode: 'sample',
  48. generatedBy: 'feedback-loop-closure:smoke'
  49. });
  50. assert(summary.passed === true, 'feedback loop closure should pass for sample fixture');
  51. assert(summary.complete === false, 'sample fixture must not close direct customer proof');
  52. assert(summary.directCustomerProof === false, 'sample fixture must not be direct customer proof');
  53. assert(summary.proofLevel === 'smoke_or_local', 'sample fixture should be smoke/local proof');
  54. assert(summary.negativeFeedbackCount === 4, 'summary should count negative feedback rows');
  55. assert(summary.blockedCreatorCount === 4, 'summary should count blocked creators');
  56. assert(summary.blockedInMemoryCount === 4, 'summary should count blocked creators written to memory');
  57. assert(summary.leakedCount === 0, 'blocked creators should not leak into second-round client-ready list');
  58. assert(summary.teamRuleSuggestionCount >= 1, 'repeated team feedback should create team rule suggestion');
  59. for (const name of blockedNames) {
  60. assert(summary.rows.some(row => row.creatorName === name && row.inMemory && !row.leakedToSecondRound), `${name} should be blocked and absent from second round`);
  61. }
  62. const report = require('./feedback-loop-closure-audit').buildFeedbackLoopClosureSummary({
  63. feedbackPath,
  64. memoryPath,
  65. resultPath,
  66. mode: 'sample',
  67. collectionMode: 'sample',
  68. generatedBy: 'feedback-loop-closure:smoke'
  69. });
  70. assert(JSON.stringify(report).includes('directCustomerProof'), 'summary should expose proof boundary');
  71. assert(!/sk-[A-Za-z0-9_-]{10,}/.test(JSON.stringify(summary)), 'summary should not contain model token patterns');
  72. assert(!/r:[A-Za-z0-9]{20,}/.test(JSON.stringify(summary)), 'summary should not contain Parse sessionToken patterns');
  73. console.log(JSON.stringify({
  74. ok: true,
  75. outputDir: out,
  76. negativeFeedbackCount: summary.negativeFeedbackCount,
  77. leakedCount: summary.leakedCount,
  78. directCustomerProof: summary.directCustomerProof
  79. }, null, 2));
  80. }
  81. function assert(condition, message) {
  82. if (!condition) throw new Error(message);
  83. }
  84. main().catch(error => {
  85. console.error(error && error.stack ? error.stack : String(error));
  86. process.exit(1);
  87. });