generate-optimization-handoff.js 116 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const { buildStatusSummary } = require('./optimization-status-audit');
  5. const { buildEvidenceIndex } = require('./evidence-index');
  6. const ROOT = path.resolve(__dirname, '..');
  7. const OUTPUTS = path.join(ROOT, 'outputs');
  8. function main() {
  9. const args = parseArgs(process.argv.slice(2));
  10. const outputDir = path.resolve(args.output || process.env.TIHAO_HANDOFF_OUTPUT || path.join(OUTPUTS, `optimization-handoff-${Date.now()}`));
  11. const summary = buildHandoffSummary({
  12. root: ROOT,
  13. outputsDir: path.resolve(args.outputs || OUTPUTS)
  14. });
  15. fs.mkdirSync(outputDir, { recursive: true });
  16. const jsonPath = path.join(outputDir, 'optimization-handoff-summary.json');
  17. const reportPath = path.join(outputDir, 'optimization-handoff-report.md');
  18. fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2), 'utf8');
  19. fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
  20. console.log(JSON.stringify({
  21. outputDir,
  22. json: jsonPath,
  23. report: reportPath,
  24. complete: summary.complete,
  25. readyCapabilities: summary.readyCapabilities.length,
  26. externalBlockers: summary.externalBlockers.length
  27. }, null, 2));
  28. }
  29. function buildHandoffSummary({ root, outputsDir }) {
  30. const status = buildStatusSummary(root);
  31. const evidence = buildEvidenceIndex({ root, outputsDir });
  32. const latestDataIntakePack = latestEvidenceDir(evidence, 'data-intake-pack');
  33. const latestBusinessProofProgress = latestEvidenceDir(evidence, 'business-proof-progress');
  34. const businessProofProgressSummary = latestBusinessProofProgress
  35. ? readJsonSummary(root, latestBusinessProofProgress, 'business-proof-progress-summary.json')
  36. : null;
  37. const latestBusinessProofNextActions = latestOutputDir(outputsDir, /^business-proof-next-actions-/);
  38. const businessProofNextActionsSummary = latestBusinessProofNextActions
  39. ? readBusinessProofNextActionsSummary(root, latestBusinessProofNextActions)
  40. : null;
  41. const latestBusinessExecutionIndex = latestOutputDir(outputsDir, /^business-execution-index-/);
  42. const businessExecutionIndexSummary = latestBusinessExecutionIndex
  43. ? readJsonSummary(root, latestBusinessExecutionIndex, 'business-execution-index-summary.json')
  44. : null;
  45. const latestProofGapSoftwareForm = latestOutputDir(outputsDir, /^tihao-proof-gap-software-form-/);
  46. const proofGapSoftwareFormSummary = latestProofGapSoftwareForm
  47. ? readJsonSummary(root, latestProofGapSoftwareForm, 'tihao-proof-gap-software-form-summary.json')
  48. : null;
  49. const latestProofGapRequest = latestOutputDir(outputsDir, /^proof-gap-request-/);
  50. const proofGapRequestSummary = latestProofGapRequest
  51. ? readJsonSummary(root, latestProofGapRequest, 'proof-gap-request-summary.json')
  52. : null;
  53. const latestProofGapClosure = latestOutputDir(outputsDir, /^proof-gap-closure-/);
  54. const proofGapClosureSummary = latestProofGapClosure
  55. ? readJsonSummary(root, latestProofGapClosure, 'proof-gap-closure-summary.json')
  56. : null;
  57. const latestFeedbackLoopClosure = latestOutputDir(outputsDir, /^feedback-loop-closure-/);
  58. const feedbackLoopClosureSummary = latestFeedbackLoopClosure
  59. ? readJsonSummary(root, latestFeedbackLoopClosure, 'feedback-loop-closure-summary.json')
  60. : null;
  61. const latestHomepageEvidenceReadiness = latestOutputDir(outputsDir, /^homepage-evidence-readiness-/);
  62. const homepageEvidenceReadinessSummary = latestHomepageEvidenceReadiness
  63. ? readJsonSummary(root, latestHomepageEvidenceReadiness, 'homepage-evidence-readiness-summary.json')
  64. : null;
  65. const latestLongRunReadiness = latestOutputDir(outputsDir, /^long-run-readiness-/);
  66. const longRunReadinessSummary = latestLongRunReadiness
  67. ? readJsonSummary(root, latestLongRunReadiness, 'long-run-readiness-summary.json')
  68. : null;
  69. const latestIntakeFieldChecklist = latestOutputDir(outputsDir, /^intake-field-checklist-/);
  70. const intakeFieldChecklistSummary = latestIntakeFieldChecklist
  71. ? readJsonSummary(root, latestIntakeFieldChecklist, 'intake-field-checklist-summary.json')
  72. : null;
  73. const latestFormIndex = latestOutputDir(outputsDir, /^latest-form-index-/);
  74. const latestFormIndexSummary = latestFormIndex
  75. ? readJsonSummary(root, latestFormIndex, 'latest-form-index-summary.json')
  76. : null;
  77. const latestLocalSeedToIntakeWorklist = latestOutputDir(outputsDir, /^local-seed-to-intake-worklist-/);
  78. const localSeedToIntakeWorklistSummary = latestLocalSeedToIntakeWorklist
  79. ? readJsonSummary(root, latestLocalSeedToIntakeWorklist, 'local-seed-to-intake-worklist-summary.json')
  80. : latestFormIndexSummary?.localSeedToIntakeWorklist || null;
  81. const latestRealGapMaterialAudit = latestOutputDir(outputsDir, /^real-gap-material-audit-/);
  82. const realGapMaterialAuditSummary = latestRealGapMaterialAudit
  83. ? readJsonSummary(root, latestRealGapMaterialAudit, 'real-gap-material-audit-summary.json')
  84. : latestFormIndexSummary?.realGapMaterialAudit || null;
  85. const latestPlanExecutionStatus = latestOutputDir(outputsDir, /^plan-execution-status-/);
  86. const planExecutionStatusSummary = latestPlanExecutionStatus
  87. ? readJsonSummary(root, latestPlanExecutionStatus, 'plan-execution-status-summary.json')
  88. : latestFormIndexSummary?.planExecutionStatus || null;
  89. const latestProofGapOperatorPack = latestOutputDir(outputsDir, /^proof-gap-operator-pack-/);
  90. const proofGapOperatorPackSummary = latestProofGapOperatorPack
  91. ? readJsonSummary(root, latestProofGapOperatorPack, 'proof-gap-operator-pack-summary.json')
  92. : latestFormIndexSummary?.proofGapOperatorPack || null;
  93. const latestRealProofIntakeBundle = latestOutputDir(outputsDir, /^real-proof-intake-bundle-/);
  94. const realProofIntakeBundleSummary = latestRealProofIntakeBundle
  95. ? readJsonSummary(root, latestRealProofIntakeBundle, 'real-proof-intake-bundle-summary.json')
  96. : latestFormIndexSummary?.realProofIntakeBundle || null;
  97. const latestRealProofClosureWorkOrder = latestOutputDir(outputsDir, /^real-proof-closure-work-order-/);
  98. const realProofClosureWorkOrderSummary = latestRealProofClosureWorkOrder
  99. ? readJsonSummary(root, latestRealProofClosureWorkOrder, 'real-proof-closure-work-order-summary.json')
  100. : latestFormIndexSummary?.realProofClosureWorkOrder || null;
  101. const latestRepairActions = latestFormIndexSummary?.repairActions || buildRepairActionsSummary(root);
  102. const latestOptimizationPipeline = latestOutputDir(outputsDir, /^optimization-pipeline-/);
  103. const optimizationPipelineSummary = latestOptimizationPipeline
  104. ? readOptimizationPipelineSummary(root, latestOptimizationPipeline)
  105. : null;
  106. const latestOptimizationCompletion = latestOutputDir(outputsDir, /^optimization-completion-/);
  107. const optimizationCompletionSummary = latestOptimizationCompletion
  108. ? readJsonSummary(root, latestOptimizationCompletion, 'optimization-completion-summary.json')
  109. : latestFormIndexSummary?.optimizationCompletion || null;
  110. const latestEvidenceIndex = latestOutputDir(outputsDir, /^evidence-index-/) || latestEvidenceDir(evidence, 'optimization-status');
  111. const proofActionOrderPath = firstExistingPath(root, [
  112. path.join('docs', 'business-proof-action-order.zh-CN.md'),
  113. path.join('docs', 'business-proof-action-order.md')
  114. ]);
  115. const supportDocs = [
  116. {
  117. title: '长期优化计划主文件',
  118. path: path.join(root, 'docs', 'tihao-experience-optimization-plan.md'),
  119. purpose: '当前目标文件;用于执行长期优化计划、同步每轮进度、确认验收标准和完成边界。'
  120. },
  121. {
  122. title: '长期优化计划中文执行版',
  123. path: path.join(root, 'docs', 'tihao-experience-optimization-plan.zh-CN.md'),
  124. purpose: '用于下一轮 AI 或项目负责人理解长期目标、策略矩阵、验收标准和完成边界。'
  125. },
  126. {
  127. title: '补证操作顺序中文执行版',
  128. path: path.join(root, 'docs', 'business-proof-action-order.zh-CN.md'),
  129. purpose: '用于商务、投放、技术/AI 按 15 步顺序补齐真实材料和验收证据。'
  130. }
  131. ,
  132. {
  133. title: '提号任务交接入口',
  134. path: path.join(root, 'docs', 'tihao-handoff-index.md'),
  135. purpose: '下一轮 AI、商务或投放接手时优先打开;按顺序定位主计划、补证顺序、最新表单索引、真实材料总包、proof-gap 请求包、商务执行总表和交接摘要。'
  136. }
  137. ]
  138. .filter(item => fs.existsSync(item.path))
  139. .map(item => ({
  140. title: item.title,
  141. doc: rel(root, item.path),
  142. purpose: item.purpose
  143. }));
  144. const readyCapabilities = status.items
  145. .filter(item => item.status === 'passed')
  146. .map(publicStatusItem);
  147. const readyNotProven = status.items
  148. .filter(item => item.status === 'ready_not_proven')
  149. .map(publicStatusItem);
  150. const externalBlockers = status.items
  151. .filter(item => item.status === 'blocked_by_external_data')
  152. .map(publicStatusItem);
  153. const businessProofNextActionSourceState = normalizeBusinessProofNextActionSourceState(businessProofNextActionsSummary?.sourceState);
  154. const latestFormNextActionSourceState = normalizeBusinessProofNextActionSourceState(latestFormIndexSummary?.nextActions?.sourceState);
  155. const pipelineStaleSummary = buildPipelineStaleSummary(businessProofNextActionSourceState, latestFormNextActionSourceState);
  156. const proofGapClosureCounts = normalizeProofGapClosureCounts(proofGapClosureSummary);
  157. return {
  158. generatedAt: new Date().toISOString(),
  159. root,
  160. outputsDir,
  161. complete: status.complete,
  162. statusCounts: status.counts,
  163. evidenceCounts: evidence.counts,
  164. evidenceByType: evidence.byType,
  165. readyCapabilities,
  166. readyNotProven,
  167. externalBlockers,
  168. pipelineStaleSummary,
  169. dataIntakePack: latestDataIntakePack ? {
  170. dir: latestDataIntakePack,
  171. historyTemplate: artifactRef(root, artifactPath(root, latestDataIntakePack, 'history-data-template.csv')),
  172. manualReviewTemplate: artifactRef(root, artifactPath(root, latestDataIntakePack, 'manual-review-template.csv')),
  173. readme: artifactRef(root, artifactPath(root, latestDataIntakePack, 'README.md'))
  174. } : null,
  175. localSeedToIntakeWorklist: latestLocalSeedToIntakeWorklist || latestFormIndexSummary?.localSeedToIntakeWorklist ? normalizeLocalSeedToIntakeWorklist({
  176. ...(latestFormIndexSummary?.localSeedToIntakeWorklist || {}),
  177. ...(localSeedToIntakeWorklistSummary || {}),
  178. markdown: latestFormIndexSummary?.localSeedToIntakeWorklist?.markdown || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'local-seed-to-intake-worklist.md')) : ''),
  179. csv: latestFormIndexSummary?.localSeedToIntakeWorklist?.csv || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'local-seed-to-intake-worklist.csv')) : ''),
  180. summary: latestFormIndexSummary?.localSeedToIntakeWorklist?.summary || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'local-seed-to-intake-worklist-summary.json')) : ''),
  181. historyDraft: latestFormIndexSummary?.localSeedToIntakeWorklist?.historyDraft || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'history-intake-draft.csv')) : ''),
  182. videoWorklist: latestFormIndexSummary?.localSeedToIntakeWorklist?.videoWorklist || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'video-resource-worklist.csv')) : ''),
  183. candidateWorklist: latestFormIndexSummary?.localSeedToIntakeWorklist?.candidateWorklist || (latestLocalSeedToIntakeWorklist ? artifactRef(root, artifactPath(root, latestLocalSeedToIntakeWorklist, 'candidate-seed-worklist.csv')) : '')
  184. }) : null,
  185. realGapMaterialAudit: latestRealGapMaterialAudit || latestFormIndexSummary?.realGapMaterialAudit ? normalizeRealGapMaterialAudit({
  186. ...(latestFormIndexSummary?.realGapMaterialAudit || {}),
  187. ...(realGapMaterialAuditSummary || {}),
  188. markdown: latestFormIndexSummary?.realGapMaterialAudit?.markdown || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit.md')) : ''),
  189. csv: latestFormIndexSummary?.realGapMaterialAudit?.csv || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit.csv')) : ''),
  190. summary: latestFormIndexSummary?.realGapMaterialAudit?.summary || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit-summary.json')) : '')
  191. }) : null,
  192. proofActionOrder: proofActionOrderPath ? {
  193. doc: rel(root, proofActionOrderPath),
  194. purpose: '先按 15 步补齐真实历史 Brief、真实视频资源、商务复核和客户效果证明,再启动长跑或宣称效果。'
  195. } : null,
  196. supportDocs,
  197. businessProofProgress: latestBusinessProofProgress ? {
  198. dir: latestBusinessProofProgress,
  199. summary: artifactRef(root, artifactPath(root, latestBusinessProofProgress, 'business-proof-progress-summary.json')),
  200. report: artifactRef(root, artifactPath(root, latestBusinessProofProgress, 'business-proof-progress-report.md')),
  201. complete: Boolean(businessProofProgressSummary?.complete),
  202. counts: businessProofProgressSummary?.counts || {},
  203. operatorPackOwnerArtifactCount: Number(businessProofProgressSummary?.operatorPackOwnerArtifactCount || (Array.isArray(businessProofProgressSummary?.operatorPackOwnerArtifacts) ? businessProofProgressSummary.operatorPackOwnerArtifacts.length : 0)),
  204. operatorPackOwnerArtifacts: normalizeProofGapOperatorOwnerArtifacts(businessProofProgressSummary?.operatorPackOwnerArtifacts || []),
  205. proofGapOperatorPackOwnerArtifactCount: Number(businessProofProgressSummary?.observed?.proofGapOperatorPackOwnerArtifactCount || 0),
  206. businessExecutionOperatorPackOwnerArtifactCount: Number(businessProofProgressSummary?.observed?.businessExecutionOperatorPackOwnerArtifactCount || 0),
  207. businessExecutionOperatorPackOwnerArtifactRowCount: Number(businessProofProgressSummary?.observed?.businessExecutionOperatorPackOwnerArtifactRowCount || 0)
  208. } : null,
  209. businessProofNextActions: latestBusinessProofNextActions ? {
  210. dir: latestBusinessProofNextActions,
  211. summary: artifactRef(root, artifactPath(root, latestBusinessProofNextActions, 'business-proof-next-actions-summary.json')),
  212. report: artifactRef(root, artifactPath(root, latestBusinessProofNextActions, 'business-proof-next-actions-report.md')),
  213. csv: artifactRef(root, artifactPath(root, latestBusinessProofNextActions, 'business-proof-next-actions.csv')),
  214. complete: Boolean(businessProofNextActionsSummary?.complete),
  215. actionCount: Number(businessProofNextActionsSummary?.actionCount ?? (Array.isArray(businessProofNextActionsSummary?.actions) ? businessProofNextActionsSummary.actions.length : 0)),
  216. ownerGroupCount: Number(businessProofNextActionsSummary?.ownerGroupCount ?? (Array.isArray(businessProofNextActionsSummary?.ownerGroups) ? businessProofNextActionsSummary.ownerGroups.length : 0)),
  217. ownerArtifactCount: Number(businessProofNextActionsSummary?.ownerArtifactCount ?? (Array.isArray(businessProofNextActionsSummary?.ownerArtifacts) ? businessProofNextActionsSummary.ownerArtifacts.length : 0)),
  218. sourceState: businessProofNextActionSourceState,
  219. topActions: normalizeBusinessProofNextTopActions(
  220. Array.isArray(businessProofNextActionsSummary?.topActions) && businessProofNextActionsSummary.topActions.length
  221. ? businessProofNextActionsSummary.topActions
  222. : businessProofNextActionsSummary?.actions
  223. ),
  224. ownerGroups: businessProofNextActionsSummary ? businessProofNextActionsSummary.ownerGroups || [] : [],
  225. ownerArtifacts: normalizeOwnerArtifacts(root, latestBusinessProofNextActions, businessProofNextActionsSummary)
  226. } : null,
  227. businessExecutionIndex: latestBusinessExecutionIndex ? {
  228. dir: latestBusinessExecutionIndex,
  229. summary: artifactRef(root, artifactPath(root, latestBusinessExecutionIndex, 'business-execution-index-summary.json')),
  230. report: artifactRef(root, artifactPath(root, latestBusinessExecutionIndex, 'business-execution-index.md')),
  231. csv: artifactRef(root, artifactPath(root, latestBusinessExecutionIndex, 'business-execution-index.csv')),
  232. passed: Boolean(businessExecutionIndexSummary?.passed),
  233. rowCount: Number(businessExecutionIndexSummary?.rowCount || 0),
  234. repairRowCount: Number(businessExecutionIndexSummary?.repairRowCount || 0),
  235. operatorPackOwnerArtifactRowCount: Number(businessExecutionIndexSummary?.operatorPackOwnerArtifactRowCount || 0),
  236. operatorPackOwnerArtifactCount: Number(businessExecutionIndexSummary?.operatorPackOwnerArtifactCount || (Array.isArray(businessExecutionIndexSummary?.operatorPackOwnerArtifacts) ? businessExecutionIndexSummary.operatorPackOwnerArtifacts.length : 0)),
  237. operatorPackOwnerArtifacts: normalizeProofGapOperatorOwnerArtifacts(businessExecutionIndexSummary?.operatorPackOwnerArtifacts || []),
  238. proofOpenCount: Number(businessExecutionIndexSummary?.proofOpenCount || 0),
  239. directCustomerProof: Boolean(businessExecutionIndexSummary?.directCustomerProof),
  240. longRunReadiness: normalizeBusinessExecutionLongRunReadiness(businessExecutionIndexSummary, longRunReadinessSummary),
  241. optimizationCompletion: normalizeBusinessExecutionOptimizationCompletion(businessExecutionIndexSummary, optimizationCompletionSummary)
  242. } : null,
  243. proofGapClosure: latestProofGapClosure ? {
  244. dir: latestProofGapClosure,
  245. summary: artifactRef(root, artifactPath(root, latestProofGapClosure, 'proof-gap-closure-summary.json')),
  246. report: artifactRef(root, artifactPath(root, latestProofGapClosure, 'proof-gap-closure-report.md')),
  247. complete: Boolean(proofGapClosureSummary?.complete),
  248. openCount: Number(proofGapClosureSummary?.openCount || 0),
  249. closedCount: Number(proofGapClosureSummary?.closedCount || 0),
  250. businessOpenCount: proofGapClosureCounts.businessOpenCount,
  251. businessClosedCount: proofGapClosureCounts.businessClosedCount,
  252. businessGapCount: proofGapClosureCounts.businessGapCount,
  253. totalRows: proofGapClosureCounts.totalRows,
  254. precheckRowCount: proofGapClosureCounts.precheckRowCount,
  255. precheckOpenCount: proofGapClosureCounts.precheckOpenCount,
  256. precheckClosedCount: proofGapClosureCounts.precheckClosedCount,
  257. countSemantics: proofGapClosureCounts.countSemantics,
  258. intakeReady: Boolean(proofGapClosureSummary?.intakeReady),
  259. rows: normalizeProofGapClosureRows(proofGapClosureSummary)
  260. } : null,
  261. feedbackLoopClosure: latestFeedbackLoopClosure ? {
  262. dir: latestFeedbackLoopClosure,
  263. summary: artifactRef(root, artifactPath(root, latestFeedbackLoopClosure, 'feedback-loop-closure-summary.json')),
  264. report: artifactRef(root, artifactPath(root, latestFeedbackLoopClosure, 'feedback-loop-closure-report.md')),
  265. csv: artifactRef(root, artifactPath(root, latestFeedbackLoopClosure, 'feedback-loop-closure.csv')),
  266. passed: Boolean(feedbackLoopClosureSummary?.passed),
  267. complete: Boolean(feedbackLoopClosureSummary?.complete),
  268. directCustomerProof: Boolean(feedbackLoopClosureSummary?.directCustomerProof),
  269. proofLevel: feedbackLoopClosureSummary?.proofLevel || '',
  270. negativeFeedbackCount: Number(feedbackLoopClosureSummary?.negativeFeedbackCount || 0),
  271. blockedCreatorCount: Number(feedbackLoopClosureSummary?.blockedCreatorCount || 0),
  272. leakedCount: Number(feedbackLoopClosureSummary?.leakedCount || 0),
  273. teamRuleSuggestionCount: Number(feedbackLoopClosureSummary?.teamRuleSuggestionCount || 0),
  274. missingInputs: feedbackLoopClosureSummary?.missingInputs || []
  275. } : null,
  276. homepageEvidenceReadiness: latestHomepageEvidenceReadiness ? {
  277. dir: latestHomepageEvidenceReadiness,
  278. summary: artifactRef(root, artifactPath(root, latestHomepageEvidenceReadiness, 'homepage-evidence-readiness-summary.json')),
  279. report: artifactRef(root, artifactPath(root, latestHomepageEvidenceReadiness, 'homepage-evidence-readiness-report.md')),
  280. csv: artifactRef(root, artifactPath(root, latestHomepageEvidenceReadiness, 'homepage-evidence-repair-actions.csv')),
  281. ready: Boolean(homepageEvidenceReadinessSummary?.ready),
  282. complete: Boolean(homepageEvidenceReadinessSummary?.complete),
  283. directCustomerProof: Boolean(homepageEvidenceReadinessSummary?.directCustomerProof),
  284. proofLevel: homepageEvidenceReadinessSummary?.proofLevel || '',
  285. failureCount: Number(homepageEvidenceReadinessSummary?.failureCount || 0),
  286. providerEvidenceCreators: Number(homepageEvidenceReadinessSummary?.counts?.providerEvidenceCreators || 0),
  287. creatorsWithPosts: Number(homepageEvidenceReadinessSummary?.counts?.creatorsWithPosts || 0),
  288. creatorsWithEnoughRecentPosts: Number(homepageEvidenceReadinessSummary?.counts?.creatorsWithEnoughRecentPosts || 0),
  289. repairActionCount: Array.isArray(homepageEvidenceReadinessSummary?.repairActions) ? homepageEvidenceReadinessSummary.repairActions.length : 0
  290. } : null,
  291. proofGapSoftwareForm: latestProofGapSoftwareForm ? {
  292. dir: latestProofGapSoftwareForm,
  293. summary: artifactRef(root, artifactPath(root, latestProofGapSoftwareForm, 'tihao-proof-gap-software-form-summary.json')),
  294. markdown: artifactRef(root, artifactPath(root, latestProofGapSoftwareForm, 'tihao-proof-gap-software-form.md')),
  295. csv: artifactRef(root, artifactPath(root, latestProofGapSoftwareForm, 'tihao-proof-gap-software-form.csv')),
  296. passed: Boolean(proofGapSoftwareFormSummary?.passed),
  297. rowCount: Number(proofGapSoftwareFormSummary?.rowCount || 0),
  298. duplicateIdCount: Number(proofGapSoftwareFormSummary?.duplicateIdCount || 0),
  299. headerMatches: Boolean(proofGapSoftwareFormSummary?.headerMatches),
  300. rowWidthOk: Boolean(proofGapSoftwareFormSummary?.rowWidthOk)
  301. } : null,
  302. intakeFieldChecklist: latestIntakeFieldChecklist ? {
  303. dir: latestIntakeFieldChecklist,
  304. summary: artifactRef(root, artifactPath(root, latestIntakeFieldChecklist, 'intake-field-checklist-summary.json')),
  305. report: artifactRef(root, artifactPath(root, latestIntakeFieldChecklist, 'intake-field-checklist.md')),
  306. csv: artifactRef(root, artifactPath(root, latestIntakeFieldChecklist, 'intake-field-checklist.csv')),
  307. passed: Boolean(intakeFieldChecklistSummary?.passed),
  308. itemCount: Number(intakeFieldChecklistSummary?.itemCount || 0),
  309. missingRequiredCount: Number(intakeFieldChecklistSummary?.missingRequiredCount || 0),
  310. placeholderCount: Number(intakeFieldChecklistSummary?.placeholderCount || 0),
  311. ownerGroups: normalizeIntakeOwnerGroups(root, intakeFieldChecklistSummary?.ownerGroups || []),
  312. recheckCommands: normalizeRecheckCommands(intakeFieldChecklistSummary?.recheckCommands || []),
  313. directCustomerProof: Boolean(intakeFieldChecklistSummary?.directCustomerProof)
  314. } : null,
  315. latestFormIndex: latestFormIndex ? {
  316. dir: latestFormIndex,
  317. summary: artifactRef(root, artifactPath(root, latestFormIndex, 'latest-form-index-summary.json')),
  318. report: artifactRef(root, artifactPath(root, latestFormIndex, 'latest-form-index.md')),
  319. passed: Boolean(latestFormIndexSummary?.passed),
  320. primaryFormCsv: latestFormIndexSummary?.primaryForm?.csv || '',
  321. clientListCsv: latestFormIndexSummary?.clientList?.csv || '',
  322. complete: Boolean(latestFormIndexSummary?.nextActions?.complete),
  323. actionCount: Number(latestFormIndexSummary?.nextActions?.actionCount || 0),
  324. proofOpenCount: Number(latestFormIndexSummary?.nextActions?.proofOpenCount || 0),
  325. ownerGroupCount: Number(latestFormIndexSummary?.nextActions?.ownerGroupCount || (Array.isArray(latestFormIndexSummary?.nextActions?.ownerGroups) ? latestFormIndexSummary.nextActions.ownerGroups.length : 0)),
  326. ownerArtifactCount: Number(latestFormIndexSummary?.nextActions?.ownerArtifactCount || (Array.isArray(latestFormIndexSummary?.nextActions?.ownerArtifacts) ? latestFormIndexSummary.nextActions.ownerArtifacts.length : 0)),
  327. sourceState: latestFormNextActionSourceState,
  328. topActions: normalizeBusinessProofNextTopActions(latestFormIndexSummary?.nextActions?.topActions),
  329. intakeFieldChecklist: buildLatestFormIndexChecklistSummary({
  330. root,
  331. latestFormIndexSummary,
  332. latestIntakeFieldChecklist,
  333. intakeFieldChecklistSummary
  334. }),
  335. manualReviewLabelGuide: normalizeManualReviewLabelGuide(latestFormIndexSummary?.manualReviewLabelGuide),
  336. businessFillGuide: normalizeBusinessFillGuide(latestFormIndexSummary?.businessFillGuide),
  337. planExecutionStatus: normalizePlanExecutionStatus(latestFormIndexSummary?.planExecutionStatus || planExecutionStatusSummary),
  338. proofGapRequest: normalizeProofGapRequest(latestFormIndexSummary?.proofGapRequest || (latestProofGapRequest ? {
  339. ...(proofGapRequestSummary || {}),
  340. markdown: artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-report.md')),
  341. csv: artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-table.csv')),
  342. summary: artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-summary.json'))
  343. } : proofGapRequestSummary)),
  344. proofGapOperatorPack: normalizeProofGapOperatorPack(latestFormIndexSummary?.proofGapOperatorPack || proofGapOperatorPackSummary),
  345. realProofIntakeBundle: normalizeRealProofIntakeBundle(latestFormIndexSummary?.realProofIntakeBundle || realProofIntakeBundleSummary),
  346. realProofClosureWorkOrder: normalizeRealProofClosureWorkOrder(latestFormIndexSummary?.realProofClosureWorkOrder || realProofClosureWorkOrderSummary),
  347. localSeedToIntakeWorklist: normalizeLocalSeedToIntakeWorklist(latestFormIndexSummary?.localSeedToIntakeWorklist || localSeedToIntakeWorklistSummary),
  348. realGapMaterialAudit: normalizeRealGapMaterialAudit(latestFormIndexSummary?.realGapMaterialAudit || realGapMaterialAuditSummary),
  349. longRunReadiness: normalizeLongRunReadiness(latestFormIndexSummary?.longRunReadiness || (latestLongRunReadiness ? {
  350. ...(longRunReadinessSummary || {}),
  351. report: artifactRef(root, artifactPath(root, latestLongRunReadiness, 'long-run-readiness-report.md')),
  352. summary: artifactRef(root, artifactPath(root, latestLongRunReadiness, 'long-run-readiness-summary.json'))
  353. } : longRunReadinessSummary)),
  354. optimizationCompletion: normalizeOptimizationCompletion(latestFormIndexSummary?.optimizationCompletion || (latestOptimizationCompletion ? {
  355. ...(optimizationCompletionSummary || {}),
  356. report: artifactRef(root, artifactPath(root, latestOptimizationCompletion, 'optimization-completion-report.md')),
  357. summary: artifactRef(root, artifactPath(root, latestOptimizationCompletion, 'optimization-completion-summary.json'))
  358. } : optimizationCompletionSummary)),
  359. repairActions: latestRepairActions
  360. } : null,
  361. planExecutionStatus: latestPlanExecutionStatus || latestFormIndexSummary?.planExecutionStatus ? normalizePlanExecutionStatus({
  362. ...(latestFormIndexSummary?.planExecutionStatus || {}),
  363. ...(planExecutionStatusSummary || {}),
  364. markdown: latestFormIndexSummary?.planExecutionStatus?.markdown || (latestPlanExecutionStatus ? artifactRef(root, artifactPath(root, latestPlanExecutionStatus, 'plan-execution-status.md')) : ''),
  365. csv: latestFormIndexSummary?.planExecutionStatus?.csv || (latestPlanExecutionStatus ? artifactRef(root, artifactPath(root, latestPlanExecutionStatus, 'plan-execution-status.csv')) : ''),
  366. summary: latestFormIndexSummary?.planExecutionStatus?.summary || (latestPlanExecutionStatus ? artifactRef(root, artifactPath(root, latestPlanExecutionStatus, 'plan-execution-status-summary.json')) : '')
  367. }) : null,
  368. proofGapRequest: latestProofGapRequest || latestFormIndexSummary?.proofGapRequest ? normalizeProofGapRequest({
  369. ...(latestFormIndexSummary?.proofGapRequest || {}),
  370. ...(proofGapRequestSummary || {}),
  371. markdown: latestFormIndexSummary?.proofGapRequest?.markdown || (latestProofGapRequest ? artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-report.md')) : ''),
  372. csv: latestFormIndexSummary?.proofGapRequest?.csv || (latestProofGapRequest ? artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-table.csv')) : ''),
  373. summary: latestFormIndexSummary?.proofGapRequest?.summary || (latestProofGapRequest ? artifactRef(root, artifactPath(root, latestProofGapRequest, 'proof-gap-request-summary.json')) : '')
  374. }) : null,
  375. proofGapOperatorPack: latestProofGapOperatorPack || latestFormIndexSummary?.proofGapOperatorPack ? normalizeProofGapOperatorPack({
  376. ...(latestFormIndexSummary?.proofGapOperatorPack || {}),
  377. ...(proofGapOperatorPackSummary || {}),
  378. ownerArtifacts: latestFormIndexSummary?.proofGapOperatorPack?.ownerArtifacts || proofGapOperatorPackSummary?.ownerArtifacts || [],
  379. ownerArtifactCount: latestFormIndexSummary?.proofGapOperatorPack?.ownerArtifactCount || proofGapOperatorPackSummary?.ownerArtifactCount || 0,
  380. markdown: latestFormIndexSummary?.proofGapOperatorPack?.markdown || (latestProofGapOperatorPack ? artifactRef(root, artifactPath(root, latestProofGapOperatorPack, 'proof-gap-operator-pack.md')) : ''),
  381. csv: latestFormIndexSummary?.proofGapOperatorPack?.csv || (latestProofGapOperatorPack ? artifactRef(root, artifactPath(root, latestProofGapOperatorPack, 'proof-gap-operator-pack.csv')) : ''),
  382. summary: latestFormIndexSummary?.proofGapOperatorPack?.summary || (latestProofGapOperatorPack ? artifactRef(root, artifactPath(root, latestProofGapOperatorPack, 'proof-gap-operator-pack-summary.json')) : '')
  383. }) : null,
  384. realProofIntakeBundle: latestRealProofIntakeBundle || latestFormIndexSummary?.realProofIntakeBundle ? normalizeRealProofIntakeBundle({
  385. ...(latestFormIndexSummary?.realProofIntakeBundle || {}),
  386. ...(realProofIntakeBundleSummary || {}),
  387. markdown: latestFormIndexSummary?.realProofIntakeBundle?.markdown || (latestRealProofIntakeBundle ? artifactRef(root, artifactPath(root, latestRealProofIntakeBundle, 'real-proof-intake-bundle.md')) : ''),
  388. csv: latestFormIndexSummary?.realProofIntakeBundle?.csv || (latestRealProofIntakeBundle ? artifactRef(root, artifactPath(root, latestRealProofIntakeBundle, 'real-proof-intake-bundle.csv')) : ''),
  389. summary: latestFormIndexSummary?.realProofIntakeBundle?.summary || (latestRealProofIntakeBundle ? artifactRef(root, artifactPath(root, latestRealProofIntakeBundle, 'real-proof-intake-bundle-summary.json')) : '')
  390. }) : null,
  391. realProofClosureWorkOrder: latestRealProofClosureWorkOrder || latestFormIndexSummary?.realProofClosureWorkOrder ? normalizeRealProofClosureWorkOrder({
  392. ...(latestFormIndexSummary?.realProofClosureWorkOrder || {}),
  393. ...(realProofClosureWorkOrderSummary || {}),
  394. markdown: latestFormIndexSummary?.realProofClosureWorkOrder?.markdown || (latestRealProofClosureWorkOrder ? artifactRef(root, artifactPath(root, latestRealProofClosureWorkOrder, 'real-proof-closure-work-order.md')) : ''),
  395. csv: latestFormIndexSummary?.realProofClosureWorkOrder?.csv || (latestRealProofClosureWorkOrder ? artifactRef(root, artifactPath(root, latestRealProofClosureWorkOrder, 'real-proof-closure-work-order.csv')) : ''),
  396. summary: latestFormIndexSummary?.realProofClosureWorkOrder?.summary || (latestRealProofClosureWorkOrder ? artifactRef(root, artifactPath(root, latestRealProofClosureWorkOrder, 'real-proof-closure-work-order-summary.json')) : '')
  397. }) : null,
  398. realGapMaterialAudit: latestRealGapMaterialAudit || latestFormIndexSummary?.realGapMaterialAudit ? normalizeRealGapMaterialAudit({
  399. ...(latestFormIndexSummary?.realGapMaterialAudit || {}),
  400. ...(realGapMaterialAuditSummary || {}),
  401. markdown: latestFormIndexSummary?.realGapMaterialAudit?.markdown || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit.md')) : ''),
  402. csv: latestFormIndexSummary?.realGapMaterialAudit?.csv || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit.csv')) : ''),
  403. summary: latestFormIndexSummary?.realGapMaterialAudit?.summary || (latestRealGapMaterialAudit ? artifactRef(root, artifactPath(root, latestRealGapMaterialAudit, 'real-gap-material-audit-summary.json')) : '')
  404. }) : null,
  405. optimizationCompletion: latestOptimizationCompletion || latestFormIndexSummary?.optimizationCompletion ? normalizeOptimizationCompletion({
  406. ...(latestFormIndexSummary?.optimizationCompletion || {}),
  407. ...(optimizationCompletionSummary || {}),
  408. report: latestFormIndexSummary?.optimizationCompletion?.markdown || latestFormIndexSummary?.optimizationCompletion?.report || (latestOptimizationCompletion ? artifactRef(root, artifactPath(root, latestOptimizationCompletion, 'optimization-completion-report.md')) : ''),
  409. summary: latestFormIndexSummary?.optimizationCompletion?.summary || (latestOptimizationCompletion ? artifactRef(root, artifactPath(root, latestOptimizationCompletion, 'optimization-completion-summary.json')) : '')
  410. }) : null,
  411. longRunReadiness: latestLongRunReadiness || latestFormIndexSummary?.longRunReadiness ? normalizeLongRunReadiness({
  412. ...(latestFormIndexSummary?.longRunReadiness || {}),
  413. ...(longRunReadinessSummary || {}),
  414. report: latestFormIndexSummary?.longRunReadiness?.report || (latestLongRunReadiness ? artifactRef(root, artifactPath(root, latestLongRunReadiness, 'long-run-readiness-report.md')) : ''),
  415. summary: latestFormIndexSummary?.longRunReadiness?.summary || (latestLongRunReadiness ? artifactRef(root, artifactPath(root, latestLongRunReadiness, 'long-run-readiness-summary.json')) : '')
  416. }) : null,
  417. optimizationPipeline: latestOptimizationPipeline ? {
  418. dir: latestOptimizationPipeline,
  419. summary: artifactRef(root, artifactPath(root, latestOptimizationPipeline, 'optimization-pipeline-summary.json')),
  420. report: artifactRef(root, artifactPath(root, latestOptimizationPipeline, 'optimization-pipeline-report.md')),
  421. counts: optimizationPipelineSummary?.counts || {},
  422. readyForClaim: Boolean(optimizationPipelineSummary?.readyForClaim),
  423. nextActions: normalizePipelineNextActions(optimizationPipelineSummary)
  424. } : null,
  425. latestEvidenceIndex,
  426. nextCommands: buildNextCommands(),
  427. guardrails: [
  428. '不能把 sample、smoke、dry-run 或 provider fallback 当成客户效果证明。',
  429. '没有真实视频 URL、封面、字幕、ASR、帧图或正文时,不能声明视频分析完成。',
  430. '没有客户选择、历史人工补号量基线和本轮人工补号量时,不能声明客户效果完成。',
  431. '任何报告、日志、模板和输出都不得写入 sessionToken、鉴权头、模型 token 或 npm token。'
  432. ]
  433. };
  434. }
  435. function publicStatusItem(item) {
  436. return {
  437. id: item.id,
  438. requirement: item.requirement,
  439. evidence: item.evidence,
  440. next: item.next
  441. };
  442. }
  443. function buildNextCommands() {
  444. const commands = [
  445. {
  446. stage: '生成真实数据收集包',
  447. command: 'npm run data:intake-template -- --output outputs\\data-intake-pack-latest',
  448. when: '商务还没有统一模板时先运行。'
  449. },
  450. {
  451. stage: '真实材料就绪预审',
  452. command: 'npm run intake:readiness -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-readiness-latest',
  453. when: '商务/投放填完历史数据、人工复核和视频资源模板后先运行;它会拦截示例数据、半成品和缺客户选择的表。'
  454. },
  455. {
  456. stage: '长跑前置门禁',
  457. 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',
  458. when: '启动 full-matrix/live 长跑或客户效果审计前运行;ready=false 时只能继续补真实材料,不能宣称长期目标完成。'
  459. },
  460. {
  461. stage: '收集包一键审计 Pipeline',
  462. 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',
  463. when: '商务/投放已经按统一收集包填完真实历史数据、人工复核和视频资源后运行;它会先跑 intake readiness,再串起 history/video/review/customer-effect/evidence。'
  464. },
  465. {
  466. stage: '历史数据导入',
  467. command: 'npm run history:from-csv -- --input <历史数据CSV> --output <history-dataset目录>',
  468. when: '商务填完历史 Brief、人工名单、客户选择和历史人工补号量基线后运行。'
  469. },
  470. {
  471. stage: '历史数据审计',
  472. command: 'npm run history:audit -- --input <history-dataset目录> --output <历史审计输出目录> --strict',
  473. when: '确认历史数据能否支撑长跑和客户效果证明。'
  474. },
  475. {
  476. stage: '商务复核指标',
  477. command: 'npm run review:metrics -- --input <已标注CSV> --output <复核指标输出目录> --strict',
  478. when: '本轮 AI 候选已经被商务标注后运行。'
  479. },
  480. {
  481. stage: '主页近期内容证据就绪审计',
  482. command: 'npm run homepage:readiness -- --input <tihao-sourcing-result.json 或 homepage evidence JSON> --output outputs\\homepage-evidence-readiness-latest',
  483. when: '真实主页近期内容 provider 或本地证据补齐后运行;检查最近 10/20 篇内容、封面、标题、互动、发布时间和风险信号是否足以支撑强推荐复核。'
  484. },
  485. {
  486. stage: '本地种子转 intake 补表 worklist',
  487. command: 'npm run local-seed-to-intake:worklist -- --outputs outputs --output outputs\\local-seed-to-intake-worklist-latest',
  488. when: '需要把本地 DHA Brief、PoC 候选和参考账号解析转成可转写的 history/video/manual-review 草稿时运行;它不写正式模板,也不证明客户效果。'
  489. },
  490. {
  491. stage: '真实证明闭环工单',
  492. command: 'npm run real-proof:work-order -- --outputs outputs --output outputs\\real-proof-closure-work-order-latest --strict',
  493. when: '需要把 intake/video/proof-gap/completion 的未关闭项拆到负责人、字段、行范围、种子线索和复验命令时运行;工单不写正式模板,也不证明客户效果。'
  494. },
  495. {
  496. stage: '视频资源收集包',
  497. command: 'npm run video:intake-template -- --output outputs\\video-intake-pack-latest',
  498. when: '需要真实测试参考视频/候选视频风格分析前运行,交给商务补视频 URL、封面、ASR、帧图或正文证据。'
  499. },
  500. {
  501. stage: '视频资源就绪审计',
  502. command: 'npm run video:resource-readiness -- --input <video-resource-template.csv> --output <视频资源审计输出目录> --strict',
  503. when: '商务补齐视频资源表后运行;通过只代表 A/B 前置资源齐备,不代表视频分析已提升命中率。'
  504. },
  505. {
  506. stage: '视频 A/B 验收',
  507. command: 'npm run acceptance:video-ab',
  508. when: '真实 sessionToken、company、VOC social provider 和视频分析 provider 都配置好后运行。'
  509. },
  510. {
  511. stage: '客户效果审计',
  512. command: 'npm run customer-effect:audit -- --review-csv <已标注CSV> --history-audit <historical-dataset-audit.json> --current-manual-supplement-count <本轮人工补号量> --output <客户效果输出目录> --strict',
  513. when: '客户选择和人工补号量都到位后运行。'
  514. },
  515. {
  516. stage: '真实证明缺口请求包',
  517. command: 'npm run proof-gap:request -- --output outputs\\proof-gap-request-latest',
  518. when: '每轮结束时生成,交给商务或下一轮 AI 对照补齐真实证明材料。'
  519. },
  520. {
  521. stage: '真实证明缺口关闭审计',
  522. command: 'npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest',
  523. when: '真实历史数据、视频资源、视频 A/B、live 长跑或客户效果产物更新后运行;只有 openCount=0 才能进入长期完成判断。'
  524. },
  525. {
  526. stage: '优化完成度审计',
  527. command: 'npm run optimization:completion -- --output outputs\\optimization-completion-latest',
  528. when: '每轮 proof-gap closure、客户效果、视频 A/B 或 live/provider 证据更新后运行;只有 readyForClaim=true 才能进入业务效果声明。'
  529. },
  530. {
  531. stage: '反馈二轮闭环审计',
  532. command: 'npm run feedback:closure -- --feedback <反馈CSV> --memory <记忆JSON> --result <第二轮tihao-sourcing-result.json> --output outputs\\feedback-loop-closure-latest',
  533. when: '商务/客户负反馈导入记忆并重跑第二轮后运行,检查拉黑、跑偏、调性不符账号是否仍泄漏进可发客户名单。'
  534. },
  535. {
  536. stage: '15 步补证进度表',
  537. command: 'npm run business-proof:progress -- --output outputs\\business-proof-progress-latest',
  538. when: '每轮结束时运行,用于同步 15 步补证当前卡点;它不替代客户效果证明。'
  539. },
  540. {
  541. stage: '下一步行动队列',
  542. command: 'npm run business-proof:next-actions -- --output outputs\\business-proof-next-actions-latest',
  543. when: '每轮结束时运行,把 blocked/fail/pending 状态转成可分派的负责人、命令和通过标准。'
  544. },
  545. {
  546. stage: '商务执行总表',
  547. command: 'npm run business-execution:index -- --output outputs\\business-execution-index-latest --strict',
  548. when: '每轮补证行动变化后运行,把负责人行动、修复清单、最新表单和验收边界汇总成商务可执行 Markdown/CSV。'
  549. },
  550. {
  551. stage: '最新提号补证表单',
  552. command: 'npm run proof-gap:software-form -- --output outputs\\tihao-proof-gap-software-form-latest --strict',
  553. when: '每轮补证任务变化后运行,导出软件端固定 17 列 CSV/Markdown 表单;它只安排补证任务,不证明客户效果。'
  554. },
  555. {
  556. stage: '最新提号表单索引',
  557. command: 'npm run latest-form:index -- --output outputs\\latest-form-index-latest --strict',
  558. when: '需要快速定位最新补证表单、软件端候选名单和负责人行动文件时运行;常规每轮沉淀优先使用 round:refresh。'
  559. },
  560. {
  561. stage: '真实验收材料收集总包',
  562. command: 'npm run proof-gap:intake-bundle -- --output outputs\\real-proof-intake-bundle-latest --strict',
  563. when: '需要给商务一个一站式入口时运行;它会集中真实材料模板、字段缺口、修复清单、证明缺口和复验命令。'
  564. },
  565. {
  566. stage: '每轮沉淀一键刷新',
  567. command: 'npm run round:refresh -- --output-root outputs --strict',
  568. when: '每轮优化结束后优先运行,按固定顺序刷新提号补证表单、proof-gap 请求/关闭审计、业务补证进度、下一步行动队列、状态、最新表单索引、交接摘要、完成度审计、证据台账和 round deposition。'
  569. },
  570. {
  571. stage: '证据台账',
  572. command: 'npm run evidence:index -- --output outputs\\evidence-index-latest',
  573. when: '需要单独刷新证明等级时运行;常规每轮沉淀优先使用 round:refresh。'
  574. },
  575. {
  576. stage: '交接摘要',
  577. command: 'npm run handoff:summary -- --output outputs\\optimization-handoff-latest',
  578. when: '需要单独刷新交接摘要时运行;常规每轮沉淀优先使用 round:refresh。'
  579. }
  580. ];
  581. commands.push({
  582. stage: '字段级补齐核对表',
  583. command: 'npm run intake:field-checklist -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-field-checklist-latest',
  584. when: '真实材料预审未通过时运行,把缺字段、占位内容和缺最小样本量拆成商务可逐格补齐的 CSV/Markdown。'
  585. });
  586. return commands;
  587. }
  588. function renderReport(summary) {
  589. const lines = [
  590. '# 提号优化交接摘要',
  591. '',
  592. `- 生成时间:${summary.generatedAt}`,
  593. `- 是否完成长期目标:${summary.complete ? '是' : '否'}`,
  594. '',
  595. '## 当前状态',
  596. '',
  597. `- 已通过能力:${summary.statusCounts.passed || 0}`,
  598. `- 已具备入口但未真实证明:${summary.statusCounts.ready_not_proven || 0}`,
  599. `- 受真实外部数据阻塞:${summary.statusCounts.blocked_by_external_data || 0}`,
  600. '',
  601. '## 真实证明缺口关闭状态',
  602. '',
  603. summary.proofGapClosure
  604. ? renderProofGapClosure(summary.proofGapClosure)
  605. : '- 尚未找到 proof-gap closure 摘要,请运行 `npm run proof-gap:closure -- --output outputs\\proof-gap-closure-latest`。',
  606. '',
  607. '## 反馈二轮闭环审计',
  608. '',
  609. summary.feedbackLoopClosure
  610. ? renderFeedbackLoopClosure(summary.feedbackLoopClosure)
  611. : '- 尚未找到反馈二轮闭环审计,请运行 `npm run feedback:closure -- --output outputs\\feedback-loop-closure-latest`。当前不得声明反馈闭环客户效果证明,`directCustomerProof=false`。',
  612. '',
  613. '## 主页近期内容证据就绪审计',
  614. '',
  615. summary.homepageEvidenceReadiness
  616. ? renderHomepageEvidenceReadiness(summary.homepageEvidenceReadiness)
  617. : '- 尚未找到主页近期内容证据就绪审计,请运行 `npm run homepage:readiness -- --output outputs\\homepage-evidence-readiness-latest`。当前不能把 fallback 当作强推荐证据。',
  618. '',
  619. '## 长跑前置门禁',
  620. '',
  621. summary.longRunReadiness
  622. ? renderLongRunReadiness(summary.longRunReadiness)
  623. : '- 尚未找到长跑前置门禁,请运行 `npm run longrun:readiness -- --mode full-matrix --output outputs\\long-run-readiness-latest`。当前不得启动真实 live/full-matrix 长跑。',
  624. '',
  625. '## 优化完成度审计',
  626. '',
  627. summary.optimizationCompletion
  628. ? renderOptimizationCompletion(summary.optimizationCompletion)
  629. : '- 尚未找到优化完成度审计,请运行 `npm run optimization:completion -- --output outputs\\optimization-completion-latest`。当前不得宣称长期优化目标完成。',
  630. '',
  631. '## 证据台账',
  632. '',
  633. `- real_evidence:${summary.evidenceCounts.real_evidence || 0}`,
  634. `- smoke_or_local:${summary.evidenceCounts.smoke_or_local || 0}`,
  635. `- not_business_proof:${summary.evidenceCounts.not_business_proof || 0}`,
  636. '',
  637. '| 类型 | 数量 |',
  638. '| --- | --- |',
  639. ...Object.entries(summary.evidenceByType || {}).sort().map(([type, count]) => `| ${escapeCell(type)} | ${count} |`),
  640. '',
  641. '## 已可本地复用的能力',
  642. '',
  643. ...summary.readyCapabilities.map(item => `- ${item.id}:${item.requirement}`),
  644. '',
  645. '## 已准备好但不能宣称效果的项目',
  646. '',
  647. ...summary.readyNotProven.map(item => `- ${item.id}:${item.requirement}。下一步:${item.next}`),
  648. '',
  649. '## 仍需真实数据的阻塞项',
  650. '',
  651. ...summary.externalBlockers.map(item => `- ${item.id}:${item.requirement}。下一步:${item.next}`),
  652. '',
  653. '## 真实数据入口',
  654. '',
  655. summary.dataIntakePack
  656. ? `- 历史数据模板:${summary.dataIntakePack.historyTemplate}\n- 商务复核模板:${summary.dataIntakePack.manualReviewTemplate}\n- 填写说明:${summary.dataIntakePack.readme}`
  657. : '- 尚未找到数据收集包,请先运行 `npm run data:intake-template -- --output outputs\\data-intake-pack-latest`。',
  658. '',
  659. '## 本地种子转 intake 补表 worklist',
  660. '',
  661. summary.localSeedToIntakeWorklist
  662. ? renderLocalSeedToIntakeWorklist(summary.localSeedToIntakeWorklist)
  663. : '- 尚未找到本地种子转 intake 补表 worklist,请运行 `npm run local-seed-to-intake:worklist -- --outputs outputs --output outputs\\local-seed-to-intake-worklist-latest`。',
  664. '',
  665. '## 中文 SOP 入口',
  666. '',
  667. summary.supportDocs && summary.supportDocs.length
  668. ? renderSupportDocs(summary.supportDocs)
  669. : '- 尚未找到中文 SOP 执行文档,请优先补齐 `docs/tihao-experience-optimization-plan.zh-CN.md` 和 `docs/business-proof-action-order.zh-CN.md`。',
  670. '',
  671. '## 补证操作顺序',
  672. '',
  673. summary.proofActionOrder
  674. ? `- 文档:${summary.proofActionOrder.doc}\n- 用途:${summary.proofActionOrder.purpose}`
  675. : '- 尚未找到 `docs/business-proof-action-order.md`,请先补齐真实补证操作顺序。',
  676. '',
  677. '## 补证进度表',
  678. '',
  679. summary.businessProofProgress
  680. ? renderBusinessProofProgress(summary.businessProofProgress)
  681. : '- 尚未找到补证进度表,请运行 `npm run business-proof:progress -- --output outputs\\business-proof-progress-latest`。',
  682. '',
  683. '## 下一步行动队列',
  684. '',
  685. summary.businessProofNextActions
  686. ? renderBusinessProofNextActions(summary.businessProofNextActions)
  687. : '- 尚未找到下一步行动队列,请运行 `npm run business-proof:next-actions -- --output outputs\\business-proof-next-actions-latest`。',
  688. '',
  689. '## Pipeline stale 摘要',
  690. '',
  691. renderPipelineStaleSummary(summary.pipelineStaleSummary),
  692. '',
  693. '## 负责人行动文件',
  694. '',
  695. summary.businessProofNextActions && summary.businessProofNextActions.ownerArtifacts.length
  696. ? renderOwnerArtifacts(summary.businessProofNextActions.ownerArtifacts)
  697. : '- 尚未找到负责人分组文件,请运行 `npm run business-proof:next-actions -- --output outputs\\business-proof-next-actions-latest`。',
  698. '',
  699. '## 商务执行总表',
  700. '',
  701. summary.businessExecutionIndex
  702. ? renderBusinessExecutionIndex(summary.businessExecutionIndex)
  703. : '- 尚未找到商务执行总表,请运行 `npm run business-execution:index -- --output outputs\\business-execution-index-latest --strict`。',
  704. '',
  705. '## 最新提号补证表单',
  706. '',
  707. summary.proofGapSoftwareForm
  708. ? renderProofGapSoftwareForm(summary.proofGapSoftwareForm)
  709. : '- 尚未找到最新提号补证表单,请运行 `npm run proof-gap:software-form -- --output outputs\\tihao-proof-gap-software-form-latest --strict`。',
  710. '',
  711. '## 真实材料字段级补齐核对表',
  712. '',
  713. summary.intakeFieldChecklist
  714. ? renderIntakeFieldChecklist(summary.intakeFieldChecklist)
  715. : '- 尚未找到字段级补齐核对表,请运行 `npm run intake:field-checklist -- --data-pack outputs\\data-intake-pack-latest --video-pack outputs\\video-intake-pack-latest --output outputs\\intake-field-checklist-latest`。',
  716. '',
  717. '## 最新提号表单索引',
  718. '',
  719. summary.latestFormIndex
  720. ? renderLatestFormIndex(summary.latestFormIndex)
  721. : '- 尚未找到最新提号表单索引,请运行 `npm run latest-form:index -- --output outputs\\latest-form-index-latest --strict`。',
  722. '',
  723. '## 长期计划执行状态面板',
  724. '',
  725. summary.planExecutionStatus
  726. ? renderPlanExecutionStatus(summary.planExecutionStatus)
  727. : '- 尚未找到长期计划执行状态面板,请运行 `npm run plan:execution-status -- --output outputs\\plan-execution-status-latest --strict`。',
  728. '',
  729. '## 真实证明缺口请求包',
  730. '',
  731. summary.proofGapRequest
  732. ? renderProofGapRequest(summary.proofGapRequest)
  733. : '- 尚未找到真实证明缺口请求包,请运行 `npm run proof-gap:request -- --output outputs\\proof-gap-request-latest`。',
  734. '',
  735. '## 提号补证操作包',
  736. '',
  737. summary.proofGapOperatorPack
  738. ? renderProofGapOperatorPack(summary.proofGapOperatorPack)
  739. : '- 尚未找到提号补证操作包,请运行 `npm run proof-gap:operator-pack -- --output outputs\\proof-gap-operator-pack-latest --strict`。',
  740. '',
  741. '## 真实验收材料收集总包',
  742. '',
  743. summary.realProofIntakeBundle
  744. ? renderRealProofIntakeBundle(summary.realProofIntakeBundle)
  745. : '- 尚未找到真实验收材料收集总包,请运行 `npm run proof-gap:intake-bundle -- --output outputs\\real-proof-intake-bundle-latest --strict`。',
  746. '',
  747. '## 真实证明闭环工单',
  748. '',
  749. summary.realProofClosureWorkOrder
  750. ? renderRealProofClosureWorkOrder(summary.realProofClosureWorkOrder)
  751. : '- 尚未找到真实证明闭环工单,请运行 `npm run real-proof:work-order -- --outputs outputs --output outputs\\real-proof-closure-work-order-latest --strict`。该工单只指导补真实材料,不证明客户效果。',
  752. '',
  753. '## Pipeline 下一步动作',
  754. '',
  755. summary.optimizationPipeline
  756. ? renderPipelineNextActions(summary.optimizationPipeline)
  757. : '- 尚未找到 `outputs/optimization-pipeline-latest`,请运行收集包一键审计 Pipeline。',
  758. '',
  759. '## 下一轮命令',
  760. '',
  761. '| 阶段 | 命令 | 使用时机 |',
  762. '| --- | --- | --- |',
  763. ...summary.nextCommands.map(item => `| ${escapeCell(item.stage)} | \`${escapeCell(item.command)}\` | ${escapeCell(item.when)} |`),
  764. '',
  765. '## 边界',
  766. '',
  767. ...summary.guardrails.map(item => `- ${item}`)
  768. ];
  769. return lines.join('\n');
  770. }
  771. function latestEvidenceDir(evidence, type) {
  772. const item = (evidence.entries || []).find(entry => entry.type === type);
  773. return item ? item.dir : '';
  774. }
  775. function latestOutputDir(outputsDir, pattern) {
  776. if (!fs.existsSync(outputsDir)) return '';
  777. const dirs = fs.readdirSync(outputsDir, { withFileTypes: true })
  778. .filter(entry => entry.isDirectory() && pattern.test(entry.name))
  779. .map(entry => path.join(outputsDir, entry.name))
  780. .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
  781. return dirs[0] ? artifactRef(ROOT, dirs[0]) : '';
  782. }
  783. function firstExistingPath(root, relativePaths) {
  784. for (const relativePath of relativePaths) {
  785. const resolved = path.join(root, relativePath);
  786. if (fs.existsSync(resolved)) return resolved;
  787. }
  788. return '';
  789. }
  790. function renderSupportDocs(docs) {
  791. return [
  792. '| 文档 | 路径 | 用途 |',
  793. '| --- | --- | --- |',
  794. ...docs.map(item => tableRow([item.title, item.doc, item.purpose]))
  795. ].join('\n');
  796. }
  797. function readBusinessProofNextActionsSummary(root, dir) {
  798. const summaryPath = artifactPath(root, dir, 'business-proof-next-actions-summary.json');
  799. if (!fs.existsSync(summaryPath)) return null;
  800. try {
  801. return JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
  802. } catch {
  803. return null;
  804. }
  805. }
  806. function readOptimizationPipelineSummary(root, dir) {
  807. return readJsonSummary(root, dir, 'optimization-pipeline-summary.json');
  808. }
  809. function readJsonSummary(root, dir, name) {
  810. const summaryPath = artifactPath(root, dir, name);
  811. if (!fs.existsSync(summaryPath)) return null;
  812. try {
  813. return JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
  814. } catch {
  815. return null;
  816. }
  817. }
  818. function renderProofGapSoftwareForm(form) {
  819. return [
  820. `- CSV:${form.csv}`,
  821. `- Markdown:${form.markdown}`,
  822. `- Summary:${form.summary}`,
  823. `- passed:${form.passed ? 'true' : 'false'}`,
  824. `- rowCount:${form.rowCount}`,
  825. `- duplicateIdCount:${form.duplicateIdCount}`,
  826. `- headerMatches:${form.headerMatches ? 'true' : 'false'}`,
  827. `- rowWidthOk:${form.rowWidthOk ? 'true' : 'false'}`
  828. ].join('\n');
  829. }
  830. function renderIntakeFieldChecklist(checklist) {
  831. const lines = [
  832. `- 报告:${checklist.report}`,
  833. `- CSV:${checklist.csv}`,
  834. `- Summary:${checklist.summary}`,
  835. `- passed:${checklist.passed ? 'true' : 'false'}`,
  836. `- itemCount:${checklist.itemCount}`,
  837. `- missingRequiredCount:${checklist.missingRequiredCount}`,
  838. `- placeholderCount:${checklist.placeholderCount}`,
  839. `- directCustomerProof:${checklist.directCustomerProof ? 'true' : 'false'}`,
  840. '- 用途:给商务/投放逐字段补齐真实历史 Brief、人工复核、客户选择和视频资源;它不证明客户效果完成。'
  841. ];
  842. if (checklist.ownerGroups?.length) {
  843. lines.push('', '### 按负责人补齐顺序', '', renderIntakeOwnerGroups(checklist.ownerGroups));
  844. }
  845. if (checklist.recheckCommands?.length) {
  846. lines.push('', '### 补齐后复验命令', '', renderRecheckCommands(checklist.recheckCommands));
  847. }
  848. return lines.join('\n');
  849. }
  850. function renderLatestFormIndex(index) {
  851. const lines = [
  852. `- 报告:${index.report}`,
  853. `- Summary:${index.summary}`,
  854. `- passed:${index.passed ? 'true' : 'false'}`,
  855. `- 最新补证表单:${index.primaryFormCsv || '缺失'}`,
  856. `- 最新候选名单:${index.clientListCsv || '缺失'}`,
  857. `- complete:${index.complete ? 'true' : 'false'}`,
  858. `- actionCount:${index.actionCount}`,
  859. `- proofOpenCount:${index.proofOpenCount}`,
  860. `- ownerGroupCount:${index.ownerGroupCount || 0}`,
  861. `- ownerArtifactCount:${index.ownerArtifactCount || 0}`,
  862. `- intakeFailureCount:${index.sourceState?.intakeFailureCount || 0}`,
  863. `- videoFailureCount:${index.sourceState?.videoFailureCount || 0}`,
  864. `- pipelineReadyForClaim:${index.sourceState?.pipelineReadyForClaim ? 'true' : 'false'}`,
  865. '- 用途:给商务和下一轮 AI 快速定位当前该打开的表单;它不证明客户效果完成。'
  866. ];
  867. if (Array.isArray(index.topActions) && index.topActions.length) {
  868. lines.push(
  869. '',
  870. '### 下一步行动队列摘要',
  871. '',
  872. '| ID | 优先级 | 负责人 | 动作 | 状态 | 命令 | 通过标准 | 缺证明项 |',
  873. '| --- | ---: | --- | --- | --- | --- | --- | ---: |',
  874. ...index.topActions.map(item => tableRow([
  875. item.id || 'missing',
  876. item.priority || item.order || 0,
  877. item.owner || 'missing',
  878. item.title || 'missing',
  879. item.status || 'missing',
  880. item.command || 'missing',
  881. item.acceptance || 'missing',
  882. item.missingProofRequirementCount || 0
  883. ]))
  884. );
  885. }
  886. if (index.intakeFieldChecklist) {
  887. lines.push(
  888. '',
  889. '### 字段补齐核对表索引',
  890. '',
  891. `- 报告:${index.intakeFieldChecklist.markdown || '缺失'}`,
  892. `- CSV:${index.intakeFieldChecklist.csv || '缺失'}`,
  893. `- ownerGroupCount:${Array.isArray(index.intakeFieldChecklist.ownerGroups) ? index.intakeFieldChecklist.ownerGroups.length : 0}`,
  894. `- recheckCommandCount:${Array.isArray(index.intakeFieldChecklist.recheckCommands) ? index.intakeFieldChecklist.recheckCommands.length : 0}`
  895. );
  896. }
  897. if (index.businessFillGuide?.rows?.length) {
  898. lines.push('', renderBusinessFillGuide(index.businessFillGuide));
  899. }
  900. if (index.manualReviewLabelGuide) {
  901. lines.push(
  902. '',
  903. '### 人工复核标签与归因指南',
  904. '',
  905. `- report: ${index.manualReviewLabelGuide.markdown || 'missing'}`,
  906. `- csv: ${index.manualReviewLabelGuide.csv || 'missing'}`,
  907. `- labelCount: ${index.manualReviewLabelGuide.labelCount || 0}`,
  908. `- attributionTypeCount: ${index.manualReviewLabelGuide.attributionTypeCount || 0}`,
  909. `- directCustomerProof: ${index.manualReviewLabelGuide.directCustomerProof ? 'true' : 'false'}`
  910. );
  911. }
  912. if (index.planExecutionStatus) {
  913. lines.push('', '### 长期计划执行状态面板', '', renderPlanExecutionStatus(index.planExecutionStatus));
  914. }
  915. if (index.proofGapRequest) {
  916. lines.push('', '### 真实证明缺口请求包', '', renderProofGapRequest(index.proofGapRequest));
  917. }
  918. if (index.proofGapOperatorPack) {
  919. lines.push('', '### 提号补证操作包', '', renderProofGapOperatorPack(index.proofGapOperatorPack));
  920. }
  921. if (index.realProofIntakeBundle) {
  922. lines.push('', '### 真实验收材料收集总包', '', renderRealProofIntakeBundle(index.realProofIntakeBundle));
  923. }
  924. if (index.realProofClosureWorkOrder) {
  925. lines.push('', '### 真实证明闭环工单', '', renderRealProofClosureWorkOrder(index.realProofClosureWorkOrder));
  926. }
  927. if (index.localSeedToIntakeWorklist) {
  928. lines.push('', '### 本地种子转 intake 补表 worklist', '', renderLocalSeedToIntakeWorklist(index.localSeedToIntakeWorklist));
  929. }
  930. if (index.realGapMaterialAudit) {
  931. lines.push('', '### three real gap material audit', '', renderRealGapMaterialAudit(index.realGapMaterialAudit));
  932. }
  933. if (index.longRunReadiness) {
  934. lines.push('', '### 长跑前置门禁', '', renderLongRunReadiness(index.longRunReadiness));
  935. }
  936. if (index.optimizationCompletion) {
  937. lines.push('', '### 优化完成度审计', '', renderOptimizationCompletion(index.optimizationCompletion));
  938. }
  939. const repairRows = renderLatestFormRepairActions(index.repairActions);
  940. if (repairRows) lines.push('', '### 修复清单入口', '', repairRows);
  941. return lines.join('\n');
  942. }
  943. function renderBusinessProofProgress(progress) {
  944. const counts = progress.counts || {};
  945. const lines = [
  946. `- 摘要:${progress.summary || 'missing'}`,
  947. `- 报告:${progress.report || 'missing'}`,
  948. `- complete: ${progress.complete ? 'true' : 'false'}`,
  949. `- pass: ${counts.pass || 0}`,
  950. `- pending: ${counts.pending || 0}`,
  951. `- fail: ${counts.fail || 0}`,
  952. `- blocked_by_external_data: ${counts.blocked_by_external_data || 0}`,
  953. `- operatorPackOwnerArtifactCount: ${progress.operatorPackOwnerArtifactCount || 0}`,
  954. `- businessExecutionOperatorPackOwnerArtifactRowCount: ${progress.businessExecutionOperatorPackOwnerArtifactRowCount || 0}`,
  955. '- 边界:该进度表只同步 15 步补证状态和负责人派工文件;complete=false 或 proof-gap 未关闭时,不得宣称客户效果、命中率提升或人工补号量下降。'
  956. ];
  957. if (Array.isArray(progress.operatorPackOwnerArtifacts) && progress.operatorPackOwnerArtifacts.length) {
  958. lines.push(
  959. '',
  960. '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
  961. '| --- | ---: | --- | --- | --- |',
  962. ...progress.operatorPackOwnerArtifacts.map(item => tableRow([
  963. item.owner,
  964. item.actionCount,
  965. item.markdown || 'missing',
  966. item.csv || 'missing',
  967. (item.gapIds || []).join(';') || 'missing'
  968. ]))
  969. );
  970. }
  971. return lines.join('\n');
  972. }
  973. function renderPlanExecutionStatus(status) {
  974. const lines = [
  975. `- report: ${status.markdown || 'missing'}`,
  976. `- csv: ${status.csv || 'missing'}`,
  977. `- summary: ${status.summary || 'missing'}`,
  978. `- passed: ${status.passed ? 'true' : 'false'}`,
  979. `- complete: ${status.complete ? 'true' : 'false'}`,
  980. `- rowCount: ${status.rowCount || 0}`,
  981. `- proofOpenCount: ${status.proofOpenCount || 0}`,
  982. `- operatorPackOwnerArtifactCount: ${status.operatorPackOwnerArtifactCount || 0}`,
  983. `- businessExecutionOperatorPackOwnerArtifactRowCount: ${status.businessExecutionOperatorPackOwnerArtifactRowCount || 0}`,
  984. `- directCustomerProof: ${status.directCustomerProof ? 'true' : 'false'}`,
  985. '- 边界:该面板只同步计划执行进度;proofOpenCount 不为 0 时,不得宣称客户效果、命中率提升或人工补号量下降。'
  986. ];
  987. if (Array.isArray(status.operatorPackOwnerArtifacts) && status.operatorPackOwnerArtifacts.length) {
  988. lines.push(
  989. '',
  990. '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
  991. '| --- | ---: | --- | --- | --- |',
  992. ...status.operatorPackOwnerArtifacts.map(item => tableRow([
  993. item.owner,
  994. item.actionCount,
  995. item.markdown || 'missing',
  996. item.csv || 'missing',
  997. (item.gapIds || []).join(';') || 'missing'
  998. ]))
  999. );
  1000. }
  1001. return lines.join('\n');
  1002. }
  1003. function renderProofGapRequest(request) {
  1004. return [
  1005. `- report: ${request.markdown || 'missing'}`,
  1006. `- csv: ${request.csv || 'missing'}`,
  1007. `- summary: ${request.summary || 'missing'}`,
  1008. `- complete: ${request.complete ? 'true' : 'false'}`,
  1009. `- unresolvedCount: ${request.unresolvedCount || 0}`,
  1010. `- rowCount: ${request.rowCount || 0}`,
  1011. `- proofGapClosureOpenCount: ${request.proofGapClosureOpenCount || 0}`,
  1012. `- directCustomerProof: ${request.directCustomerProof ? 'true' : 'false'}`,
  1013. '- 边界:该请求包只列出仍需补齐的真实证明材料;不能作为客户效果、命中率提升或人工补号量下降证明。'
  1014. ].join('\n');
  1015. }
  1016. function renderProofGapOperatorPack(pack) {
  1017. const lines = [
  1018. `- report: ${pack.markdown || 'missing'}`,
  1019. `- csv: ${pack.csv || 'missing'}`,
  1020. `- summary: ${pack.summary || 'missing'}`,
  1021. `- passed: ${pack.passed ? 'true' : 'false'}`,
  1022. `- complete: ${pack.complete ? 'true' : 'false'}`,
  1023. `- openCount: ${pack.openCount || 0}`,
  1024. `- rowCount: ${pack.rowCount || 0}`,
  1025. `- gapIds: ${(pack.gapIds || []).join(';') || 'missing'}`,
  1026. `- fillFileCount: ${pack.fillFileCount || 0}`,
  1027. `- recheckCommandCount: ${pack.recheckCommandCount || 0}`,
  1028. `- ownerGroupCount: ${pack.ownerGroupCount || 0}`,
  1029. `- ownerArtifactCount: ${pack.ownerArtifactCount || 0}`,
  1030. `- evidenceRowCount: ${pack.evidenceRowCount || 0}`,
  1031. `- nextActionCount: ${pack.nextActionCount || 0}`,
  1032. `- missingProofRequirementCount: ${pack.missingProofRequirementCount || 0}`,
  1033. `- directCustomerProof: ${pack.directCustomerProof ? 'true' : 'false'}`,
  1034. '- 边界:该操作包只用于补证派工;proofOpenCount 不为 0 时,不得宣称客户效果、命中率提升或人工补号量下降。'
  1035. ];
  1036. if (Array.isArray(pack.ownerArtifacts) && pack.ownerArtifacts.length) {
  1037. lines.push(
  1038. '',
  1039. '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
  1040. '| --- | ---: | --- | --- | --- |',
  1041. ...pack.ownerArtifacts.map(item => tableRow([
  1042. item.owner,
  1043. item.actionCount,
  1044. item.markdown || 'missing',
  1045. item.csv || 'missing',
  1046. (item.gapIds || []).join(';') || 'missing'
  1047. ]))
  1048. );
  1049. }
  1050. return lines.join('\n');
  1051. }
  1052. function renderRealProofIntakeBundle(bundle) {
  1053. const lines = [
  1054. `- report: ${bundle.markdown || 'missing'}`,
  1055. `- csv: ${bundle.csv || 'missing'}`,
  1056. `- summary: ${bundle.summary || 'missing'}`,
  1057. `- passed: ${bundle.passed ? 'true' : 'false'}`,
  1058. `- complete: ${bundle.complete ? 'true' : 'false'}`,
  1059. `- proofOpenCount: ${bundle.proofOpenCount || 0}`,
  1060. `- fillRowCount: ${bundle.fillRowCount || 0}`,
  1061. `- proofGapRowCount: ${bundle.proofGapRowCount || 0}`,
  1062. `- recheckCommandCount: ${bundle.recheckCommandCount || 0}`,
  1063. `- operatorPackEvidenceRowCount: ${bundle.operatorPackEvidenceRowCount || 0}`,
  1064. `- operatorPackNextActionCount: ${bundle.operatorPackNextActionCount || 0}`,
  1065. `- operatorPackMissingProofRequirementCount: ${bundle.operatorPackMissingProofRequirementCount || 0}`,
  1066. `- operatorPackOwnerArtifactCount: ${bundle.operatorPackOwnerArtifactCount || 0}`,
  1067. `- directCustomerProof: ${bundle.directCustomerProof ? 'true' : 'false'}`,
  1068. '- 边界:该总包只集中真实材料模板、字段缺口、修复清单和复验命令;proofOpenCount 不为 0 时,不得宣称客户效果。'
  1069. ];
  1070. if (Array.isArray(bundle.operatorPackOwnerArtifacts) && bundle.operatorPackOwnerArtifacts.length) {
  1071. lines.push(
  1072. '',
  1073. '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
  1074. '| --- | ---: | --- | --- | --- |',
  1075. ...bundle.operatorPackOwnerArtifacts.map(item => tableRow([
  1076. item.owner,
  1077. item.actionCount,
  1078. item.markdown || 'missing',
  1079. item.csv || 'missing',
  1080. (item.gapIds || []).join(';') || 'missing'
  1081. ]))
  1082. );
  1083. }
  1084. return lines.join('\n');
  1085. }
  1086. function renderRealProofClosureWorkOrder(workOrder) {
  1087. const lines = [
  1088. `- report: ${workOrder.markdown || 'missing'}`,
  1089. `- csv: ${workOrder.csv || 'missing'}`,
  1090. `- summary: ${workOrder.summary || 'missing'}`,
  1091. `- passed: ${workOrder.passed ? 'true' : 'false'}`,
  1092. `- complete: ${workOrder.complete ? 'true' : 'false'}`,
  1093. `- workOrderCount: ${workOrder.workOrderCount || 0}`,
  1094. `- ownerGroupCount: ${workOrder.ownerGroupCount || 0}`,
  1095. `- proofGapOpenCount: ${workOrder.proofGapOpenCount || 0}`,
  1096. `- intakeFailureCount: ${workOrder.intakeFailureCount || 0}`,
  1097. `- videoFailureCount: ${workOrder.videoFailureCount || 0}`,
  1098. `- realCandidateRows: ${workOrder.realCandidateRows || 0}`,
  1099. `- canCloseProofGap: ${workOrder.canCloseProofGap ? 'true' : 'false'}`,
  1100. `- directCustomerProof: ${workOrder.directCustomerProof ? 'true' : 'false'}`,
  1101. '- 边界:该工单只把缺口拆到目标模板、字段、行范围、种子线索和复验命令;不写入正式模板,不证明客户效果。'
  1102. ];
  1103. if (Array.isArray(workOrder.ownerArtifacts) && workOrder.ownerArtifacts.length) {
  1104. lines.push(
  1105. '',
  1106. '| 负责人 | 工单数 | Markdown | CSV | 缺口 | 目标文件 |',
  1107. '| --- | ---: | --- | --- | --- | --- |',
  1108. ...workOrder.ownerArtifacts.map(item => tableRow([
  1109. item.owner,
  1110. item.workOrderCount,
  1111. item.markdown || 'missing',
  1112. item.csv || 'missing',
  1113. (item.gapIds || []).join(';') || 'missing',
  1114. (item.targetFiles || []).join(';') || 'missing'
  1115. ]))
  1116. );
  1117. }
  1118. return lines.join('\n');
  1119. }
  1120. function renderBusinessFillGuide(guide) {
  1121. return [
  1122. '### 商务填表执行视图',
  1123. '',
  1124. `- rowCount:${Number(guide.rowCount || guide.rows.length || 0)}`,
  1125. `- directCustomerProof:${guide.directCustomerProof ? 'true' : 'false'}`,
  1126. `- proofLevel:${guide.proofLevel || 'unknown'}`,
  1127. '',
  1128. '| 顺序 | 负责人 | 表单区域 | 填表文件 | 优先项 | 问题数 | 填表动作 | 补齐后复验 | 验收 | 边界 |',
  1129. '| ---: | --- | --- | --- | --- | ---: | --- | --- | --- | --- |',
  1130. ...guide.rows.map(item => tableRow([
  1131. item.order,
  1132. item.owner,
  1133. item.section,
  1134. item.file || '缺失',
  1135. (item.firstItemIds || []).join('、') || '无',
  1136. item.issueCount,
  1137. item.fillAction,
  1138. item.recheckCommand || '待补齐后运行 round:refresh',
  1139. item.acceptance,
  1140. item.boundary
  1141. ])),
  1142. '',
  1143. `- ${guide.guardrail || '本视图只帮助商务按表补齐真实材料,不代表客户效果已达标。'}`
  1144. ].join('\n');
  1145. }
  1146. function renderIntakeOwnerGroups(ownerGroups) {
  1147. return [
  1148. '| 负责人 | 范围 | 字段项 | 缺必填 | 占位 | 文件 | 下一步 | 通过标准 | 边界 |',
  1149. '| --- | --- | ---: | ---: | ---: | --- | --- | --- | --- |',
  1150. ...ownerGroups.map(item => tableRow([
  1151. item.owner,
  1152. item.section,
  1153. item.itemCount,
  1154. item.missingRequiredCount,
  1155. item.placeholderCount,
  1156. (item.files || []).join(';') || '缺失',
  1157. item.nextAction,
  1158. item.acceptance,
  1159. item.boundary
  1160. ]))
  1161. ].join('\n');
  1162. }
  1163. function renderRecheckCommands(recheckCommands) {
  1164. return [
  1165. '| 阶段 | 负责人 | 命令 | 通过标准 | 边界 |',
  1166. '| --- | --- | --- | --- | --- |',
  1167. ...recheckCommands.map(item => tableRow([
  1168. item.stage,
  1169. item.owner,
  1170. item.command,
  1171. item.acceptance,
  1172. item.boundary
  1173. ]))
  1174. ].join('\n');
  1175. }
  1176. function renderLatestFormRepairActions(repairActions) {
  1177. if (!repairActions) return '';
  1178. const rows = [repairActions.dataIntake, repairActions.videoResource, repairActions.homepageEvidence].filter(Boolean);
  1179. if (!rows.length) return '';
  1180. return [
  1181. '| 类型 | CSV | 当前问题数 | 修复动作数 | 状态 |',
  1182. '| --- | --- | ---: | ---: | --- |',
  1183. ...rows.map(item => tableRow([
  1184. item.title || '修复清单',
  1185. item.csv || '缺失',
  1186. Number(item.failureCount || 0),
  1187. Number(item.repairActionCount || 0),
  1188. item.ready || item.readyForVideoAbPreflight ? '已就绪' : '待补齐'
  1189. ]))
  1190. ].join('\n');
  1191. }
  1192. function buildRepairActionsSummary(root) {
  1193. const intakeReadinessDir = latestOutputDir(/^intake-readiness-/) || 'outputs/intake-readiness-latest';
  1194. const videoResourceReadinessDir = latestOutputDir(/^video-resource-readiness-/) || 'outputs/video-resource-readiness-latest';
  1195. const homepageEvidenceReadinessDir = latestOutputDir(/^homepage-evidence-readiness-/) || 'outputs/homepage-evidence-readiness-latest';
  1196. const intakeReadinessSummary = readJsonSummary(root, intakeReadinessDir, 'intake-readiness-summary.json');
  1197. const videoResourceReadinessSummary = readJsonSummary(root, videoResourceReadinessDir, 'video-resource-readiness-summary.json');
  1198. const homepageEvidenceReadinessSummary = readJsonSummary(root, homepageEvidenceReadinessDir, 'homepage-evidence-readiness-summary.json');
  1199. return {
  1200. dataIntake: {
  1201. title: '历史数据/商务复核修复清单',
  1202. csv: relIfExists(root, path.join(root, intakeReadinessDir, 'intake-readiness-repair-actions.csv')),
  1203. report: relIfExists(root, path.join(root, intakeReadinessDir, 'intake-readiness-report.md')),
  1204. summary: relIfExists(root, path.join(root, intakeReadinessDir, 'intake-readiness-summary.json')),
  1205. failureCount: Number(intakeReadinessSummary?.failureCount || 0),
  1206. repairActionCount: Number(intakeReadinessSummary?.repairActions?.length || 0),
  1207. ready: Boolean(intakeReadinessSummary?.acceptance?.overallReady)
  1208. },
  1209. videoResource: {
  1210. title: '视频资源修复清单',
  1211. csv: relIfExists(root, path.join(root, videoResourceReadinessDir, 'video-resource-repair-actions.csv')),
  1212. report: relIfExists(root, path.join(root, videoResourceReadinessDir, 'video-resource-readiness-report.md')),
  1213. summary: relIfExists(root, path.join(root, videoResourceReadinessDir, 'video-resource-readiness-summary.json')),
  1214. failureCount: Number(videoResourceReadinessSummary?.failureCount || 0),
  1215. repairActionCount: Number(videoResourceReadinessSummary?.repairActions?.length || 0),
  1216. readyForVideoAbPreflight: Boolean(videoResourceReadinessSummary?.acceptance?.readyForVideoAbPreflight)
  1217. },
  1218. homepageEvidence: {
  1219. title: '主页近期内容证据修复清单',
  1220. csv: relIfExists(root, path.join(root, homepageEvidenceReadinessDir, 'homepage-evidence-repair-actions.csv')),
  1221. report: relIfExists(root, path.join(root, homepageEvidenceReadinessDir, 'homepage-evidence-readiness-report.md')),
  1222. summary: relIfExists(root, path.join(root, homepageEvidenceReadinessDir, 'homepage-evidence-readiness-summary.json')),
  1223. failureCount: Number(homepageEvidenceReadinessSummary?.failureCount || 0),
  1224. repairActionCount: Number(homepageEvidenceReadinessSummary?.repairActions?.length || 0),
  1225. ready: Boolean(homepageEvidenceReadinessSummary?.ready),
  1226. directCustomerProof: Boolean(homepageEvidenceReadinessSummary?.directCustomerProof)
  1227. }
  1228. };
  1229. }
  1230. function buildLatestFormIndexChecklistSummary({
  1231. root,
  1232. latestFormIndexSummary,
  1233. latestIntakeFieldChecklist,
  1234. intakeFieldChecklistSummary
  1235. }) {
  1236. const indexed = latestFormIndexSummary?.intakeFieldChecklist || null;
  1237. const ownerGroups = Array.isArray(indexed?.ownerGroups) && indexed.ownerGroups.length
  1238. ? indexed.ownerGroups
  1239. : normalizeIntakeOwnerGroups(root, intakeFieldChecklistSummary?.ownerGroups || []);
  1240. const recheckCommands = Array.isArray(indexed?.recheckCommands) && indexed.recheckCommands.length
  1241. ? indexed.recheckCommands
  1242. : normalizeRecheckCommands(intakeFieldChecklistSummary?.recheckCommands || []);
  1243. if (!indexed && !latestIntakeFieldChecklist && !ownerGroups.length && !recheckCommands.length) return null;
  1244. return {
  1245. ...(indexed || {}),
  1246. markdown: indexed?.markdown || (latestIntakeFieldChecklist ? artifactRef(root, artifactPath(root, latestIntakeFieldChecklist, 'intake-field-checklist.md')) : ''),
  1247. csv: indexed?.csv || (latestIntakeFieldChecklist ? artifactRef(root, artifactPath(root, latestIntakeFieldChecklist, 'intake-field-checklist.csv')) : ''),
  1248. ownerGroups,
  1249. recheckCommands
  1250. };
  1251. }
  1252. function normalizeBusinessFillGuide(guide) {
  1253. if (!guide || !Array.isArray(guide.rows) || guide.rows.length === 0) return null;
  1254. const rows = guide.rows.map((item, index) => ({
  1255. order: Number(item.order || index + 1),
  1256. owner: item.owner || '',
  1257. section: item.section || '',
  1258. file: item.file || '',
  1259. firstItemIds: Array.isArray(item.firstItemIds) ? item.firstItemIds : [],
  1260. issueCount: Number(item.issueCount || 0),
  1261. missingRequiredCount: Number(item.missingRequiredCount || 0),
  1262. placeholderCount: Number(item.placeholderCount || 0),
  1263. fillAction: item.fillAction || '',
  1264. recheckCommand: item.recheckCommand || '',
  1265. acceptance: item.acceptance || '',
  1266. boundary: item.boundary || ''
  1267. }));
  1268. return {
  1269. title: guide.title || '商务填表执行视图',
  1270. directCustomerProof: Boolean(guide.directCustomerProof),
  1271. proofLevel: guide.proofLevel || 'smoke_or_local',
  1272. rowCount: Number(guide.rowCount || rows.length),
  1273. rows,
  1274. guardrail: guide.guardrail || '本视图只帮助商务按表补齐真实材料,不代表客户效果已达标。'
  1275. };
  1276. }
  1277. function normalizeManualReviewLabelGuide(guide) {
  1278. if (!guide) return null;
  1279. return {
  1280. markdown: guide.markdown || '',
  1281. csv: guide.csv || '',
  1282. summary: guide.summary || '',
  1283. passed: Boolean(guide.passed),
  1284. directCustomerProof: Boolean(guide.directCustomerProof),
  1285. proofLevel: guide.proofLevel || 'smoke_or_local',
  1286. labelCount: Number(guide.labelCount || 0),
  1287. positiveLabelCount: Number(guide.positiveLabelCount || 0),
  1288. negativeLabelCount: Number(guide.negativeLabelCount || 0),
  1289. attributionTypeCount: Number(guide.attributionTypeCount || 0),
  1290. negativeLabelsRequireAttribution: Boolean(guide.negativeLabelsRequireAttribution)
  1291. };
  1292. }
  1293. function normalizePlanExecutionStatus(status) {
  1294. if (!status) return null;
  1295. return {
  1296. markdown: status.markdown || status.report || status.files?.report || '',
  1297. csv: status.csv || status.files?.csv || '',
  1298. summary: status.summary || status.files?.summary || '',
  1299. passed: Boolean(status.passed),
  1300. complete: Boolean(status.complete),
  1301. directCustomerProof: false,
  1302. rowCount: Number(status.rowCount || 0),
  1303. proofOpenCount: Number(status.proofOpenCount || 0),
  1304. operatorPackOwnerArtifactCount: Number(status.operatorPackOwnerArtifactCount || (Array.isArray(status.operatorPackOwnerArtifacts) ? status.operatorPackOwnerArtifacts.length : 0)),
  1305. operatorPackOwnerArtifacts: normalizeProofGapOperatorOwnerArtifacts(status.operatorPackOwnerArtifacts || []),
  1306. businessExecutionOperatorPackOwnerArtifactCount: Number(status.businessExecutionOperatorPackOwnerArtifactCount || 0),
  1307. businessExecutionOperatorPackOwnerArtifactRowCount: Number(status.businessExecutionOperatorPackOwnerArtifactRowCount || 0),
  1308. roundPassed: Boolean(status.roundPassed),
  1309. guardrailCount: Array.isArray(status.guardrails) ? status.guardrails.length : Number(status.guardrailCount || 0)
  1310. };
  1311. }
  1312. function normalizeProofGapRequest(request) {
  1313. if (!request) return null;
  1314. return {
  1315. markdown: request.markdown || request.report || request.files?.report || '',
  1316. csv: request.csv || request.files?.csv || '',
  1317. summary: request.summary || request.files?.summary || '',
  1318. complete: Boolean(request.complete),
  1319. directCustomerProof: false,
  1320. proofLevel: request.proofLevel || 'smoke_or_local',
  1321. unresolvedCount: Number(request.unresolvedCount || 0),
  1322. rowCount: Array.isArray(request.rows) ? request.rows.length : Number(request.rowCount || 0),
  1323. proofGapClosureOpenCount: Number(request.proofGapClosureOpenCount || request.observed?.proofGapClosureOpenCount || 0)
  1324. };
  1325. }
  1326. function normalizeProofGapOperatorPack(pack) {
  1327. if (!pack) return null;
  1328. return {
  1329. markdown: pack.markdown || pack.report || pack.files?.report || '',
  1330. csv: pack.csv || pack.files?.csv || '',
  1331. summary: pack.summary || pack.files?.summary || '',
  1332. passed: Boolean(pack.passed),
  1333. complete: Boolean(pack.complete),
  1334. directCustomerProof: false,
  1335. proofLevel: pack.proofLevel || 'not_business_proof',
  1336. rowCount: Number(pack.rowCount || 0),
  1337. openCount: Number(pack.openCount || 0),
  1338. gapIds: Array.isArray(pack.gapIds)
  1339. ? pack.gapIds
  1340. : (Array.isArray(pack.rows) ? pack.rows.map(row => row.gapId).filter(Boolean) : []),
  1341. fillFileCount: Number(pack.fillFileCount || 0),
  1342. recheckCommandCount: Number(pack.recheckCommandCount || 0),
  1343. ownerGroupCount: Number(pack.ownerGroupCount || (Array.isArray(pack.ownerGroups) ? pack.ownerGroups.length : 0)),
  1344. ownerArtifactCount: Number(pack.ownerArtifactCount || (Array.isArray(pack.ownerArtifacts) ? pack.ownerArtifacts.length : 0)),
  1345. ownerArtifacts: normalizeProofGapOperatorOwnerArtifacts(pack.ownerArtifacts || []),
  1346. evidenceRowCount: Number(pack.evidenceRowCount || 0),
  1347. nextActionCount: Number(pack.nextActionCount || 0),
  1348. missingProofRequirementCount: Number(pack.missingProofRequirementCount || 0)
  1349. };
  1350. }
  1351. function normalizeProofGapOperatorOwnerArtifacts(ownerArtifacts) {
  1352. if (!Array.isArray(ownerArtifacts)) return [];
  1353. return ownerArtifacts.map(item => ({
  1354. owner: item.owner || '',
  1355. actionCount: Number(item.actionCount || 0),
  1356. topPriority: Number(item.topPriority || 0),
  1357. markdown: item.markdown || '',
  1358. csv: item.csv || '',
  1359. gapIds: Array.isArray(item.gapIds) ? item.gapIds : [],
  1360. evidenceRowCount: Number(item.evidenceRowCount || 0),
  1361. nextActionCount: Number(item.nextActionCount || 0)
  1362. }));
  1363. }
  1364. function normalizeWorkOrderOwnerArtifacts(ownerArtifacts) {
  1365. if (!Array.isArray(ownerArtifacts)) return [];
  1366. return ownerArtifacts.map(item => ({
  1367. owner: item.owner || '',
  1368. workOrderCount: Number(item.workOrderCount || item.actionCount || 0),
  1369. topPriority: Number(item.topPriority || 0),
  1370. markdown: item.markdown || '',
  1371. csv: item.csv || '',
  1372. targetFiles: Array.isArray(item.targetFiles) ? item.targetFiles : [],
  1373. gapIds: Array.isArray(item.gapIds) ? item.gapIds : []
  1374. }));
  1375. }
  1376. function normalizeRealProofIntakeBundle(bundle) {
  1377. if (!bundle) return null;
  1378. return {
  1379. markdown: bundle.markdown || bundle.report || bundle.files?.report || '',
  1380. csv: bundle.csv || bundle.files?.csv || '',
  1381. summary: bundle.summary || bundle.files?.summary || '',
  1382. passed: Boolean(bundle.passed),
  1383. complete: Boolean(bundle.complete),
  1384. directCustomerProof: false,
  1385. proofLevel: bundle.proofLevel || 'not_business_proof',
  1386. proofOpenCount: Number(bundle.proofOpenCount || 0),
  1387. fillRowCount: Number(bundle.fillRowCount || 0),
  1388. proofGapRowCount: Number(bundle.proofGapRowCount || 0),
  1389. repairRowCount: Number(bundle.repairRowCount || 0),
  1390. recheckCommandCount: Number(bundle.recheckCommandCount || 0),
  1391. exportRowCount: Number(bundle.exportRowCount || 0),
  1392. operatorPackEvidenceRowCount: Number(bundle.operatorPackEvidenceRowCount || bundle.sourceState?.operatorPackEvidenceRowCount || 0),
  1393. operatorPackNextActionCount: Number(bundle.operatorPackNextActionCount || bundle.sourceState?.operatorPackNextActionCount || 0),
  1394. operatorPackMissingProofRequirementCount: Number(bundle.operatorPackMissingProofRequirementCount || bundle.sourceState?.operatorPackMissingProofRequirementCount || 0),
  1395. operatorPackOwnerArtifactCount: Number(bundle.operatorPackOwnerArtifactCount || bundle.sourceState?.operatorPackOwnerArtifactCount || (Array.isArray(bundle.operatorPackOwnerArtifacts) ? bundle.operatorPackOwnerArtifacts.length : 0)),
  1396. operatorPackOwnerArtifacts: normalizeProofGapOperatorOwnerArtifacts(bundle.operatorPackOwnerArtifacts || [])
  1397. };
  1398. }
  1399. function normalizeRealProofClosureWorkOrder(workOrder) {
  1400. if (!workOrder) return null;
  1401. return {
  1402. markdown: workOrder.markdown || workOrder.report || workOrder.files?.report || '',
  1403. csv: workOrder.csv || workOrder.files?.csv || '',
  1404. summary: workOrder.summary || workOrder.files?.summary || '',
  1405. passed: Boolean(workOrder.passed),
  1406. complete: Boolean(workOrder.complete),
  1407. directCustomerProof: false,
  1408. canCloseProofGap: Boolean(workOrder.canCloseProofGap),
  1409. proofLevel: workOrder.proofLevel || 'not_business_proof',
  1410. materialType: workOrder.materialType || 'real_proof_closure_work_order',
  1411. workOrderCount: Number(workOrder.workOrderCount || 0),
  1412. ownerGroupCount: Number(workOrder.ownerGroupCount || 0),
  1413. seedSuggestionCount: Number(workOrder.seedSuggestionCount || 0),
  1414. proofGapOpenCount: Number(workOrder.proofGapOpenCount || workOrder.sourceState?.proofGapOpenCount || 0),
  1415. intakeFailureCount: Number(workOrder.intakeFailureCount || workOrder.sourceState?.intakeFailureCount || 0),
  1416. videoFailureCount: Number(workOrder.videoFailureCount || workOrder.sourceState?.videoFailureCount || 0),
  1417. realCandidateRows: Number(workOrder.realCandidateRows || workOrder.sourceState?.realCandidateRows || 0),
  1418. ownerArtifacts: normalizeWorkOrderOwnerArtifacts(workOrder.ownerArtifacts || [])
  1419. };
  1420. }
  1421. function normalizeLocalSeedToIntakeWorklist(worklist) {
  1422. if (!worklist) return null;
  1423. const counts = worklist.counts || {};
  1424. const countOf = (value, fallback) => {
  1425. if (Array.isArray(value)) return value.length;
  1426. if (Number.isFinite(Number(value))) return Number(value);
  1427. if (Array.isArray(fallback)) return fallback.length;
  1428. return Number(fallback || 0);
  1429. };
  1430. return {
  1431. markdown: worklist.markdown || worklist.report || '',
  1432. csv: worklist.csv || '',
  1433. summary: worklist.summary || '',
  1434. historyDraft: worklist.historyDraft || '',
  1435. videoWorklist: worklist.videoWorklist || '',
  1436. candidateWorklist: worklist.candidateWorklist || '',
  1437. proofLevel: worklist.proofLevel || 'not_business_proof',
  1438. materialType: worklist.materialType || 'seed_to_intake_worklist',
  1439. complete: Boolean(worklist.complete),
  1440. directCustomerProof: false,
  1441. canCloseProofGap: Boolean(worklist.canCloseProofGap),
  1442. historyDraftRows: countOf(worklist.historyDraftRows, counts.historyDraftRows),
  1443. referenceSeedRows: countOf(worklist.referenceSeedRows, counts.referenceSeedRows),
  1444. candidateSeedRows: countOf(worklist.candidateSeedRows, counts.candidateSeedRows),
  1445. videoWorklistRows: countOf(worklist.videoWorklistRows, counts.videoWorklistRows),
  1446. videoCandidateNeedsResourceRows: countOf(worklist.videoCandidateNeedsResourceRows, counts.videoCandidateNeedsResourceRows),
  1447. missingProofRequirementCount: Number(worklist.missingProofRequirementCount ?? (Array.isArray(worklist.missingProofRequirements) ? worklist.missingProofRequirements.length : 0)),
  1448. nextActionCount: Number(worklist.nextActionCount ?? (Array.isArray(worklist.nextActions) ? worklist.nextActions.length : 0)),
  1449. targetFiles: Array.isArray(worklist.targetFiles) ? worklist.targetFiles : []
  1450. };
  1451. }
  1452. function normalizeLongRunReadiness(readiness) {
  1453. if (!readiness) return null;
  1454. return {
  1455. report: readiness.report || readiness.markdown || readiness.files?.report || '',
  1456. summary: readiness.summary || readiness.files?.summary || '',
  1457. mode: readiness.mode || '',
  1458. ready: Boolean(readiness.ready),
  1459. directCustomerProof: false,
  1460. proofLevel: readiness.proofLevel || (readiness.ready ? 'smoke_or_local' : 'not_business_proof'),
  1461. passCount: Number(readiness.passCount ?? readiness.counts?.pass ?? 0),
  1462. failCount: Number(readiness.failCount ?? readiness.counts?.fail ?? 0),
  1463. warnCount: Number(readiness.warnCount ?? readiness.counts?.warn ?? 0),
  1464. nextCommandCount: Number(readiness.nextCommandCount ?? (Array.isArray(readiness.nextCommands) ? readiness.nextCommands.length : 0)),
  1465. boundaryCount: Number(readiness.boundaryCount ?? (Array.isArray(readiness.boundaries) ? readiness.boundaries.length : 0))
  1466. };
  1467. }
  1468. function normalizeRealGapMaterialAudit(audit) {
  1469. if (!audit) return null;
  1470. return {
  1471. markdown: audit.markdown || audit.report || '',
  1472. csv: audit.csv || '',
  1473. summary: audit.summary || '',
  1474. proofLevel: audit.proofLevel || 'not_business_proof',
  1475. materialType: audit.materialType || 'three_real_gap_material_audit',
  1476. complete: Boolean(audit.complete),
  1477. directCustomerProof: false,
  1478. canCloseProofGap: Boolean(audit.canCloseProofGap),
  1479. gapCount: Number(audit.gapCount || 0),
  1480. materialFoundCount: Number(audit.materialFoundCount || 0),
  1481. openGapCount: Number(audit.openGapCount || 0),
  1482. sourceState: audit.sourceState || {},
  1483. gapIds: Array.isArray(audit.gapIds)
  1484. ? audit.gapIds
  1485. : (Array.isArray(audit.gaps) ? audit.gaps.map(item => item.id).filter(Boolean) : [])
  1486. };
  1487. }
  1488. function normalizeOptimizationCompletion(completion) {
  1489. if (!completion) return null;
  1490. const blockingReasonCount = Number(completion.blockingReasonCount ?? (Array.isArray(completion.blockingReasons) ? completion.blockingReasons.length : 0));
  1491. const blockingReasonsWithMissingProofRequirements = Number(completion.blockingReasonsWithMissingProofRequirements ?? countRowsWithMissingProofRequirements(completion.blockingReasons));
  1492. const missingProofRequirementCount = Number(completion.missingProofRequirementCount ?? uniqueMissingProofRequirementCount(completion.blockingReasons));
  1493. return {
  1494. report: completion.report || completion.markdown || completion.files?.report || '',
  1495. summary: completion.summary || completion.files?.summary || '',
  1496. complete: Boolean(completion.complete),
  1497. readyForClaim: Boolean(completion.readyForClaim),
  1498. proofGapOpenCount: Number(completion.proofGapOpenCount || 0),
  1499. blockingReasonCount,
  1500. blockingReasonsWithMissingProofRequirements,
  1501. missingProofRequirementCount,
  1502. videoAbPreflightReadyForVideoAb: Boolean(completion.videoAbPreflightReadyForVideoAb),
  1503. videoAbPreflightFailureCount: Number.isFinite(Number(completion.videoAbPreflightFailureCount)) ? Number(completion.videoAbPreflightFailureCount) : null,
  1504. directCustomerProof: false,
  1505. proofLevel: completion.readyForClaim ? 'real_evidence' : 'not_business_proof',
  1506. canClaim: {
  1507. tihaoRateImproved: Boolean(completion.canClaim?.tihaoRateImproved),
  1508. customerEffectAchieved: Boolean(completion.canClaim?.customerEffectAchieved),
  1509. manualSupplementReduced: Boolean(completion.canClaim?.manualSupplementReduced)
  1510. },
  1511. nextRequiredCommandCount: Array.isArray(completion.nextRequiredCommands) ? completion.nextRequiredCommands.length : 0
  1512. };
  1513. }
  1514. function normalizeBusinessExecutionLongRunReadiness(indexSummary, readinessSummary) {
  1515. if (!indexSummary) return null;
  1516. const row = (Array.isArray(indexSummary.rows) ? indexSummary.rows : [])
  1517. .find(item => String(item.title || '').includes('长跑前置门禁报告'));
  1518. const summary = indexSummary.sourceFiles?.longRunReadiness || readinessSummary?.summary || readinessSummary?.files?.summary || '';
  1519. if (!row && !summary) return null;
  1520. const ready = Boolean(readinessSummary?.ready);
  1521. const failCount = Number(readinessSummary?.counts?.fail ?? readinessSummary?.failCount ?? row?.actionCount ?? 0);
  1522. return {
  1523. report: row?.mainFile || readinessSummary?.report || readinessSummary?.files?.report || '',
  1524. summary,
  1525. status: row?.status || (ready ? 'ready' : 'blocked'),
  1526. actionCount: Number(row?.actionCount ?? failCount),
  1527. ready,
  1528. failCount,
  1529. expectedArtifact: row?.expectedArtifact || summary,
  1530. acceptance: row?.acceptance || '',
  1531. boundary: row?.boundary || '',
  1532. directCustomerProof: false,
  1533. proofLevel: ready ? 'smoke_or_local' : 'not_business_proof'
  1534. };
  1535. }
  1536. function normalizeBusinessExecutionOptimizationCompletion(indexSummary, completionSummary) {
  1537. if (!indexSummary) return null;
  1538. const row = (Array.isArray(indexSummary.rows) ? indexSummary.rows : [])
  1539. .find(item => String(item.title || '').includes('优化完成度审计报告'));
  1540. const summary = indexSummary.sourceFiles?.optimizationCompletion || completionSummary?.summary || completionSummary?.files?.summary || '';
  1541. if (!row && !summary) return null;
  1542. const readyForClaim = Boolean(completionSummary?.readyForClaim);
  1543. const blockingReasonCount = Number(completionSummary?.blockingReasonCount ?? (Array.isArray(completionSummary?.blockingReasons) ? completionSummary.blockingReasons.length : row?.actionCount ?? 0));
  1544. const blockingReasonsWithMissingProofRequirements = Number(completionSummary?.blockingReasonsWithMissingProofRequirements ?? countRowsWithMissingProofRequirements(completionSummary?.blockingReasons));
  1545. const missingProofRequirementCount = Number(completionSummary?.missingProofRequirementCount ?? uniqueMissingProofRequirementCount(completionSummary?.blockingReasons));
  1546. return {
  1547. report: row?.mainFile || completionSummary?.report || completionSummary?.files?.report || '',
  1548. summary,
  1549. status: row?.status || (readyForClaim ? 'ready_for_claim' : 'blocked'),
  1550. actionCount: Number(row?.actionCount ?? blockingReasonCount),
  1551. complete: Boolean(completionSummary?.complete),
  1552. readyForClaim,
  1553. proofGapOpenCount: Number(completionSummary?.proofGapOpenCount || 0),
  1554. blockingReasonCount,
  1555. blockingReasonsWithMissingProofRequirements,
  1556. missingProofRequirementCount,
  1557. videoAbPreflightReadyForVideoAb: Boolean(completionSummary?.videoAbPreflightReadyForVideoAb),
  1558. videoAbPreflightFailureCount: Number.isFinite(Number(completionSummary?.videoAbPreflightFailureCount)) ? Number(completionSummary.videoAbPreflightFailureCount) : null,
  1559. expectedArtifact: row?.expectedArtifact || summary,
  1560. acceptance: row?.acceptance || '',
  1561. boundary: row?.boundary || '',
  1562. directCustomerProof: false,
  1563. proofLevel: readyForClaim ? 'real_evidence' : 'not_business_proof'
  1564. };
  1565. }
  1566. function normalizeIntakeOwnerGroups(root, ownerGroups) {
  1567. if (!Array.isArray(ownerGroups)) return [];
  1568. return ownerGroups.map(item => ({
  1569. owner: item.owner || '',
  1570. section: item.section || '',
  1571. itemCount: Number(item.itemCount || 0),
  1572. missingRequiredCount: Number(item.missingRequiredCount || 0),
  1573. placeholderCount: Number(item.placeholderCount || 0),
  1574. files: Array.isArray(item.files) ? item.files.map(file => artifactRef(root, file)) : [],
  1575. firstItemIds: Array.isArray(item.firstItemIds) ? item.firstItemIds : [],
  1576. nextAction: item.nextAction || '',
  1577. acceptance: item.acceptance || '',
  1578. boundary: item.boundary || ''
  1579. }));
  1580. }
  1581. function normalizeRecheckCommands(recheckCommands) {
  1582. if (!Array.isArray(recheckCommands)) return [];
  1583. return recheckCommands.map(item => ({
  1584. stage: item.stage || '',
  1585. owner: item.owner || '',
  1586. command: item.command || '',
  1587. acceptance: item.acceptance || '',
  1588. boundary: item.boundary || ''
  1589. }));
  1590. }
  1591. function normalizeBusinessProofNextActionSourceState(sourceState) {
  1592. if (!sourceState || typeof sourceState !== 'object') return {};
  1593. return {
  1594. progressComplete: Boolean(sourceState.progressComplete),
  1595. progressCounts: sourceState.progressCounts || {},
  1596. proofGapOpenCount: Number(sourceState.proofGapOpenCount || 0),
  1597. intakeFailureCount: Number(sourceState.intakeFailureCount || 0),
  1598. videoFailureCount: Number(sourceState.videoFailureCount || 0),
  1599. homepageFailureCount: Number(sourceState.homepageFailureCount || 0),
  1600. pipelineReadyForClaim: Boolean(sourceState.pipelineReadyForClaim),
  1601. pipelineFailCount: Number(sourceState.pipelineFailCount || 0),
  1602. pipelineNextActionCount: Number(sourceState.pipelineNextActionCount || 0),
  1603. pipelineRawNextActionCount: Number(sourceState.pipelineRawNextActionCount || 0),
  1604. pipelineSuppressedNextActionCount: Number(sourceState.pipelineSuppressedNextActionCount || 0),
  1605. pipelineStale: Boolean(sourceState.pipelineStale),
  1606. pipelineStaleReason: sourceState.pipelineStaleReason || '',
  1607. pipelineStaleMissingProofRequirements: Array.isArray(sourceState.pipelineStaleMissingProofRequirements)
  1608. ? sourceState.pipelineStaleMissingProofRequirements
  1609. : []
  1610. };
  1611. }
  1612. function buildPipelineStaleSummary(primarySourceState, fallbackSourceState) {
  1613. const primaryHasState = primarySourceState && Object.keys(primarySourceState).length > 0;
  1614. const fallbackHasState = fallbackSourceState && Object.keys(fallbackSourceState).length > 0;
  1615. const sourceState = primaryHasState ? primarySourceState : (fallbackHasState ? fallbackSourceState : {});
  1616. const missingProofRequirements = Array.isArray(sourceState.pipelineStaleMissingProofRequirements)
  1617. ? sourceState.pipelineStaleMissingProofRequirements
  1618. : [];
  1619. return {
  1620. pipelineStale: Boolean(sourceState.pipelineStale),
  1621. pipelineReadyForClaim: Boolean(sourceState.pipelineReadyForClaim),
  1622. pipelineNextActionCount: Number(sourceState.pipelineNextActionCount || 0),
  1623. pipelineRawNextActionCount: Number(sourceState.pipelineRawNextActionCount || 0),
  1624. pipelineSuppressedNextActionCount: Number(sourceState.pipelineSuppressedNextActionCount || 0),
  1625. pipelineStaleReason: sourceState.pipelineStaleReason || '',
  1626. pipelineStaleMissingProofRequirementCount: missingProofRequirements.length,
  1627. pipelineStaleMissingProofRequirements: missingProofRequirements,
  1628. boundary: 'stale optimization-pipeline nextActions are not current dispatch or completion evidence; rerun optimization:pipeline only after real materials are ready.'
  1629. };
  1630. }
  1631. function normalizeBusinessProofNextTopActions(actions, limit = 5) {
  1632. if (!Array.isArray(actions)) return [];
  1633. return actions
  1634. .slice()
  1635. .sort((left, right) => actionSortValue(left) - actionSortValue(right))
  1636. .slice(0, limit)
  1637. .map(item => ({
  1638. id: item.id || '',
  1639. source: item.source || '',
  1640. order: Number(item.order || 0),
  1641. priority: Number(item.priority || 0),
  1642. owner: item.owner || '',
  1643. title: item.title || '',
  1644. status: item.status || '',
  1645. evidence: item.evidence || '',
  1646. command: item.command || '',
  1647. expectedArtifact: item.expectedArtifact || '',
  1648. acceptance: item.acceptance || '',
  1649. missingProofRequirementCount: Array.isArray(item.missingProofRequirements) ? item.missingProofRequirements.length : 0,
  1650. missingProofRequirements: Array.isArray(item.missingProofRequirements) ? item.missingProofRequirements : []
  1651. }));
  1652. }
  1653. function actionSortValue(item) {
  1654. const priority = Number(item?.priority);
  1655. const order = Number(item?.order);
  1656. const primary = Number.isFinite(priority) && priority > 0 ? priority : 999999;
  1657. const secondary = Number.isFinite(order) && order > 0 ? order / 100000 : 0;
  1658. return primary + secondary;
  1659. }
  1660. function normalizeOwnerArtifacts(root, dir, summary) {
  1661. if (!summary || !Array.isArray(summary.ownerArtifacts)) return [];
  1662. return summary.ownerArtifacts.map(item => ({
  1663. owner: item.owner,
  1664. actionCount: item.actionCount,
  1665. topPriority: item.topPriority,
  1666. markdown: item.markdown ? artifactRef(root, artifactPath(root, dir, item.markdown)) : '',
  1667. csv: item.csv ? artifactRef(root, artifactPath(root, dir, item.csv)) : '',
  1668. repairArtifacts: normalizeRepairArtifacts(item.repairArtifacts || [])
  1669. }));
  1670. }
  1671. function renderPipelineStaleSummary(summary) {
  1672. if (!summary) return '- 尚未生成 pipeline stale 摘要。';
  1673. return [
  1674. `- pipelineStale: ${summary.pipelineStale ? 'true' : 'false'}`,
  1675. `- pipelineReadyForClaim: ${summary.pipelineReadyForClaim ? 'true' : 'false'}`,
  1676. `- pipelineNextActionCount: ${summary.pipelineNextActionCount || 0}`,
  1677. `- pipelineRawNextActionCount: ${summary.pipelineRawNextActionCount || 0}`,
  1678. `- pipelineSuppressedNextActionCount: ${summary.pipelineSuppressedNextActionCount || 0}`,
  1679. `- pipelineStaleMissingProofRequirementCount: ${summary.pipelineStaleMissingProofRequirementCount || 0}`,
  1680. summary.pipelineStaleReason ? `- pipelineStaleReason: ${summary.pipelineStaleReason}` : '',
  1681. `- 边界:${summary.boundary || 'stale pipeline 动作不能作为当前派工或完成证据。'}`
  1682. ].filter(Boolean).join('\n');
  1683. }
  1684. function renderBusinessProofNextActions(queue) {
  1685. const sourceState = queue.sourceState || {};
  1686. const lines = [
  1687. `- 摘要:${queue.summary || 'missing'}`,
  1688. `- 报告:${queue.report || 'missing'}`,
  1689. `- CSV:${queue.csv || 'missing'}`,
  1690. `- complete: ${queue.complete ? 'true' : 'false'}`,
  1691. `- actionCount: ${queue.actionCount || 0}`,
  1692. `- ownerGroupCount: ${queue.ownerGroupCount || 0}`,
  1693. `- ownerArtifactCount: ${queue.ownerArtifactCount || 0}`,
  1694. `- proofGapOpenCount: ${sourceState.proofGapOpenCount || 0}`,
  1695. `- intakeFailureCount: ${sourceState.intakeFailureCount || 0}`,
  1696. `- videoFailureCount: ${sourceState.videoFailureCount || 0}`,
  1697. `- pipelineReadyForClaim: ${sourceState.pipelineReadyForClaim ? 'true' : 'false'}`,
  1698. `- pipelineStale: ${sourceState.pipelineStale ? 'true' : 'false'}`,
  1699. `- pipelineSuppressedNextActionCount: ${sourceState.pipelineSuppressedNextActionCount || 0}`,
  1700. sourceState.pipelineStaleReason ? `- pipelineStaleReason: ${sourceState.pipelineStaleReason}` : '',
  1701. '- 边界:该行动队列只把 blocked/fail/pending 转成派工动作;actionCount 不代表业务证明完成,proof-gap 未关闭时不得宣称提号率、客户效果或人工补号量改善。'
  1702. ];
  1703. if (Array.isArray(queue.topActions) && queue.topActions.length) {
  1704. lines.push(
  1705. '',
  1706. '| ID | 优先级 | 负责人 | 动作 | 状态 | 命令 | 通过标准 | 缺证明项 |',
  1707. '| --- | ---: | --- | --- | --- | --- | --- | ---: |',
  1708. ...queue.topActions.map(item => tableRow([
  1709. item.id || 'missing',
  1710. item.priority || item.order || 0,
  1711. item.owner || 'missing',
  1712. item.title || 'missing',
  1713. item.status || 'missing',
  1714. item.command || 'missing',
  1715. item.acceptance || 'missing',
  1716. item.missingProofRequirementCount || 0
  1717. ]))
  1718. );
  1719. }
  1720. return lines.join('\n');
  1721. }
  1722. function renderOwnerArtifacts(ownerArtifacts) {
  1723. const lines = [
  1724. '| 负责人 | 行动数 | 最高优先级 | Markdown | CSV | 修复附件 |',
  1725. '| --- | --- | --- | --- | --- | --- |'
  1726. ];
  1727. for (const item of ownerArtifacts) {
  1728. lines.push(`| ${escapeCell(item.owner)} | ${item.actionCount} | ${item.topPriority} | ${escapeCell(item.markdown)} | ${escapeCell(item.csv)} | ${escapeCell(renderRepairArtifactSummary(item.repairArtifacts || []))} |`);
  1729. }
  1730. return lines.join('\n');
  1731. }
  1732. function renderBusinessExecutionIndex(index) {
  1733. const lines = [
  1734. `- 报告:${index.report}`,
  1735. `- CSV:${index.csv}`,
  1736. `- Summary:${index.summary}`,
  1737. `- passed:${index.passed ? 'true' : 'false'}`,
  1738. `- rowCount:${index.rowCount}`,
  1739. `- repairRowCount:${index.repairRowCount}`,
  1740. `- operatorPackOwnerArtifactCount:${index.operatorPackOwnerArtifactCount || 0}`,
  1741. `- proofOpenCount:${index.proofOpenCount}`,
  1742. `- directCustomerProof:${index.directCustomerProof ? 'true' : 'false'}`
  1743. ];
  1744. if (Array.isArray(index.operatorPackOwnerArtifacts) && index.operatorPackOwnerArtifacts.length) {
  1745. lines.push(
  1746. '',
  1747. '| 负责人 | 动作数 | Markdown | CSV | 缺口 |',
  1748. '| --- | ---: | --- | --- | --- |',
  1749. ...index.operatorPackOwnerArtifacts.map(item => tableRow([
  1750. item.owner,
  1751. item.actionCount,
  1752. item.markdown || 'missing',
  1753. item.csv || 'missing',
  1754. (item.gapIds || []).join(';') || 'missing'
  1755. ]))
  1756. );
  1757. }
  1758. if (index.longRunReadiness) {
  1759. lines.push(
  1760. `- longRunReadiness.report:${index.longRunReadiness.report || 'missing'}`,
  1761. `- longRunReadiness.summary:${index.longRunReadiness.summary || 'missing'}`,
  1762. `- longRunReadiness.status:${index.longRunReadiness.status || 'unknown'}`,
  1763. `- longRunReadiness.ready:${index.longRunReadiness.ready ? 'true' : 'false'}`,
  1764. `- longRunReadiness.failCount:${index.longRunReadiness.failCount}`,
  1765. `- longRunReadiness.directCustomerProof:${index.longRunReadiness.directCustomerProof ? 'true' : 'false'}`
  1766. );
  1767. }
  1768. if (index.optimizationCompletion) {
  1769. lines.push(
  1770. `- optimizationCompletion.report:${index.optimizationCompletion.report || 'missing'}`,
  1771. `- optimizationCompletion.summary:${index.optimizationCompletion.summary || 'missing'}`,
  1772. `- optimizationCompletion.status:${index.optimizationCompletion.status || 'unknown'}`,
  1773. `- optimizationCompletion.readyForClaim:${index.optimizationCompletion.readyForClaim ? 'true' : 'false'}`,
  1774. `- optimizationCompletion.proofGapOpenCount:${index.optimizationCompletion.proofGapOpenCount}`,
  1775. `- optimizationCompletion.blockingReasonCount:${index.optimizationCompletion.blockingReasonCount}`,
  1776. `- optimizationCompletion.videoAbPreflightReadyForVideoAb:${index.optimizationCompletion.videoAbPreflightReadyForVideoAb ? 'true' : 'false'}`,
  1777. `- optimizationCompletion.videoAbPreflightFailureCount:${index.optimizationCompletion.videoAbPreflightFailureCount ?? 'unknown'}`,
  1778. `- optimizationCompletion.directCustomerProof:${index.optimizationCompletion.directCustomerProof ? 'true' : 'false'}`
  1779. );
  1780. }
  1781. return lines.join('\n');
  1782. }
  1783. function normalizeProofGapClosureRows(summary) {
  1784. if (!summary || !Array.isArray(summary.rows)) return [];
  1785. return summary.rows.map(row => ({
  1786. id: row.id || '',
  1787. title: row.title || '',
  1788. status: row.status || '',
  1789. evidence: row.evidence || '',
  1790. missingProofRequirements: Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements.filter(Boolean) : [],
  1791. required: row.required || '',
  1792. command: row.command || '',
  1793. expectedArtifact: row.expectedArtifact || ''
  1794. }));
  1795. }
  1796. function normalizeProofGapClosureCounts(summary) {
  1797. const rows = Array.isArray(summary?.rows) ? summary.rows : [];
  1798. const precheckRows = rows.filter(row => row.id === 'intake-readiness');
  1799. const businessRows = rows.filter(row => row.id !== 'intake-readiness');
  1800. const fallbackBusinessOpen = businessRows.filter(row => row.status === 'open').length;
  1801. const fallbackBusinessClosed = businessRows.filter(row => row.status === 'closed').length;
  1802. const fallbackPrecheckOpen = precheckRows.filter(row => row.status === 'open').length;
  1803. const fallbackPrecheckClosed = precheckRows.filter(row => row.status === 'closed').length;
  1804. return {
  1805. businessOpenCount: numberOr(summary?.businessOpenCount, numberOr(summary?.openCount, fallbackBusinessOpen)),
  1806. businessClosedCount: numberOr(summary?.businessClosedCount, numberOr(summary?.closedCount, fallbackBusinessClosed)),
  1807. businessGapCount: numberOr(summary?.businessGapCount, businessRows.length),
  1808. totalRows: numberOr(summary?.totalRows, rows.length),
  1809. precheckRowCount: numberOr(summary?.precheckRowCount, precheckRows.length),
  1810. precheckOpenCount: numberOr(summary?.precheckOpenCount, fallbackPrecheckOpen),
  1811. precheckClosedCount: numberOr(summary?.precheckClosedCount, fallbackPrecheckClosed),
  1812. countSemantics: summary?.countSemantics || {
  1813. openCount: 'business_gap_open_count_excludes_precheck_rows',
  1814. closedCount: 'business_gap_closed_count_excludes_precheck_rows',
  1815. businessOpenCount: 'same_as_openCount',
  1816. businessClosedCount: 'same_as_closedCount',
  1817. precheckOpenCount: 'precheck_rows_reported_separately'
  1818. }
  1819. };
  1820. }
  1821. function numberOr(value, fallback) {
  1822. const number = Number(value);
  1823. return Number.isFinite(number) ? number : fallback;
  1824. }
  1825. function renderProofGapClosure(closure) {
  1826. const lines = [
  1827. `- Summary:${closure.summary}`,
  1828. `- 报告:${closure.report}`,
  1829. `- complete:${closure.complete ? 'true' : 'false'}`,
  1830. `- openCount:${closure.openCount}`,
  1831. `- closedCount:${closure.closedCount}`,
  1832. `- businessOpenCount:${closure.businessOpenCount}`,
  1833. `- businessClosedCount:${closure.businessClosedCount}`,
  1834. `- businessGapCount:${closure.businessGapCount}`,
  1835. `- totalRows:${closure.totalRows}`,
  1836. `- precheckRowCount:${closure.precheckRowCount}`,
  1837. `- precheckOpenCount:${closure.precheckOpenCount}`,
  1838. `- intakeReady:${closure.intakeReady ? 'true' : 'false'}`,
  1839. `- countSemantics.openCount:${closure.countSemantics?.openCount || 'unknown'}`,
  1840. ''
  1841. ];
  1842. if (!closure.rows.length) {
  1843. lines.push('- 暂无 proof-gap closure 行。');
  1844. return lines.join('\n');
  1845. }
  1846. lines.push('| 缺口ID | 状态 | 当前证据 | 缺失证明 | 验收标准 | 下一步命令 |');
  1847. lines.push('| --- | --- | --- | --- | --- | --- |');
  1848. for (const row of closure.rows) {
  1849. if (row.status !== 'open') continue;
  1850. lines.push(`| ${escapeCell(row.id)} | ${escapeCell(row.status)} | ${escapeCell(row.evidence)} | ${escapeCell(row.missingProofRequirements.join(';') || '-')} | ${escapeCell(row.required)} | \`${escapeCell(row.command)}\` |`);
  1851. }
  1852. return lines.join('\n');
  1853. }
  1854. function renderFeedbackLoopClosure(closure) {
  1855. return [
  1856. `- Summary:${closure.summary}`,
  1857. `- 报告:${closure.report}`,
  1858. `- CSV:${closure.csv}`,
  1859. `- passed:${closure.passed ? 'true' : 'false'}`,
  1860. `- complete:${closure.complete ? 'true' : 'false'}`,
  1861. `- directCustomerProof:${closure.directCustomerProof ? 'true' : 'false'}`,
  1862. `- proofLevel:${closure.proofLevel || 'unknown'}`,
  1863. `- negativeFeedbackCount:${closure.negativeFeedbackCount}`,
  1864. `- blockedCreatorCount:${closure.blockedCreatorCount}`,
  1865. `- leakedCount:${closure.leakedCount}`,
  1866. `- teamRuleSuggestionCount:${closure.teamRuleSuggestionCount}`,
  1867. `- missingInputs:${closure.missingInputs.length ? closure.missingInputs.join(';') : '无'}`
  1868. ].join('\n');
  1869. }
  1870. function renderHomepageEvidenceReadiness(readiness) {
  1871. return [
  1872. `- Summary:${readiness.summary}`,
  1873. `- 报告:${readiness.report}`,
  1874. `- 修复 CSV:${readiness.csv}`,
  1875. `- ready:${readiness.ready ? 'true' : 'false'}`,
  1876. `- complete:${readiness.complete ? 'true' : 'false'}`,
  1877. `- directCustomerProof:${readiness.directCustomerProof ? 'true' : 'false'}`,
  1878. `- proofLevel:${readiness.proofLevel || 'unknown'}`,
  1879. `- failureCount:${readiness.failureCount}`,
  1880. `- providerEvidenceCreators:${readiness.providerEvidenceCreators}`,
  1881. `- creatorsWithPosts:${readiness.creatorsWithPosts}`,
  1882. `- creatorsWithEnoughRecentPosts:${readiness.creatorsWithEnoughRecentPosts}`,
  1883. `- repairActionCount:${readiness.repairActionCount}`,
  1884. '- 边界:只证明主页证据是否可用于复核,不证明客户命中率提升。'
  1885. ].join('\n');
  1886. }
  1887. function renderLongRunReadiness(readiness) {
  1888. return [
  1889. `- report: ${readiness.report || 'missing'}`,
  1890. `- summary: ${readiness.summary || 'missing'}`,
  1891. `- mode: ${readiness.mode || 'unknown'}`,
  1892. `- ready=${readiness.ready ? 'true' : 'false'}`,
  1893. `- passCount: ${readiness.passCount || 0}`,
  1894. `- failCount: ${readiness.failCount || 0}`,
  1895. `- warnCount: ${readiness.warnCount || 0}`,
  1896. `- directCustomerProof: ${readiness.directCustomerProof ? 'true' : 'false'}`,
  1897. `- proofLevel: ${readiness.proofLevel || 'not_business_proof'}`,
  1898. `- nextCommandCount: ${readiness.nextCommandCount || 0}`,
  1899. `- boundaryCount: ${readiness.boundaryCount || 0}`,
  1900. '- 边界:该门禁只判断 full-matrix/live 长跑是否可启动;ready=false 时不得宣称客户效果、命中率提升或人工补号量下降。'
  1901. ].join('\n');
  1902. }
  1903. function renderLocalSeedToIntakeWorklist(worklist) {
  1904. return [
  1905. `- 报告:${worklist.markdown || 'missing'}`,
  1906. `- 汇总 CSV:${worklist.csv || 'missing'}`,
  1907. `- Summary:${worklist.summary || 'missing'}`,
  1908. `- history-intake-draft:${worklist.historyDraft || 'missing'}`,
  1909. `- video-resource-worklist:${worklist.videoWorklist || 'missing'}`,
  1910. `- candidate-seed-worklist:${worklist.candidateWorklist || 'missing'}`,
  1911. `- proofLevel: ${worklist.proofLevel || 'not_business_proof'}`,
  1912. `- materialType: ${worklist.materialType || 'seed_to_intake_worklist'}`,
  1913. `- directCustomerProof: ${worklist.directCustomerProof ? 'true' : 'false'}`,
  1914. `- canCloseProofGap: ${worklist.canCloseProofGap ? 'true' : 'false'}`,
  1915. `- complete: ${worklist.complete ? 'true' : 'false'}`,
  1916. `- historyDraftRows: ${worklist.historyDraftRows || 0}`,
  1917. `- referenceSeedRows: ${worklist.referenceSeedRows || 0}`,
  1918. `- candidateSeedRows: ${worklist.candidateSeedRows || 0}`,
  1919. `- videoWorklistRows: ${worklist.videoWorklistRows || 0}`,
  1920. `- videoCandidateNeedsResourceRows: ${worklist.videoCandidateNeedsResourceRows || 0}`,
  1921. `- missingProofRequirementCount: ${worklist.missingProofRequirementCount || 0}`,
  1922. `- nextActionCount: ${worklist.nextActionCount || 0}`,
  1923. `- targetFiles: ${(worklist.targetFiles || []).join(';') || 'missing'}`,
  1924. '- 边界:该 worklist 只把本地种子转成补表草稿;空字段仍需商务/投放补真实材料,不能关闭 proof-gap。'
  1925. ].join('\n');
  1926. }
  1927. function renderRealGapMaterialAudit(audit) {
  1928. return [
  1929. `- report: ${audit.markdown || 'missing'}`,
  1930. `- csv: ${audit.csv || 'missing'}`,
  1931. `- summary: ${audit.summary || 'missing'}`,
  1932. `- proofLevel: ${audit.proofLevel || 'not_business_proof'}`,
  1933. `- materialType: ${audit.materialType || 'three_real_gap_material_audit'}`,
  1934. `- directCustomerProof: ${audit.directCustomerProof ? 'true' : 'false'}`,
  1935. `- canCloseProofGap: ${audit.canCloseProofGap ? 'true' : 'false'}`,
  1936. `- complete: ${audit.complete ? 'true' : 'false'}`,
  1937. `- materialFound: ${audit.materialFoundCount || 0}/${audit.gapCount || 0}`,
  1938. `- openGapCount: ${audit.openGapCount || 0}`,
  1939. `- gapIds: ${(audit.gapIds || []).join('; ') || 'missing'}`,
  1940. '- boundary: material coverage does not prove tihao-rate improvement, customer effect, video A/B lift, or manual supplement reduction.'
  1941. ].join('\n');
  1942. }
  1943. function renderOptimizationCompletion(completion) {
  1944. return [
  1945. `- report: ${completion.report || 'missing'}`,
  1946. `- summary: ${completion.summary || 'missing'}`,
  1947. `- complete: ${completion.complete ? 'true' : 'false'}`,
  1948. `- readyForClaim: ${completion.readyForClaim ? 'true' : 'false'}`,
  1949. `- proofGapOpenCount: ${completion.proofGapOpenCount}`,
  1950. `- blockingReasonCount: ${completion.blockingReasonCount}`,
  1951. `- blockingReasonsWithMissingProofRequirements: ${completion.blockingReasonsWithMissingProofRequirements || 0}`,
  1952. `- missingProofRequirementCount: ${completion.missingProofRequirementCount || 0}`,
  1953. `- videoAbPreflightReadyForVideoAb: ${completion.videoAbPreflightReadyForVideoAb ? 'true' : 'false'}`,
  1954. `- videoAbPreflightFailureCount: ${completion.videoAbPreflightFailureCount ?? 'unknown'}`,
  1955. `- directCustomerProof: ${completion.directCustomerProof ? 'true' : 'false'}`,
  1956. `- proofLevel: ${completion.proofLevel || 'not_business_proof'}`,
  1957. `- canClaim.tihaoRateImproved: ${completion.canClaim?.tihaoRateImproved ? 'true' : 'false'}`,
  1958. `- canClaim.customerEffectAchieved: ${completion.canClaim?.customerEffectAchieved ? 'true' : 'false'}`,
  1959. `- canClaim.manualSupplementReduced: ${completion.canClaim?.manualSupplementReduced ? 'true' : 'false'}`,
  1960. `- nextRequiredCommandCount: ${completion.nextRequiredCommandCount || 0}`,
  1961. '- 边界:readyForClaim=false 时不得宣称提号率提升、客户效果达标或人工补号量下降。'
  1962. ].join('\n');
  1963. }
  1964. function normalizeRepairArtifacts(repairArtifacts) {
  1965. return repairArtifacts.map(item => ({
  1966. title: item.title || '',
  1967. csv: item.csv || '',
  1968. actionCount: Number(item.actionCount || 0),
  1969. topPriority: Number(item.topPriority || 0),
  1970. fields: Array.isArray(item.fields) ? item.fields : []
  1971. }));
  1972. }
  1973. function renderRepairArtifactSummary(repairArtifacts) {
  1974. if (!repairArtifacts.length) return '无';
  1975. return repairArtifacts
  1976. .map(item => `${item.title || '修复清单'}:${item.csv || '缺失'}(${item.actionCount || 0})`)
  1977. .join(';');
  1978. }
  1979. function tableRow(cells) {
  1980. return `| ${cells.map(escapeCell).join(' | ')} |`;
  1981. }
  1982. function normalizePipelineNextActions(summary) {
  1983. if (!summary || !Array.isArray(summary.nextActions)) return [];
  1984. return summary.nextActions.map(item => ({
  1985. priority: item.priority,
  1986. owner: item.owner,
  1987. title: item.title,
  1988. step: item.step,
  1989. command: item.command,
  1990. expectedArtifact: item.expectedArtifact,
  1991. acceptance: item.acceptance
  1992. }));
  1993. }
  1994. function renderPipelineNextActions(pipeline) {
  1995. const lines = [
  1996. `- 摘要:${pipeline.summary}`,
  1997. `- 报告:${pipeline.report}`,
  1998. `- readyForClaim:${pipeline.readyForClaim ? 'true' : 'false'}`,
  1999. `- pass:${pipeline.counts.pass || 0}`,
  2000. `- fail:${pipeline.counts.fail || 0}`,
  2001. `- nextActions:${pipeline.nextActions.length}`,
  2002. ''
  2003. ];
  2004. if (!pipeline.nextActions.length) {
  2005. lines.push('- 暂无 pipeline 下一步动作。');
  2006. return lines.join('\n');
  2007. }
  2008. lines.push('| 优先级 | 负责人 | 动作 | 触发步骤 | 执行命令 | 通过标准 |');
  2009. lines.push('| --- | --- | --- | --- | --- | --- |');
  2010. for (const item of pipeline.nextActions) {
  2011. lines.push(`| ${item.priority} | ${escapeCell(item.owner)} | ${escapeCell(item.title)} | ${escapeCell(item.step)} | ${escapeCell(item.command)} | ${escapeCell(item.acceptance)} |`);
  2012. }
  2013. return lines.join('\n');
  2014. }
  2015. function parseArgs(argv) {
  2016. const args = {};
  2017. for (let index = 0; index < argv.length; index += 1) {
  2018. const raw = argv[index];
  2019. if (!raw.startsWith('--')) continue;
  2020. const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
  2021. const next = argv[index + 1];
  2022. if (!next || next.startsWith('--')) args[key] = true;
  2023. else {
  2024. args[key] = next;
  2025. index += 1;
  2026. }
  2027. }
  2028. return args;
  2029. }
  2030. function rel(root, file) {
  2031. return path.relative(root, file).replace(/\\/g, '/');
  2032. }
  2033. function artifactPath(root, dir, ...parts) {
  2034. const base = path.isAbsolute(dir) ? dir : path.join(root, dir);
  2035. return path.join(base, ...parts);
  2036. }
  2037. function artifactRef(root, file) {
  2038. const resolvedRoot = path.resolve(root);
  2039. const resolvedFile = path.resolve(file);
  2040. const relative = path.relative(resolvedRoot, resolvedFile);
  2041. if (relative && (relative.startsWith('..') || path.isAbsolute(relative))) return resolvedFile.replace(/\\/g, '/');
  2042. return relative.replace(/\\/g, '/');
  2043. }
  2044. function relIfExists(root, file) {
  2045. return fs.existsSync(file) ? rel(root, file) : null;
  2046. }
  2047. function countRowsWithMissingProofRequirements(rows) {
  2048. if (!Array.isArray(rows)) return 0;
  2049. return rows.filter(row =>
  2050. Array.isArray(row.missingProofRequirements) &&
  2051. row.missingProofRequirements.length >= 1
  2052. ).length;
  2053. }
  2054. function uniqueMissingProofRequirementCount(rows) {
  2055. if (!Array.isArray(rows)) return 0;
  2056. const values = new Set();
  2057. for (const row of rows) {
  2058. const requirements = Array.isArray(row.missingProofRequirements) ? row.missingProofRequirements : [];
  2059. for (const requirement of requirements) {
  2060. if (requirement) values.add(String(requirement));
  2061. }
  2062. }
  2063. return values.size;
  2064. }
  2065. function escapeCell(value) {
  2066. return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' ');
  2067. }
  2068. function withBom(text) {
  2069. return `\uFEFF${text}`;
  2070. }
  2071. if (require.main === module) main();
  2072. module.exports = {
  2073. buildHandoffSummary,
  2074. renderReport
  2075. };