business-execution-index-smoke.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { buildBusinessExecutionIndex } = require('./business-execution-index');
  6. function main() {
  7. const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-business-execution-index-'));
  8. const outputs = path.join(root, 'outputs');
  9. seed(outputs);
  10. const summary = buildBusinessExecutionIndex({ root, outputsDir: outputs });
  11. assert(summary.passed === true, 'business execution index should pass with seeded artifacts');
  12. assert(summary.directCustomerProof === false, 'business execution index should not be direct customer proof');
  13. assert(summary.proofLevel === 'smoke_or_local', 'business execution index should stay smoke/local evidence');
  14. assert(summary.proofOpenCount === 5, 'should expose proof open count');
  15. assert(summary.actionRowCount === 3, 'should expose owner action rows');
  16. assert(summary.repairRowCount === 2, 'should expose repair action rows');
  17. assert(summary.operatorPackOwnerArtifactRowCount === 3, 'should expose operator pack owner attachment rows');
  18. assert(summary.operatorPackOwnerArtifactCount === 3, 'should expose operator pack owner attachment count');
  19. assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === 3, 'should carry operator pack owner attachments');
  20. assert(summary.fieldOwnerRowCount === 2, 'should expose field checklist owner group rows');
  21. assert(summary.recheckCommandRowCount === 4, 'should expose field checklist recheck command rows');
  22. assert(summary.fileRowCount >= 2, 'should expose primary file rows');
  23. assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('intake-readiness-repair-actions.csv') && row.actionCount === 6), 'should include business intake repair file');
  24. assert(summary.rows.some(row => row.type === '修复清单' && row.repairFile.includes('video-resource-repair-actions.csv') && row.actionCount === 1), 'should include video repair file');
  25. assert(summary.sourceFiles.proofGapOperatorPack?.includes('proof-gap-operator-pack-summary.json'), 'should expose proof-gap operator pack source file');
  26. assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务' && row.mainFile.includes('by-owner/business.md') && row.csv.includes('by-owner/business.csv') && row.acceptance.includes('historical-dataset')), 'should include business proof-gap operator owner attachment');
  27. assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '商务/投放' && row.mainFile.includes('by-owner/business-media.md') && row.csv.includes('by-owner/business-media.csv')), 'should include business/media proof-gap operator owner attachment');
  28. assert(summary.rows.some(row => row.type === '操作包负责人附件' && row.owner === '技术/AI' && row.mainFile.includes('by-owner/tech-ai.md') && row.csv.includes('by-owner/tech-ai.csv')), 'should include tech/AI proof-gap operator owner attachment');
  29. assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('历史 Brief') && row.repairFile.includes('history-data-template.csv')), 'should include history field owner row');
  30. assert(summary.rows.some(row => row.type === '字段补齐负责人' && row.title.includes('视频资源') && row.repairFile.includes('video-resource-template.csv')), 'should include video field owner row');
  31. assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('intake:readiness')), 'should include intake readiness recheck row');
  32. assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('acceptance:video-ab')), 'should include video A/B recheck row');
  33. assert(summary.rows.some(row => row.type === '补齐后复验命令' && row.command.includes('customer-effect:audit')), 'should include customer effect recheck row');
  34. assert(summary.sourceFiles.longRunReadiness?.includes('long-run-readiness-summary.json'), 'should expose long-run readiness source file');
  35. assert(summary.sourceFiles.optimizationCompletion?.includes('optimization-completion-summary.json'), 'should expose optimization completion source file');
  36. assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('长跑前置门禁') && row.mainFile.includes('long-run-readiness-report.md') && row.acceptance.includes('ready=false')), 'should include long-run readiness key file row');
  37. assert(summary.rows.some(row => row.title.includes('长跑前置门禁') && row.boundary.includes('不能启动')), 'long-run readiness row should keep startup boundary');
  38. assert(summary.rows.some(row => row.type === '关键文件' && row.title.includes('优化完成度审计') && row.mainFile.includes('optimization-completion-report.md') && row.acceptance.includes('readyForClaim=false')), 'should include optimization completion key file row');
  39. assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('missingProofRequirementCount=2')), 'optimization completion row should expose missing proof requirement count');
  40. assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightReadyForVideoAb=false')), 'optimization completion row should expose video A/B preflight readiness');
  41. assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.acceptance.includes('videoAbPreflightFailureCount=4')), 'optimization completion row should expose video A/B preflight failure count');
  42. assert(summary.rows.some(row => row.title.includes('优化完成度审计') && row.boundary.includes('不得宣称提号率提升')), 'optimization completion row should keep forbidden-claim boundary');
  43. const missingBoundaryRows = summary.rows.filter(row => !boundaryOk(row.boundary));
  44. assert(missingBoundaryRows.length === 0, `each row should carry proof boundary: ${JSON.stringify(missingBoundaryRows.map(row => ({ type: row.type, title: row.title, boundary: row.boundary })))}`);
  45. assert(summary.rows.some(row => row.title.includes('A/B') && row.acceptance.includes('proofContext.mode=live')), 'video A/B row should expose missing proof requirements');
  46. console.log(JSON.stringify({
  47. ok: true,
  48. rowCount: summary.rowCount,
  49. repairRowCount: summary.repairRowCount,
  50. fieldOwnerRowCount: summary.fieldOwnerRowCount,
  51. recheckCommandRowCount: summary.recheckCommandRowCount
  52. }, null, 2));
  53. }
  54. function seed(outputs) {
  55. const nextActionsDir = path.join(outputs, 'business-proof-next-actions-latest');
  56. const latestFormDir = path.join(outputs, 'latest-form-index-latest');
  57. const proofFormDir = path.join(outputs, 'tihao-proof-gap-software-form-latest');
  58. const closureDir = path.join(outputs, 'proof-gap-closure-latest');
  59. const evidenceDir = path.join(outputs, 'evidence-index-latest');
  60. const fieldChecklistDir = path.join(outputs, 'intake-field-checklist-latest');
  61. const longRunReadinessDir = path.join(outputs, 'long-run-readiness-latest');
  62. const optimizationCompletionDir = path.join(outputs, 'optimization-completion-latest');
  63. const proofGapOperatorPackDir = path.join(outputs, 'proof-gap-operator-pack-latest');
  64. for (const dir of [nextActionsDir, latestFormDir, proofFormDir, closureDir, evidenceDir, fieldChecklistDir, longRunReadinessDir, optimizationCompletionDir, proofGapOperatorPackDir]) fs.mkdirSync(dir, { recursive: true });
  65. fs.mkdirSync(path.join(nextActionsDir, 'by-owner'), { recursive: true });
  66. fs.mkdirSync(path.join(proofGapOperatorPackDir, 'by-owner'), { recursive: true });
  67. fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.md'), '# 商务\n', 'utf8');
  68. fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business.csv'), '标题\n', 'utf8');
  69. fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.md'), '# 商务/投放\n', 'utf8');
  70. fs.writeFileSync(path.join(nextActionsDir, 'by-owner', 'business-media.csv'), '标题\n', 'utf8');
  71. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.md'), '# 商务 proof-gap\n', 'utf8');
  72. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business.csv'), 'gapId\n', 'utf8');
  73. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.md'), '# 商务/投放 proof-gap\n', 'utf8');
  74. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'business-media.csv'), 'gapId\n', 'utf8');
  75. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.md'), '# 技术/AI proof-gap\n', 'utf8');
  76. fs.writeFileSync(path.join(proofGapOperatorPackDir, 'by-owner', 'tech-ai.csv'), 'gapId\n', 'utf8');
  77. writeJson(path.join(nextActionsDir, 'business-proof-next-actions-summary.json'), {
  78. complete: false,
  79. sourceState: { proofGapOpenCount: 5 },
  80. actions: [
  81. { owner: '商务', title: '填真实历史 Brief', priority: 1, status: 'blocked_by_external_data', command: '填写 history-data-template.csv', expectedArtifact: 'history-data-template.csv', acceptance: 'historyReady=true' },
  82. { owner: '商务/投放', title: '填真实视频资源', priority: 2, status: 'blocked_by_external_data', command: '填写 video-resource-template.csv', expectedArtifact: 'video-resource-template.csv', acceptance: 'readyForVideoAbPreflight=true' },
  83. { owner: '技术/AI', title: '真实视频 A/B 验收', priority: 24, status: 'open', command: 'npm run acceptance:video-ab', expectedArtifact: 'video-hit-rate-summary.json', acceptance: '真实 provider 和真实视频资源下通过', missingProofRequirements: ['proofContext.mode=live', 'proofContext.generatedBy=acceptance:video-ab'] }
  84. ],
  85. ownerArtifacts: [
  86. {
  87. owner: '商务',
  88. actionCount: 1,
  89. markdown: 'by-owner/business.md',
  90. csv: 'by-owner/business.csv',
  91. repairArtifacts: [
  92. { title: '历史数据/商务复核修复清单', csv: 'outputs/intake-readiness-latest/intake-readiness-repair-actions.csv', actionCount: 6, topPriority: 1 }
  93. ]
  94. },
  95. {
  96. owner: '商务/投放',
  97. actionCount: 1,
  98. markdown: 'by-owner/business-media.md',
  99. csv: 'by-owner/business-media.csv',
  100. repairArtifacts: [
  101. { title: '视频资源修复清单', csv: 'outputs/video-resource-readiness-latest/video-resource-repair-actions.csv', actionCount: 1, topPriority: 1 }
  102. ]
  103. }
  104. ]
  105. });
  106. writeJson(path.join(latestFormDir, 'latest-form-index-summary.json'), {
  107. passed: true,
  108. primaryForm: { csv: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form.csv', summary: 'outputs/tihao-proof-gap-software-form-latest/tihao-proof-gap-software-form-summary.json' },
  109. clientList: { exists: true, csv: 'outputs/software-client-latest/software-client-list.final.csv', summary: 'outputs/software-client-latest/software-client-list.final.summary.json', rowCount: 28 },
  110. handoff: { complete: false, report: 'outputs/optimization-handoff-latest/optimization-handoff-report.md', summary: 'outputs/optimization-handoff-latest/optimization-handoff-summary.json' }
  111. });
  112. fs.writeFileSync(path.join(latestFormDir, 'latest-form-index.md'), '# 最新提号表单索引\n', 'utf8');
  113. writeJson(path.join(proofFormDir, 'tihao-proof-gap-software-form-summary.json'), {
  114. passed: true,
  115. rowCount: 5,
  116. duplicateIdCount: 0
  117. });
  118. writeJson(path.join(fieldChecklistDir, 'intake-field-checklist-summary.json'), {
  119. passed: true,
  120. proofLevel: 'smoke_or_local',
  121. directCustomerProof: false,
  122. itemCount: 6,
  123. missingRequiredCount: 2,
  124. placeholderCount: 4,
  125. ownerGroups: [
  126. {
  127. owner: '商务',
  128. section: 'history',
  129. itemCount: 3,
  130. missingRequiredCount: 1,
  131. placeholderCount: 2,
  132. files: ['outputs/data-intake-pack-latest/history-data-template.csv'],
  133. nextAction: '补真实历史 Brief',
  134. acceptance: 'historyReady=true',
  135. boundary: '补齐材料,不证明客户效果'
  136. },
  137. {
  138. owner: '商务/投放',
  139. section: 'video',
  140. itemCount: 3,
  141. missingRequiredCount: 1,
  142. placeholderCount: 2,
  143. files: ['outputs/video-intake-pack-latest/video-resource-template.csv'],
  144. nextAction: '补真实视频 URL',
  145. acceptance: 'videoReady=true',
  146. boundary: '补齐材料,不证明客户效果'
  147. }
  148. ],
  149. recheckCommands: [
  150. { stage: '真实材料预审', owner: '技术/AI', command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest', acceptance: 'overallReady=true', boundary: '预审通过不证明客户效果' },
  151. { stage: '刷新本轮交接', owner: '技术/AI', command: 'npm run round:refresh -- --output-root outputs --strict', acceptance: 'round-deposition.passed=true', boundary: '刷新交接不证明业务效果' },
  152. { stage: '客户效果审计', owner: '技术/AI', command: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict', acceptance: 'overallPass=true', boundary: '缺客户选择不得宣称效果' },
  153. { stage: '视频 A/B 验收', owner: '技术/AI', command: 'npm run acceptance:video-ab', acceptance: '真实 provider 下通过', boundary: 'sample 不证明视频提升提号率' }
  154. ],
  155. items: []
  156. });
  157. fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.md'), '# 真实材料字段级补齐核对表\n', 'utf8');
  158. fs.writeFileSync(path.join(fieldChecklistDir, 'intake-field-checklist.csv'), 'ID,负责人,区域\n', 'utf8');
  159. writeJson(path.join(closureDir, 'proof-gap-closure-summary.json'), {
  160. complete: false,
  161. openCount: 5
  162. });
  163. writeJson(path.join(evidenceDir, 'evidence-index-summary.json'), {
  164. counts: { real_evidence: 1, smoke_or_local: 3, not_business_proof: 2 }
  165. });
  166. writeJson(path.join(proofGapOperatorPackDir, 'proof-gap-operator-pack-summary.json'), {
  167. passed: true,
  168. complete: false,
  169. directCustomerProof: false,
  170. proofLevel: 'not_business_proof',
  171. openCount: 5,
  172. rowCount: 5,
  173. ownerArtifactCount: 3,
  174. ownerArtifacts: [
  175. {
  176. owner: '商务',
  177. actionCount: 2,
  178. topPriority: 1,
  179. markdown: 'by-owner/business.md',
  180. csv: 'by-owner/business.csv',
  181. gapIds: ['historical-dataset', 'manual-review-and-customer-effect'],
  182. evidenceRowCount: 2,
  183. nextActionCount: 2
  184. },
  185. {
  186. owner: '商务/投放',
  187. actionCount: 1,
  188. topPriority: 2,
  189. markdown: 'by-owner/business-media.md',
  190. csv: 'by-owner/business-media.csv',
  191. gapIds: ['video-real-candidate'],
  192. evidenceRowCount: 1,
  193. nextActionCount: 1
  194. },
  195. {
  196. owner: '技术/AI',
  197. actionCount: 2,
  198. topPriority: 3,
  199. markdown: 'by-owner/tech-ai.md',
  200. csv: 'by-owner/tech-ai.csv',
  201. gapIds: ['video-ab-live-proof', 'live-provider-overnight-proof'],
  202. evidenceRowCount: 2,
  203. nextActionCount: 2
  204. }
  205. ]
  206. });
  207. writeJson(path.join(longRunReadinessDir, 'long-run-readiness-summary.json'), {
  208. mode: 'full-matrix',
  209. ready: false,
  210. counts: { pass: 1, fail: 5, warn: 0 }
  211. });
  212. fs.writeFileSync(path.join(longRunReadinessDir, 'long-run-readiness-report.md'), '# 提号长跑就绪度审计\n', 'utf8');
  213. writeJson(path.join(optimizationCompletionDir, 'optimization-completion-summary.json'), {
  214. complete: false,
  215. readyForClaim: false,
  216. proofGapOpenCount: 5,
  217. blockingReasonCount: 2,
  218. blockingReasonsWithMissingProofRequirements: 2,
  219. missingProofRequirementCount: 2,
  220. videoAbPreflightReadyForVideoAb: false,
  221. videoAbPreflightFailureCount: 4,
  222. blockingReasons: [
  223. { id: 'proof-gap-closure', status: 'blocked', missingProofRequirements: ['proofGapClosure.openCount=0'] },
  224. { id: 'longrun-readiness', status: 'blocked', missingProofRequirements: ['longRunReadiness.ready=true'] }
  225. ],
  226. canClaim: {
  227. tihaoRateImproved: false,
  228. customerEffectAchieved: false,
  229. manualSupplementReduced: false
  230. }
  231. });
  232. fs.writeFileSync(path.join(optimizationCompletionDir, 'optimization-completion-report.md'), '# 优化完成度审计\n', 'utf8');
  233. }
  234. function boundaryOk(value) {
  235. const text = String(value || '');
  236. return text.includes('不证明') ||
  237. text.includes('不能证明') ||
  238. text.includes('补证') ||
  239. text.includes('只证明') ||
  240. text.includes('不得宣称');
  241. }
  242. function writeJson(file, value) {
  243. fs.mkdirSync(path.dirname(file), { recursive: true });
  244. fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
  245. }
  246. function assert(condition, message) {
  247. if (!condition) throw new Error(message);
  248. }
  249. if (require.main === module) main();