| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { buildSourcingReport } = require('../mcp/src/features/tihao-sourcing/report');
- function main() {
- const root = path.resolve(__dirname, '..');
- const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-candidate-pool-'));
- const fullPool = buildSourcingReport({
- criteria: {
- briefId: '候选池验收',
- brand: '候选池验收',
- targetCount: 4,
- platforms: ['xiaohongshu']
- },
- candidates: sampleCandidates(),
- collectionMode: 'sample',
- outputDir: path.join(outputRoot, 'full')
- });
- assert(fullPool.summary.reviewPoolTarget === 6, 'targetCount=4 should require a 1.5x review pool of 6');
- assert(fullPool.summary.clientReadyCount === 6, 'software table should output 6 reviewable candidates');
- assert(fullPool.summary.duplicateCount === 1, 'duplicate homepage URL should be reported');
- assertNoDuplicateSoftwareRows(path.join(outputRoot, 'full', 'tihao-sourcing-client-list.csv'));
- const shortPool = buildSourcingReport({
- criteria: {
- briefId: '候选池不足验收',
- brand: '候选池不足验收',
- targetCount: 10,
- platforms: ['xiaohongshu']
- },
- candidates: sampleCandidates(),
- collectionMode: 'sample',
- outputDir: path.join(outputRoot, 'short')
- });
- const shortJson = JSON.parse(fs.readFileSync(path.join(outputRoot, 'short', 'tihao-sourcing-result.json'), 'utf8'));
- assert(shortPool.summary.reviewPoolTarget === 15, 'targetCount=10 should require a 1.5x review pool of 15');
- assert(shortPool.summary.clientReadyCount === 7, 'short software table should keep all available non-excluded candidates');
- assert(shortJson.warnings.some(item => item.includes('1.5 倍候选池 15 位')), 'short pool should warn about 1.5x candidate pool gap');
- assert(shortPool.summary.calibrationQuestions.some(item => item.includes('1.5 倍候选池')), 'calibration question should mention 1.5x pool');
- console.log(JSON.stringify({
- ok: true,
- outputRoot,
- fullPool: fullPool.summary,
- shortPool: shortPool.summary
- }, null, 2));
- }
- function sampleCandidates() {
- return [
- creator('账号A', 88, 'https://example.com/a'),
- creator('账号A-高分重复', 93, 'https://example.com/a'),
- creator('账号B', 87, 'https://example.com/b'),
- creator('账号C', 86, 'https://example.com/c'),
- creator('账号D', 85, 'https://example.com/d'),
- creator('账号E', 84, 'https://example.com/e'),
- creator('账号F', 83, 'https://example.com/f'),
- creator('账号G', 82, 'https://example.com/g')
- ];
- }
- function creator(displayName, score, profileUrl) {
- return {
- platform: 'xiaohongshu',
- displayName,
- profileUrl,
- score,
- briefFitScore: score,
- referenceStyleFitScore: 80,
- recentContentFitScore: 78,
- visualQualityScore: 76,
- toneConsistencyScore: 79,
- recommendStatus: score >= 86 ? '强推荐' : '备选',
- recommendReason: `${displayName} 命中 Brief 核心人群和参考风格。`,
- riskNote: '需商务复核报价有效期。'
- };
- }
- function assertNoDuplicateSoftwareRows(csvPath) {
- const lines = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '').trim().split(/\r?\n/);
- const header = lines[0].split(',');
- const rows = lines.slice(1).map(line => parseCsvLine(line, header));
- const keys = new Set();
- for (const row of rows) {
- const briefId = pick(row, ['brief编号']);
- const platform = pick(row, ['平台']);
- const profileUrl = pick(row, ['主页链接']);
- const displayName = pick(row, ['账号名称', '博主名称']);
- const key = profileUrl
- ? `${briefId}|${platform}|${profileUrl.toLowerCase()}`
- : `${briefId}|${platform}|${displayName.toLowerCase()}`;
- assert(!keys.has(key), `duplicate software row key: ${key}`);
- keys.add(key);
- }
- assert(rows.map(row => Number(pick(row, ['序号', '排名']))).join(',') === '1,2,3,4,5,6', 'software rows should be reranked continuously');
- }
- function pick(row, keys) {
- for (const key of keys) {
- if (row[key] !== undefined && row[key] !== '') return row[key];
- }
- return '';
- }
- function parseCsvLine(line, header) {
- const cells = [];
- let current = '';
- let quoted = false;
- for (let i = 0; i < line.length; i += 1) {
- const ch = line[i];
- if (ch === '"' && quoted && line[i + 1] === '"') {
- current += '"';
- i += 1;
- } else if (ch === '"') {
- quoted = !quoted;
- } else if (ch === ',' && !quoted) {
- cells.push(current);
- current = '';
- } else {
- current += ch;
- }
- }
- cells.push(current);
- return Object.fromEntries(header.map((key, index) => [key, cells[index] || '']));
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- main();
|