reference-baselines.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. const fs = require('fs');
  2. const path = require('path');
  3. function attachReferenceBaselines(criteria, input = {}) {
  4. const baselines = loadReferenceBaselines(input);
  5. const providerStatus = input.referenceProviderStatus || {};
  6. if (!baselines.length) {
  7. return {
  8. ...criteria,
  9. referenceBaselines: [],
  10. referenceEvidenceStatus: buildEvidenceStatus(criteria, [], providerStatus)
  11. };
  12. }
  13. return {
  14. ...criteria,
  15. referenceBaselines: baselines,
  16. referenceBaselineSignals: [...new Set(baselines.flatMap(item => item.topSignals || []))],
  17. referenceEvidenceStatus: buildEvidenceStatus(criteria, baselines, providerStatus)
  18. };
  19. }
  20. function loadReferenceBaselines(input = {}) {
  21. const fromInput = Array.isArray(input.referenceBaselines) ? input.referenceBaselines : [];
  22. const baselinePath = input.referenceBaselinePath || input.referenceBaselinesPath || '';
  23. const fromPath = readBaselineFile(baselinePath);
  24. return dedupe([...fromInput, ...fromPath].map(normalizeBaseline).filter(Boolean));
  25. }
  26. function readBaselineFile(file) {
  27. if (!file) return [];
  28. const resolved = path.resolve(file);
  29. if (!fs.existsSync(resolved)) return [];
  30. try {
  31. const parsed = JSON.parse(fs.readFileSync(resolved, 'utf8'));
  32. if (Array.isArray(parsed)) return parsed;
  33. if (Array.isArray(parsed.referenceBaselines)) return parsed.referenceBaselines;
  34. if (Array.isArray(parsed.data)) return parsed.data;
  35. return [];
  36. } catch {
  37. return [];
  38. }
  39. }
  40. function normalizeBaseline(item) {
  41. if (!item || typeof item !== 'object') return null;
  42. const topSignals = normalizeSignals(item.topSignals || item.signals || item.referenceSignals || []);
  43. return {
  44. bucket: String(item.bucket || item.referenceBucket || '参考账号'),
  45. platform: item.platform || 'xiaohongshu',
  46. name: String(item.name || item.nickname || item.displayName || '').trim(),
  47. redId: String(item.redId || item.red_id || '').trim(),
  48. userId: String(item.userId || item.user_id || '').trim(),
  49. fans: Number(item.fans || item.fansCount || item.fansNum || 0),
  50. location: String(item.location || item.ipLocation || ''),
  51. profileDesc: String(item.profileDesc || item.desc || ''),
  52. sampleCount: Number(item.sampleCount || item.notesCount || 0),
  53. typeMix: String(item.typeMix || ''),
  54. collectMedian: toNullableNumber(item.collectMedian),
  55. commentMedian: toNullableNumber(item.commentMedian),
  56. topSignals,
  57. sampleTitles: normalizeTextList(item.sampleTitles || item.titles || []),
  58. source: item.source || 'reference-baseline-cache'
  59. };
  60. }
  61. function normalizeSignals(values) {
  62. return normalizeTextList(values)
  63. .map(item => item.replace(/\(\d+\)\s*$/, '').trim())
  64. .filter(Boolean);
  65. }
  66. function normalizeTextList(values) {
  67. return (Array.isArray(values) ? values : [values])
  68. .flatMap(item => String(item || '').split(/[、,;;]/))
  69. .map(item => item.trim())
  70. .filter(Boolean);
  71. }
  72. function buildEvidenceStatus(criteria, baselines, providerStatus = {}) {
  73. const linkCount = (criteria.referenceLinks || []).length;
  74. const status = baselines.length ? 'baseline_loaded' : linkCount ? 'needs_social_analysis_enrichment' : 'no_reference_links';
  75. return {
  76. referenceLinkCount: linkCount,
  77. baselineCount: baselines.length,
  78. status,
  79. providerRequested: Boolean(providerStatus.requested),
  80. providerStatus: providerStatus.status || 'not_requested',
  81. providerHttpStatus: providerStatus.httpStatus || 0,
  82. providerWarning: providerStatus.warning || '',
  83. note: providerStatus.warning || (baselines.length
  84. ? '已加载参考账号基线,可用于相似度与报告复核。'
  85. : linkCount
  86. ? '已识别参考链接,但尚未通过社媒分析服务加载账号详情。'
  87. : 'Brief 未提供明确参考链接。')
  88. };
  89. }
  90. function dedupe(items) {
  91. const seen = new Set();
  92. const result = [];
  93. for (const item of items) {
  94. const key = `${item.platform}:${item.userId || item.redId || item.name}:${item.bucket}`;
  95. if (seen.has(key)) continue;
  96. seen.add(key);
  97. result.push(item);
  98. }
  99. return result;
  100. }
  101. function toNullableNumber(value) {
  102. const number = Number(value);
  103. return Number.isFinite(number) ? number : null;
  104. }
  105. module.exports = {
  106. attachReferenceBaselines,
  107. loadReferenceBaselines,
  108. normalizeBaseline
  109. };