| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869 |
- #!/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 outputDir = path.resolve(args.output || process.env.TIHAO_EVIDENCE_INDEX_OUTPUT || path.join(OUTPUTS, `evidence-index-${Date.now()}`));
- const summary = buildEvidenceIndex({ root: ROOT, outputsDir: path.resolve(args.outputs || OUTPUTS) });
- fs.mkdirSync(outputDir, { recursive: true });
- const jsonPath = path.join(outputDir, 'evidence-index-summary.json');
- const reportPath = path.join(outputDir, 'evidence-index-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,
- total: summary.total,
- realEvidence: summary.counts.real_evidence || 0,
- smokeOrLocal: summary.counts.smoke_or_local || 0,
- notProof: summary.counts.not_business_proof || 0
- }, null, 2));
- }
- function buildEvidenceIndex({ root, outputsDir }) {
- const dirs = fs.existsSync(outputsDir)
- ? fs.readdirSync(outputsDir, { withFileTypes: true }).filter(item => item.isDirectory()).map(item => path.join(outputsDir, item.name))
- : [];
- const entries = dirs.flatMap(dir => classifyDir(root, dir)).sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
- return {
- generatedAt: new Date().toISOString(),
- root,
- outputsDir,
- total: entries.length,
- counts: countBy(entries, 'proofLevel'),
- byType: countBy(entries, 'type'),
- entries
- };
- }
- function classifyDir(root, dir) {
- const entries = [];
- const aggregate = readJson(path.join(dir, 'aggregate-summary.json'));
- if (aggregate) entries.push(classifyOvernight(root, dir, aggregate));
- const status = readJson(path.join(dir, 'optimization-status-summary.json'));
- if (status) entries.push(classifyStatus(root, dir, status));
- const readiness = readJson(path.join(dir, 'long-run-readiness-summary.json'));
- if (readiness) entries.push(classifyReadiness(root, dir, readiness));
- const pipeline = readJson(path.join(dir, 'optimization-pipeline-summary.json'));
- if (pipeline) entries.push(classifyPipeline(root, dir, pipeline));
- const completion = readJson(path.join(dir, 'optimization-completion-summary.json'));
- if (completion) entries.push(classifyOptimizationCompletion(root, dir, completion));
- const handoff = readJson(path.join(dir, 'optimization-handoff-summary.json'));
- if (handoff) entries.push(classifyHandoff(root, dir, handoff));
- const proofGap = readJson(path.join(dir, 'proof-gap-request-summary.json'));
- if (proofGap) entries.push(classifyProofGap(root, dir, proofGap));
- const proofGapClosure = readJson(path.join(dir, 'proof-gap-closure-summary.json'));
- if (proofGapClosure) entries.push(classifyProofGapClosure(root, dir, proofGapClosure));
- const proofGapOperatorPack = readJson(path.join(dir, 'proof-gap-operator-pack-summary.json'));
- if (proofGapOperatorPack) entries.push(classifyProofGapOperatorPack(root, dir, proofGapOperatorPack));
- const realProofIntakeBundle = readJson(path.join(dir, 'real-proof-intake-bundle-summary.json'));
- if (realProofIntakeBundle) entries.push(classifyRealProofIntakeBundle(root, dir, realProofIntakeBundle));
- const realProofClosureWorkOrder = readJson(path.join(dir, 'real-proof-closure-work-order-summary.json'));
- if (realProofClosureWorkOrder) entries.push(classifyRealProofClosureWorkOrder(root, dir, realProofClosureWorkOrder));
- const realGapMaterialAudit = readJson(path.join(dir, 'real-gap-material-audit-summary.json'));
- if (realGapMaterialAudit) entries.push(classifyRealGapMaterialAudit(root, dir, realGapMaterialAudit));
- const localSeedMaterialIndex = readJson(path.join(dir, 'local-seed-material-index-summary.json'));
- if (localSeedMaterialIndex) entries.push(classifyLocalSeedMaterialIndex(root, dir, localSeedMaterialIndex));
- const localSeedToIntakeWorklist = readJson(path.join(dir, 'local-seed-to-intake-worklist-summary.json'));
- if (localSeedToIntakeWorklist) entries.push(classifyLocalSeedToIntakeWorklist(root, dir, localSeedToIntakeWorklist));
- const experienceTranscriptIndex = readJson(path.join(dir, 'experience-transcript-index-summary.json'));
- if (experienceTranscriptIndex) entries.push(classifyExperienceTranscriptIndex(root, dir, experienceTranscriptIndex));
- const feedbackLoopClosure = readJson(path.join(dir, 'feedback-loop-closure-summary.json'));
- if (feedbackLoopClosure) entries.push(classifyFeedbackLoopClosure(root, dir, feedbackLoopClosure));
- const homepageEvidenceReadiness = readJson(path.join(dir, 'homepage-evidence-readiness-summary.json'));
- if (homepageEvidenceReadiness) entries.push(classifyHomepageEvidenceReadiness(root, dir, homepageEvidenceReadiness));
- const proofGapSoftwareForm = readJson(path.join(dir, 'tihao-proof-gap-software-form-summary.json'));
- if (proofGapSoftwareForm) entries.push(classifyProofGapSoftwareForm(root, dir, proofGapSoftwareForm));
- const latestFormIndex = readJson(path.join(dir, 'latest-form-index-summary.json'));
- if (latestFormIndex) entries.push(classifyLatestFormIndex(root, dir, latestFormIndex));
- const roundDeposition = readJson(path.join(dir, 'round-deposition-summary.json'));
- if (roundDeposition) entries.push(classifyRoundDeposition(root, dir, roundDeposition));
- const businessProofProgress = readJson(path.join(dir, 'business-proof-progress-summary.json'));
- if (businessProofProgress) entries.push(classifyBusinessProofProgress(root, dir, businessProofProgress));
- const businessProofNextActions = readJson(path.join(dir, 'business-proof-next-actions-summary.json'));
- if (businessProofNextActions) entries.push(classifyBusinessProofNextActions(root, dir, businessProofNextActions));
- const businessExecutionIndex = readJson(path.join(dir, 'business-execution-index-summary.json'));
- if (businessExecutionIndex) entries.push(classifyBusinessExecutionIndex(root, dir, businessExecutionIndex));
- const intakeReadiness = readJson(path.join(dir, 'intake-readiness-summary.json'));
- if (intakeReadiness) entries.push(classifyIntakeReadiness(root, dir, intakeReadiness));
- const intakeFieldChecklist = readJson(path.join(dir, 'intake-field-checklist-summary.json'));
- if (intakeFieldChecklist) entries.push(classifyIntakeFieldChecklist(root, dir, intakeFieldChecklist));
- if (isDataIntakePack(dir)) entries.push(classifyDataIntakePack(root, dir));
- if (isVideoIntakePack(dir)) entries.push(classifyVideoIntakePack(root, dir));
- const preflight = readJson(path.join(dir, 'live-preflight-summary.json'));
- if (preflight) entries.push(classifyPreflight(root, dir, preflight));
- const review = readJson(path.join(dir, 'review-metrics-summary.json'));
- if (review) entries.push(classifyReview(root, dir, review));
- const customerEffect = readJson(path.join(dir, 'customer-effect-summary.json')) ||
- readJson(path.join(dir, 'customer-effect-audit', 'customer-effect-summary.json'));
- if (customerEffect) entries.push(classifyCustomerEffect(root, dir, customerEffect, { pipeline }));
- const history = readJson(path.join(dir, 'historical-dataset-audit.json'));
- if (history) entries.push(classifyHistory(root, dir, history));
- const video = readJson(path.join(dir, 'video-hit-rate-summary.json'));
- if (video) entries.push(classifyVideo(root, dir, video));
- const videoRuntimePreflight = readJson(path.join(dir, 'video-hit-rate-preflight-summary.json'));
- if (videoRuntimePreflight) entries.push(classifyVideoRuntimePreflight(root, dir, videoRuntimePreflight));
- const videoResourceReadiness = readJson(path.join(dir, 'video-resource-readiness-summary.json'));
- if (videoResourceReadiness) entries.push(classifyVideoResourceReadiness(root, dir, videoResourceReadiness));
- if (fs.existsSync(path.join(dir, 'intake-readiness-repair-actions.csv'))) {
- entries.push(classifyRepairActions(root, dir, 'intake-repair-actions', 'intake-readiness-repair-actions.csv'));
- }
- if (fs.existsSync(path.join(dir, 'video-resource-repair-actions.csv'))) {
- entries.push(classifyRepairActions(root, dir, 'video-resource-repair-actions', 'video-resource-repair-actions.csv'));
- }
- if (fs.existsSync(path.join(dir, 'homepage-evidence-repair-actions.csv'))) {
- entries.push(classifyRepairActions(root, dir, 'homepage-evidence-repair-actions', 'homepage-evidence-repair-actions.csv'));
- }
- return entries;
- }
- function classifyOvernight(root, dir, aggregate) {
- const liveProof = Boolean(aggregate.manifest?.liveEnabled) &&
- aggregate.acceptance?.overallPass === true &&
- Number(aggregate.failureCount || 0) === 0 &&
- Number(aggregate.acceptance?.failedGateCount ?? aggregate.failedGateCount ?? 0) === 0;
- return {
- type: 'overnight-quality',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: liveProof ? 'real_evidence' : aggregate.manifest?.liveEnabled ? 'not_business_proof' : 'smoke_or_local',
- status: aggregate.acceptance?.overallPass ? 'pass' : 'not_passed',
- liveEnabled: Boolean(aggregate.manifest?.liveEnabled),
- runCount: aggregate.runCount || 0,
- failureCount: aggregate.failureCount || 0,
- failedGateCount: aggregate.acceptance?.failedGateCount ?? aggregate.failedGateCount ?? 0,
- summary: `fixtures=${aggregate.fixtureCount || 0}, variants=${aggregate.variantCount || 0}, runs=${aggregate.runCount || 0}`,
- next: liveProof
- ? '可作为 live 自动化证据;仍需人工复核和客户选择数据证明真实业务效果。'
- : aggregate.manifest?.liveEnabled
- ? 'live 产物未通过全部门槛,不能作为业务证明。'
- : 'sample/smoke 只能证明结构和本地门槛。'
- };
- }
- function classifyStatus(root, dir, status) {
- return {
- type: 'optimization-status',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: status.complete ? 'real_evidence' : 'not_business_proof',
- status: status.complete ? 'complete' : 'incomplete',
- summary: `passed=${status.counts?.passed || 0}, ready_not_proven=${status.counts?.ready_not_proven || 0}, blocked_by_external_data=${status.counts?.blocked_by_external_data || 0}`,
- next: status.complete ? '状态审计已完成。' : '仍有 ready_not_proven 或 blocked_by_external_data,不能宣布长期目标完成。'
- };
- }
- function classifyReadiness(root, dir, readiness) {
- return {
- type: 'long-run-readiness',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: readiness.ready ? 'smoke_or_local' : 'not_business_proof',
- status: readiness.ready ? 'ready' : 'not_ready',
- mode: readiness.mode,
- summary: `mode=${readiness.mode}, fail=${readiness.counts?.fail || 0}, warn=${readiness.counts?.warn || 0}`,
- next: readiness.ready ? '可作为启动前置检查;不等同于 live 或客户效果证明。' : '先补齐失败项,再启动对应长跑。'
- };
- }
- function classifyPipeline(root, dir, pipeline) {
- const allStepsPass = Array.isArray(pipeline.results) && pipeline.results.length > 0 &&
- pipeline.results.every(item => item.status === 'pass' || item.status === 'planned');
- const closure = pipeline.proofGapClosure || {};
- const closureComplete = Boolean(closure.complete);
- const readyForClaim = Boolean(pipeline.readyForClaim) && closureComplete;
- return {
- type: 'optimization-pipeline',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: allStepsPass ? 'smoke_or_local' : 'not_business_proof',
- status: allStepsPass ? (pipeline.dryRun ? 'planned' : 'pass') : 'not_passed',
- summary: `steps=${pipeline.results?.length || 0}, pass=${pipeline.counts?.pass || 0}, planned=${pipeline.counts?.planned || 0}, fail=${pipeline.counts?.fail || 0}, nextActions=${pipeline.nextActions?.length || 0}, readyForClaim=${readyForClaim}, closureComplete=${closureComplete}, closureOpen=${closure.openCount ?? 'unknown'}`,
- next: readyForClaim
- ? 'pipeline 已跑通并且 proof-gap:closure complete=true;仍需回看 customer-effect、video A/B 和 live/provider 证据细项。'
- : 'pipeline 只能证明编排链路或局部审计完成;proof-gap:closure 未关闭前不能宣称业务效果。'
- };
- }
- function classifyOptimizationCompletion(root, dir, completion) {
- const readyForClaim = Boolean(completion.complete && completion.readyForClaim && Number(completion.proofGapOpenCount || 0) === 0);
- return {
- type: 'optimization-completion',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: readyForClaim ? 'real_evidence' : 'not_business_proof',
- status: readyForClaim ? 'ready_for_claim' : 'incomplete',
- summary: `complete=${Boolean(completion.complete)}, readyForClaim=${Boolean(completion.readyForClaim)}, proofGapOpen=${completion.proofGapOpenCount ?? 'unknown'}, blockers=${completion.blockingReasons?.length ?? 'unknown'}`,
- next: readyForClaim
- ? '完成度审计允许进入业务效果声明;仍需回看具体客户效果、视频 A/B 和 live/provider 证据。'
- : '完成度审计显示仍有阻塞项,不能宣称提号率提升、客户效果达标或人工补号量下降。'
- };
- }
- function classifyDataIntakePack(root, dir) {
- return {
- type: 'data-intake-pack',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: 'ready',
- summary: 'history template, manual review template, and README are present',
- next: '交给商务填写真实历史 Brief、客户选择、历史人工补号基线和本轮人工补号量;模板本身不证明业务效果。'
- };
- }
- function classifyVideoIntakePack(root, dir) {
- return {
- type: 'video-intake-pack',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: 'ready',
- summary: 'video resource template and README are present',
- next: '交给商务补齐真实参考视频和候选视频的 URL、封面、ASR、帧图或正文证据;模板本身不证明视频 A/B 通过。'
- };
- }
- function classifyHandoff(root, dir, handoff) {
- return {
- type: 'optimization-handoff',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: handoff.complete ? 'complete_reported' : 'incomplete_reported',
- summary: `ready=${handoff.readyCapabilities?.length || 0}, readyNotProven=${handoff.readyNotProven?.length || 0}, blockers=${handoff.externalBlockers?.length || 0}`,
- next: handoff.complete
- ? '交接摘要声称完成时必须回看 optimization:status 和客户效果证据。'
- : '可用于下一轮 AI 或商务交接;不证明客户效果。'
- };
- }
- function classifyProofGap(root, dir, proofGap) {
- return {
- type: 'proof-gap-request',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: proofGap.unresolvedCount ? 'gaps_requested' : 'no_gaps_reported',
- summary: `gaps=${proofGap.rows?.length || 0}, unresolved=${proofGap.unresolvedCount || 0}`,
- next: '可作为商务/下一轮 AI 补真实证明数据的请求清单;不证明业务效果。'
- };
- }
- function classifyProofGapClosure(root, dir, closure) {
- return {
- type: 'proof-gap-closure',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: closure.complete ? 'real_evidence' : 'not_business_proof',
- status: closure.complete ? 'all_closed' : 'open_gaps',
- summary: `closed=${closure.closedCount || 0}, open=${closure.openCount || 0}`,
- next: closure.complete
- ? '所有真实证明缺口均已关闭;仍需回看 optimization:status 是否 complete。'
- : '仍有 proof gap 未关闭,不能宣布长期优化目标完成。'
- };
- }
- function classifyProofGapOperatorPack(root, dir, pack) {
- return {
- type: 'proof-gap-operator-pack',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- complete: Boolean(pack.complete),
- directCustomerProof: false,
- summary: `openCount=${Number(pack.openCount || 0)}, rowCount=${Number(pack.rowCount || 0)}`,
- next: '按负责人、填写文件、复验命令和边界补齐真实证明;不证明客户效果。'
- };
- }
- function classifyRealProofIntakeBundle(root, dir, bundle) {
- return {
- type: 'real-proof-intake-bundle',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: bundle.complete ? 'all_real_proof_closed' : 'materials_requested',
- complete: Boolean(bundle.complete),
- directCustomerProof: false,
- summary: `proofOpen=${Number(bundle.proofOpenCount || 0)}, fillRows=${Number(bundle.fillRowCount || 0)}, proofGaps=${Number(bundle.proofGapRowCount || 0)}, commands=${Number(bundle.recheckCommandCount || 0)}`,
- next: bundle.complete
- ? '真实材料总包显示 proofOpenCount=0;仍需回看 customer-effect、video A/B 和 live/provider 证据细项。'
- : '按总包中的模板、字段缺口、修复清单和复验命令补齐真实材料;不证明客户效果。'
- };
- }
- function classifyRealProofClosureWorkOrder(root, dir, summary) {
- return {
- type: 'real-proof-closure-work-order',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: summary.complete ? 'all_work_orders_closed' : 'work_orders_open',
- complete: Boolean(summary.complete),
- directCustomerProof: false,
- canCloseProofGap: false,
- summary: `workOrders=${Number(summary.workOrderCount || 0)}, owners=${Number(summary.ownerGroupCount || 0)}, proofOpen=${summary.sourceState?.proofGapOpenCount ?? 'unknown'}, intakeFailures=${summary.sourceState?.intakeFailureCount ?? 'unknown'}, realCandidateRows=${summary.sourceState?.realCandidateRows ?? 'unknown'}`,
- next: summary.complete
- ? '闭环工单显示无待补项;仍需回看 proof-gap:closure 和 optimization:completion 是否允许完成声明。'
- : '按负责人附件逐字段补真实历史 Brief、真实候选视频、客户选择和人工补号量;工单本身不证明客户效果。'
- };
- }
- function classifyRealGapMaterialAudit(root, dir, summary) {
- return {
- type: 'real-gap-material-audit',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: summary.canCloseProofGap ? 'claimable' : 'open_gaps_have_materials',
- complete: Boolean(summary.complete),
- directCustomerProof: false,
- canCloseProofGap: false,
- summary: `gaps=${Number(summary.gapCount || 0)}, materialFound=${Number(summary.materialFoundCount || 0)}, open=${Number(summary.openGapCount || 0)}`,
- next: 'Use this audit to prove project materials were found for the three open gaps; do not use it as customer-effect, video A/B, or manual-supplement business proof.'
- };
- }
- function classifyProofGapSoftwareForm(root, dir, form) {
- return {
- type: 'proof-gap-software-form',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: form.passed ? 'smoke_or_local' : 'not_business_proof',
- status: form.passed ? 'pass' : 'not_passed',
- summary: `rows=${form.rowCount || 0}, duplicateId=${form.duplicateIdCount || 0}, header=${Boolean(form.headerMatches)}, rowWidth=${Boolean(form.rowWidthOk)}`,
- next: '用于软件端/商务补证任务表;不证明客户效果或命中率提升。'
- };
- }
- function classifyLatestFormIndex(root, dir, index) {
- return {
- type: 'latest-form-index',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: index.passed ? 'smoke_or_local' : 'not_business_proof',
- status: index.passed ? 'pass' : 'not_passed',
- summary: `actions=${index.nextActions?.actionCount || 0}, proofOpen=${index.nextActions?.proofOpenCount ?? 'unknown'}, primaryForm=${Boolean(index.primaryForm?.csv)}, clientList=${Boolean(index.clientList?.csv)}`,
- next: '用于快速定位最新补证表单、候选名单和负责人行动文件;不证明客户效果或命中率提升。'
- };
- }
- function classifyRoundDeposition(root, dir, roundDeposition) {
- return {
- type: 'round-deposition',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: roundDeposition.passed ? 'smoke_or_local' : 'not_business_proof',
- status: roundDeposition.passed ? 'pass' : 'not_passed',
- summary: `pass=${roundDeposition.counts?.pass || 0}, warn=${roundDeposition.counts?.warn || 0}, fail=${roundDeposition.counts?.fail || 0}`,
- next: roundDeposition.passed
- ? '可证明本轮交接沉淀完整;仍不证明客户效果或命中率提升。'
- : '先补齐实施日志、证据台账、状态审计或交接摘要,再进入下一轮。'
- };
- }
- function classifyBusinessProofProgress(root, dir, progress) {
- return {
- type: 'business-proof-progress',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: progress.complete ? 'all_steps_pass' : 'in_progress',
- summary: `pass=${progress.counts?.pass || 0}, fail=${progress.counts?.fail || 0}, pending=${progress.counts?.pending || 0}, blocked=${progress.counts?.blocked_by_external_data || 0}`,
- next: progress.complete
- ? '15 步补证进度均为 pass;仍需回看 proof-gap:closure 和 customer-effect:audit 是否真实通过。'
- : '用于实时同步补证步骤进度;不证明客户效果或命中率提升。'
- };
- }
- function classifyBusinessProofNextActions(root, dir, nextActions) {
- return {
- type: 'business-proof-next-actions',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: nextActions.complete ? 'no_actions_needed' : 'actions_open',
- summary: `actions=${nextActions.actions?.length || 0}, progressComplete=${Boolean(nextActions.sourceState?.progressComplete)}, proofGapOpen=${nextActions.sourceState?.proofGapOpenCount ?? 'unknown'}`,
- next: nextActions.complete
- ? '行动队列显示无待办;仍需回看 proof-gap:closure 和 customer-effect:audit 是否真实通过。'
- : '用于把 blocked/fail/pending 转成可分派任务;不证明客户效果或命中率提升。'
- };
- }
- function classifyBusinessExecutionIndex(root, dir, index) {
- return {
- type: 'business-execution-index',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: index.passed ? (index.proofOpenCount > 0 ? 'actions_open' : 'ready_for_claim_audit') : 'not_passed',
- summary: `rows=${index.rowCount || 0}, actions=${index.actionRowCount || 0}, repairs=${index.repairRowCount || 0}, proofOpen=${index.proofOpenCount ?? 'unknown'}`,
- next: '用于商务集中定位补证表单、负责人文件和修复清单;不证明客户效果或命中率提升。'
- };
- }
- function classifyLocalSeedMaterialIndex(root, dir, summary) {
- return {
- type: 'local-seed-material-index',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: summary.existingMaterialCount > 0 ? 'seed_materials_available' : 'seed_materials_not_found',
- complete: false,
- directCustomerProof: false,
- canCloseProofGap: false,
- summary: `materials=${summary.existingMaterialCount || 0}/${summary.materialCount || 0}, liveCandidates=${summary.promisingLiveAggregateCount || 0}, canCloseProofGap=false`,
- next: '可用于补真实 Brief、候选池和视频资源模板;不能替代客户选择、人工补号量、真实候选视频或 proof-gap:closure。'
- };
- }
- function classifyLocalSeedToIntakeWorklist(root, dir, summary) {
- return {
- type: 'local-seed-to-intake-worklist',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: Number(summary.counts?.historyDraftRows || 0) > 0 ? 'seed_worklist_ready' : 'seed_worklist_empty',
- complete: false,
- directCustomerProof: false,
- canCloseProofGap: false,
- summary: `historyDraft=${summary.counts?.historyDraftRows || 0}, references=${summary.counts?.referenceSeedRows || 0}, candidates=${summary.counts?.candidateSeedRows || 0}, videoWorklist=${summary.counts?.videoWorklistRows || 0}, canCloseProofGap=false`,
- next: '可作为商务/投放把本地 DHA 种子转写进真实 intake 模板的补表清单;不能替代客户选择、人工补号量、真实候选视频或 proof-gap:closure。'
- };
- }
- function classifyExperienceTranscriptIndex(root, dir, summary) {
- return {
- type: 'experience-transcript-index',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: Number(summary.transcriptCount || 0) > 0 ? 'experience_seed_available' : 'experience_seed_missing',
- complete: false,
- directCustomerProof: false,
- canCloseProofGap: false,
- summary: `transcripts=${summary.transcriptCount || 0}, rules=${summary.coveredRuleCount || 0}/${summary.ruleCount || 0}, canCloseProofGap=false`,
- next: '可用于追踪提号经验来源和校准规则实现;不能替代真实历史 Brief、客户选择、人工补号量或 customer-effect:audit。'
- };
- }
- function classifyFeedbackLoopClosure(root, dir, closure) {
- return {
- type: 'feedback-loop-closure',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: closure.directCustomerProof ? 'real_evidence' : closure.passed ? 'smoke_or_local' : 'not_business_proof',
- status: closure.directCustomerProof ? 'direct_customer_proof' : closure.passed ? 'local_loop_passed' : 'missing_or_open',
- summary: `negative=${closure.negativeFeedbackCount || 0}, blocked=${closure.blockedCreatorCount || 0}, leaked=${closure.leakedCount || 0}, directCustomerProof=${Boolean(closure.directCustomerProof)}`,
- next: closure.directCustomerProof
- ? '反馈二轮闭环已有真实客户反馈和真实二轮结果;仍需 customer-effect:audit 证明客户效果。'
- : '用于证明反馈剔除/降权闭环是否执行;缺真实客户反馈或真实二轮结果时不能证明客户效果。'
- };
- }
- function classifyHomepageEvidenceReadiness(root, dir, readiness) {
- return {
- type: 'homepage-evidence-readiness',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: readiness.ready ? 'smoke_or_local' : 'not_business_proof',
- status: readiness.ready ? 'ready' : 'not_ready',
- summary: `candidates=${readiness.counts?.candidates || 0}, providerEvidence=${readiness.counts?.providerEvidenceCreators || 0}, posts=${readiness.counts?.creatorsWithPosts || 0}, failureCount=${readiness.failureCount || 0}`,
- next: readiness.ready
- ? '可作为主页近期内容证据进入强推荐复核;仍需真实人工复核和客户选择证明命中率。'
- : '先补齐最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号,不能把 fallback 当成强推荐证据。'
- };
- }
- function classifyIntakeReadiness(root, dir, intakeReadiness) {
- const ready = intakeReadiness.acceptance?.overallReady === true &&
- Number(intakeReadiness.failureCount || 0) === 0;
- return {
- type: 'intake-readiness',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: ready ? 'smoke_or_local' : 'not_business_proof',
- status: ready ? 'ready' : 'not_ready',
- summary: `history=${intakeReadiness.acceptance?.historyReady ? 'ready' : 'not_ready'}, video=${intakeReadiness.acceptance?.videoReady ? 'ready' : 'not_ready'}, review=${intakeReadiness.acceptance?.reviewMetricsReady ? 'ready' : 'not_ready'}, customerEffect=${intakeReadiness.acceptance?.customerEffectReady ? 'ready' : 'not_ready'}, failureCount=${intakeReadiness.failureCount || 0}`,
- next: ready
- ? '可进入 history:audit、video:resource-readiness、review:metrics 和 customer-effect:audit;仍不等同于业务效果证明。'
- : '先把模板占位替换为真实历史 Brief、真实视频资源、客户选择和本轮人工补号量。'
- };
- }
- function classifyIntakeFieldChecklist(root, dir, checklist) {
- return {
- type: 'intake-field-checklist',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'smoke_or_local',
- status: Number(checklist.itemCount || 0) > 0 ? 'actions_open' : 'empty',
- summary: `items=${checklist.itemCount || 0}, missingRequired=${checklist.missingRequiredCount || 0}, placeholders=${checklist.placeholderCount || 0}`,
- next: '用于指导商务/投放逐字段补齐真实历史 Brief、人工复核、客户选择和视频资源;不证明客户效果。'
- };
- }
- function classifyPreflight(root, dir, preflight) {
- const failureCount = Array.isArray(preflight.checks) ? preflight.checks.filter(item => item.status === 'fail').length : 0;
- const warnCount = Array.isArray(preflight.checks) ? preflight.checks.filter(item => item.status === 'warn').length : 0;
- return {
- type: 'live-preflight',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: preflight.readyForLiveAcceptance ? 'smoke_or_local' : 'not_business_proof',
- status: preflight.readyForLiveAcceptance ? 'ready' : 'not_ready',
- readyForVideoAb: Boolean(preflight.readyForVideoAb),
- summary: `live=${preflight.readyForLiveAcceptance ? 'ready' : 'not_ready'}, videoAb=${preflight.readyForVideoAb ? 'ready' : 'not_ready'}, fail=${failureCount}, warn=${warnCount}`,
- next: preflight.readyForLiveAcceptance
- ? preflight.readyForVideoAb
- ? '可作为 live/video A/B 启动前置证据;仍不等同于业务效果证明。'
- : '可启动 live 子集;视频 A/B 还需补齐视频分析配置。'
- : '缺 sessionToken、company 或 provider 关键配置时,不能启动 live 长跑。'
- };
- }
- function classifyReview(root, dir, review) {
- return {
- type: 'review-metrics',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: review.acceptance?.customerSelectedRatePass === true ? 'real_evidence' : 'not_business_proof',
- status: review.acceptance?.overallPass ? 'pass' : 'not_passed',
- summary: `rows=${review.total || 0}, usable=${pct(review.businessUsableRate)}, negative=${pct(review.offTargetHardFailRate)}, selected=${review.selectedCount ? pct(review.customerSelectedRate) : 'unmeasured'}`,
- next: review.selectedCount ? '可用于客户选择效果复盘。' : '缺客户选择字段时不能证明客户选中率。'
- };
- }
- function classifyCustomerEffect(root, dir, customerEffect, context = {}) {
- const effectProof = customerEffect.acceptance?.overallPass === true &&
- customerEffect.acceptance?.customerSelectedRate30Pass === true &&
- customerEffect.acceptance?.referenceCustomerSelectedRate40Pass !== false &&
- customerEffect.acceptance?.historyReadyForCustomerEffectProof === true &&
- customerEffect.acceptance?.manualSupplementBaselineAvailable === true &&
- customerEffect.acceptance?.currentManualSupplementAvailable === true &&
- customerEffect.acceptance?.manualSupplementReduction50Pass === true;
- const pipelineBlocker = getPipelineScopedCustomerEffectBlocker(context.pipeline);
- const acceptedProof = effectProof && !pipelineBlocker;
- return {
- type: 'customer-effect',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: acceptedProof ? 'real_evidence' : 'not_business_proof',
- status: customerEffect.acceptance?.overallPass ? 'pass' : 'not_passed',
- pipelineScoped: Boolean(context.pipeline),
- pipelineClaimable: context.pipeline ? !pipelineBlocker : null,
- summary: `selected=${customerEffect.acceptance?.customerSelectedRateMeasured ? pct(customerEffect.customerSelectedRate) : 'unmeasured'}, reference=${customerEffect.acceptance?.referenceCustomerSelectedRateMeasured ? pct(customerEffect.referenceCustomerSelectedRate) : 'unmeasured'}, supplementReduction=${customerEffect.manualSupplementReductionRate === null || customerEffect.manualSupplementReductionRate === undefined ? 'unmeasured' : pct(customerEffect.manualSupplementReductionRate)}${pipelineBlocker ? `, pipelineBlocked=${pipelineBlocker.reasons.join('+')}` : ''}`,
- next: acceptedProof
- ? '可作为客户效果证明:客户选中率、参考链路和人工补号减少门槛已通过。'
- : pipelineBlocker
- ? '该 customer-effect 位于未完成或不可声明的 optimization-pipeline 中;必须先刷新当前 proof-gap closure 和 completion,不能用旧 pipeline 内嵌产物证明客户效果完成。'
- : '缺客户选择、历史补号基线、本轮补号量或减少率未达标时,不能证明客户效果完成。'
- };
- }
- function getPipelineScopedCustomerEffectBlocker(pipeline) {
- if (!pipeline) return null;
- const openCount = Number(pipeline.proofGapClosure?.openCount || 0);
- const closureComplete = pipeline.proofGapClosure?.complete === true && openCount === 0;
- if (pipeline.readyForClaim === true && closureComplete) return null;
- const reasons = [];
- if (pipeline.readyForClaim !== true) reasons.push('readyForClaim=false');
- if (pipeline.proofGapClosure?.complete !== true) reasons.push('proofGapClosure.complete=false');
- if (openCount !== 0) reasons.push(`proofGapClosure.openCount=${openCount}`);
- return { reasons };
- }
- function classifyHistory(root, dir, history) {
- const missingCustomerEffectProof = getHistoryCustomerEffectProofMissingRequirements(history);
- const readyForCustomerEffectProof = missingCustomerEffectProof.length === 0;
- const readyForLongRun = readyForCustomerEffectProof || isHistoryReadyForLongRun(history);
- return {
- type: 'historical-dataset',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: readyForCustomerEffectProof ? 'real_evidence' : readyForLongRun ? 'smoke_or_local' : 'not_business_proof',
- status: readyForLongRun ? 'ready_for_long_run' : 'not_ready',
- summary: `briefs=${history.briefCount || 0}, categories=${history.categoryCount || 0}, customerDecision=${history.withCustomerDecision || 0}, missing=${missingCustomerEffectProof.length}`,
- missingProofRequirements: missingCustomerEffectProof,
- next: readyForCustomerEffectProof ? '可用于客户效果证明前置数据。' : '缺客户选择、拒绝原因、人工补号量基线或可审计明细时不能证明客户效果。'
- };
- }
- function isHistoryReadyForLongRun(history) {
- if (!history) return false;
- const acceptance = history.acceptance || {};
- return Boolean(
- acceptance.readyForLongRun === true &&
- acceptance.parseOk === true &&
- acceptance.minBriefsMet === true &&
- acceptance.allHaveBriefText === true &&
- acceptance.allHaveManualFinalList === true &&
- acceptance.allHaveCustomerDecision === true &&
- acceptance.allHaveFeedbackReason === true &&
- acceptance.categoryCoverageMet === true &&
- Number(history.briefCount || 0) >= 5 &&
- Array.isArray(history.items) &&
- history.items.length >= 5 &&
- Number(history.missingCriticalCount || 0) === 0
- );
- }
- function getHistoryCustomerEffectProofMissingRequirements(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 uniqueStrings(missing);
- }
- function classifyVideo(root, dir, video) {
- const videoProof = video.acceptance?.passed === true &&
- video.acceptance?.realReferenceResourceLoaded === true &&
- video.acceptance?.realVideoResourceLoaded === true &&
- video.acceptance?.coverOrFrameOrAsrLoaded === true &&
- video.acceptance?.evidenceCardsNotPending === true &&
- video.acceptance?.evidenceCardsHaveSignals === true &&
- video.acceptance?.strongNotDegraded === true &&
- video.acceptance?.top10EvidenceImproved === true;
- return {
- type: 'video-ab',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: videoProof ? 'real_evidence' : 'not_business_proof',
- status: video.acceptance?.passed ? 'pass' : 'not_passed',
- summary: `strongDelta=${video.delta?.strong ?? 'n/a'}, evidenceDelta=${video.delta?.top10EvidenceHitCandidates ?? 'n/a'}`,
- next: videoProof ? '可作为视频证据提升的 A/B 证明。' : '不能宣称视频分析提升命中率。'
- };
- }
- function classifyVideoRuntimePreflight(root, dir, preflight) {
- return {
- type: 'video-ab-runtime-preflight',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: 'not_business_proof',
- status: preflight.readyForVideoAb ? 'ready_to_run' : 'not_ready',
- summary: `readyForVideoAb=${Boolean(preflight.readyForVideoAb)}, failureCount=${preflight.failureCount || 0}, generatedBy=${preflight.proofContext?.generatedBy || 'unknown'}`,
- next: preflight.readyForVideoAb
- ? '只表示运行环境可启动 acceptance:video-ab;必须生成 video-hit-rate-summary.json 才能进入 proof-gap closure。'
- : '补齐运行时凭证、company、VOC social provider 和视频分析 provider 后再运行 acceptance:video-ab,并生成 live video-hit-rate-summary.json;preflight 失败不证明视频提升。'
- };
- }
- function renderReport(summary) {
- const latest = pickLatestUseful(summary.entries);
- const lines = [
- '# 提号优化证据台账',
- '',
- `- 生成时间:${summary.generatedAt}`,
- `- 扫描目录:${summary.outputsDir}`,
- `- 证据条目:${summary.total}`,
- '',
- '## 汇总',
- '',
- ...Object.entries(summary.counts).map(([key, value]) => `- ${key}: ${value}`),
- '',
- '## 最新可用证据',
- '',
- '| 类型 | 证明等级 | 状态 | 目录 | 摘要 | 下一步 |',
- '| --- | --- | --- | --- | --- | --- |',
- ...latest.map(item => row(item)),
- '',
- '## 全部条目',
- '',
- '| 类型 | 证明等级 | 状态 | 更新时间 | 目录 | 摘要 | 下一步 |',
- '| --- | --- | --- | --- | --- | --- | --- |',
- ...summary.entries.map(item => row(item, true)),
- '',
- '## 说明',
- '',
- '- `real_evidence` 表示该产物包含真实 live、客户选择、历史数据或视频 A/B 证据,且关键门槛通过。',
- '- `smoke_or_local` 表示可证明结构、门槛或启动前置条件,但不能证明真实客户效果。',
- '- `not_business_proof` 表示该产物明确显示仍缺真实数据或状态未完成。'
- ];
- return lines.join('\n');
- }
- function classifyVideoResourceReadiness(root, dir, readiness) {
- return {
- type: 'video-resource-readiness',
- dir: rel(root, dir),
- updatedAt: mtime(dir),
- proofLevel: readiness.acceptance?.readyForVideoAbPreflight ? 'smoke_or_local' : 'not_business_proof',
- status: readiness.acceptance?.readyForVideoAbPreflight ? 'ready' : 'not_ready',
- summary: `realReference=${readiness.counts?.realReferenceRows || 0}, realCandidate=${readiness.counts?.realCandidateRows || 0}, videoUrl=${readiness.counts?.videoUrlRows || 0}, failureCount=${readiness.failureCount || 0}`,
- next: readiness.acceptance?.readyForVideoAbPreflight
- ? '可作为视频 A/B 启动前资源证明;仍需运行 acceptance:video-ab 证明 provider、证据卡和 Top10 非退化。'
- : '先补齐真实参考视频、候选视频、视频 URL、封面/ASR/帧图或正文证据。'
- };
- }
- function classifyRepairActions(root, dir, type, fileName) {
- const csvPath = path.join(dir, fileName);
- const actionCount = countCsvRows(csvPath);
- const isVideo = type === 'video-resource-repair-actions';
- const isHomepage = type === 'homepage-evidence-repair-actions';
- return {
- type,
- dir: rel(root, dir),
- updatedAt: fs.statSync(csvPath).mtime.toISOString(),
- proofLevel: 'smoke_or_local',
- status: actionCount > 0 ? 'actions_open' : 'empty',
- summary: `csv=${fileName}, actions=${actionCount}`,
- next: isHomepage
- ? '用于指导商务/投放补齐主页近期内容、封面、发布时间、互动和风险信号;不证明客户效果。'
- : isVideo
- ? '用于指导商务/投放补齐真实视频 URL、封面、ASR、帧图或正文证据;不证明视频 A/B 或命中率提升。'
- : '用于指导商务补齐真实历史 Brief、客户选择、人工补号基线和复核字段;不证明客户效果。'
- };
- }
- function pickLatestUseful(entries) {
- const byType = new Map();
- for (const entry of entries) {
- if (!byType.has(entry.type)) byType.set(entry.type, entry);
- }
- return [...byType.values()];
- }
- function row(item, includeTime = false) {
- const cells = includeTime
- ? [item.type, item.proofLevel, item.status, item.updatedAt, item.dir, item.summary, item.next]
- : [item.type, item.proofLevel, item.status, item.dir, item.summary, item.next];
- return `| ${cells.map(escapeCell).join(' | ')} |`;
- }
- function readJson(file) {
- if (!fs.existsSync(file)) return null;
- try {
- return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- } catch {
- return null;
- }
- }
- function isDataIntakePack(dir) {
- return fs.existsSync(path.join(dir, 'history-data-template.csv')) &&
- fs.existsSync(path.join(dir, 'manual-review-template.csv')) &&
- fs.existsSync(path.join(dir, 'README.md'));
- }
- function isVideoIntakePack(dir) {
- return fs.existsSync(path.join(dir, 'video-resource-template.csv')) &&
- fs.existsSync(path.join(dir, 'README.md'));
- }
- function countBy(entries, key) {
- return entries.reduce((acc, item) => {
- acc[item[key]] = (acc[item[key]] || 0) + 1;
- return acc;
- }, {});
- }
- function mtime(dir) {
- return fs.statSync(dir).mtime.toISOString();
- }
- function rel(root, file) {
- return path.relative(root, file).replace(/\\/g, '/');
- }
- function pct(value) {
- return `${Math.round(Number(value || 0) * 100)}%`;
- }
- function countCsvRows(file) {
- const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '').trim();
- if (!text) return 0;
- return Math.max(0, text.split(/\r?\n/).length - 1);
- }
- function uniqueStrings(values) {
- return [...new Set((values || []).filter(Boolean).map(value => String(value)))];
- }
- 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 = {
- buildEvidenceIndex,
- classifyDir
- };
|