_audit-liver-voc.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // 审计 Ch1-Ch3 实际展示的 VOC 证据质量:
  2. // 对每一条 getEvidence(...) 调用复现其结果,
  3. // 打印 "slide · 角色 · 平台 · 昵称 · 点赞 · 内容" → 让人眼见即可评估是否对路。
  4. const LA = require('./liver-analyze.js');
  5. const data = LA.loadMerged();
  6. const items = data.items;
  7. function printList(title, arr) {
  8. console.log('\n──────────────────────────────────────');
  9. console.log(title);
  10. console.log('──────────────────────────────────────');
  11. if (!arr || !arr.length) { console.log(' (空)'); return; }
  12. arr.forEach((it, i) => {
  13. const rating = typeof it.rating === 'number' ? ` ★${it.rating}` : '';
  14. const title = it.title ? ` | "${it.title.slice(0, 40)}"` : '';
  15. console.log(
  16. ` [${i + 1}] ${it.platform.toUpperCase()} · ${it.keyword} · ${it.nickname}${rating} · ♥${it.likes}${title}`
  17. );
  18. console.log(` 「${(it.content || '').replace(/\n/g, ' ').slice(0, 180)}」`);
  19. });
  20. }
  21. console.log('╔════════════════════════════════════════════════════════╗');
  22. console.log('║ 江中肝纯片 VOC 报告 · Ch1-Ch3 evidence 审计 ║');
  23. console.log('╚════════════════════════════════════════════════════════╝');
  24. // ===== 1.1 DMY 认知空白 =====
  25. printList('【1.1】XHS "肝纯片" + 水飞蓟提及(mind1)',
  26. LA.getEvidence(items, { keyword: '肝纯片', contentMatch: /水飞蓟|silymarin/i, top: 3, seed: 5 }));
  27. printList('【1.1】XHS "江中肝纯片" + 总黄酮提及(mind2)',
  28. LA.getEvidence(items, { keyword: '江中肝纯片', contentMatch: /总黄酮|成分|黄酮|护肝片/i, top: 2, seed: 7 }));
  29. printList('【1.1】XHS "藤茶" top 3(按 likes)',
  30. items.filter(it => it.keyword === '藤茶').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  31. printList('【1.1】Amazon dihydromyricetin ★≥4 top 3',
  32. items.filter(it => it.keyword === 'dihydromyricetin' && it.rating >= 4)
  33. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  34. // ===== 1.2 海王金樽警示 =====
  35. printList('【1.2】"海王金樽" + 广告/老哒/硬广/[捂脸]',
  36. items.filter(it => it.keyword === '海王金樽' && /广告|老哒|硬广|洗脑|杠杠的|捂脸|福/i.test(it.content || ''))
  37. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 4));
  38. printList('【1.2】"海王金樽" 正向寡淡(likes<10)',
  39. items.filter(it => it.keyword === '海王金樽' && /神|可以|性价比|get/i.test(it.content || '') && (it.likes || 0) < 10)
  40. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  41. printList('【1.2】"解酒神器" 品类整体情绪',
  42. items.filter(it => it.keyword === '解酒神器' && /横竖|死呗|对手|笑哭|酒蒙子|催吐|商家/i.test(it.content || ''))
  43. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  44. // ===== 2.1 藤茶千年食饮 =====
  45. printList('【2.1】XHS "藤茶" top 4(取前 4 作文化叙事)',
  46. items.filter(it => it.keyword === '藤茶').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 4));
  47. // ===== 2.2 DMY 科学力 =====
  48. printList('【2.2】Amazon dihydromyricetin ★≥4 top 4',
  49. items.filter(it => it.keyword === 'dihydromyricetin' && it.rating >= 4)
  50. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 4));
  51. // ===== 2.3 水飞蓟反面 =====
  52. printList('【2.3】XHS "水飞蓟" + 怀疑/副作用关键词',
  53. items.filter(it => it.keyword === '水飞蓟' &&
  54. /效果寥寥|没效|不行|差远|双环醇|尿.*变|变橙|胃疼|不知道|是不是|进口|医生/i.test(it.content || ''))
  55. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  56. printList('【2.3】XHS "奶蓟草" + 花/野花/苏格兰',
  57. items.filter(it => it.keyword === '奶蓟草' && /花|蓟|野花|苏格兰|止血/i.test(it.content || ''))
  58. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  59. printList('【2.3】Amazon milk thistle ★≤2 top 3',
  60. items.filter(it => it.keyword === 'milk thistle' && it.rating <= 2)
  61. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  62. printList('【2.3】Amazon silymarin ★≤2 top 2',
  63. items.filter(it => it.keyword === 'silymarin' && it.rating <= 2)
  64. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  65. // ===== 3.2 五大竞品 =====
  66. printList('【3.2】Swisse top 2', items.filter(it => it.keyword === 'swisse护肝片').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  67. printList('【3.2】易善复 top 2', items.filter(it => it.keyword === '易善复').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  68. printList('【3.2】葵花护肝片 top 2', items.filter(it => it.keyword === '葵花护肝片').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  69. printList('【3.2】片仔癀护肝 top 2', items.filter(it => it.keyword === '片仔癀护肝').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  70. printList('【3.2】海王金樽 top 2', items.filter(it => it.keyword === '海王金樽').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 2));
  71. printList('【3.2】汤臣倍健护肝 top 1', items.filter(it => it.keyword === '汤臣倍健护肝').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 1));
  72. // ===== 3.3 三大人群 =====
  73. printList('【3.3A】脂肪肝 top 3',
  74. items.filter(it => it.keyword === '脂肪肝').sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  75. printList('【3.3B】熬夜伤肝/熬夜护肝 top 3',
  76. items.filter(it => it.keyword === '熬夜伤肝' || it.keyword === '熬夜护肝')
  77. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  78. printList('【3.3C】应酬解酒/解酒神器/酒局必备 top 3',
  79. items.filter(it => /应酬|解酒|酒局/.test(it.keyword || ''))
  80. .sort((a, b) => (b.likes || 0) - (a.likes || 0)).slice(0, 3));
  81. console.log('\n\n========== 数据质量自检 ==========');
  82. // 检查重复问题
  83. const dupMap = new Map();
  84. for (const it of items) {
  85. const key = `${it.platform}|${it.content?.slice(0, 50)}`;
  86. dupMap.set(key, (dupMap.get(key) || 0) + 1);
  87. }
  88. const dups = [...dupMap.entries()].filter(([k, v]) => v > 1).sort((a, b) => b[1] - a[1]);
  89. console.log(`重复评论(前 50 字相同): ${dups.length} 组`);
  90. console.log('Top 5 重复条目:');
  91. dups.slice(0, 5).forEach(([k, v]) => console.log(` ×${v} ${k.slice(0, 120)}`));
  92. // 空内容
  93. const empty = items.filter(it => !(it.content || '').trim());
  94. console.log(`\n空内容: ${empty.length}`);
  95. // 平台统计
  96. const byPlatform = {};
  97. for (const it of items) byPlatform[it.platform] = (byPlatform[it.platform] || 0) + 1;
  98. console.log('\n平台分布:', byPlatform);
  99. // 评论内容和 keyword 的 "相关性" 启发式
  100. function topicHit(it) {
  101. const c = it.content || '';
  102. const kw = it.keyword || '';
  103. // 如果评论字数 < 4 或 只有表情符号 视为无内容
  104. if (c.replace(/\s/g, '').length < 4) return 'too-short';
  105. // 如果评论里出现了 kw 的任何片段 视为相关
  106. if (kw && kw.length >= 2) {
  107. const parts = kw.split(/护肝|片|解酒/).filter(Boolean);
  108. for (const p of parts) { if (p.length >= 2 && c.includes(p)) return 'kw-hit'; }
  109. if (c.includes(kw)) return 'kw-hit';
  110. }
  111. return 'may-drift';
  112. }
  113. const hitStats = { 'kw-hit': 0, 'too-short': 0, 'may-drift': 0 };
  114. for (const it of items) hitStats[topicHit(it)]++;
  115. console.log('\n相关性启发式:', hitStats);
  116. console.log(` kw-hit = 评论文本直接提到 keyword 片段`);
  117. console.log(` too-short = 评论长度 < 4 字符(表情/短回复)`);
  118. console.log(` may-drift = 长评论但可能跑题`);