#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const OUTPUTS = path.join(ROOT, 'outputs'); function main() { const args = parseArgs(process.argv.slice(2)); const outputsDir = path.resolve(args.outputs || OUTPUTS); const outputDir = path.resolve(args.output || process.env.TIHAO_OPTIMIZATION_COMPLETION_OUTPUT || path.join(outputsDir, `optimization-completion-${Date.now()}`)); const overnightFile = args.overnight || bestOvernightAggregate(outputsDir) || latestFile(outputsDir, 'aggregate-summary.json'); const historyFile = args.historyAudit || bestHistoryAudit(outputsDir) || latestFile(outputsDir, 'historical-dataset-audit.json'); const customerEffectFile = args.customerEffect || bestCustomerEffectSummary(outputsDir, historyFile, ROOT) || latestFile(outputsDir, 'customer-effect-summary.json'); const videoAbFile = args.videoAb || process.env.TIHAO_VIDEO_AB_SUMMARY || latestFile(outputsDir, 'video-hit-rate-summary.json'); const videoAbPreflightFile = args.videoAbPreflight || process.env.TIHAO_VIDEO_AB_PREFLIGHT_SUMMARY || latestFile(outputsDir, 'video-hit-rate-preflight-summary.json'); const summary = buildCompletionSummary({ root: ROOT, outputsDir, sourceFiles: { overnight: relIfInside(ROOT, overnightFile), history: relIfInside(ROOT, historyFile), customerEffect: relIfInside(ROOT, customerEffectFile), videoAb: relIfInside(ROOT, videoAbFile), videoAbPreflight: relIfInside(ROOT, videoAbPreflightFile) }, history: readJsonArg(historyFile), intakeReadiness: readJsonArg(args.intakeReadiness || latestFile(outputsDir, 'intake-readiness-summary.json')), videoReadiness: readJsonArg(args.videoReadiness || latestFile(outputsDir, 'video-resource-readiness-summary.json')), proofGapClosure: readJsonArg(args.proofGapClosure || latestFile(outputsDir, 'proof-gap-closure-summary.json')), handoff: readJsonArg(args.handoff || latestFile(outputsDir, 'optimization-handoff-summary.json')), businessProgress: readJsonArg(args.businessProgress || latestFile(outputsDir, 'business-proof-progress-summary.json')), longRunReadiness: readJsonArg(args.longRunReadiness || latestFile(outputsDir, 'long-run-readiness-summary.json')), videoAb: readJsonArg(videoAbFile), videoAbPreflight: readJsonArg(videoAbPreflightFile), overnight: readJsonArg(overnightFile), customerEffect: readJsonArg(customerEffectFile), optimizationPipeline: readJsonArg(args.optimizationPipeline || latestFile(outputsDir, 'optimization-pipeline-summary.json')) }); fs.mkdirSync(outputDir, { recursive: true }); const jsonPath = path.join(outputDir, 'optimization-completion-summary.json'); const reportPath = path.join(outputDir, 'optimization-completion-report.md'); fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8'); fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8'); console.log(JSON.stringify({ outputDir, json: jsonPath, report: reportPath, complete: summary.complete, readyForClaim: summary.readyForClaim, proofGapOpenCount: summary.proofGapOpenCount, blockingReasonCount: summary.blockingReasonCount }, null, 2)); if ((args.strict || process.env.TIHAO_OPTIMIZATION_COMPLETION_STRICT === 'true') && !summary.complete) { process.exitCode = 2; } } function buildCompletionSummary({ root, outputsDir, history, intakeReadiness, videoReadiness, proofGapClosure, handoff, businessProgress, longRunReadiness, videoAb, videoAbPreflight, overnight, customerEffect, optimizationPipeline, sourceFiles = {} }) { const optimizationPipelineFreshness = getOptimizationPipelineFreshness(optimizationPipeline, { history, intakeReadiness, videoReadiness, proofGapClosure, customerEffect }); const intakeReadinessMissingRequirements = getIntakeReadinessMissingRequirements(intakeReadiness); const videoResourceMissingRequirements = getVideoResourceMissingRequirements(videoReadiness); const proofGapClosureMissingRequirements = getProofGapClosureMissingRequirements(proofGapClosure); const videoAbMissingRequirements = getVideoAbMissingRequirements(videoAb, videoAbPreflight); const overnightMissingRequirements = getOvernightMissingRequirements(overnight); const customerEffectMissingRequirements = getCustomerEffectMissingRequirements(customerEffect, history, sourceFiles, root); const optimizationPipelineMissingRequirements = getOptimizationPipelineMissingRequirements(optimizationPipeline, optimizationPipelineFreshness); const handoffMissingRequirements = getHandoffMissingRequirements(handoff); const businessProgressMissingRequirements = getBusinessProofProgressMissingRequirements(businessProgress); const longRunReadinessMissingRequirements = getLongRunReadinessMissingRequirements(longRunReadiness); const gates = [ gate({ id: 'intake-readiness', title: '真实材料 intake readiness', passed: intakeReadinessMissingRequirements.length === 0, evidence: intakeReadiness ? `overallReady=${Boolean(intakeReadiness.acceptance?.overallReady)}, failureCount=${intakeReadiness.failureCount ?? 'unknown'}` : 'missing intake-readiness-summary.json', required: 'intake:readiness 必须 overallReady=true 且 failureCount=0。', command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', missingProofRequirements: intakeReadinessMissingRequirements }), gate({ id: 'video-resource-readiness', title: '真实候选视频资源 readiness', passed: videoResourceMissingRequirements.length === 0, evidence: videoReadiness ? `readyForVideoAbPreflight=${Boolean(videoReadiness.acceptance?.readyForVideoAbPreflight)}, failureCount=${videoReadiness.failureCount ?? 'unknown'}, realCandidateRows=${videoReadiness.counts?.realCandidateRows ?? 'unknown'}` : 'missing video-resource-readiness-summary.json', required: 'video:resource-readiness 必须 readyForVideoAbPreflight=true、failureCount=0,且包含真实候选视频。', command: 'npm run video:resource-readiness -- --input outputs\\video-intake-pack-latest\\video-resource-template.csv --output outputs\\video-resource-readiness-latest --strict', missingProofRequirements: videoResourceMissingRequirements }), gate({ id: 'proof-gap-closure', title: '真实证明缺口全部关闭', passed: proofGapClosureMissingRequirements.length === 0, evidence: proofGapClosure ? `complete=${Boolean(proofGapClosure.complete)}, openCount=${proofGapClosure.openCount ?? 'unknown'}, closedCount=${proofGapClosure.closedCount ?? 'unknown'}` : 'missing proof-gap-closure-summary.json', required: 'proof-gap:closure 必须 complete=true、openCount=0。', command: 'npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest', missingProofRequirements: proofGapClosureMissingRequirements }), gate({ id: 'video-ab-live-proof', title: '真实视频 A/B live proof', passed: videoAbMissingRequirements.length === 0, evidence: formatVideoAbEvidence(videoAb, videoAbPreflight), required: 'acceptance:video-ab 必须在 live 模式、真实 provider、真实视频资源下通过。', command: 'npm run acceptance:video-ab', missingProofRequirements: videoAbMissingRequirements }), gate({ id: 'live-provider-overnight-proof', title: '真实 live/provider 长跑证明', passed: overnightMissingRequirements.length === 0, evidence: overnight ? `liveEnabled=${Boolean(overnight.manifest?.liveEnabled)}, overallPass=${Boolean(overnight.acceptance?.overallPass)}, failureCount=${overnight.failureCount ?? 'unknown'}, failedGateCount=${overnight.acceptance?.failedGateCount ?? overnight.failedGateCount ?? 'unknown'}, source=${sourceFiles.overnight || 'unknown'}` : 'missing aggregate-summary.json', required: 'overnight aggregate 必须 liveEnabled=true、overallPass=true、failureCount=0、failedGateCount=0。', command: 'npm run longrun:readiness;真实 provider 就绪后运行 npm run overnight:quality', missingProofRequirements: overnightMissingRequirements }), gate({ id: 'customer-effect-proof', title: '商务复核和客户效果证明', passed: customerEffectMissingRequirements.length === 0, evidence: customerEffect ? `overallPass=${Boolean(customerEffect.acceptance?.overallPass)}, customerSelectedRate=${customerEffect.customerSelectedRate ?? 'unknown'}, referenceCustomerSelectedRate=${customerEffect.referenceCustomerSelectedRate ?? 'unknown'}, manualSupplementReductionRate=${customerEffect.manualSupplementReductionRate ?? 'unknown'}, historyAuditPath=${customerEffect.historyAuditPath || 'missing'}` : 'missing customer-effect-summary.json', required: 'customer-effect:audit 必须 overallPass=true,并带真实客户选择、拒绝归因和人工补号量。', command: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict', missingProofRequirements: customerEffectMissingRequirements }), gate({ id: 'optimization-pipeline', title: '端到端优化 pipeline 可声明状态', passed: optimizationPipelineMissingRequirements.length === 0, evidence: formatOptimizationPipelineEvidence(optimizationPipeline, optimizationPipelineFreshness), required: 'optimization:pipeline 必须在真实材料下 readyForClaim=true,且 proof-gap closure 已关闭。', command: 'npm run optimization:pipeline -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict', missingProofRequirements: optimizationPipelineMissingRequirements }), gate({ id: 'handoff-complete', title: '交接摘要完成状态', passed: handoffMissingRequirements.length === 0, evidence: handoff ? `complete=${Boolean(handoff.complete)}, ready=${handoff.readyCapabilities?.length ?? 0}, readyNotProven=${handoff.readyNotProven?.length ?? 0}, blockers=${handoff.externalBlockers?.length ?? 0}` : 'missing optimization-handoff-summary.json', required: 'handoff summary 必须 complete=true,且不得有未证明或外部阻塞项。', command: 'npm run handoff:summary -- --output outputs\\optimization-handoff-latest', missingProofRequirements: handoffMissingRequirements }), gate({ id: 'business-proof-progress', title: '15 步补证进度全部通过', passed: businessProgressMissingRequirements.length === 0, evidence: businessProgress ? `complete=${Boolean(businessProgress.complete)}, pass=${businessProgress.counts?.pass ?? 0}, fail=${businessProgress.counts?.fail ?? 0}, pending=${businessProgress.counts?.pending ?? 0}, blocked=${businessProgress.counts?.blocked_by_external_data ?? 0}` : 'missing business-proof-progress-summary.json', required: 'business-proof:progress 必须 complete=true,15 步补证均 pass。', command: 'npm run business-proof:progress -- --outputs outputs --output outputs\\business-proof-progress-latest', missingProofRequirements: businessProgressMissingRequirements }), gate({ id: 'longrun-readiness', title: '真实长跑启动前门禁', passed: longRunReadinessMissingRequirements.length === 0, evidence: longRunReadiness ? `ready=${Boolean(longRunReadiness.ready)}, fail=${longRunReadiness.counts?.fail ?? 0}, pass=${longRunReadiness.counts?.pass ?? 0}` : 'missing long-run-readiness-summary.json', required: 'longrun:readiness 必须 ready=true 且 fail=0。', command: 'npm run longrun:readiness', missingProofRequirements: longRunReadinessMissingRequirements }) ]; const blockingReasons = gates .filter(item => !item.passed) .map(item => ({ id: item.id, title: item.title, evidence: item.evidence, required: item.required, missingProofRequirements: item.missingProofRequirements, command: item.command })); const proofGapOpenCount = Number(proofGapClosure?.openCount ?? 0); const complete = gates.every(item => item.passed); const readyForClaim = complete && proofGapOpenCount === 0; const blockingReasonCount = blockingReasons.length; const blockingReasonsWithMissingProofRequirements = countRowsWithMissingProofRequirements(blockingReasons); const missingProofRequirementCount = uniqueMissingProofRequirementCount(blockingReasons); return { generatedAt: new Date().toISOString(), root, outputsDir, sourceFiles, materialType: 'optimization_completion_audit', directCustomerProof: false, proofLevel: readyForClaim ? 'real_evidence' : 'not_business_proof', complete, readyForClaim, proofGapOpenCount, proofGapClosedCount: Number(proofGapClosure?.closedCount ?? 0), intakeFailureCount: Number(intakeReadiness?.failureCount ?? 0), videoResourceFailureCount: Number(videoReadiness?.failureCount ?? 0), realCandidateRows: Number(videoReadiness?.counts?.realCandidateRows ?? 0), videoAbPreflightReadyForVideoAb: Boolean(videoAbPreflight?.readyForVideoAb), videoAbPreflightFailureCount: videoAbPreflight ? Number(videoAbPreflight.failureCount ?? 0) : null, blockingReasonCount, blockingReasonsWithMissingProofRequirements, missingProofRequirementCount, blockingReasons, gates, canClaim: { tihaoRateImproved: readyForClaim, customerEffectAchieved: readyForClaim, manualSupplementReduced: readyForClaim }, forbiddenClaimsWhenIncomplete: [ '提号率已经提升', '客户效果已经达标', '人工补号量已经下降' ], nextRequiredCommands: [ 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', 'npm run video:resource-readiness -- --input outputs\\video-intake-pack-latest\\video-resource-template.csv --output outputs\\video-resource-readiness-latest --strict', 'npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest', 'npm run optimization:pipeline -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --current-manual-supplement-count <本轮人工补号量> --output <输出目录> --strict', 'npm run round:refresh -- --output-root outputs --strict' ], guardrails: [ '本审计是完成度判定入口;审计产物本身不是业务证明。', 'real-proof-intake-bundle、proof-gap-request、proof-gap-operator-pack、索引和执行表都是协调/收集材料入口,不是业务证明。', '缺真实材料、真实候选视频、live/provider 长跑或客户效果证明时,不得宣称提号率提升、客户效果达标或人工补号量下降。', '不得在输入、报告、模板或日志中写入 sessionToken、Authorization、模型 token 或 npm token。' ] }; } function gate({ id, title, passed, evidence, required, command, missingProofRequirements = [] }) { return { id, title, status: passed ? 'pass' : 'blocked', passed: Boolean(passed), evidence, required, command, missingProofRequirements }; } function getIntakeReadinessMissingRequirements(intakeReadiness) { if (!intakeReadiness) return ['missing intake-readiness-summary.json']; const missing = []; const acceptance = intakeReadiness.acceptance || {}; if (acceptance.overallReady !== true) missing.push('acceptance.overallReady=true'); if (Number(intakeReadiness.failureCount || 0) !== 0) missing.push('failureCount=0'); if (acceptance.historyReady !== true) missing.push('acceptance.historyReady=true'); if (acceptance.videoReady !== true) missing.push('acceptance.videoReady=true'); if (acceptance.reviewMetricsReady !== true) missing.push('acceptance.reviewMetricsReady=true'); if (acceptance.customerEffectReady !== true) missing.push('acceptance.customerEffectReady=true'); return missing; } function getVideoResourceMissingRequirements(videoReadiness) { if (!videoReadiness) return ['missing video-resource-readiness-summary.json']; const missing = []; if (videoReadiness.acceptance?.readyForVideoAbPreflight !== true) missing.push('acceptance.readyForVideoAbPreflight=true'); if (Number(videoReadiness.failureCount || 0) !== 0) missing.push('failureCount=0'); if (Number(videoReadiness.counts?.realCandidateRows || 0) < 1) missing.push('counts.realCandidateRows>=1'); if (Number(videoReadiness.counts?.realReferenceRows || 0) < 1) missing.push('counts.realReferenceRows>=1'); if (Number(videoReadiness.counts?.videoUrlRows || 0) < 1) missing.push('counts.videoUrlRows>=1'); return missing; } function getProofGapClosureMissingRequirements(proofGapClosure) { if (!proofGapClosure) return ['missing proof-gap-closure-summary.json']; const missing = []; if (proofGapClosure.complete !== true) missing.push('proofGapClosure.complete=true'); if (Number(proofGapClosure.openCount || 0) !== 0) missing.push('proofGapClosure.openCount=0'); if (Number(proofGapClosure.closedCount || 0) < 5) missing.push('proofGapClosure.closedCount>=5'); if (Number(proofGapClosure.businessGapCount || 0) < 5) missing.push('proofGapClosure.businessGapCount>=5'); const rows = Array.isArray(proofGapClosure.rows) ? proofGapClosure.rows : []; const businessRows = rows.filter(row => row.id !== 'intake-readiness'); if (businessRows.length < 5) missing.push('proofGapClosure.businessRows.length>=5'); for (const row of rows) { if (row.status === 'closed' || row.id === 'intake-readiness') continue; for (const requirement of row.missingProofRequirements || []) { missing.push(`${row.id}: ${requirement}`); } } return uniqueStrings(missing); } function isRealVideoAbProof(videoAb) { return getVideoAbMissingRequirements(videoAb).length === 0; } function getVideoAbMissingRequirements(videoAb, videoAbPreflight = null) { const missing = []; if (!videoAb) { missing.push('missing video-hit-rate-summary.json'); } else { if (!videoAb.acceptance?.passed) missing.push('acceptance.passed=true'); const context = videoAb.proofContext || {}; if (context.mode !== 'live') missing.push('proofContext.mode=live'); if (context.collectionMode !== 'live') missing.push('proofContext.collectionMode=live'); if (context.generatedBy !== 'acceptance:video-ab') missing.push('proofContext.generatedBy=acceptance:video-ab'); if (context.requiresRuntimeCredential !== true) missing.push('proofContext.requiresRuntimeCredential=true'); if (context.requiresVocSocialProvider !== true) missing.push('proofContext.requiresVocSocialProvider=true'); if (context.requiresVideoAnalysisProvider !== true) missing.push('proofContext.requiresVideoAnalysisProvider=true'); if (videoAb.enhanced?.provider?.evidence?.providerStatus !== 'ok') missing.push('enhanced.provider.evidence.providerStatus=ok'); } if (missing.length) { missing.push(...getVideoAbPreflightMissingRequirements(videoAbPreflight)); } return uniqueStrings(missing); } function getVideoAbPreflightMissingRequirements(videoAbPreflight) { if (!videoAbPreflight) return []; const missing = []; if (videoAbPreflight.proofLevel !== 'not_business_proof') missing.push('videoAbPreflight.proofLevel=not_business_proof'); if (videoAbPreflight.canCloseProofGap !== false) missing.push('videoAbPreflight.canCloseProofGap=false'); if (videoAbPreflight.proofContext?.generatedBy !== 'acceptance:video-ab-preflight') missing.push('videoAbPreflight.proofContext.generatedBy=acceptance:video-ab-preflight'); if (videoAbPreflight.readyForVideoAb !== true) { missing.push('videoAbPreflight.readyForVideoAb=true'); for (const requirement of videoAbPreflight.missingProofRequirements || []) { missing.push(`videoAbPreflight.${requirement}`); } } return missing; } function formatVideoAbEvidence(videoAb, videoAbPreflight) { const liveEvidence = videoAb ? `passed=${Boolean(videoAb.acceptance?.passed)}, mode=${videoAb.proofContext?.mode || 'unknown'}, collectionMode=${videoAb.proofContext?.collectionMode || 'unknown'}, generatedBy=${videoAb.proofContext?.generatedBy || 'unknown'}, provider=${videoAb.enhanced?.provider?.evidence?.providerStatus || 'unknown'}` : 'missing video-hit-rate-summary.json'; if (!videoAbPreflight || getVideoAbMissingRequirements(videoAb).length === 0) return liveEvidence; const runtime = videoAbPreflight.runtime || {}; const preflightEvidence = [ `preflight.readyForVideoAb=${Boolean(videoAbPreflight.readyForVideoAb)}`, `preflight.failureCount=${videoAbPreflight.failureCount ?? 'unknown'}`, `preflight.generatedBy=${videoAbPreflight.proofContext?.generatedBy || 'unknown'}`, `preflight.proofLevel=${videoAbPreflight.proofLevel || 'unknown'}`, `preflight.canCloseProofGap=${Boolean(videoAbPreflight.canCloseProofGap)}`, `runtimeCredentialPresent=${Boolean(runtime.runtimeCredentialPresent)}`, `companyResolved=${Boolean(runtime.companyResolved)}`, `vocSocialProviderTokenPresent=${Boolean(runtime.vocSocialProviderTokenPresent)}`, `videoAnalysisTokenPresent=${Boolean(runtime.videoAnalysisTokenPresent)}` ].join(', '); return `${liveEvidence}; ${preflightEvidence}`; } function getOvernightMissingRequirements(overnight) { if (!overnight) return ['missing aggregate-summary.json']; if (overnight.manifest?.liveEnabled === true && overnight.acceptance?.overallPass === true && Number(overnight.failureCount || 0) === 0 && Number(overnight.acceptance?.failedGateCount ?? overnight.failedGateCount ?? 0) === 0 && overnight.acceptance?.tokenLeak !== true) { return []; } const missing = []; if (overnight.manifest?.liveEnabled !== true) missing.push('manifest.liveEnabled=true'); if (overnight.acceptance?.overallPass !== true) missing.push('acceptance.overallPass=true'); if (Number(overnight.failureCount || 0) !== 0) missing.push('failureCount=0'); const failedGateCount = Number(overnight.acceptance?.failedGateCount ?? overnight.failedGateCount ?? 0); if (failedGateCount !== 0) missing.push('failedGateCount=0'); if (overnight.acceptance?.tokenLeak === true) missing.push('acceptance.tokenLeak=false'); return missing; } function bestOvernightAggregate(outputsDir) { const files = findFiles(outputsDir, 'aggregate-summary.json'); const annotated = files .map(file => ({ file, json: readJsonArg(file), mtimeMs: fs.statSync(file).mtimeMs })) .filter(item => item.json); const closable = annotated .filter(item => isClosableOvernightAggregate(item.json)) .sort((a, b) => b.mtimeMs - a.mtimeMs); if (closable.length) return closable[0].file; return annotated.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]?.file || ''; } function findFiles(dir, fileName) { if (!fs.existsSync(dir)) return []; const result = []; const stack = [dir]; while (stack.length) { const current = stack.pop(); for (const entry of fs.readdirSync(current, { withFileTypes: true })) { const full = path.join(current, entry.name); if (entry.isDirectory()) stack.push(full); else if (entry.isFile() && entry.name === fileName) result.push(full); } } return result; } function isClosableOvernightAggregate(aggregate) { return Boolean( aggregate?.manifest?.liveEnabled === true && aggregate?.acceptance?.overallPass === true && Number(aggregate?.failureCount || 0) === 0 && Number(aggregate?.acceptance?.failedGateCount ?? aggregate?.failedGateCount ?? 0) === 0 && aggregate?.acceptance?.tokenLeak !== true ); } function getCustomerEffectMissingRequirements(customerEffect, history, sourceFiles = {}, root = process.cwd()) { if (!customerEffect) return ['missing customer-effect-summary.json']; const missing = []; const acceptance = customerEffect.acceptance || {}; if (acceptance.overallPass !== true) missing.push('acceptance.overallPass=true'); if (acceptance.reviewMetricsOverallUsable !== true) missing.push('acceptance.reviewMetricsOverallUsable=true'); if (acceptance.customerSelectedRateMeasured !== true) missing.push('acceptance.customerSelectedRateMeasured=true'); if (acceptance.customerSelectedRate30Pass !== true) missing.push('acceptance.customerSelectedRate30Pass=true'); if (acceptance.referenceCustomerSelectedRateMeasured !== true) missing.push('acceptance.referenceCustomerSelectedRateMeasured=true'); if (acceptance.referenceCustomerSelectedRate40Pass !== true) missing.push('acceptance.referenceCustomerSelectedRate40Pass=true'); if (acceptance.historyReadyForCustomerEffectProof !== true) missing.push('acceptance.historyReadyForCustomerEffectProof=true'); if (acceptance.manualSupplementBaselineAvailable !== true) missing.push('acceptance.manualSupplementBaselineAvailable=true'); if (acceptance.currentManualSupplementAvailable !== true) missing.push('acceptance.currentManualSupplementAvailable=true'); if (acceptance.manualSupplementReduction50Pass !== true) missing.push('acceptance.manualSupplementReduction50Pass=true'); if (!customerEffect.reviewPath) missing.push('reviewPath=provided'); if (!customerEffect.historyAuditPath) missing.push('historyAuditPath=provided'); if (sourceFiles.history && customerEffect.historyAuditPath && !samePath(root, customerEffect.historyAuditPath, sourceFiles.history)) { missing.push('historyAuditPath=current-history-audit'); } if (!history) { missing.push('currentHistoryAudit=present'); } else { for (const requirement of getHistoryMissingRequirements(history)) { missing.push(`currentHistoryAudit.${requirement}`); } } if (!Number.isFinite(Number(customerEffect.customerSelectedRate))) missing.push('customerSelectedRate=number'); if (!Number.isFinite(Number(customerEffect.referenceCustomerSelectedRate))) missing.push('referenceCustomerSelectedRate=number'); if (!Number.isFinite(Number(customerEffect.manualSupplementReductionRate))) missing.push('manualSupplementReductionRate=number'); if (customerEffect.manualSupplementBaseline?.available !== true) missing.push('manualSupplementBaseline.available=true'); if (customerEffect.currentManualSupplement?.available !== true) missing.push('currentManualSupplement.available=true'); return uniqueStrings([...(customerEffect.missingEvidence || []), ...missing]); } function getOptimizationPipelineFreshness(optimizationPipeline, sources = {}) { if (!optimizationPipeline) { return { current: false, reason: 'missing optimization-pipeline-summary.json' }; } const pipelineTime = parseGeneratedAt(optimizationPipeline.generatedAt); const sourceTimes = [ sources.history, sources.intakeReadiness, sources.videoReadiness, sources.proofGapClosure, sources.customerEffect ] .map(source => parseGeneratedAt(source?.generatedAt)) .filter(value => Number.isFinite(value)); if (!Number.isFinite(pipelineTime) || sourceTimes.length === 0) { return { current: true, reason: 'freshness not comparable', pipelineGeneratedAt: optimizationPipeline.generatedAt || '' }; } const latestSourceTime = Math.max(...sourceTimes); const current = pipelineTime >= latestSourceTime; return { current, reason: current ? 'pipeline generated after current proof sources' : `stale optimization-pipeline-summary.json: generatedAt=${optimizationPipeline.generatedAt || 'unknown'} older than latest proof source=${new Date(latestSourceTime).toISOString()}`, pipelineGeneratedAt: optimizationPipeline.generatedAt || '', latestProofSourceGeneratedAt: new Date(latestSourceTime).toISOString() }; } function parseGeneratedAt(value) { if (!value) return NaN; const time = Date.parse(value); return Number.isFinite(time) ? time : NaN; } function getHistoryMissingRequirements(history) { if (!history) return ['missing historical-dataset-audit.json']; const missing = []; const acceptance = history.acceptance || {}; if (acceptance.readyForCustomerEffectProof !== true) missing.push('acceptance.readyForCustomerEffectProof=true'); if (Number(history.briefCount || 0) < 5) missing.push('briefCount>=5'); if (acceptance.parseOk !== true) missing.push('acceptance.parseOk=true'); if (acceptance.minBriefsMet !== true) missing.push('acceptance.minBriefsMet=true'); if (acceptance.allHaveBriefText !== true) missing.push('acceptance.allHaveBriefText=true'); if (acceptance.allHaveManualFinalList !== true) missing.push('acceptance.allHaveManualFinalList=true'); if (acceptance.allHaveCustomerDecision !== true) missing.push('acceptance.allHaveCustomerDecision=true'); if (acceptance.allHaveFeedbackReason !== true) missing.push('acceptance.allHaveFeedbackReason=true'); if (acceptance.allHaveManualSupplementBaseline !== true) missing.push('acceptance.allHaveManualSupplementBaseline=true'); if (acceptance.categoryCoverageMet !== true) missing.push('acceptance.categoryCoverageMet=true'); if (Number(history.missingCriticalCount || 0) !== 0) missing.push('missingCriticalCount=0'); if (!Array.isArray(history.items) || history.items.length < 5) missing.push('items.length>=5'); if (Number(history.withManualFinalList || 0) < 5) missing.push('withManualFinalList>=5'); if (Number(history.withCustomerDecision || 0) < 5) missing.push('withCustomerDecision>=5'); if (Number(history.withRejectionReason || 0) < 1) missing.push('withRejectionReason>=1'); if (Number(history.withManualSupplementBaseline || 0) < 5) missing.push('withManualSupplementBaseline>=5'); return missing; } function samePath(root, left, right) { const normalize = value => { const resolved = path.resolve(root || process.cwd(), value || ''); return process.platform === 'win32' ? resolved.toLowerCase() : resolved; }; return normalize(left) === normalize(right); } function formatOptimizationPipelineEvidence(optimizationPipeline, freshness) { if (!optimizationPipeline) return 'missing optimization-pipeline-summary.json'; const base = `readyForClaim=${Boolean(optimizationPipeline.readyForClaim)}, proofGapClosure.complete=${Boolean(optimizationPipeline.proofGapClosure?.complete)}, proofGapClosure.openCount=${optimizationPipeline.proofGapClosure?.openCount ?? 'unknown'}, proofGapClosure.closedCount=${optimizationPipeline.proofGapClosure?.closedCount ?? 'unknown'}`; if (freshness?.current === false) return `${base}, stale=true, ${freshness.reason}`; return `${base}, stale=false`; } function getOptimizationPipelineMissingRequirements(optimizationPipeline, freshness = { current: true }) { if (!optimizationPipeline) return ['missing optimization-pipeline-summary.json']; if (optimizationPipeline.readyForClaim === true && freshness.current !== false && optimizationPipeline.proofGapClosure?.complete === true && Number(optimizationPipeline.proofGapClosure?.openCount || 0) === 0 && Number(optimizationPipeline.proofGapClosure?.closedCount || 0) >= 5 && Number(optimizationPipeline.proofGapClosure?.businessGapCount || 0) >= 5) { return []; } const missing = []; if (freshness.current === false) missing.push('optimizationPipeline.generatedAt>=currentProofSources'); if (optimizationPipeline.readyForClaim !== true) missing.push('optimizationPipeline.readyForClaim=true'); if (optimizationPipeline.proofGapClosure?.complete !== true) missing.push('optimizationPipeline.proofGapClosure.complete=true'); if (Number(optimizationPipeline.proofGapClosure?.openCount || 0) !== 0) missing.push('optimizationPipeline.proofGapClosure.openCount=0'); if (Number(optimizationPipeline.proofGapClosure?.closedCount || 0) < 5) missing.push('optimizationPipeline.proofGapClosure.closedCount>=5'); if (Number(optimizationPipeline.proofGapClosure?.businessGapCount || 0) < 5) missing.push('optimizationPipeline.proofGapClosure.businessGapCount>=5'); const failedSteps = Array.isArray(optimizationPipeline.results) ? optimizationPipeline.results.filter(item => item.status && item.status !== 'pass') : []; for (const step of failedSteps) missing.push(`pipeline step ${step.id}=pass`); return uniqueStrings(missing); } function getHandoffMissingRequirements(handoff) { if (!handoff) return ['missing optimization-handoff-summary.json']; const missing = []; if (handoff.complete !== true) missing.push('handoff.complete=true'); if ((handoff.readyNotProven || []).length) missing.push('handoff.readyNotProven.length=0'); if ((handoff.externalBlockers || []).length) missing.push('handoff.externalBlockers.length=0'); return missing; } function getBusinessProofProgressMissingRequirements(businessProgress) { if (!businessProgress) return ['missing business-proof-progress-summary.json']; const missing = []; const counts = businessProgress.counts || {}; if (businessProgress.complete !== true) missing.push('businessProofProgress.complete=true'); if (Number(counts.fail || 0) !== 0) missing.push('businessProofProgress.counts.fail=0'); if (Number(counts.pending || 0) !== 0) missing.push('businessProofProgress.counts.pending=0'); if (Number(counts.blocked_by_external_data || 0) !== 0) missing.push('businessProofProgress.counts.blocked_by_external_data=0'); if (Number(counts.pass || 0) < 15) missing.push('businessProofProgress.counts.pass>=15'); if (!Array.isArray(businessProgress.rows) || businessProgress.rows.length < 15) missing.push('businessProofProgress.rows.length>=15'); const openRows = Array.isArray(businessProgress.rows) ? businessProgress.rows.filter(row => row.status && row.status !== 'pass') : []; for (const row of openRows) missing.push(`step-${row.order || '?'} ${row.title || '补证步骤'}=pass`); return uniqueStrings(missing); } function getLongRunReadinessMissingRequirements(longRunReadiness) { if (!longRunReadiness) return ['missing long-run-readiness-summary.json']; const missing = []; if (longRunReadiness.ready !== true) missing.push('longRunReadiness.ready=true'); if (Number(longRunReadiness.counts?.fail || 0) !== 0) missing.push('longRunReadiness.counts.fail=0'); const failedChecks = Array.isArray(longRunReadiness.checks) ? longRunReadiness.checks.filter(item => item.status === 'fail') : []; for (const item of failedChecks) missing.push(`${item.name}=pass`); return uniqueStrings(missing); } function uniqueStrings(values) { return [...new Set((values || []).filter(Boolean).map(value => String(value)))]; } function countRowsWithMissingProofRequirements(rows) { if (!Array.isArray(rows)) return 0; return rows.filter(row => Array.isArray(row.missingProofRequirements) && row.missingProofRequirements.length >= 1 ).length; } function uniqueMissingProofRequirementCount(rows) { if (!Array.isArray(rows)) return 0; const values = new Set(); for (const row of rows) { const requirements = Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements : []; for (const requirement of requirements) { if (requirement) values.add(String(requirement)); } } return values.size; } function renderReport(summary) { return [ '# 提号长期优化完成度审计', '', `- 生成时间:${summary.generatedAt}`, `- 是否完成:${summary.complete ? '是' : '否'}`, `- 是否可声明业务效果:${summary.readyForClaim ? '是' : '否'}`, `- 证明等级:${summary.proofLevel}`, `- 直接客户证明:${summary.directCustomerProof ? '是' : '否'}`, `- proof-gap openCount:${summary.proofGapOpenCount}`, `- 阻塞项数量:${summary.blockingReasonCount}`, `- 带缺失证明要求的阻塞项:${summary.blockingReasonsWithMissingProofRequirements}`, `- 缺失证明要求唯一计数:${summary.missingProofRequirementCount}`, '', '## 禁用宣称', '', `- 提号率提升:${summary.canClaim.tihaoRateImproved ? '可声明' : '不可声明'}`, `- 客户效果达标:${summary.canClaim.customerEffectAchieved ? '可声明' : '不可声明'}`, `- 人工补号量下降:${summary.canClaim.manualSupplementReduced ? '可声明' : '不可声明'}`, '', '## 完成门槛', '', '| 门槛 | 状态 | 当前证据 | 缺失证明 | 通过标准 | 复验命令 |', '| --- | --- | --- | --- | --- | --- |', ...summary.gates.map(item => `| ${item.title} | ${item.status} | ${escapeCell(item.evidence)} | ${escapeCell((item.missingProofRequirements || []).join(';') || '-')} | ${escapeCell(item.required)} | \`${escapeCell(item.command)}\` |`), '', '## 下一步', '', ...summary.nextRequiredCommands.map(command => `- \`${command}\``), '', '## 边界', '', ...summary.guardrails.map(item => `- ${item}`) ].join('\n'); } function latestFile(outputsDir, fileName) { if (!fs.existsSync(outputsDir)) return ''; return fs.readdirSync(outputsDir, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .map(entry => path.join(outputsDir, entry.name, fileName)) .filter(file => fs.existsSync(file)) .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0] || ''; } function bestHistoryAudit(outputsDir) { return bestProofArtifact(outputsDir, 'historical-dataset-audit.json', { isClaimable: item => getHistoryMissingRequirements(item.json).length === 0 }); } function bestCustomerEffectSummary(outputsDir, historyFile = '', root = process.cwd()) { const history = readJsonArg(historyFile); const sourceFiles = { history: relIfInside(root, historyFile) }; return bestProofArtifact(outputsDir, 'customer-effect-summary.json', { isClaimable: item => getCustomerEffectMissingRequirements(item.json, history, sourceFiles, root).length === 0, score: item => { if (!historyFile || !item.json?.historyAuditPath) return 0; return samePath(root, item.json.historyAuditPath, historyFile) ? 2 : 0; } }); } function bestProofArtifact(outputsDir, fileName, { isClaimable = () => false, score = () => 0 } = {}) { const annotated = findFiles(outputsDir, fileName) .filter(file => isBusinessArtifactPath(outputsDir, file)) .map(file => ({ file, json: readJsonArg(file), mtimeMs: fs.statSync(file).mtimeMs })) .filter(item => item.json); if (!annotated.length) return ''; return annotated .sort((left, right) => { const rightClaimable = safePredicate(isClaimable, right) ? 1 : 0; const leftClaimable = safePredicate(isClaimable, left) ? 1 : 0; if (rightClaimable !== leftClaimable) return rightClaimable - leftClaimable; const rightScore = Number(score(right) || 0); const leftScore = Number(score(left) || 0); if (rightScore !== leftScore) return rightScore - leftScore; return right.mtimeMs - left.mtimeMs; })[0].file; } function safePredicate(predicate, item) { try { return Boolean(predicate(item)); } catch (_error) { return false; } } function isBusinessArtifactPath(outputsDir, file) { const relative = path.relative(outputsDir, file).replace(/\\/g, '/').toLowerCase(); const segments = relative.split('/').filter(Boolean); return !segments.some(segment => segment.includes('smoke') || segment.includes('sample') || segment.includes('debug') || segment === 'fixture' || segment === 'fixtures' || segment === '__tests__' || segment === 'test' || segment === 'tests' || segment === 'tmp' || segment.startsWith('tmp-') || segment === 'temp' || segment.startsWith('temp-') ); } function readJsonArg(file) { if (!file) return null; const resolved = path.resolve(file); if (!fs.existsSync(resolved)) return null; return JSON.parse(fs.readFileSync(resolved, 'utf8').replace(/^\uFEFF/, '')); } function relIfInside(root, file) { if (!file) return ''; const resolvedRoot = path.resolve(root); const resolvedFile = path.resolve(file); const relative = path.relative(resolvedRoot, resolvedFile); if (relative && (relative.startsWith('..') || path.isAbsolute(relative))) return resolvedFile.replace(/\\/g, '/'); return relative.replace(/\\/g, '/'); } function parseArgs(argv) { const args = {}; for (let index = 0; index < argv.length; index += 1) { const raw = argv[index]; if (!raw.startsWith('--')) continue; const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase()); const next = argv[index + 1]; if (!next || next.startsWith('--')) args[key] = true; else { args[key] = next; index += 1; } } return args; } function escapeCell(value) { return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' '); } function withBom(text) { return `\uFEFF${text}`; } if (require.main === module) main(); module.exports = { buildCompletionSummary, renderReport, getVideoAbMissingRequirements, getVideoAbPreflightMissingRequirements, formatVideoAbEvidence, isRealVideoAbProof, bestHistoryAudit, bestCustomerEffectSummary, bestOvernightAggregate, isClosableOvernightAggregate };