| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const ROOT = path.resolve(__dirname, '..');
- const OUTPUTS = path.join(ROOT, 'outputs');
- const DEFAULT_OUTPUT = path.join(OUTPUTS, 'real-proof-intake-bundle-latest');
- function main() {
- const args = parseArgs(process.argv.slice(2));
- const outputsDir = path.resolve(args.outputs || OUTPUTS);
- const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
- const summary = buildRealProofIntakeBundle({ root: ROOT, outputsDir });
- fs.mkdirSync(outputDir, { recursive: true });
- const summaryPath = path.join(outputDir, 'real-proof-intake-bundle-summary.json');
- const reportPath = path.join(outputDir, 'real-proof-intake-bundle.md');
- const csvPath = path.join(outputDir, 'real-proof-intake-bundle.csv');
- fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8');
- fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
- fs.writeFileSync(csvPath, withBom(renderCsv(summary.exportRows)), 'utf8');
- console.log(JSON.stringify({
- outputDir,
- summary: summaryPath,
- report: reportPath,
- csv: csvPath,
- passed: summary.passed,
- complete: summary.complete,
- proofOpenCount: summary.proofOpenCount,
- exportRowCount: summary.exportRowCount
- }, null, 2));
- if (args.strict && !summary.passed) process.exitCode = 1;
- }
- function buildRealProofIntakeBundle({ root, outputsDir }) {
- const dirs = {
- dataPack: pickDir(outputsDir, /^data-intake-pack-/) || path.join(outputsDir, 'data-intake-pack-latest'),
- videoPack: pickDir(outputsDir, /^video-intake-pack-/) || path.join(outputsDir, 'video-intake-pack-latest'),
- intakeChecklist: pickDir(outputsDir, /^intake-field-checklist-/) || path.join(outputsDir, 'intake-field-checklist-latest'),
- operatorPack: pickDir(outputsDir, /^proof-gap-operator-pack-/) || path.join(outputsDir, 'proof-gap-operator-pack-latest'),
- proofGapClosure: pickDir(outputsDir, /^proof-gap-closure-/) || path.join(outputsDir, 'proof-gap-closure-latest'),
- latestFormIndex: pickDir(outputsDir, /^latest-form-index-/) || path.join(outputsDir, 'latest-form-index-latest'),
- manualReviewGuide: pickDir(outputsDir, /^manual-review-label-guide-/) || path.join(outputsDir, 'manual-review-label-guide-latest'),
- businessExecution: pickDir(outputsDir, /^business-execution-index-/) || path.join(outputsDir, 'business-execution-index-latest'),
- intakeReadiness: pickDir(outputsDir, /^intake-readiness-/) || path.join(outputsDir, 'intake-readiness-latest'),
- videoReadiness: pickDir(outputsDir, /^video-resource-readiness-/) || path.join(outputsDir, 'video-resource-readiness-latest'),
- homepageReadiness: pickDir(outputsDir, /^homepage-evidence-readiness-/) || path.join(outputsDir, 'homepage-evidence-readiness-latest'),
- planStatus: pickDir(outputsDir, /^plan-execution-status-/) || path.join(outputsDir, 'plan-execution-status-latest'),
- optimizationCompletion: pickDir(outputsDir, /^optimization-completion-/) || path.join(outputsDir, 'optimization-completion-latest'),
- longRunReadiness: pickDir(outputsDir, /^long-run-readiness-/) || path.join(outputsDir, 'long-run-readiness-latest')
- };
- const summaries = {
- intakeChecklist: readJson(path.join(dirs.intakeChecklist, 'intake-field-checklist-summary.json')),
- operatorPack: readJson(path.join(dirs.operatorPack, 'proof-gap-operator-pack-summary.json')),
- proofGapClosure: readJson(path.join(dirs.proofGapClosure, 'proof-gap-closure-summary.json')),
- latestFormIndex: readJson(path.join(dirs.latestFormIndex, 'latest-form-index-summary.json')),
- manualReviewGuide: readJson(path.join(dirs.manualReviewGuide, 'manual-review-label-guide-summary.json')),
- businessExecution: readJson(path.join(dirs.businessExecution, 'business-execution-index-summary.json')),
- intakeReadiness: readJson(path.join(dirs.intakeReadiness, 'intake-readiness-summary.json')),
- videoReadiness: readJson(path.join(dirs.videoReadiness, 'video-resource-readiness-summary.json')),
- homepageReadiness: readJson(path.join(dirs.homepageReadiness, 'homepage-evidence-readiness-summary.json')),
- planStatus: readJson(path.join(dirs.planStatus, 'plan-execution-status-summary.json')),
- optimizationCompletion: readJson(path.join(dirs.optimizationCompletion, 'optimization-completion-summary.json')),
- longRunReadiness: readJson(path.join(dirs.longRunReadiness, 'long-run-readiness-summary.json'))
- };
- const templateFiles = buildTemplateFiles(root, dirs, summaries);
- const fillRows = buildFillRows(summaries);
- const proofGapRows = buildProofGapRows(summaries.operatorPack, summaries.proofGapClosure);
- const operatorPackOwnerArtifacts = normalizeOperatorPackOwnerArtifacts(root, dirs.operatorPack, summaries.operatorPack);
- const repairRows = buildRepairRows(root, dirs, summaries);
- const recheckCommands = buildRecheckCommands(summaries);
- const exportRows = [
- ...templateFiles.map((item, index) => exportRow('打开文件', index + 1, item.owner, item.title, item.path, item.status, item.usage, item.boundary)),
- ...operatorPackOwnerArtifacts.map((item, index) => exportRow(
- '负责人附件',
- index + 1,
- item.owner,
- 'proof-gap 操作包负责人附件',
- [item.markdown, item.csv].filter(Boolean).join('; '),
- `actionCount=${item.actionCount}`,
- `topPriority=${item.topPriority || 'unknown'};gapIds=${item.gapIds.join('; ') || 'missing'}`,
- '负责人附件只用于补证派工,不证明客户效果。',
- item.gapIds.length,
- `evidenceRowCount=${item.evidenceRowCount}`,
- `nextActionCount=${item.nextActionCount}`
- )),
- ...fillRows.map(item => exportRow('填表顺序', item.order, item.owner, item.title, item.file, item.status, item.acceptance, item.boundary, item.issueCount)),
- ...proofGapRows.map(item => exportRow('证明缺口', item.priority, item.owner, item.title, item.fillFiles.join('; '), item.status, item.acceptance, item.boundary, '', item.evidence, item.nextAction)),
- ...repairRows.map((item, index) => exportRow('修复清单', index + 1, item.owner, item.title, item.csv, item.status, item.acceptance, item.boundary, item.issueCount)),
- ...recheckCommands.map((item, index) => exportRow('复验命令', index + 1, item.owner, item.stage, item.command, '待真实材料补齐后运行', item.acceptance, item.boundary))
- ];
- const proofOpenCount = Number(summaries.proofGapClosure?.openCount ?? summaries.operatorPack?.openCount ?? summaries.planStatus?.proofOpenCount ?? 0);
- const operatorPackEvidenceRowCount = Number(summaries.operatorPack?.evidenceRowCount || countRowsWith(proofGapRows, row => row.evidence));
- const operatorPackNextActionCount = Number(summaries.operatorPack?.nextActionCount || countRowsWith(proofGapRows, row => row.nextAction));
- const operatorPackMissingProofRequirementCount = Number(summaries.operatorPack?.missingProofRequirementCount || proofGapRows.reduce((total, row) => total + row.missingProofRequirements.length, 0));
- const operatorPackOwnerArtifactCount = Number(summaries.operatorPack?.ownerArtifactCount || operatorPackOwnerArtifacts.length || 0);
- const expectedOperatorPackOwnerArtifactCount = expectedOwnerArtifactCount(summaries.operatorPack, operatorPackOwnerArtifacts);
- const complete = Boolean(
- summaries.proofGapClosure?.complete &&
- summaries.planStatus?.complete &&
- summaries.optimizationCompletion?.complete &&
- summaries.optimizationCompletion?.readyForClaim &&
- proofOpenCount === 0
- );
- const passed = templateFiles.some(item => item.path && item.path.includes('history-data-template.csv')) &&
- templateFiles.some(item => item.path && item.path.includes('manual-review-template.csv')) &&
- templateFiles.some(item => item.path && item.path.includes('video-resource-template.csv')) &&
- templateFiles.some(item => item.path && item.path.includes('long-run-readiness-report.md')) &&
- fillRows.length >= 3 &&
- proofGapRows.length >= 5 &&
- recheckCommands.some(item => item.command.includes('intake:readiness')) &&
- recheckCommands.some(item => item.command.includes('customer-effect:audit')) &&
- recheckCommands.some(item => item.command.includes('acceptance:video-ab')) &&
- recheckCommands.some(item => item.command.includes('longrun:readiness')) &&
- operatorPackEvidenceRowCount >= proofGapRows.length &&
- operatorPackNextActionCount >= proofGapRows.length &&
- operatorPackOwnerArtifactCount >= expectedOperatorPackOwnerArtifactCount &&
- operatorPackOwnerArtifacts.every(item => item.markdown && item.csv && item.evidenceRowCount >= 1 && item.nextActionCount >= 1) &&
- proofGapRows.every(item => item.evidence && item.nextAction) &&
- exportRows.every(item => /不.*证明|不得.*宣称|不能|只证明/.test(item.boundary || ''));
- return {
- generatedAt: new Date().toISOString(),
- root,
- outputsDir,
- passed,
- complete,
- directCustomerProof: false,
- proofLevel: 'not_business_proof',
- proofOpenCount,
- proofGapRowCount: proofGapRows.length,
- fillRowCount: fillRows.length,
- repairRowCount: repairRows.length,
- recheckCommandCount: recheckCommands.length,
- exportRowCount: exportRows.length,
- operatorPackEvidenceRowCount,
- operatorPackNextActionCount,
- operatorPackMissingProofRequirementCount,
- operatorPackOwnerArtifactCount,
- expectedOperatorPackOwnerArtifactCount,
- sourceState: {
- intakeChecklistPassed: Boolean(summaries.intakeChecklist?.passed),
- intakeFieldItemCount: Number(summaries.intakeChecklist?.itemCount || 0),
- intakeMissingRequiredCount: Number(summaries.intakeChecklist?.missingRequiredCount || 0),
- intakePlaceholderCount: Number(summaries.intakeChecklist?.placeholderCount || 0),
- operatorPackPassed: Boolean(summaries.operatorPack?.passed),
- operatorPackOpenCount: Number(summaries.operatorPack?.openCount || 0),
- operatorPackEvidenceRowCount,
- operatorPackNextActionCount,
- operatorPackMissingProofRequirementCount,
- operatorPackOwnerArtifactCount,
- expectedOperatorPackOwnerArtifactCount,
- latestFormPassed: Boolean(summaries.latestFormIndex?.passed),
- planExecutionComplete: Boolean(summaries.planStatus?.complete),
- optimizationCompletionComplete: Boolean(summaries.optimizationCompletion?.complete),
- optimizationCompletionReadyForClaim: Boolean(summaries.optimizationCompletion?.readyForClaim),
- longRunReadinessReady: Boolean(summaries.longRunReadiness?.ready),
- longRunReadinessFailCount: Number(summaries.longRunReadiness?.counts?.fail ?? summaries.longRunReadiness?.failCount ?? 0),
- longRunReadinessProofLevel: summaries.longRunReadiness ? 'not_business_proof' : '',
- longRunReadinessDirectCustomerProof: false,
- intakeReady: Boolean(summaries.intakeReadiness?.acceptance?.overallReady),
- videoReady: Boolean(summaries.videoReadiness?.acceptance?.readyForVideoAbPreflight)
- },
- files: {
- summary: 'outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle-summary.json',
- report: 'outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle.md',
- csv: 'outputs/real-proof-intake-bundle-latest/real-proof-intake-bundle.csv',
- operatorPackByOwnerDir: relIfExists(root, path.join(dirs.operatorPack, 'by-owner'))
- },
- templateFiles,
- operatorPackOwnerArtifacts,
- fillRows,
- proofGapRows,
- repairRows,
- recheckCommands,
- exportRows,
- guardrails: [
- '本总包只把真实材料入口、字段缺口、修复清单和复验命令集中到一个地方,不证明客户效果。',
- '必须把 example.com、示例、候选、待补、待客户反馈等占位内容替换成真实客户材料。',
- 'proofOpenCount 不为 0 时,不得宣称提号率提升、客户效果达标或人工补号量下降。',
- 'optimization-completion.readyForClaim=false 时,不得宣称长期优化完成。',
- 'longrun:readiness.ready=false 时,不能启动或宣称 full-matrix/live 长跑完成。',
- '缺真实视频 URL、封面、字幕、ASR、帧图或正文时,不能声明视频分析完成。',
- '不得在表格、报告、日志或命令中写入 sessionToken、鉴权头、模型 token 或 npm token。'
- ]
- };
- }
- function buildTemplateFiles(root, dirs, summaries) {
- return [
- {
- owner: '商务',
- title: '历史 Brief 数据模板',
- path: relIfExists(root, path.join(dirs.dataPack, 'history-data-template.csv')),
- status: summaries.intakeReadiness?.acceptance?.historyReady ? '已就绪' : '待填真实历史数据',
- usage: '填写真实历史 Brief、参考账号/视频、人工最终名单、客户选择和历史人工补号量基线。',
- boundary: '模板存在不证明客户效果。'
- },
- {
- owner: '商务',
- title: '人工复核模板',
- path: relIfExists(root, path.join(dirs.dataPack, 'manual-review-template.csv')),
- status: summaries.intakeReadiness?.acceptance?.reviewReady ? '已就绪' : '待填人工复核和客户选择',
- usage: '填写候选人工复核标签、客户选择、负样本归因、反馈原因和本轮人工补号量。',
- boundary: '复核模板存在不证明客户效果。'
- },
- {
- owner: '商务/投放',
- title: '视频资源模板',
- path: relIfExists(root, path.join(dirs.videoPack, 'video-resource-template.csv')),
- status: summaries.videoReadiness?.acceptance?.readyForVideoAbPreflight ? '可进入视频 A/B 前置' : '待补真实视频资源',
- usage: '填写真实参考视频和真实候选视频 URL,并补封面、ASR、帧图、标题或内容摘要证据。',
- boundary: '视频资源模板存在不证明视频提升提号率。'
- },
- {
- owner: '商务/投放',
- title: '字段级补齐核对表',
- path: relIfExists(root, path.join(dirs.intakeChecklist, 'intake-field-checklist.md')),
- status: summaries.intakeChecklist?.passed ? `待处理 ${Number(summaries.intakeChecklist.itemCount || 0)} 项` : '缺失或未生成',
- usage: '按行号、字段、负责人和修复动作逐格补齐真实材料。',
- boundary: '字段核对表只安排补齐动作,不证明客户效果。'
- },
- {
- owner: '商务/技术/AI',
- title: '提号补证操作包',
- path: relIfExists(root, path.join(dirs.operatorPack, 'proof-gap-operator-pack.md')),
- status: summaries.operatorPack?.passed ? `openCount=${Number(summaries.operatorPack.openCount || 0)}` : '缺失或未生成',
- usage: `把五类真实证明缺口转成负责人、当前证据、下一步、填写文件、待补字段、复验命令和通过标准;当前 evidenceRowCount=${Number(summaries.operatorPack?.evidenceRowCount || 0)},nextActionCount=${Number(summaries.operatorPack?.nextActionCount || 0)}。`,
- boundary: '操作包只安排补证,不证明客户效果。'
- },
- {
- owner: '商务',
- title: '人工复核标签与归因指南',
- path: relIfExists(root, path.join(dirs.manualReviewGuide, 'manual-review-label-guide.md')),
- status: summaries.manualReviewGuide?.passed ? `标签 ${Number(summaries.manualReviewGuide.labelCount || 0)} 个` : '缺失或未生成',
- usage: '规范商务复核标签、负样本归因和客户反馈填写口径。',
- boundary: '标签指南只统一口径,不证明客户效果。'
- },
- {
- owner: '商务/技术/AI',
- title: '商务执行总表',
- path: relIfExists(root, path.join(dirs.businessExecution, 'business-execution-index.md')),
- status: summaries.businessExecution?.passed ? `rowCount=${Number(summaries.businessExecution.rowCount || 0)}` : '缺失或未生成',
- usage: '在一张表里查看负责人行动、修复清单、字段补齐负责人和复验命令。',
- boundary: '执行总表只组织工作流,不证明客户效果。'
- },
- {
- owner: '商务/技术/AI',
- title: '最新提号表单索引',
- path: relIfExists(root, path.join(dirs.latestFormIndex, 'latest-form-index.md')),
- status: summaries.latestFormIndex?.passed ? '已索引' : '缺失或未生成',
- usage: '快速定位最新补证表单、软件端候选名单、负责人行动文件和中文 SOP。',
- boundary: '索引入口不证明客户效果。'
- },
- {
- owner: '技术/AI',
- title: '优化完成度审计报告',
- path: relIfExists(root, path.join(dirs.optimizationCompletion, 'optimization-completion-report.md')),
- status: summaries.optimizationCompletion
- ? `readyForClaim=${Boolean(summaries.optimizationCompletion.readyForClaim)},proofGapOpenCount=${Number(summaries.optimizationCompletion.proofGapOpenCount || 0)}`
- : '缺失或未生成',
- usage: '复查长期优化是否具备完成声明条件,并汇总仍阻塞的真实证明缺口。',
- boundary: '完成度审计入口本身不证明客户效果;readyForClaim=false 时不得宣称长期优化完成。'
- },
- {
- owner: '技术/AI',
- title: '长跑前置门禁报告',
- path: relIfExists(root, path.join(dirs.longRunReadiness, 'long-run-readiness-report.md')),
- status: summaries.longRunReadiness
- ? `ready=${Boolean(summaries.longRunReadiness.ready)},failCount=${Number(summaries.longRunReadiness?.counts?.fail ?? summaries.longRunReadiness?.failCount ?? 0)}`
- : '缺失或未生成',
- usage: '复查 full-matrix/live 长跑是否具备启动条件,并定位 live preflight、历史数据、intake readiness 和 proof-gap closure 卡点。',
- boundary: '长跑前置门禁入口不证明客户效果;ready=false 时不能启动或宣称 full-matrix/live 长跑完成。'
- }
- ];
- }
- function buildFillRows(summaries) {
- const indexedRows = summaries.latestFormIndex?.businessFillGuide?.rows;
- const rows = Array.isArray(indexedRows) && indexedRows.length
- ? indexedRows
- : (summaries.intakeChecklist?.ownerGroups || []).map((group, index) => ({
- order: index + 1,
- owner: group.owner,
- section: group.section,
- file: (group.files || [])[0] || '',
- firstItemIds: group.firstItemIds || [],
- issueCount: Number(group.itemCount || 0),
- fillAction: group.nextAction || '',
- recheckCommand: '',
- acceptance: group.acceptance || '',
- boundary: group.boundary || '补齐材料,不证明客户效果'
- }));
- return rows.map((row, index) => {
- const section = row.section || '';
- const videoReady = section === 'video' &&
- Boolean(summaries.videoReadiness?.acceptance?.readyForVideoAbPreflight) &&
- Number(summaries.videoReadiness?.failureCount || 0) === 0;
- const issueCount = videoReady ? 0 : Number(row.issueCount || 0);
- return {
- order: Number(row.order || index + 1),
- owner: row.owner || '',
- section,
- title: sectionTitle(section),
- file: normalizePath(row.file || ''),
- firstItemIds: Array.isArray(row.firstItemIds) ? row.firstItemIds : [],
- issueCount,
- status: videoReady ? '可进入视频 A/B 前置' : (issueCount > 0 ? '待补齐' : '待复验'),
- fillAction: videoReady ? '保持真实参考视频和真实候选视频证据链,下一步进入真实视频 A/B live proof。' : (row.fillAction || ''),
- recheckCommand: row.recheckCommand || '',
- acceptance: row.acceptance || '',
- boundary: row.boundary || '补齐材料,不证明客户效果。'
- };
- });
- }
- function buildProofGapRows(operatorPack, closure) {
- const closureRows = new Map((Array.isArray(closure?.rows) ? closure.rows : []).map(row => [row.id, row]));
- return (Array.isArray(operatorPack?.rows) ? operatorPack.rows : []).map(row => {
- const closureRow = closureRows.get(row.gapId) || {};
- const status = closureRow.status || row.status || 'open';
- const missingProofRequirements = Array.isArray(closureRow.missingProofRequirements)
- ? closureRow.missingProofRequirements
- : (Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements : []);
- return {
- gapId: row.gapId || '',
- title: row.title || closureRow.title || '',
- owner: row.owner || '',
- priority: Number(row.priority || 99),
- status,
- fillFiles: Array.isArray(row.fillFiles) ? row.fillFiles.map(normalizePath) : [],
- fields: Array.isArray(row.fields) ? row.fields : [],
- recheckCommands: Array.isArray(row.recheckCommands) ? row.recheckCommands : [],
- evidence: closureRow.evidence || row.evidence || '',
- missingProofRequirements,
- nextAction: status === 'closed'
- ? (closureRow.next || '保持证据链并纳入 evidence:index。')
- : (row.nextAction || closureRow.next || ''),
- acceptance: row.acceptance || closureRow.required || '',
- boundary: row.boundary || '补齐材料,不证明客户效果。'
- };
- }).sort((a, b) => a.priority - b.priority);
- }
- function expectedOwnerArtifactCount(operatorPack, ownerArtifacts) {
- if (Array.isArray(operatorPack?.ownerArtifacts) && operatorPack.ownerArtifacts.length) {
- return operatorPack.ownerArtifacts.length;
- }
- if (Number.isFinite(Number(operatorPack?.ownerArtifactCount)) && Number(operatorPack.ownerArtifactCount) > 0) {
- return Number(operatorPack.ownerArtifactCount);
- }
- if (Array.isArray(operatorPack?.ownerGroups) && operatorPack.ownerGroups.length) {
- return operatorPack.ownerGroups.length;
- }
- if (Array.isArray(ownerArtifacts) && ownerArtifacts.length) return ownerArtifacts.length;
- return 1;
- }
- function normalizeOperatorPackOwnerArtifacts(root, operatorPackDir, operatorPack) {
- if (!Array.isArray(operatorPack?.ownerArtifacts)) return [];
- return operatorPack.ownerArtifacts.map(item => ({
- owner: item.owner || '',
- actionCount: Number(item.actionCount || 0),
- topPriority: Number(item.topPriority || 0),
- markdown: relIfExists(root, resolveArtifactPath(root, operatorPackDir, item.markdown)),
- csv: relIfExists(root, resolveArtifactPath(root, operatorPackDir, item.csv)),
- gapIds: Array.isArray(item.gapIds) ? item.gapIds : [],
- evidenceRowCount: Number(item.evidenceRowCount || 0),
- nextActionCount: Number(item.nextActionCount || 0)
- })).filter(item => item.owner || item.markdown || item.csv);
- }
- function resolveArtifactPath(root, baseDir, value) {
- if (!value) return '';
- const text = String(value);
- const normalized = text.replace(/\\/g, '/');
- if (path.isAbsolute(text)) return text;
- if (normalized.startsWith('outputs/')) return path.join(root, normalized);
- return path.join(baseDir, text);
- }
- function buildRepairRows(root, dirs, summaries) {
- return [
- {
- owner: '商务',
- title: '历史数据/人工复核修复清单',
- csv: relIfExists(root, path.join(dirs.intakeReadiness, 'intake-readiness-repair-actions.csv')),
- issueCount: Number(summaries.intakeReadiness?.failureCount || 0),
- actionCount: Array.isArray(summaries.intakeReadiness?.repairActions) ? summaries.intakeReadiness.repairActions.length : 0,
- status: summaries.intakeReadiness?.acceptance?.overallReady ? '已就绪' : '待补齐',
- acceptance: 'intake:readiness overallReady=true 且 failureCount=0。',
- boundary: '修复清单只帮助补齐材料,不证明客户效果。'
- },
- {
- owner: '商务/投放',
- title: '视频资源修复清单',
- csv: relIfExists(root, path.join(dirs.videoReadiness, 'video-resource-repair-actions.csv')),
- issueCount: Number(summaries.videoReadiness?.failureCount || 0),
- actionCount: Array.isArray(summaries.videoReadiness?.repairActions) ? summaries.videoReadiness.repairActions.length : 0,
- status: summaries.videoReadiness?.acceptance?.readyForVideoAbPreflight ? '可进入视频 A/B 前置' : '待补视频资源',
- acceptance: 'video:resource-readiness readyForVideoAbPreflight=true 且 failureCount=0。',
- boundary: '视频修复清单不证明视频分析提升提号率。'
- },
- {
- owner: '商务/投放',
- title: '主页近期内容证据修复清单',
- csv: relIfExists(root, path.join(dirs.homepageReadiness, 'homepage-evidence-repair-actions.csv')),
- issueCount: Number(summaries.homepageReadiness?.failureCount || 0),
- actionCount: Array.isArray(summaries.homepageReadiness?.repairActions) ? summaries.homepageReadiness.repairActions.length : 0,
- status: summaries.homepageReadiness?.ready ? '主页证据已就绪' : '待补主页证据',
- acceptance: 'homepage:readiness ready=true,且强推荐具备近期内容证据。',
- boundary: '主页证据修复清单不证明客户效果。'
- }
- ];
- }
- function buildRecheckCommands(summaries) {
- const commands = [];
- for (const item of summaries.intakeChecklist?.recheckCommands || []) {
- commands.push({
- stage: item.stage || '',
- owner: item.owner || '技术/AI',
- command: item.command || '',
- acceptance: item.acceptance || '',
- boundary: item.boundary || '复验通过不等于客户效果证明。'
- });
- }
- for (const row of summaries.operatorPack?.rows || []) {
- for (const command of row.recheckCommands || []) {
- commands.push({
- stage: row.title || row.gapId || '证明缺口复验',
- owner: row.owner || '技术/AI',
- command,
- acceptance: row.acceptance || '',
- boundary: row.boundary || '复验通过不等于客户效果证明。'
- });
- }
- }
- commands.push(
- {
- stage: '长跑前置门禁',
- owner: '技术/AI',
- command: 'npm run longrun:readiness -- --mode full-matrix --intake-readiness outputs\\intake-readiness-latest\\intake-readiness-summary.json --proof-gap outputs\\proof-gap-request-latest\\proof-gap-request-summary.json --proof-gap-closure outputs\\proof-gap-closure-latest\\proof-gap-closure-summary.json --output outputs\\long-run-readiness-latest',
- acceptance: 'ready=true 且 failCount=0 后,才允许进入真实 live/provider 长跑启动判断。',
- boundary: 'ready=false 时不能启动或宣称 full-matrix/live 长跑完成。'
- },
- {
- stage: '优化完成度审计',
- owner: '技术/AI',
- command: 'npm run optimization:completion -- --output outputs\\optimization-completion-latest',
- acceptance: 'complete=true、readyForClaim=true、proofGapOpenCount=0 才能进入完成判断。',
- boundary: 'readyForClaim=false 时不得宣称长期优化完成。'
- },
- {
- stage: '真实材料一键审计 Pipeline',
- owner: '技术/AI',
- 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',
- acceptance: '真实历史数据、视频资源、复核指标和客户效果审计按 pipeline 通过。',
- boundary: 'pipeline 未关闭 proof-gap:closure 前不得宣称客户效果。'
- },
- {
- stage: '每轮沉淀刷新',
- owner: '技术/AI',
- command: 'npm run round:refresh -- --output-root outputs --strict',
- acceptance: 'round-deposition.passed=true 且 failCount=0。',
- boundary: '刷新沉淀不证明客户效果。'
- }
- );
- return dedupeBy(commands.filter(item => item.command), item => item.command);
- }
- function exportRow(type, order, owner, title, target, status, acceptance, boundary, issueCount = '', evidence = '', nextAction = '') {
- return {
- type,
- order,
- owner: owner || '',
- title: title || '',
- target: normalizePath(target || ''),
- status: status || '',
- issueCount,
- evidence: evidence || '',
- nextAction: nextAction || '',
- acceptance: acceptance || '',
- boundary: boundary || '不证明客户效果。'
- };
- }
- function renderReport(summary) {
- const lines = [
- '# 真实验收材料收集总包',
- '',
- `- 生成时间:${summary.generatedAt}`,
- `- 是否完成长期目标:${summary.complete ? '是' : '否'}`,
- `- proofOpenCount:${summary.proofOpenCount}`,
- `- operatorPackEvidenceRowCount:${summary.operatorPackEvidenceRowCount}`,
- `- operatorPackNextActionCount:${summary.operatorPackNextActionCount}`,
- `- operatorPackMissingProofRequirementCount:${summary.operatorPackMissingProofRequirementCount}`,
- `- operatorPackOwnerArtifactCount:${summary.operatorPackOwnerArtifactCount}`,
- `- 用途:把真实材料模板、字段缺口、修复清单、证明缺口和复验命令集中成一个商务可打开的入口;不证明客户效果。`,
- '',
- '## 打开顺序',
- '',
- '| 负责人 | 文件 | 状态 | 用法 | 边界 |',
- '| --- | --- | --- | --- | --- |',
- ...summary.templateFiles.map(item => tableRow([item.owner, item.path || '缺失', item.status, item.usage, item.boundary])),
- '',
- '## 操作包负责人附件',
- '',
- '| 负责人 | 动作数 | Markdown | CSV | 缺口 | 当前证据行 | 下一步动作 |',
- '| --- | ---: | --- | --- | --- | ---: | ---: |',
- ...summary.operatorPackOwnerArtifacts.map(item => tableRow([
- item.owner,
- item.actionCount,
- item.markdown || '缺失',
- item.csv || '缺失',
- item.gapIds.join(';') || '缺失',
- item.evidenceRowCount,
- item.nextActionCount
- ])),
- '',
- '## 商务填表顺序',
- '',
- '| 顺序 | 负责人 | 区域 | 文件 | 优先项 | 问题数 | 填表动作 | 补齐后复验 | 验收 | 边界 |',
- '| ---: | --- | --- | --- | --- | ---: | --- | --- | --- | --- |',
- ...summary.fillRows.map(row => tableRow([
- row.order,
- row.owner,
- row.title,
- row.file || '缺失',
- row.firstItemIds.join('、') || '无',
- row.issueCount,
- row.fillAction,
- row.recheckCommand || '补齐后运行 round:refresh',
- row.acceptance,
- row.boundary
- ])),
- '',
- '## 证明缺口闭环',
- '',
- '| 优先级 | 缺口 | 负责人 | 状态 | 填写文件 | 当前证据 | 下一步 | 待补字段/证明 | 通过标准 | 边界 |',
- '| ---: | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
- ...summary.proofGapRows.map(row => tableRow([
- row.priority,
- `${row.title} (${row.gapId})`,
- row.owner,
- row.status,
- row.fillFiles.join(';') || '缺失',
- row.evidence || '缺失',
- row.nextAction || '按操作包补齐',
- row.fields.join(';') || '按操作包补齐',
- row.acceptance,
- row.boundary
- ])),
- '',
- '## 修复清单入口',
- '',
- '| 负责人 | 修复清单 | 当前问题数 | 修复动作数 | 状态 | 验收 | 边界 |',
- '| --- | --- | ---: | ---: | --- | --- | --- |',
- ...summary.repairRows.map(row => tableRow([row.owner, row.csv || '缺失', row.issueCount, row.actionCount, row.status, row.acceptance, row.boundary])),
- '',
- '## 补齐后复验命令',
- '',
- '| 阶段 | 负责人 | 命令 | 验收 | 边界 |',
- '| --- | --- | --- | --- | --- |',
- ...summary.recheckCommands.map(item => tableRow([item.stage, item.owner, item.command, item.acceptance, item.boundary])),
- '',
- '## 防误用边界',
- '',
- ...summary.guardrails.map(item => `- ${item}`)
- ];
- return lines.join('\n');
- }
- function renderCsv(rows) {
- const headers = ['类型', '顺序', '负责人', '标题', '文件或命令', '状态', '问题数', '当前证据', '下一步', '验收标准', '边界'];
- return [
- headers.join(','),
- ...rows.map(row => [
- row.type,
- row.order,
- row.owner,
- row.title,
- row.target,
- row.status,
- row.issueCount,
- row.evidence,
- row.nextAction,
- row.acceptance,
- row.boundary
- ].map(csvCell).join(','))
- ].join('\n');
- }
- function sectionTitle(section) {
- if (section === 'history') return '历史 Brief 数据';
- if (section === 'review') return '人工复核和客户选择';
- if (section === 'video') return '视频资源证据';
- return section || '真实材料';
- }
- function dedupeBy(items, keyFn) {
- const seen = new Set();
- const result = [];
- for (const item of items) {
- const key = keyFn(item);
- if (seen.has(key)) continue;
- seen.add(key);
- result.push(item);
- }
- return result;
- }
- function countRowsWith(rows, predicate) {
- return rows.reduce((count, row) => count + (predicate(row) ? 1 : 0), 0);
- }
- function pickDir(outputsDir, pattern) {
- if (!fs.existsSync(outputsDir)) return '';
- const latest = fs.readdirSync(outputsDir, { withFileTypes: true })
- .filter(entry => entry.isDirectory() && pattern.test(entry.name))
- .map(entry => path.join(outputsDir, entry.name))
- .filter(fs.existsSync)
- .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
- return latest || '';
- }
- function readJson(file) {
- if (!file || !fs.existsSync(file)) return null;
- try {
- return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- } catch {
- return null;
- }
- }
- function relIfExists(root, file) {
- return file && fs.existsSync(file) ? path.relative(root, file).replace(/\\/g, '/') : '';
- }
- function normalizePath(value) {
- return String(value || '').replace(/\\/g, '/');
- }
- function tableRow(cells) {
- return `| ${cells.map(cell => String(cell ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ')).join(' | ')} |`;
- }
- function csvCell(value) {
- const text = String(value ?? '').replace(/\r?\n/g, ' ');
- return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
- }
- function parseArgs(argv) {
- const result = {};
- for (let index = 0; index < argv.length; index += 1) {
- const arg = argv[index];
- if (!arg.startsWith('--')) continue;
- const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
- const next = argv[index + 1];
- if (!next || next.startsWith('--')) result[key] = true;
- else {
- result[key] = next;
- index += 1;
- }
- }
- return result;
- }
- function withBom(text) {
- return `\uFEFF${text}`;
- }
- if (require.main === module) main();
- module.exports = {
- buildRealProofIntakeBundle
- };
|