#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { buildBusinessExecutionIndex } = require('./business-execution-index'); function main() { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-execution-index-')); const outputs = path.join(root, 'outputs'); seed(outputs); const summary = buildBusinessExecutionIndex({ root, outputsDir: outputs }); assert(summary.passed === true, 'business execution index should pass with seeded artifacts'); assert(summary.directCustomerProof === false, 'business execution index should not be direct customer proof'); assert(summary.proofLevel === 'smoke_or_local', 'business execution index should stay smoke/local evidence'); assert(summary.proofOpenCount === 5, 'should expose proof open count'); assert(summary.actionRowCount === 3, 'should expose owner action rows'); assert(summary.repairRowCount === 2, 'should expose repair action rows'); assert(summary.operatorPackOwnerArtifactRowCount === 3, 'should expose operator pack owner attachment rows'); assert(summary.operatorPackOwnerArtifactCount === 3, 'should expose operator pack owner attachment count'); assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'should carry operator pack owner attachments'); assert(summary.fieldOwnerRowCount === 2, 'should expose field checklist owner group rows'); assert(summary.recheckCommandRowCount === 4, 'should expose field checklist recheck command rows'); assert(summary.fileRowCount >= 2, 'should expose primary file rows'); assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('intake-readiness-repair-actions.csv') && row.actionCount === 6), 'should include business intake repair file'); assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('video-resource-repair-actions.csv') && row.actionCount === 1), 'should include video repair file'); assert(summary.sourceFiles.proofGapOperatorPack?.includes('proof-gap-operator-pack-summary.json'), 'should expose proof-gap operator pack source file'); assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务' && row.mainFile.includes('by-owner/business.md') && row.csv.includes('by-owner/business.csv') && row.acceptance.includes('historical-dataset')), 'should include business proof-gap operator owner attachment'); assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务/投放' && row.mainFile.includes('by-owner/business-media.md') && row.csv.includes('by-owner/business-media.csv')), 'should include business/media proof-gap operator owner attachment'); assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '技术/AI' && row.mainFile.includes('by-owner/tech-ai.md') && row.csv.includes('by-owner/tech-ai.csv')), 'should include tech/AI proof-gap operator owner attachment'); assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('历史 Brief') && row.repairFile.includes('history-data-template.csv')), 'should include history field owner row'); assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('视频资源') && row.repairFile.includes('video-resource-template.csv')), 'should include video field owner row'); assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('intake:readiness')), 'should include intake readiness recheck row'); assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('acceptance:video-ab')), 'should include video A/B recheck row'); assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('customer-effect:audit')), 'should include customer effect recheck row'); assert(summary.sourceFiles.longRunReadiness?.includes('long-run-readiness-summary.json'), 'should expose long-run readiness source file'); assert(summary.sourceFiles.optimizationCompletion?.includes('optimization-completion-summary.json'), 'should expose optimization completion source file'); assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('长跑前置门禁') && row.mainFile.includes('long-run-readiness-report.md') && row.acceptance.includes('ready=false')), 'should include long-run readiness key file row'); assert(summary.rows.some(row => row.title.includes('长跑前置门禁') && row.boundary.includes('不能启动')), 'long-run readiness row should keep startup boundary'); assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('优化完成度审计') && row.mainFile.includes('optimization-completion-report.md') && row.acceptance.includes('readyForClaim=false')), 'should include optimization completion key file row'); assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('missingProofRequirementCount=2')), 'optimization completion row should expose missing proof requirement count'); assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightReadyForVideoAb=false')), 'optimization completion row should expose video A/B preflight readiness'); assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightFailureCount=4')), 'optimization completion row should expose video A/B preflight failure count'); assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.boundary.includes('不得宣称提号率提升')), 'optimization completion row should keep forbidden-claim boundary'); const missingBoundaryRows = summary.rows.filter(row => !boundaryOk(row.boundary)); assert(missingBoundaryRows.length === 0, `each row should carry proof boundary: ${JSON.stringify(missingBoundaryRows.map(row => ({ type: row.type, title: row.title, boundary: row.boundary })))}`); assert(summary.rows.some(row => row.title.includes('A/B') && row.acceptance.includes('proofContext.mode=live')), 'video A/B row should expose missing proof requirements'); console.log(JSON.stringify({ ok: true, rowCount: summary.rowCount, repairRowCount: summary.repairRowCount, fieldOwnerRowCount: summary.fieldOwnerRowCount, recheckCommandRowCount: summary.recheckCommandRowCount }, null, 2)); } function seed(outputs) { const nextActionsDir = path.join(outputs, 'business-proof-next-actions-latest'); const latestFormDir = path.join(outputs, 'latest-form-index-latest'); const proofFormDir = path.join(outputs, 'tihao-proof-gap-software-form-latest'); const closureDir = path.join(outputs, 'proof-gap-closure-latest'); const evidenceDir = path.join(outputs, 'evidence-index-latest'); const fieldChecklistDir = path.join(outputs, 'intake-field-checklist-latest'); const longRunReadinessDir = path.join(outputs, 'long-run-readiness-latest'); const optimizationCompletionDir = path.join(outputs, 'optimization-completion-latest'); const proofGapOperatorPackDir = path.join(outputs, 'proof-gap-operator-pack-latest'); for (const dir of [nextActionsDir, latestFormDir, proofFormDir, closureDir, evidenceDir, fieldChecklistDir, longRunReadinessDir, optimizationCompletionDir, proofGapOperatorPackDir]) fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(path.join(nextActionsDir, 'by-owner'), { recursive: true }); fs.mkdirSync(path.join(proofGapOperatorPackDir, 'by-owner'), { recursive: true }); fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.md'), '# 商务\n', 'utf8'); fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.csv'), '标题\n', 'utf8'); fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.md'), '# 商务/投放\n', 'utf8'); fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.csv'), '标题\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.md'), '# 商务 proof-gap\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.csv'), 'gapId\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.md'), '# 商务/投放 proof-gap\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.csv'), 'gapId\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.md'), '# 技术/AI proof-gap\n', 'utf8'); fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.csv'), 'gapId\n', 'utf8'); writeJson(path.join(nextActionsDir, 'business-proof-next-actions-summary.json'), { complete: false, sourceState: { proofGapOpenCount: 5 }, actions: [ { owner: '商务', title: '填真实历史 Brief', priority: 1, status: 'blocked_by_external_data', command: '填写 history-data-template.csv', expectedArtifact: 'history-data-template.csv', acceptance: 'historyReady=true' }, { owner: '商务/投放', title: '填真实视频资源', priority: 2, status: 'blocked_by_external_data', command: '填写 video-resource-template.csv', expectedArtifact: 'video-resource-template.csv', acceptance: 'readyForVideoAbPreflight=true' }, { owner: '技术/AI', title: '真实视频 A/B 验收', priority: 24, status: 'open', command: 'npm run acceptance:video-ab', expectedArtifact: 'video-hit-rate-summary.json', acceptance: '真实 provider 和真实视频资源下通过', missingProofRequirements: ['proofContext.mode=live', 'proofContext.generatedBy=acceptance:video-ab'] } ], ownerArtifacts: [ { owner: '商务', actionCount: 1, markdown: 'by-owner/business.md', csv: 'by-owner/business.csv', repairArtifacts: [ { title: '历史数据/商务复核修复清单', csv: 'outputs/intake-readiness-latest/intake-readiness-repair-actions.csv', actionCount: 6, topPriority: 1 } ] }, { owner: '商务/投放', actionCount: 1, markdown: 'by-owner/business-media.md', csv: 'by-owner/business-media.csv', repairArtifacts: [ { title: '视频资源修复清单', csv: 'outputs/video-resource-readiness-latest/video-resource-repair-actions.csv', actionCount: 1, topPriority: 1 } ] } ] }); writeJson(path.join(latestFormDir, 'latest-form-index-summary.json'), { passed: true, primaryForm: { csv: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv', summary: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json' }, clientList: { exists: true, csv: 'outputs/software-client-latest/software-client-list.final.csv', summary: 'outputs/software-client-latest/software-client-list.final.summary.json', rowCount: 28 }, handoff: { complete: false, report: 'outputs/optimization-handoff-latest/optimization-handoff-report.md', summary: 'outputs/optimization-handoff-latest/optimization-handoff-summary.json' } }); fs.writeFileSync(path.join(latestFormDir, 'latest-form-index.md'), '# 最新提号表单索引\n', 'utf8'); writeJson(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json'), { passed: true, rowCount: 5, duplicateIdCount: 0 }); writeJson(path.join(fieldChecklistDir, 'intake-field-checklist-summary.json'), { passed: true, proofLevel: 'smoke_or_local', directCustomerProof: false, itemCount: 6, missingRequiredCount: 2, placeholderCount: 4, ownerGroups: [ { owner: '商务', section: 'history', itemCount: 3, missingRequiredCount: 1, placeholderCount: 2, files: ['outputs/data-intake-pack-latest/history-data-template.csv'], nextAction: '补真实历史 Brief', acceptance: 'historyReady=true', boundary: '补齐材料,不证明客户效果' }, { owner: '商务/投放', section: 'video', itemCount: 3, missingRequiredCount: 1, placeholderCount: 2, files: ['outputs/video-intake-pack-latest/video-resource-template.csv'], nextAction: '补真实视频 URL', acceptance: 'videoReady=true', boundary: '补齐材料,不证明客户效果' } ], recheckCommands: [ { stage: '真实材料预审', owner: '技术/AI', command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', acceptance: 'overallReady=true', boundary: '预审通过不证明客户效果' }, { stage: '刷新本轮交接', owner: '技术/AI', command: 'npm run round:refresh -- --output-root outputs --strict', acceptance: 'round-deposition.passed=true', boundary: '刷新交接不证明业务效果' }, { stage: '客户效果审计', owner: '技术/AI', command: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict', acceptance: 'overallPass=true', boundary: '缺客户选择不得宣称效果' }, { stage: '视频 A/B 验收', owner: '技术/AI', command: 'npm run acceptance:video-ab', acceptance: '真实 provider 下通过', boundary: 'sample 不证明视频提升提号率' } ], items: [] }); fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.md'), '# 真实材料字段级补齐核对表\n', 'utf8'); fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.csv'), 'ID,负责人,区域\n', 'utf8'); writeJson(path.join(closureDir, 'proof-gap-closure-summary.json'), { complete: false, openCount: 5 }); writeJson(path.join(evidenceDir, 'evidence-index-summary.json'), { counts: { real_evidence: 1, smoke_or_local: 3, not_business_proof: 2 } }); writeJson(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json'), { passed: true, complete: false, directCustomerProof: false, proofLevel: 'not_business_proof', openCount: 5, rowCount: 5, ownerArtifactCount: 3, ownerArtifacts: [ { owner: '商务', actionCount: 2, topPriority: 1, markdown: 'by-owner/business.md', csv: 'by-owner/business.csv', gapIds: ['historical-dataset', 'manual-review-and-customer-effect'], evidenceRowCount: 2, nextActionCount: 2 }, { owner: '商务/投放', actionCount: 1, topPriority: 2, markdown: 'by-owner/business-media.md', csv: 'by-owner/business-media.csv', gapIds: ['video-real-candidate'], evidenceRowCount: 1, nextActionCount: 1 }, { owner: '技术/AI', actionCount: 2, topPriority: 3, markdown: 'by-owner/tech-ai.md', csv: 'by-owner/tech-ai.csv', gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'], evidenceRowCount: 2, nextActionCount: 2 } ] }); writeJson(path.join(longRunReadinessDir, 'long-run-readiness-summary.json'), { mode: 'full-matrix', ready: false, counts: { pass: 1, fail: 5, warn: 0 } }); fs.writeFileSync(path.join(longRunReadinessDir, 'long-run-readiness-report.md'), '# 提号长跑就绪度审计\n', 'utf8'); writeJson(path.join(optimizationCompletionDir, 'optimization-completion-summary.json'), { complete: false, readyForClaim: false, proofGapOpenCount: 5, blockingReasonCount: 2, blockingReasonsWithMissingProofRequirements: 2, missingProofRequirementCount: 2, videoAbPreflightReadyForVideoAb: false, videoAbPreflightFailureCount: 4, blockingReasons: [ { id: 'proof-gap-closure', status: 'blocked', missingProofRequirements: ['proofGapClosure.openCount=0'] }, { id: 'longrun-readiness', status: 'blocked', missingProofRequirements: ['longRunReadiness.ready=true'] } ], canClaim: { tihaoRateImproved: false, customerEffectAchieved: false, manualSupplementReduced: false } }); fs.writeFileSync(path.join(optimizationCompletionDir, 'optimization-completion-report.md'), '# 优化完成度审计\n', 'utf8'); } function boundaryOk(value) { const text = String(value || ''); return text.includes('不证明') || text.includes('不能证明') || text.includes('补证') || text.includes('只证明') || text.includes('不得宣称'); } 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); } if (require.main === module) main();