#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { enrichCriteriaWithExperience } = require('../mcp/src/features/tihao-sourcing/experience-rules'); const { rankCreators } = require('../mcp/src/features/tihao-sourcing/ranker'); const { buildSourcingReport } = require('../mcp/src/features/tihao-sourcing/report'); function main() { const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-hard-rules-')); const criteria = enrichCriteriaWithExperience({ brand: 'Ulike', rawText: 'Ulike 脱毛仪,偏女性精致护理场景,不要竞品X,预算 3000-10000。', category: '美容仪/脱毛仪', platforms: ['xiaohongshu'], keywords: ['脱毛仪', '护肤', '女性', '精致'], stylePreferences: ['女性', '精致', '护肤', '真人出镜'], referenceSignals: ['女性', '护肤', '护理'], exclusions: ['竞品X'], performanceRequirements: ['近30天需有更新', '近30天平均点赞≥100', '复核数据真实性/重复评论风险'], fanRange: { min: 10000, max: 500000 }, budgetRange: { min: 3000, max: 10000 }, targetCount: 3 }); const ranked = rankCreators([ { platform: 'xiaohongshu', platformUserId: 'good-female-care', displayName: '精致护理真实记录', profileUrl: 'https://www.xiaohongshu.com/user/profile/good-female-care', fansCount: 68000, minPrice: 8000, contentTags: ['脱毛仪', '护肤', '女性', '精致', '真人出镜'], personaTags: ['美妆', '生活方式', '护理'], recentEvidence: ['近期内容是女性精致护理和脱毛仪真实体验'] }, { platform: 'xiaohongshu', platformUserId: 'male-generic', displayName: '男生游戏汽车日常', profileUrl: 'https://www.xiaohongshu.com/user/profile/male-generic', fansCount: 90000, minPrice: 7000, contentTags: ['男生', '游戏', '汽车', '脱毛仪'], personaTags: ['男性泛账号', '体育'], recentEvidence: ['近期以男生游戏汽车内容为主,偶尔提到脱毛仪'] }, { platform: 'xiaohongshu', platformUserId: 'competitor-account', displayName: '竞品X护理测评号', profileUrl: 'https://www.xiaohongshu.com/user/profile/competitor-account', fansCount: 75000, minPrice: 8500, contentTags: ['脱毛仪', '女性', '精致', '竞品X'], personaTags: ['美妆', '护理'], recentEvidence: ['近期多次出现竞品X合作内容'] }, { platform: 'xiaohongshu', platformUserId: 'over-budget', displayName: '高报价精致护理号', profileUrl: 'https://www.xiaohongshu.com/user/profile/over-budget', fansCount: 82000, minPrice: 30000, contentTags: ['脱毛仪', '护肤', '女性', '精致'], personaTags: ['美妆', '生活方式'], recentEvidence: ['近期内容匹配女性护理,但报价明显高于预算'] }, { platform: 'xiaohongshu', platformUserId: 'low-homepage-quality', displayName: '低质封面护理号', profileUrl: 'https://www.xiaohongshu.com/user/profile/low-homepage-quality', fansCount: 86000, minPrice: 7000, contentTags: ['脱毛仪', '护肤', '女性', '精致', '真人出镜'], personaTags: ['美妆', '生活方式', '护理'], recentEvidence: ['近期内容是女性精致护理和脱毛仪真实体验,但封面下沉、封面混乱、排版混乱'] }, { platform: 'xiaohongshu', platformUserId: 'stale-fake-data', displayName: '停更数据异常护理号', profileUrl: 'https://www.xiaohongshu.com/user/profile/stale-fake-data', fansCount: 72000, minPrice: 6500, recentAverageLikes: 72, lastPostDaysAgo: 46, repeatedCommenterCount: 3, contentTags: ['脱毛仪', '护肤', '女性', '精致', '真人出镜'], personaTags: ['美妆', '生活方式', '护理'], recentEvidence: ['近一个月没有发,平均点赞低于100,同一个人评论多篇,封面统一但数据真实性存疑'] }, { platform: 'douyin', platformUserId: 'wrong-platform', displayName: '错平台优质护理号', profileUrl: 'https://www.douyin.com/user/wrong-platform', fansCount: 60000, minPrice: 7000, contentTags: ['脱毛仪', '女性', '护肤', '精致'], personaTags: ['美妆'], recentEvidence: ['平台不符合 brief'] } ], criteria); const byId = Object.fromEntries(ranked.map(item => [item.platformUserId, item])); assert(!byId['wrong-platform'], 'wrong platform creator should be filtered before ranking'); assert(byId['good-female-care'].recommendStatus === '强推荐', 'matching female care creator should be strong recommendation'); assert(byId['male-generic'].recommendStatus !== '强推荐', 'male generic account should not be strong recommendation for beauty device'); assert((byId['male-generic'].implicitRuleViolations || []).length > 0, 'male generic account should carry implicit rule violation'); assert(byId['competitor-account'].recommendStatus === '已剔除', 'explicit competitor exclusion should be removed'); assert((byId['competitor-account'].exclusionHits || []).includes('竞品X'), 'competitor account should record exclusion hit'); assert(byId['over-budget'].recommendStatus !== '强推荐', 'over-budget creator should not be strong recommendation'); assert(String(byId['over-budget'].riskNote || '').includes('报价高于预算'), 'over-budget creator should carry budget risk note'); assert(byId['low-homepage-quality'].recommendStatus !== '强推荐', 'low homepage quality creator should not be strong recommendation'); assert((byId['low-homepage-quality'].homepageQualityRisks || []).includes('封面下沉'), 'low homepage quality creator should carry visual risk signals'); assert(String(byId['low-homepage-quality'].riskNote || '').includes('主页质感风险'), 'low homepage quality creator should explain homepage quality risk'); assert(byId['stale-fake-data'].recommendStatus !== '强推荐', 'stale/fake data creator should not be strong recommendation'); assert((byId['stale-fake-data'].homepageDataRisks || []).some(item => item.includes('近30天')), 'stale creator should carry recent update data risk'); assert(String(byId['stale-fake-data'].riskNote || '').includes('数据真实性/活跃风险'), 'stale creator should explain data authenticity risk'); assert((byId['stale-fake-data'].manualReviewFields || []).includes('近30天更新'), 'stale creator should require recent update review'); assert((byId['stale-fake-data'].manualReviewFields || []).includes('平均点赞'), 'stale creator should require average-like review'); assert((byId['stale-fake-data'].manualReviewFields || []).includes('重复评论/数据真实性'), 'stale creator should require repeated-comment review'); const report = buildSourcingReport({ criteria, candidates: ranked, collectionMode: 'sample', outputDir }); const csvPath = path.join(outputDir, 'tihao-sourcing-client-list.csv'); const csv = fs.readFileSync(csvPath, 'utf8'); assert(!csv.includes('竞品X护理测评号'), 'excluded competitor should not enter software CSV'); assert(!report.data.candidates.some(item => item.recommendStatus === '已剔除'), 'excluded creators should not enter client-ready candidates'); console.log(JSON.stringify({ ok: true, outputDir, strong: ranked.filter(item => item.recommendStatus === '强推荐').length, excluded: ranked.filter(item => item.recommendStatus === '已剔除').length }, null, 2)); } function assert(condition, message) { if (!condition) throw new Error(message); } main();