| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const ROOT = path.resolve(__dirname, '..');
- const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'business-execution-index-latest');
- function main() {
- const args = parseArgs(process.argv.slice(2));
- const outputsDir = path.resolve(args.outputs || path.join(ROOT, 'outputs'));
- const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
- const summary = buildBusinessExecutionIndex({ root: ROOT, outputsDir });
- fs.mkdirSync(outputDir, { recursive: true });
- const jsonPath = path.join(outputDir, 'business-execution-index-summary.json');
- const reportPath = path.join(outputDir, 'business-execution-index.md');
- const csvPath = path.join(outputDir, 'business-execution-index.csv');
- fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
- fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
- fs.writeFileSync(csvPath, withBom(renderCsv(summary.rows)), 'utf8');
- console.log(JSON.stringify({
- outputDir,
- json: jsonPath,
- report: reportPath,
- csv: csvPath,
- passed: summary.passed,
- rowCount: summary.rowCount,
- proofOpenCount: summary.proofOpenCount
- }, null, 2));
- if (args.strict && !summary.passed) process.exitCode = 1;
- }
- function buildBusinessExecutionIndex({ root, outputsDir }) {
- const nextActionsDir = pickDir(outputsDir, 'business-proof-next-actions-latest', /^business-proof-next-actions-/);
- const latestFormDir = pickDir(outputsDir, 'latest-form-index-latest', /^latest-form-index-/);
- const proofFormDir = pickDir(outputsDir, 'tihao-proof-gap-software-form-latest', /^tihao-proof-gap-software-form-/);
- const proofClosureDir = pickDir(outputsDir, 'proof-gap-closure-latest', /^proof-gap-closure-/);
- const evidenceDir = pickDir(outputsDir, 'evidence-index-latest', /^evidence-index-/);
- const fieldChecklistDir = pickDir(outputsDir, 'intake-field-checklist-latest', /^intake-field-checklist-/);
- const longRunReadinessDir = pickDir(outputsDir, 'long-run-readiness-latest', /^long-run-readiness-/);
- const optimizationCompletionDir = pickDir(outputsDir, 'optimization-completion-latest', /^optimization-completion-/);
- const proofGapOperatorPackDir = pickDir(outputsDir, 'proof-gap-operator-pack-latest', /^proof-gap-operator-pack-/);
- const nextActions = readJson(path.join(nextActionsDir, 'business-proof-next-actions-summary.json'));
- const latestForm = readJson(path.join(latestFormDir, 'latest-form-index-summary.json'));
- const proofForm = readJson(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json'));
- const closure = readJson(path.join(proofClosureDir, 'proof-gap-closure-summary.json'));
- const evidence = readJson(path.join(evidenceDir, 'evidence-index-summary.json'));
- const fieldChecklist = readJson(path.join(fieldChecklistDir, 'intake-field-checklist-summary.json'));
- const longRunReadiness = readJson(path.join(longRunReadinessDir, 'long-run-readiness-summary.json'));
- const optimizationCompletion = readJson(path.join(optimizationCompletionDir, 'optimization-completion-summary.json'));
- const proofGapOperatorPack = readJson(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json'));
- const operatorPackOwnerArtifacts = normalizeProofGapOperatorOwnerArtifacts(root, proofGapOperatorPackDir, proofGapOperatorPack);
- const rows = [
- ...buildOwnerActionRows(root, nextActionsDir, nextActions),
- ...buildRepairRows(root, nextActionsDir, nextActions),
- ...buildProofGapOperatorOwnerRows(operatorPackOwnerArtifacts),
- ...buildFieldChecklistOwnerRows(root, fieldChecklistDir, fieldChecklist),
- ...buildFieldChecklistRecheckRows(root, fieldChecklistDir, fieldChecklist),
- ...buildPrimaryFileRows(root, latestFormDir, latestForm, proofForm, fieldChecklistDir, fieldChecklist, longRunReadinessDir, longRunReadiness, optimizationCompletionDir, optimizationCompletion)
- ].map(row => ({
- ...row,
- boundary: normalizeProofBoundary(row.boundary)
- }));
- const proofOpenCount = Number(nextActions?.sourceState?.proofGapOpenCount ?? closure?.openCount ?? latestForm?.nextActions?.proofOpenCount ?? 0);
- const passed = rows.length > 0 && rows.every(row => row.owner && row.title && row.acceptance && row.boundary);
- return {
- generatedAt: new Date().toISOString(),
- root,
- outputsDir,
- passed,
- rowCount: rows.length,
- actionRowCount: rows.filter(row => row.type === '负责人行动').length,
- repairRowCount: rows.filter(row => row.type === '修复清单').length,
- operatorPackOwnerArtifactRowCount: rows.filter(row => row.type === '操作包负责人附件').length,
- operatorPackOwnerArtifactCount: operatorPackOwnerArtifacts.length,
- operatorPackOwnerArtifacts,
- fieldOwnerRowCount: rows.filter(row => row.type === '字段补齐负责人').length,
- recheckCommandRowCount: rows.filter(row => row.type === '补齐后复验命令').length,
- fileRowCount: rows.filter(row => row.type === '关键文件').length,
- proofOpenCount,
- closureComplete: Boolean(closure?.complete),
- status: proofOpenCount === 0 && closure?.complete ? 'ready_for_claim_audit' : 'actions_open',
- evidenceCounts: evidence?.counts || {},
- proofLevel: 'smoke_or_local',
- directCustomerProof: false,
- files: {
- report: 'outputs/business-execution-index-latest/business-execution-index.md',
- csv: 'outputs/business-execution-index-latest/business-execution-index.csv',
- summary: 'outputs/business-execution-index-latest/business-execution-index-summary.json'
- },
- sourceFiles: {
- nextActions: rel(root, path.join(nextActionsDir, 'business-proof-next-actions-summary.json')),
- latestFormIndex: rel(root, path.join(latestFormDir, 'latest-form-index-summary.json')),
- proofGapSoftwareForm: rel(root, path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json')),
- proofGapClosure: rel(root, path.join(proofClosureDir, 'proof-gap-closure-summary.json')),
- intakeFieldChecklist: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
- longRunReadiness: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-summary.json')),
- optimizationCompletion: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-summary.json')),
- proofGapOperatorPack: rel(root, path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json')),
- evidenceIndex: rel(root, path.join(evidenceDir, 'evidence-index-summary.json'))
- },
- guardrails: [
- '本表只用于商务补证执行分派,不证明客户效果或命中率提升。',
- 'sample、smoke、模板、provider fallback、接口 200 都不能作为业务证明。',
- '只有 proof-gap-closure openCount=0 且 customer-effect/video/live 真实审计通过后,才能进入效果宣称。',
- 'optimization-completion readyForClaim=false 时,本表不证明客户效果,不得宣称提号率提升、客户效果达标或人工补号量下降。',
- '本表不得包含 sessionToken、Authorization、模型 token 或 npm token。'
- ],
- rows
- };
- }
- function buildOwnerActionRows(root, nextActionsDir, nextActions) {
- const actions = Array.isArray(nextActions?.actions) ? nextActions.actions : [];
- return actions.map(action => {
- const ownerArtifact = findOwnerArtifact(nextActions, action.owner);
- return {
- owner: action.owner || '未分配',
- type: '负责人行动',
- title: action.title || '',
- priority: Number(action.priority || action.order || 0),
- status: action.status || 'open',
- mainFile: ownerArtifact?.markdown ? rel(root, path.join(nextActionsDir, ownerArtifact.markdown)) : rel(root, path.join(nextActionsDir, 'business-proof-next-actions-report.md')),
- csv: ownerArtifact?.csv ? rel(root, path.join(nextActionsDir, ownerArtifact.csv)) : rel(root, path.join(nextActionsDir, 'business-proof-next-actions.csv')),
- repairFile: '',
- actionCount: 1,
- command: action.command || '',
- expectedArtifact: action.expectedArtifact || '',
- acceptance: appendMissingProofRequirements(action.acceptance || '', action.missingProofRequirements),
- boundary: '补证任务,不证明业务效果'
- };
- });
- }
- function buildRepairRows(root, nextActionsDir, nextActions) {
- const ownerArtifacts = Array.isArray(nextActions?.ownerArtifacts) ? nextActions.ownerArtifacts : [];
- return ownerArtifacts.flatMap(owner => {
- const repairs = Array.isArray(owner.repairArtifacts) ? owner.repairArtifacts : [];
- const ownerMarkdown = owner.markdown ? rel(root, path.join(nextActionsDir, owner.markdown)) : '';
- const ownerCsv = owner.csv ? rel(root, path.join(nextActionsDir, owner.csv)) : '';
- return repairs.map(repair => ({
- owner: owner.owner || repair.owner || '未分配',
- type: '修复清单',
- title: repair.title || '修复清单',
- priority: Number(repair.topPriority || 0),
- status: Number(repair.actionCount || 0) > 0 ? 'actions_open' : 'empty',
- mainFile: ownerMarkdown,
- csv: ownerCsv,
- repairFile: repair.csv || '',
- actionCount: Number(repair.actionCount || 0),
- command: '按修复清单补齐字段后重新运行 round:refresh',
- expectedArtifact: repair.csv || '',
- acceptance: Number(repair.actionCount || 0) > 0 ? `修复动作数=${Number(repair.actionCount || 0)},补齐后对应 readiness failureCount=0` : '无修复动作',
- boundary: '修复清单是补证执行材料,不证明客户效果'
- }));
- });
- }
- function buildProofGapOperatorOwnerRows(ownerArtifacts) {
- return ownerArtifacts.map(item => ({
- owner: item.owner || '未分配',
- type: '操作包负责人附件',
- title: 'proof-gap 操作包负责人附件',
- priority: Number(item.topPriority || 0),
- status: Number(item.actionCount || 0) > 0 ? 'actions_open' : 'empty',
- mainFile: item.markdown || '',
- csv: item.csv || '',
- repairFile: item.csv || '',
- actionCount: Number(item.actionCount || 0),
- command: '打开负责人附件,按 gapId 补齐当前证据和下一步动作后重新运行 proof-gap:operator-pack 与 round:refresh',
- expectedArtifact: [item.markdown, item.csv].filter(Boolean).join(';'),
- acceptance: [
- `actionCount=${Number(item.actionCount || 0)}`,
- `evidenceRowCount=${Number(item.evidenceRowCount || 0)}`,
- `nextActionCount=${Number(item.nextActionCount || 0)}`,
- `gapIds=${(item.gapIds || []).join(';') || 'none'}`
- ].join(';'),
- boundary: '负责人附件只用于补证派工,不证明客户效果'
- }));
- }
- function buildFieldChecklistOwnerRows(root, fieldChecklistDir, fieldChecklist) {
- const groups = Array.isArray(fieldChecklist?.ownerGroups) ? fieldChecklist.ownerGroups : [];
- return groups.map((group, index) => ({
- owner: group.owner || '未分配',
- type: '字段补齐负责人',
- title: `${sectionLabel(group.section)}字段补齐`,
- priority: index + 1,
- status: Number(group.missingRequiredCount || 0) > 0 || Number(group.placeholderCount || 0) > 0 ? 'actions_open' : 'ready',
- mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
- csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
- repairFile: Array.isArray(group.files) ? group.files.join(';') : '',
- actionCount: Number(group.itemCount || 0),
- command: group.nextAction || '按字段核对表补齐真实材料',
- expectedArtifact: Array.isArray(group.files) ? group.files.join(';') : rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
- acceptance: [
- `字段项=${Number(group.itemCount || 0)}`,
- `缺必填=${Number(group.missingRequiredCount || 0)}`,
- `占位=${Number(group.placeholderCount || 0)}`,
- group.acceptance || ''
- ].filter(Boolean).join(';'),
- boundary: group.boundary || '补齐材料,不证明客户效果'
- }));
- }
- function buildFieldChecklistRecheckRows(root, fieldChecklistDir, fieldChecklist) {
- const commands = Array.isArray(fieldChecklist?.recheckCommands) ? fieldChecklist.recheckCommands : [];
- return commands.map((item, index) => ({
- owner: item.owner || '技术/AI',
- type: '补齐后复验命令',
- title: item.stage || `复验命令 ${index + 1}`,
- priority: index + 1,
- status: 'pending_after_fill',
- mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
- csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
- repairFile: '',
- actionCount: 1,
- command: item.command || '',
- expectedArtifact: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
- acceptance: item.acceptance || '复验命令通过',
- boundary: item.boundary || '复验通过不证明客户效果'
- }));
- }
- function buildPrimaryFileRows(root, latestFormDir, latestForm, proofForm, fieldChecklistDir, fieldChecklist, longRunReadinessDir, longRunReadiness, optimizationCompletionDir, optimizationCompletion) {
- const rows = [];
- if (latestForm?.primaryForm?.csv || proofForm) {
- rows.push({
- owner: '商务',
- type: '关键文件',
- title: '最新提号补证表单',
- priority: 0,
- status: proofForm?.passed ? 'pass' : 'not_ready',
- mainFile: latestForm?.primaryForm?.csv || 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv',
- csv: latestForm?.primaryForm?.csv || '',
- repairFile: '',
- actionCount: Number(proofForm?.rowCount || 0),
- command: '打开 CSV 分派补证任务',
- expectedArtifact: latestForm?.primaryForm?.summary || '',
- acceptance: `rowCount=${Number(proofForm?.rowCount || 0)},duplicateId=${Number(proofForm?.duplicateIdCount || 0)}`,
- boundary: '补证表单不证明客户效果'
- });
- }
- if (latestForm?.clientList?.csv) {
- rows.push({
- owner: '商务',
- type: '关键文件',
- title: '最新软件端候选名单',
- priority: 0,
- status: latestForm.clientList.exists === false ? 'missing' : 'ready',
- mainFile: latestForm.clientList.csv,
- csv: latestForm.clientList.csv,
- repairFile: '',
- actionCount: Number(latestForm.clientList.rowCount || 0),
- command: '用于商务复核候选博主',
- expectedArtifact: latestForm.clientList.summary || '',
- acceptance: `候选行数=${Number(latestForm.clientList.rowCount || 0)},仍需人工复核和客户反馈`,
- boundary: '候选名单不证明命中率提升'
- });
- }
- if (fieldChecklist?.passed) {
- rows.push({
- owner: '商务',
- type: '关键文件',
- title: '真实材料字段级补齐核对表',
- priority: 0,
- status: Number(fieldChecklist.itemCount || 0) > 0 ? 'actions_open' : 'empty',
- mainFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.md')),
- csv: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
- repairFile: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist.csv')),
- actionCount: Number(fieldChecklist.itemCount || 0),
- command: '按字段级核对表逐格补齐真实材料后重新运行 round:refresh',
- expectedArtifact: rel(root, path.join(fieldChecklistDir, 'intake-field-checklist-summary.json')),
- acceptance: `itemCount=${Number(fieldChecklist.itemCount || 0)},placeholder=${Number(fieldChecklist.placeholderCount || 0)},missingRequired=${Number(fieldChecklist.missingRequiredCount || 0)}`,
- boundary: '字段级核对表只指导补齐材料,不证明客户效果'
- });
- }
- if (longRunReadiness || fs.existsSync(path.join(longRunReadinessDir, 'long-run-readiness-report.md'))) {
- const failCount = Number(longRunReadiness?.counts?.fail ?? longRunReadiness?.failCount ?? 0);
- rows.push({
- owner: '技术/AI',
- type: '关键文件',
- title: '长跑前置门禁报告',
- priority: 0,
- status: longRunReadiness?.ready ? 'ready' : 'blocked',
- mainFile: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-report.md')),
- csv: '',
- repairFile: '',
- actionCount: failCount,
- command: '补齐真实材料后运行 longrun:readiness,再判断是否允许启动 full-matrix/live 长跑',
- expectedArtifact: rel(root, path.join(longRunReadinessDir, 'long-run-readiness-summary.json')),
- acceptance: `ready=${Boolean(longRunReadiness?.ready)},failCount=${failCount};ready=true 且 failCount=0 后才允许进入真实 live/provider 长跑启动判断。`,
- boundary: '长跑前置门禁只判断启动条件,不证明客户效果;ready=false 时不能启动或宣称 full-matrix/live 长跑完成。'
- });
- }
- if (optimizationCompletion || fs.existsSync(path.join(optimizationCompletionDir, 'optimization-completion-report.md'))) {
- const blockingReasonCount = Number(optimizationCompletion?.blockingReasonCount ?? (Array.isArray(optimizationCompletion?.blockingReasons)
- ? optimizationCompletion.blockingReasons.length
- : 0));
- const missingProofRequirementCount = Number(optimizationCompletion?.missingProofRequirementCount ?? uniqueMissingProofRequirementCount(optimizationCompletion?.blockingReasons));
- const proofGapOpenCount = Number(optimizationCompletion?.proofGapOpenCount || 0);
- const videoAbPreflightReadyForVideoAb = Boolean(optimizationCompletion?.videoAbPreflightReadyForVideoAb);
- const videoAbPreflightFailureCount = Number.isFinite(Number(optimizationCompletion?.videoAbPreflightFailureCount))
- ? Number(optimizationCompletion.videoAbPreflightFailureCount)
- : null;
- rows.push({
- owner: '技术/AI',
- type: '关键文件',
- title: '优化完成度审计报告',
- priority: 0,
- status: optimizationCompletion?.readyForClaim ? 'ready_for_claim' : 'blocked',
- mainFile: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-report.md')),
- csv: '',
- repairFile: '',
- actionCount: blockingReasonCount,
- command: '补齐真实材料和证明缺口后运行 optimization:completion,再判断是否允许声明长期目标完成',
- expectedArtifact: rel(root, path.join(optimizationCompletionDir, 'optimization-completion-summary.json')),
- acceptance: `complete=${Boolean(optimizationCompletion?.complete)},readyForClaim=${Boolean(optimizationCompletion?.readyForClaim)},proofGapOpenCount=${proofGapOpenCount},blockingReasons=${blockingReasonCount},missingProofRequirementCount=${missingProofRequirementCount},videoAbPreflightReadyForVideoAb=${videoAbPreflightReadyForVideoAb},videoAbPreflightFailureCount=${videoAbPreflightFailureCount ?? 'unknown'}`,
- boundary: '优化完成度审计只判断能否进入完成声明,不证明客户效果;readyForClaim=false 时不得宣称提号率提升、客户效果达标或人工补号量下降。'
- });
- }
- if (latestForm?.handoff?.report) {
- rows.push({
- owner: '技术/AI',
- type: '关键文件',
- title: '下一轮交接摘要',
- priority: 0,
- status: latestForm.handoff.complete ? 'complete_reported' : 'incomplete_reported',
- mainFile: latestForm.handoff.report,
- csv: '',
- repairFile: '',
- actionCount: 1,
- command: '下一轮 AI 先读 handoff 和本执行总表',
- expectedArtifact: latestForm.handoff.summary || '',
- acceptance: `handoff.complete=${Boolean(latestForm.handoff.complete)}`,
- boundary: '交接摘要不证明客户效果'
- });
- } else if (fs.existsSync(path.join(latestFormDir, 'latest-form-index.md'))) {
- rows.push({
- owner: '技术/AI',
- type: '关键文件',
- title: '最新表单索引',
- priority: 0,
- status: latestForm?.passed ? 'pass' : 'not_ready',
- mainFile: rel(root, path.join(latestFormDir, 'latest-form-index.md')),
- csv: '',
- repairFile: '',
- actionCount: 1,
- command: '定位最新补证表单、候选名单和负责人文件',
- expectedArtifact: rel(root, path.join(latestFormDir, 'latest-form-index-summary.json')),
- acceptance: `latestFormIndex.passed=${Boolean(latestForm?.passed)}`,
- boundary: '索引不证明客户效果'
- });
- }
- return rows;
- }
- function renderReport(summary) {
- const lines = [
- '# 商务补证执行总表',
- '',
- `- 生成时间:${summary.generatedAt}`,
- `- 是否通过:${summary.passed ? '是' : '否'}`,
- `- 当前状态:${summary.status}`,
- `- 未关闭证明缺口:${summary.proofOpenCount}`,
- `- 行数:${summary.rowCount}`,
- '',
- '## 执行总表',
- '',
- '| 负责人 | 类型 | 标题 | 优先级 | 状态 | 主文件 | 修复清单 | 动作数 | 验收标准 | 边界 |',
- '| --- | --- | --- | ---: | --- | --- | --- | ---: | --- | --- |',
- ...summary.rows.map(row => tableRow([
- row.owner,
- row.type,
- row.title,
- row.priority,
- row.status,
- row.mainFile || row.csv || '',
- row.repairFile || '',
- row.actionCount,
- row.acceptance,
- row.boundary
- ])),
- '',
- '## 来源',
- '',
- ...Object.entries(summary.sourceFiles).map(([key, value]) => `- ${key}:${value || '缺失'}`),
- '',
- '## 防误用边界',
- '',
- ...summary.guardrails.map(item => `- ${item}`)
- ];
- return lines.join('\n');
- }
- function renderCsv(rows) {
- const headers = ['负责人', '类型', '标题', '优先级', '状态', '主文件', '负责人CSV', '修复清单', '动作数', '命令/动作', '预期产物', '验收标准', '边界'];
- return [
- headers.join(','),
- ...rows.map(row => [
- row.owner,
- row.type,
- row.title,
- row.priority,
- row.status,
- row.mainFile,
- row.csv,
- row.repairFile,
- row.actionCount,
- row.command,
- row.expectedArtifact,
- row.acceptance,
- row.boundary
- ].map(csvCell).join(','))
- ].join('\n');
- }
- function appendMissingProofRequirements(acceptance, missingProofRequirements) {
- const items = Array.isArray(missingProofRequirements) ? missingProofRequirements.filter(Boolean) : [];
- if (!items.length) return acceptance;
- return `${acceptance}${acceptance ? ';' : ''}缺失证明:${items.join(';')}`;
- }
- function normalizeProofBoundary(boundary) {
- const text = String(boundary || '').trim();
- const fallback = '不证明客户效果';
- if (!text) return fallback;
- if (text.includes('不证明') || text.includes('不能证明')) return text;
- if (text.includes('不得宣称') || text.includes('不得宣布') || text.includes('只证明')) {
- return `${text};${fallback}`;
- }
- return `${text};${fallback}`;
- }
- 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 sectionLabel(section) {
- const value = String(section || '');
- if (value === 'history') return '历史 Brief';
- if (value === 'video') return '视频资源';
- if (value === 'review') return '商务复核';
- return value || '真实材料';
- }
- function findOwnerArtifact(summary, owner) {
- return (summary?.ownerArtifacts || []).find(item => item.owner === owner);
- }
- function normalizeProofGapOperatorOwnerArtifacts(root, dir, summary) {
- const artifacts = Array.isArray(summary?.ownerArtifacts) ? summary.ownerArtifacts : [];
- return artifacts.map(item => ({
- owner: item.owner || '',
- actionCount: Number(item.actionCount || 0),
- topPriority: Number(item.topPriority || 0),
- markdown: artifactRef(root, dir, item.markdown || ''),
- csv: artifactRef(root, dir, item.csv || ''),
- gapIds: Array.isArray(item.gapIds) ? item.gapIds.filter(Boolean) : [],
- evidenceRowCount: Number(item.evidenceRowCount || 0),
- nextActionCount: Number(item.nextActionCount || 0)
- }));
- }
- function artifactRef(root, dir, file) {
- const value = String(file || '').replace(/\\/g, '/');
- if (!value) return '';
- if (path.isAbsolute(file)) return rel(root, file);
- if (value.startsWith('outputs/')) return value;
- return rel(root, path.join(dir, file));
- }
- function pickDir(outputsDir, latestName, pattern) {
- const latest = path.join(outputsDir, latestName);
- if (fs.existsSync(latest)) return latest;
- if (!fs.existsSync(outputsDir)) return latest;
- const dirs = fs.readdirSync(outputsDir, { withFileTypes: true })
- .filter(entry => entry.isDirectory() && pattern.test(entry.name))
- .map(entry => path.join(outputsDir, entry.name))
- .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
- return dirs[0] || latest;
- }
- function readJson(file) {
- if (!fs.existsSync(file)) return null;
- return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
- }
- function rel(root, file) {
- return file ? path.relative(root, file).replace(/\\/g, '/') : '';
- }
- function tableRow(cells) {
- return `| ${cells.map(escapeCell).join(' | ')} |`;
- }
- function escapeCell(value) {
- return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
- }
- function csvCell(value) {
- const text = String(value ?? '');
- return /[",\r\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 = {
- buildBusinessExecutionIndex
- };
|