| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- #!/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 <historical-dataset-audit.json> --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();
|