evidence-quality.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. function asArray(value) {
  2. return Array.isArray(value) ? value : [];
  3. }
  4. function hasSourceUrl(item) {
  5. return Boolean(item && (item.sourceUrl || item.webUrl || item.url));
  6. }
  7. function hasImage(item) {
  8. return Boolean(
  9. item &&
  10. (item.localCoverPath ||
  11. item.coverUrl ||
  12. (Array.isArray(item.imageUrls) && item.imageUrls.length))
  13. );
  14. }
  15. function hasText(item) {
  16. return Boolean(
  17. item &&
  18. String(item.contentExcerpt || item.rawExcerpt || item.content || item.desc || item.title || '').trim()
  19. );
  20. }
  21. function assessEvidenceQuality({ items, comments, itemLabel = '样本', collectionMode = 'sample' }) {
  22. const rows = asArray(items);
  23. const commentRows = asArray(comments);
  24. const sourceUrlCount = rows.filter(hasSourceUrl).length;
  25. const imageCount = rows.filter(hasImage).length;
  26. const textCount = rows.filter(hasText).length;
  27. const highLikeCommentCount = commentRows.filter(comment => Number(comment.likeCount || 0) > 0).length;
  28. let score = 0;
  29. if (rows.length >= 8) score += 2;
  30. else if (rows.length >= 3) score += 1;
  31. if (commentRows.length >= 50) score += 2;
  32. else if (commentRows.length >= 10) score += 1;
  33. if (sourceUrlCount >= Math.min(rows.length, 3)) score += 1;
  34. if (imageCount >= Math.min(rows.length, 3)) score += 1;
  35. if (highLikeCommentCount >= 3) score += 1;
  36. if (textCount >= Math.min(rows.length, 3)) score += 1;
  37. const grade = score >= 7 ? 'A' : score >= 4 ? 'B' : 'C';
  38. const canMakeBusinessDecision = grade === 'A' || (grade === 'B' && collectionMode === 'live');
  39. const recommendation = canMakeBusinessDecision
  40. ? '可以作为第一轮经营判断依据,但仍建议结合门店/销售/私域反馈校准。'
  41. : '只适合作为初步机会假设,不建议直接当最终经营结论。';
  42. return {
  43. grade,
  44. itemLabel,
  45. itemCount: rows.length,
  46. commentCount: commentRows.length,
  47. sourceUrlCount,
  48. imageCount,
  49. textCount,
  50. highLikeCommentCount,
  51. canMakeBusinessDecision,
  52. recommendation
  53. };
  54. }
  55. function buildEvidenceQualitySection(quality) {
  56. return [
  57. '## 证据质量门槛',
  58. '',
  59. `- 本轮证据质量:${quality.grade}`,
  60. `- ${quality.itemLabel}数量:${quality.itemCount}`,
  61. `- 评论数量:${quality.commentCount}`,
  62. `- 含原文/原视频链接:${quality.sourceUrlCount}`,
  63. `- 含图片/封面:${quality.imageCount}`,
  64. `- 含文本摘录:${quality.textCount}`,
  65. `- 含点赞评论:${quality.highLikeCommentCount}`,
  66. `- 是否适合直接做经营判断:${quality.canMakeBusinessDecision ? '适合第一轮判断' : '仅适合初步假设'}`,
  67. `- 使用建议:${quality.recommendation}`
  68. ];
  69. }
  70. function buildNoSampleFallbackMessage({ platformLabel, profile, examples = [] }) {
  71. const keywords = asArray(profile && profile.keywords).slice(0, 5).join('、') || '当前关键词';
  72. const exampleLines = examples.length ? examples.map(item => `- ${item}`) : [
  73. '- 换成更具体的品类词、场景词或城市词',
  74. '- 从产品词扩展到用户真实说法',
  75. '- 先用 sample 模式演示完整流程,但明确标注非真实采集'
  76. ];
  77. return [
  78. `本次 live 模式没有采到可用于分析的${platformLabel}样本。`,
  79. '',
  80. '这不代表行业没有机会,更可能是关键词过窄、平台内容表达不一样,或本轮上游没有返回可用内容。',
  81. '',
  82. '建议按这三步继续:',
  83. `1. 先保留当前方向:${keywords}`,
  84. '2. 自动换一组更贴近用户说法的关键词再试一次',
  85. '3. 如果仍然没有样本,降级到相关大词做第一轮机会假设,并明确标注“非真实采集/待验证”',
  86. '',
  87. '可尝试的关键词方向:',
  88. ...exampleLines
  89. ].join('\n');
  90. }
  91. module.exports = {
  92. assessEvidenceQuality,
  93. buildEvidenceQualitySection,
  94. buildNoSampleFallbackMessage
  95. };