#!/usr/bin/env node const { buildAcceptanceGates, buildRuntimePreflight, computeVideoAcceptance } = require('./video-hit-rate-audit'); function main() { const passing = buildSummary(); passing.acceptance = computeVideoAcceptance(passing); passing.gates = buildAcceptanceGates(passing); assert(passing.acceptance.passed, 'complete video A/B evidence should pass'); assert(allGatesPass(passing), 'all gates should pass for complete video A/B evidence'); assert(hasGate(passing, '真实视频 URL 已加载'), 'should gate real video URL loading'); assert(hasGate(passing, 'Top10 证据命中提升且分数不下降'), 'should gate top10 evidence and score non-degradation'); assert(hasGate(passing, '强推荐数量不下降'), 'should gate strong recommendation non-degradation'); assertRuntimePreflight(); assertFails('missing video url should fail', summary => { summary.enhanced.resourceEvidence.videoUrlCount = 0; }, 'realVideoResourceLoaded'); assertFails('provider failure should fail', summary => { summary.enhanced.provider.evidence.providerStatus = 'fallback'; }, 'videoAnalysisProviderOk'); assertFails('pending evidence cards should fail', summary => { summary.enhanced.evidenceQuality.verifiedCardCount = 0; }, 'evidenceCardsNotPending'); assertFails('top10 score degradation should fail', summary => { summary.delta.top10AvgScore = -0.1; }, 'top10EvidenceImproved'); assertFails('strong count degradation should fail', summary => { summary.delta.strong = -1; }, 'strongNotDegraded'); console.log('video hit-rate audit smoke ok'); } function assertRuntimePreflight() { const missing = buildRuntimePreflight({ runtimeCredential: '', company: '', vocSocialToken: '', videoToken: '', videoBaseUrl: 'https://api.fmode.cn', videoModel: 'doubao-seed-2-0-pro', brief: 'brief.xlsx', referenceUrl: 'https://example.com/video', resultFirstMode: false }, 'out'); assert(!missing.readyForVideoAb, 'missing runtime inputs should not be ready for video A/B'); assert(missing.failureCount === 4, 'missing runtime inputs should expose four failed checks'); assert(missing.proofLevel === 'not_business_proof', 'runtime preflight should not be business proof'); assert(missing.canCloseProofGap === false, 'runtime preflight cannot close proof gap'); assert(missing.missingProofRequirements.includes('proofContext.requiresRuntimeCredential=true'), 'should expose runtime credential requirement'); assert(missing.missingProofRequirements.includes('proofContext.requiresVideoAnalysisProvider=true'), 'should expose video provider requirement'); assert(missing.proofContext.generatedBy === 'acceptance:video-ab-preflight', 'preflight should not masquerade as acceptance:video-ab'); const ready = buildRuntimePreflight({ runtimeCredential: 'runtime-token', company: 'company-id', vocSocialToken: 'voc-social-token', videoToken: 'video-token', videoBaseUrl: 'https://api.fmode.cn', videoModel: 'doubao-seed-2-0-pro', brief: 'brief.xlsx', referenceUrl: 'https://example.com/video', resultFirstMode: true }, 'out'); assert(ready.readyForVideoAb, 'complete runtime inputs should be ready to run video A/B'); assert(ready.failureCount === 0, 'complete runtime inputs should have no failed checks'); assert(ready.canCloseProofGap === false, 'ready preflight still cannot close proof gap'); assert(ready.nextActions.some(item => item.includes('acceptance:video-ab')), 'ready preflight should point to live A/B run'); } function buildSummary() { return { baseline: { counts: { candidates: 10, strong: 2, evidenceCards: 0, referenceResources: 0 }, top10: { avgScore: 72, avgReferenceStyleFitScore: 20, evidenceHitCandidates: 0 } }, enhanced: { counts: { candidates: 14, strong: 3, evidenceCards: 4, referenceResources: 2 }, provider: { evidence: { providerStatus: 'ok' } }, resourceEvidence: { referenceResources: 2, videoUrlCount: 1, coverUrlCount: 1, asrTextCount: 0, frameUrlCount: 0 }, evidenceQuality: { verifiedCardCount: 3, cardsWithSignals: 3 }, top10: { avgScore: 73, avgReferenceStyleFitScore: 24, evidenceHitCandidates: 3 } }, delta: { candidates: 4, strong: 1, evidenceCards: 4, referenceResources: 2, top10AvgReferenceStyleFitScore: 4, top10AvgScore: 1, top10EvidenceHitCandidates: 3 } }; } function assertFails(message, mutate, failedKey) { const summary = buildSummary(); mutate(summary); summary.acceptance = computeVideoAcceptance(summary); summary.gates = buildAcceptanceGates(summary); assert(!summary.acceptance.passed, message); assert(summary.acceptance[failedKey] === false, `${message}: ${failedKey} should be false`); assert(summary.gates.some(gate => gate.status === 'fail'), `${message}: at least one gate should fail`); } function allGatesPass(summary) { return summary.gates.every(gate => gate.status === 'pass'); } function hasGate(summary, name) { return summary.gates.some(gate => gate.name === name); } function assert(condition, message) { if (!condition) throw new Error(message); } if (require.main === module) main();