const fs = require('fs'); const path = require('path'); function attachReferenceBaselines(criteria, input = {}) { const baselines = loadReferenceBaselines(input); const providerStatus = input.referenceProviderStatus || {}; if (!baselines.length) { return { ...criteria, referenceBaselines: [], referenceEvidenceStatus: buildEvidenceStatus(criteria, [], providerStatus) }; } return { ...criteria, referenceBaselines: baselines, referenceBaselineSignals: [...new Set(baselines.flatMap(item => item.topSignals || []))], referenceEvidenceStatus: buildEvidenceStatus(criteria, baselines, providerStatus) }; } function loadReferenceBaselines(input = {}) { const fromInput = Array.isArray(input.referenceBaselines) ? input.referenceBaselines : []; const baselinePath = input.referenceBaselinePath || input.referenceBaselinesPath || ''; const fromPath = readBaselineFile(baselinePath); return dedupe([...fromInput, ...fromPath].map(normalizeBaseline).filter(Boolean)); } function readBaselineFile(file) { if (!file) return []; const resolved = path.resolve(file); if (!fs.existsSync(resolved)) return []; try { const parsed = JSON.parse(fs.readFileSync(resolved, 'utf8')); if (Array.isArray(parsed)) return parsed; if (Array.isArray(parsed.referenceBaselines)) return parsed.referenceBaselines; if (Array.isArray(parsed.data)) return parsed.data; return []; } catch { return []; } } function normalizeBaseline(item) { if (!item || typeof item !== 'object') return null; const topSignals = normalizeSignals(item.topSignals || item.signals || item.referenceSignals || []); return { bucket: String(item.bucket || item.referenceBucket || '参考账号'), platform: item.platform || 'xiaohongshu', name: String(item.name || item.nickname || item.displayName || '').trim(), redId: String(item.redId || item.red_id || '').trim(), userId: String(item.userId || item.user_id || '').trim(), fans: Number(item.fans || item.fansCount || item.fansNum || 0), location: String(item.location || item.ipLocation || ''), profileDesc: String(item.profileDesc || item.desc || ''), sampleCount: Number(item.sampleCount || item.notesCount || 0), typeMix: String(item.typeMix || ''), collectMedian: toNullableNumber(item.collectMedian), commentMedian: toNullableNumber(item.commentMedian), topSignals, sampleTitles: normalizeTextList(item.sampleTitles || item.titles || []), source: item.source || 'reference-baseline-cache' }; } function normalizeSignals(values) { return normalizeTextList(values) .map(item => item.replace(/\(\d+\)\s*$/, '').trim()) .filter(Boolean); } function normalizeTextList(values) { return (Array.isArray(values) ? values : [values]) .flatMap(item => String(item || '').split(/[、,;;]/)) .map(item => item.trim()) .filter(Boolean); } function buildEvidenceStatus(criteria, baselines, providerStatus = {}) { const linkCount = (criteria.referenceLinks || []).length; const status = baselines.length ? 'baseline_loaded' : linkCount ? 'needs_social_analysis_enrichment' : 'no_reference_links'; return { referenceLinkCount: linkCount, baselineCount: baselines.length, status, providerRequested: Boolean(providerStatus.requested), providerStatus: providerStatus.status || 'not_requested', providerHttpStatus: providerStatus.httpStatus || 0, providerWarning: providerStatus.warning || '', note: providerStatus.warning || (baselines.length ? '已加载参考账号基线,可用于相似度与报告复核。' : linkCount ? '已识别参考链接,但尚未通过社媒分析服务加载账号详情。' : 'Brief 未提供明确参考链接。') }; } function dedupe(items) { const seen = new Set(); const result = []; for (const item of items) { const key = `${item.platform}:${item.userId || item.redId || item.name}:${item.bucket}`; if (seen.has(key)) continue; seen.add(key); result.push(item); } return result; } function toNullableNumber(value) { const number = Number(value); return Number.isFinite(number) ? number : null; } module.exports = { attachReferenceBaselines, loadReferenceBaselines, normalizeBaseline };