business-proof-progress-smoke.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const root = path.resolve(__dirname, '..');
  7. function main() {
  8. const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-proof-progress-'));
  9. const outputs = path.join(tmp, 'outputs');
  10. fs.mkdirSync(outputs, { recursive: true });
  11. makeDir(path.join(outputs, 'data-intake-pack-latest'), {
  12. 'history-data-template.csv': 'brief编号,客户原始Brief\n',
  13. 'manual-review-template.csv': 'brief编号,人工复核标签\n',
  14. 'README.md': '# 数据收集包\n'
  15. });
  16. makeDir(path.join(outputs, 'video-intake-pack-latest'), {
  17. 'video-resource-template.csv': 'brief编号,视频链接\n',
  18. 'README.md': '# 视频资源收集包\n'
  19. });
  20. writeJson(path.join(outputs, 'intake-readiness-latest', 'intake-readiness-summary.json'), {
  21. failureCount: 9,
  22. acceptance: {
  23. historyReady: false,
  24. videoReady: false,
  25. reviewMetricsReady: false,
  26. customerEffectReady: false,
  27. overallReady: false
  28. },
  29. history: { issues: [{ type: 'placeholder' }] }
  30. });
  31. writeJson(path.join(outputs, 'video-resource-readiness-latest', 'video-resource-readiness-summary.json'), {
  32. failureCount: 1,
  33. counts: { realCandidateRows: 0 },
  34. acceptance: { readyForVideoAbPreflight: false }
  35. });
  36. writeJson(path.join(outputs, 'long-run-readiness-latest', 'long-run-readiness-summary.json'), {
  37. ready: false,
  38. counts: { pass: 2, fail: 5 }
  39. });
  40. writeJson(path.join(outputs, 'proof-gap-closure-latest', 'proof-gap-closure-summary.json'), {
  41. complete: false,
  42. openCount: 4,
  43. rows: [
  44. { id: 'video-ab-live-proof', status: 'closed', evidence: 'passed=true, strongDelta=5, evidenceDelta=20' }
  45. ]
  46. });
  47. writeJson(path.join(outputs, 'historical-dataset-thin', 'historical-dataset-audit.json'), {
  48. briefCount: 5,
  49. categoryCount: 3,
  50. withCustomerDecision: 5,
  51. acceptance: {
  52. readyForLongRun: true,
  53. readyForCustomerEffectProof: true
  54. }
  55. });
  56. const operatorOwnerArtifacts = [
  57. {
  58. owner: '商务',
  59. markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business.md',
  60. csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business.csv',
  61. actionCount: 2,
  62. topPriority: 1,
  63. gapIds: ['historical-dataset', 'manual-review-and-customer-effect'],
  64. evidenceRowCount: 2,
  65. nextActionCount: 2
  66. },
  67. {
  68. owner: '商务/投放',
  69. markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.md',
  70. csv: 'outputs/proof-gap-operator-pack-latest/by-owner/business-media.csv',
  71. actionCount: 1,
  72. topPriority: 2,
  73. gapIds: ['video-real-candidate'],
  74. evidenceRowCount: 1,
  75. nextActionCount: 1
  76. },
  77. {
  78. owner: '技术/AI',
  79. markdown: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.md',
  80. csv: 'outputs/proof-gap-operator-pack-latest/by-owner/tech-ai.csv',
  81. actionCount: 2,
  82. topPriority: 3,
  83. gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'],
  84. evidenceRowCount: 2,
  85. nextActionCount: 2
  86. }
  87. ];
  88. writeJson(path.join(outputs, 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json'), {
  89. passed: true,
  90. complete: false,
  91. ownerArtifactCount: 3,
  92. ownerArtifacts: operatorOwnerArtifacts
  93. });
  94. writeJson(path.join(outputs, 'business-execution-index-latest', 'business-execution-index-summary.json'), {
  95. passed: true,
  96. operatorPackOwnerArtifactCount: 3,
  97. operatorPackOwnerArtifactRowCount: 3,
  98. operatorPackOwnerArtifacts: operatorOwnerArtifacts
  99. });
  100. writeJson(path.join(outputs, 'optimization-handoff-latest', 'optimization-handoff-summary.json'), {
  101. complete: false,
  102. statusCounts: { passed: 25, ready_not_proven: 2, blocked_by_external_data: 2 }
  103. });
  104. writeJson(path.join(outputs, 'evidence-index-latest', 'evidence-index-summary.json'), {
  105. counts: { real_evidence: 5, smoke_or_local: 23, not_business_proof: 102 }
  106. });
  107. writeJson(path.join(outputs, 'round-deposition-latest', 'round-deposition-summary.json'), {
  108. passed: true,
  109. counts: { pass: 40, fail: 0, warn: 0 }
  110. });
  111. writeJson(path.join(outputs, 'optimization-status-latest', 'optimization-status-summary.json'), {
  112. complete: false,
  113. counts: { passed: 27, ready_not_proven: 2, blocked_by_external_data: 2 },
  114. items: [
  115. {
  116. id: 'video-ab-proof',
  117. status: 'ready_not_proven',
  118. evidence: 'acceptance:video-ab smoke is ready but live proof is missing'
  119. }
  120. ]
  121. });
  122. const outputDir = path.join(outputs, 'business-proof-progress-latest');
  123. const result = spawnSync(process.execPath, [
  124. path.join(root, 'scripts', 'business-proof-progress.js'),
  125. '--outputs',
  126. outputs,
  127. '--output',
  128. outputDir
  129. ], { cwd: root, encoding: 'utf8' });
  130. if (result.status !== 0) {
  131. process.stderr.write(result.stderr || result.stdout || '');
  132. throw new Error('business-proof-progress should run');
  133. }
  134. const summary = JSON.parse(fs.readFileSync(path.join(outputDir, 'business-proof-progress-summary.json'), 'utf8'));
  135. const report = fs.readFileSync(path.join(outputDir, 'business-proof-progress-report.md'), 'utf8').replace(/^\uFEFF/, '');
  136. assert(summary.complete === false, 'progress must not report complete with blocked proof data');
  137. assert(summary.rows.length === 15, 'progress should include 15 steps');
  138. assert(summary.counts.pass === 4, 'template packs, history import presence, and deposition refresh should pass in fixture');
  139. assert(summary.counts.fail === 2, 'intake preaudit and longrun gate should fail in fixture');
  140. assert(summary.counts.blocked_by_external_data === 7, 'external data blockers should be explicit');
  141. assert(summary.counts.pending === 2, 'pipeline and unproven video A/B should remain pending');
  142. assert(summary.rows.some(row => row.order === 6 && row.status === 'pass'), 'history import should pass when audit file exists');
  143. assert(summary.rows.some(row => row.order === 7 && row.status === 'blocked_by_external_data' && row.evidence.includes('missing=')), 'thin history audit should stay blocked and expose missing details');
  144. assert(summary.rows.some(row => row.title === '客户效果审计' && row.status === 'blocked_by_external_data'), 'customer effect should be blocked by real data');
  145. assert(summary.rows.some(row => row.title === '视频 A/B 验收' && row.status === 'pending' && row.evidence.includes('ready_not_proven')), 'video A/B should stay pending when status audit is ready_not_proven');
  146. assert(summary.rows.every(row => row.owner !== '投放/商务'), 'progress owner labels should be normalized');
  147. assert(summary.observed.videoAbProofStatus === 'ready_not_proven', 'progress should expose video A/B status audit result');
  148. assert(summary.operatorPackOwnerArtifactCount === 3, 'progress should expose proof-gap operator owner attachment count');
  149. assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'progress should carry proof-gap operator owner attachments');
  150. assert(summary.operatorPackOwnerArtifacts.some(item => item.markdown.includes('by-owner/business.md') && item.csv.includes('by-owner/business.csv')), 'progress should carry business owner operator attachment');
  151. assert(summary.observed.businessExecutionOperatorPackOwnerArtifactRowCount === 3, 'progress should expose business execution operator attachment rows');
  152. assert(report.includes('# 提号补证 15 步进度表'), 'report should be Chinese');
  153. assert(report.includes('blocked_by_external_data'), 'report should show blocked status');
  154. assert(report.includes('by-owner/business-media.md'), 'report should show proof-gap operator owner attachments');
  155. assert(report.includes('不能把 sample、smoke'), 'report should preserve proof boundary');
  156. assert(!/sk-[A-Za-z0-9_-]{10,}/.test(report), 'report should not contain model token patterns');
  157. assert(!/r:[A-Za-z0-9]{20,}/.test(report), 'report should not contain Parse sessionToken patterns');
  158. console.log(JSON.stringify({
  159. ok: true,
  160. outputDir,
  161. complete: summary.complete,
  162. counts: summary.counts
  163. }, null, 2));
  164. }
  165. function makeDir(dir, files) {
  166. fs.mkdirSync(dir, { recursive: true });
  167. for (const [name, content] of Object.entries(files)) {
  168. fs.writeFileSync(path.join(dir, name), content, 'utf8');
  169. }
  170. }
  171. function writeJson(file, value) {
  172. fs.mkdirSync(path.dirname(file), { recursive: true });
  173. fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
  174. }
  175. function assert(condition, message) {
  176. if (!condition) throw new Error(message);
  177. }
  178. main();