| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- #!/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();
|