#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawnSync } = require('child_process'); const { buildLocalSeedToIntakeWorklist } = require('./local-seed-to-intake-worklist'); const ROOT = path.resolve(__dirname, '..'); function main() { const fakeSeedIndex = { proofLevel: 'not_business_proof', directCustomerProof: false, canCloseProofGap: false, materialCount: 2, existingMaterialCount: 2, materials: [ { id: 'dha-brief-workbook', path: '../dha_brief.xlsx', exists: true, workbook: { sheets: [ { name: '参考链接解析', sampleRows: [ ['视频', 'http://xhslink.com/o/example', 'note-1', '参考妈妈', 'uid-1', 'xhs-1', '', '', '已解析', 'https://www.xiaohongshu.com/discovery/item/note-1?xsec_token=secret#frag'] ] } ] } }, { id: 'dha-poc-recommendation-workbook', path: '../output/dha-tihao-poc/poc.xlsx', exists: true, workbook: { sheets: [ { name: '主推账号', sampleRows: [ ['主推', '小爱十月', 'aixiaolan94', '山东 潍坊', '30454', '3800', '4500', '视频', '4500', '172419', '6545', '0.026'], ['主推', '我是在在吖', 'wangshuyun711', '浙江 宁波', '16631', '2400', '3200', '图文', '2400', '7475', '815', '0.321'] ] }, { name: '备选复核', sampleRows: [ ['备选/补充', '育婴师 Joyce', '567679811', '安徽 亳州', '26297', '4000', '4800', '图文', '4000', '18964', '0', '0.211'] ] } ] } } ] }; const summary = buildLocalSeedToIntakeWorklist({ root: ROOT, seedIndex: fakeSeedIndex }); assert(summary.proofLevel === 'not_business_proof', 'worklist should stay not_business_proof'); assert(summary.directCustomerProof === false, 'worklist should not be direct customer proof'); assert(summary.canCloseProofGap === false, 'worklist should not close proof gaps'); assert(summary.historyDraftRows.length === 3, 'worklist should expand candidate seeds into history draft rows'); assert(summary.reviewDraftRows.length === 3, 'worklist should create manual review draft rows'); assert(summary.referenceSeedRows.length === 1, 'worklist should extract reference seed rows'); assert(summary.referenceSeedRows[0].finalLink === 'https://www.xiaohongshu.com/discovery/item/note-1', 'worklist should sanitize reference URLs'); assert(summary.candidateSeedRows.length === 3, 'worklist should extract candidate seed rows'); assert(summary.proofGapMaterialBridge.length === 3, 'worklist should map seed material to the three real proof gaps'); assert(summary.videoWorklistRows.some(row => row.role === '候选视频' && row.creatorName === '小爱十月'), 'worklist should create candidate video fill row'); assert(summary.missingProofRequirements.includes('customer-effect:audit overallPass=true'), 'worklist should keep customer-effect missing proof'); const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-seed-worklist-')); const output = path.join(temp, 'worklist'); const result = spawnSync(process.execPath, [ 'scripts/local-seed-to-intake-worklist.js', '--workspace-root', path.join(temp, 'workspace'), '--outputs', path.join(temp, 'outputs'), '--output', output, '--strict' ], { cwd: ROOT, encoding: 'utf8', shell: false }); if (result.stdout) process.stdout.write(result.stdout); if (result.stderr) process.stderr.write(result.stderr); assert(result.status === 0, 'CLI should pass even when no seed files exist'); const cliSummary = readJson(path.join(output, 'local-seed-to-intake-worklist-summary.json')); const report = fs.readFileSync(path.join(output, 'local-seed-to-intake-worklist.md'), 'utf8'); const csv = fs.readFileSync(path.join(output, 'local-seed-to-intake-worklist.csv'), 'utf8'); assert(cliSummary.proofLevel === 'not_business_proof', 'CLI summary should keep proof boundary'); assert(cliSummary.canCloseProofGap === false, 'CLI summary should not close proof gap'); assert(report.includes('本地种子转 intake 补表 worklist'), 'report should render title'); assert(csv.includes('类型'), 'unified CSV should render headers'); assert(fs.existsSync(path.join(output, 'history-intake-draft.csv')), 'CLI should write history draft CSV'); assert(fs.existsSync(path.join(output, 'manual-review-draft.csv')), 'CLI should write manual review draft CSV'); assert(fs.existsSync(path.join(output, 'video-resource-worklist.csv')), 'CLI should write video worklist CSV'); assert(fs.existsSync(path.join(output, 'candidate-seed-worklist.csv')), 'CLI should write candidate worklist CSV'); assert(!report.includes('xsec_token'), 'report should not leak xsec token'); assert(!csv.includes('xsec_token'), 'csv should not leak xsec token'); console.log(JSON.stringify({ ok: true, historyDraftRows: summary.historyDraftRows.length, reviewDraftRows: summary.reviewDraftRows.length, referenceSeedRows: summary.referenceSeedRows.length, candidateSeedRows: summary.candidateSeedRows.length, cliHistoryDraftRows: cliSummary.historyDraftRows.length }, null, 2)); } function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); } function assert(condition, message) { if (!condition) throw new Error(message); } main();