| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- #!/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-business-proof-progress-'));
- const outputs = path.join(tmp, 'outputs');
- fs.mkdirSync(outputs, { recursive: true });
- makeDir(path.join(outputs, 'data-intake-pack-latest'), {
- 'history-data-template.csv': 'brief编号,客户原始Brief\n',
- 'manual-review-template.csv': 'brief编号,人工复核标签\n',
- 'README.md': '# 数据收集包\n'
- });
- makeDir(path.join(outputs, 'video-intake-pack-latest'), {
- 'video-resource-template.csv': 'brief编号,视频链接\n',
- 'README.md': '# 视频资源收集包\n'
- });
- writeJson(path.join(outputs, 'intake-readiness-latest', 'intake-readiness-summary.json'), {
- failureCount: 9,
- acceptance: {
- historyReady: false,
- videoReady: false,
- reviewMetricsReady: false,
- customerEffectReady: false,
- overallReady: false
- },
- history: { issues: [{ type: 'placeholder' }] }
- });
- writeJson(path.join(outputs, 'video-resource-readiness-latest', 'video-resource-readiness-summary.json'), {
- failureCount: 1,
- counts: { realCandidateRows: 0 },
- acceptance: { readyForVideoAbPreflight: false }
- });
- writeJson(path.join(outputs, 'long-run-readiness-latest', 'long-run-readiness-summary.json'), {
- ready: false,
- counts: { pass: 2, fail: 5 }
- });
- writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
- complete: false,
- openCount: 4,
- rows: [
- { id: 'video-ab-live-proof', status: 'closed', evidence: 'passed=true, strongDelta=5, evidenceDelta=20' }
- ]
- });
- writeJson(path.join(outputs, 'historical-dataset-thin', 'historical-dataset-audit.json'), {
- briefCount: 5,
- categoryCount: 3,
- withCustomerDecision: 5,
- acceptance: {
- readyForLongRun: true,
- readyForCustomerEffectProof: true
- }
- });
- const operatorOwnerArtifacts = [
- {
- owner: '商务',
- markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business.md',
- csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business.csv',
- actionCount: 2,
- topPriority: 1,
- gapIds: ['historical-dataset', 'manual-review-and-customer-effect'],
- evidenceRowCount: 2,
- nextActionCount: 2
- },
- {
- owner: '商务/投放',
- markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.md',
- csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.csv',
- actionCount: 1,
- topPriority: 2,
- gapIds: ['video-real-candidate'],
- evidenceRowCount: 1,
- nextActionCount: 1
- },
- {
- owner: '技术/AI',
- markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.md',
- csv: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.csv',
- actionCount: 2,
- topPriority: 3,
- gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'],
- evidenceRowCount: 2,
- nextActionCount: 2
- }
- ];
- writeJson(path.join(outputs, 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json'), {
- passed: true,
- complete: false,
- ownerArtifactCount: 3,
- ownerArtifacts: operatorOwnerArtifacts
- });
- writeJson(path.join(outputs, 'business-execution-index-latest', 'business-execution-index-summary.json'), {
- passed: true,
- operatorPackOwnerArtifactCount: 3,
- operatorPackOwnerArtifactRowCount: 3,
- operatorPackOwnerArtifacts: operatorOwnerArtifacts
- });
- writeJson(path.join(outputs, 'optimization-handoff-latest', 'optimization-handoff-summary.json'), {
- complete: false,
- statusCounts: { passed: 25, ready_not_proven: 2, blocked_by_external_data: 2 }
- });
- writeJson(path.join(outputs, 'evidence-index-latest', 'evidence-index-summary.json'), {
- counts: { real_evidence: 5, smoke_or_local: 23, not_business_proof: 102 }
- });
- writeJson(path.join(outputs, 'round-deposition-latest', 'round-deposition-summary.json'), {
- passed: true,
- counts: { pass: 40, fail: 0, warn: 0 }
- });
- writeJson(path.join(outputs, 'optimization-status-latest', 'optimization-status-summary.json'), {
- complete: false,
- counts: { passed: 27, ready_not_proven: 2, blocked_by_external_data: 2 },
- items: [
- {
- id: 'video-ab-proof',
- status: 'ready_not_proven',
- evidence: 'acceptance:video-ab smoke is ready but live proof is missing'
- }
- ]
- });
- const outputDir = path.join(outputs, 'business-proof-progress-latest');
- const result = spawnSync(process.execPath, [
- path.join(root, 'scripts', 'business-proof-progress.js'),
- '--outputs',
- outputs,
- '--output',
- outputDir
- ], { cwd: root, encoding: 'utf8' });
- if (result.status !== 0) {
- process.stderr.write(result.stderr || result.stdout || '');
- throw new Error('business-proof-progress should run');
- }
- const summary = JSON.parse(fs.readFileSync(path.join(outputDir, 'business-proof-progress-summary.json'), 'utf8'));
- const report = fs.readFileSync(path.join(outputDir, 'business-proof-progress-report.md'), 'utf8').replace(/^\uFEFF/, '');
- assert(summary.complete === false, 'progress must not report complete with blocked proof data');
- assert(summary.rows.length === 15, 'progress should include 15 steps');
- assert(summary.counts.pass === 4, 'template packs, history import presence, and deposition refresh should pass in fixture');
- assert(summary.counts.fail === 2, 'intake preaudit and longrun gate should fail in fixture');
- assert(summary.counts.blocked_by_external_data === 7, 'external data blockers should be explicit');
- assert(summary.counts.pending === 2, 'pipeline and unproven video A/B should remain pending');
- assert(summary.rows.some(row => row.order === 6 && row.status === 'pass'), 'history import should pass when audit file exists');
- assert(summary.rows.some(row => row.order === 7 && row.status === 'blocked_by_external_data' && row.evidence.includes('missing=')), 'thin history audit should stay blocked and expose missing details');
- assert(summary.rows.some(row => row.title === '客户效果审计' && row.status === 'blocked_by_external_data'), 'customer effect should be blocked by real data');
- assert(summary.rows.some(row => row.title === '视频 A/B 验收' && row.status === 'pending' && row.evidence.includes('ready_not_proven')), 'video A/B should stay pending when status audit is ready_not_proven');
- assert(summary.rows.every(row => row.owner !== '投放/商务'), 'progress owner labels should be normalized');
- assert(summary.observed.videoAbProofStatus === 'ready_not_proven', 'progress should expose video A/B status audit result');
- assert(summary.operatorPackOwnerArtifactCount === 3, 'progress should expose proof-gap operator owner attachment count');
- assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'progress should carry proof-gap operator owner attachments');
- assert(summary.operatorPackOwnerArtifacts.some(item => item.markdown.includes('by-owner/business.md') && item.csv.includes('by-owner/business.csv')), 'progress should carry business owner operator attachment');
- assert(summary.observed.businessExecutionOperatorPackOwnerArtifactRowCount === 3, 'progress should expose business execution operator attachment rows');
- assert(report.includes('# 提号补证 15 步进度表'), 'report should be Chinese');
- assert(report.includes('blocked_by_external_data'), 'report should show blocked status');
- assert(report.includes('by-owner/business-media.md'), 'report should show proof-gap operator owner attachments');
- assert(report.includes('不能把 sample、smoke'), 'report should preserve proof boundary');
- assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'report should not contain model token patterns');
- assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not contain Parse sessionToken patterns');
- console.log(JSON.stringify({
- ok: true,
- outputDir,
- complete: summary.complete,
- counts: summary.counts
- }, null, 2));
- }
- function makeDir(dir, files) {
- fs.mkdirSync(dir, { recursive: true });
- for (const [name, content] of Object.entries(files)) {
- fs.writeFileSync(path.join(dir, name), content, 'utf8');
- }
- }
- function writeJson(file, value) {
- fs.mkdirSync(path.dirname(file), { recursive: true });
- fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- main();
|