#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawnSync } = require('child_process'); const root = path.resolve(__dirname, '..'); function main() { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-intake-field-checklist-')); const dataPack = path.join(tmp, 'data'); const videoPack = path.join(tmp, 'video'); const output = path.join(tmp, 'output'); fs.mkdirSync(dataPack, { recursive: true }); fs.mkdirSync(videoPack, { recursive: true }); fs.writeFileSync(path.join(dataPack, 'history-data-template.csv'), [ 'brief编号,项目名称,类目,客户原始Brief,历史人工补号量基线,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因', 'brief-001,示例项目,母婴,填写客户原始 Brief,6,https://example.com/reference-video,小红书,示例博主,https://example.com/profile,可直接发客户,待客户反馈,' ].join('\n'), 'utf8'); fs.writeFileSync(path.join(dataPack, 'manual-review-template.csv'), [ 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型,反馈原因,本轮人工补号量', 'brief-001,result-first-broad,1,小红书,示例博主,86,82,78,80,79,81,4,0,命中 Brief,需复核,https://example.com/profile,商务复核,待客户反馈,,,3' ].join('\n'), 'utf8'); fs.writeFileSync(path.join(videoPack, 'video-resource-template.csv'), [ 'brief编号,项目名称,平台,资源角色,博主名称,主页链接,视频链接,封面链接,字幕或ASR文本,帧图链接,标题,发布时间,内容摘要,风格调性标签,画面人设场景信号,风险提示,来源接口或备注,是否真实资源', 'brief-001,示例项目,小红书,参考视频,示例博主,https://example.com/profile,,https://example.com/cover.jpg,待补ASR,,示例标题,,填写内容摘要,待补调性,,待确认,人工待补,是' ].join('\n'), 'utf8'); const result = spawnSync(process.execPath, [ path.join(root, 'scripts', 'intake-field-checklist.js'), '--data-pack', dataPack, '--video-pack', videoPack, '--output', output ], { cwd: root, encoding: 'utf8' }); if (result.stdout) process.stdout.write(result.stdout); if (result.stderr) process.stderr.write(result.stderr); assert(result.status === 0, 'field checklist should run'); const summary = JSON.parse(fs.readFileSync(path.join(output, 'intake-field-checklist-summary.json'), 'utf8')); const report = fs.readFileSync(path.join(output, 'intake-field-checklist.md'), 'utf8').replace(/^\uFEFF/, ''); const csv = fs.readFileSync(path.join(output, 'intake-field-checklist.csv'), 'utf8').replace(/^\uFEFF/, ''); assert(summary.passed === true, 'summary should pass as checklist generated'); assert(summary.directCustomerProof === false, 'summary should preserve proof boundary'); assert(summary.proofLevel === 'smoke_or_local', 'summary should keep local proof level'); assert(summary.itemCount >= 10, 'summary should include field-level checklist items'); assert(summary.missingRequiredCount >= 4, 'summary should include missing required fields and minimum rows'); assert(summary.placeholderCount >= 5, 'summary should include placeholder items'); assert(Array.isArray(summary.ownerGroups) && summary.ownerGroups.length >= 3, 'summary should include owner grouped repair order'); assert(summary.ownerGroups.some(item => item.owner === '商务' && item.section === 'history' && item.nextAction), 'summary should include business history repair group'); assert(summary.ownerGroups.some(item => item.owner === '商务/投放' && item.section === 'video' && item.nextAction), 'summary should include media video repair group'); assert(Array.isArray(summary.recheckCommands) && summary.recheckCommands.some(item => item.command.includes('intake:readiness')), 'summary should include intake readiness recheck command'); assert(summary.recheckCommands.some(item => item.command.includes('round:refresh')), 'summary should include round refresh recheck command'); assert(summary.items.some(item => item.issueType === 'missing_minimum_row' && item.section === 'history'), 'summary should include missing history rows'); assert(summary.items.some(item => item.section === 'video' && item.field === '整行'), 'summary should include missing real candidate video'); assert(report.includes('# 真实材料字段级补齐核对表'), 'report should be Chinese'); assert(report.includes('directCustomerProof:false'), 'report should show proof boundary'); assert(report.includes('## 按负责人补齐顺序'), 'report should include owner grouped repair order'); assert(report.includes('## 补齐后复验命令'), 'report should include recheck commands'); assert(report.includes('npm run intake:readiness'), 'report should include intake readiness command'); assert(report.includes('npm run round:refresh'), 'report should include round refresh command'); assert(csv.includes('负责人,区域,问题类型'), 'csv should use software-friendly Chinese columns'); assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report + csv), 'outputs should not contain model tokens'); assert(!/r:[A-Za-z0-9]{20,}/.test(report + csv), 'outputs should not contain session tokens'); console.log(JSON.stringify({ ok: true, output, itemCount: summary.itemCount, missingRequiredCount: summary.missingRequiredCount, placeholderCount: summary.placeholderCount }, null, 2)); } function assert(condition, message) { if (!condition) throw new Error(message); } main();