_gen_s9_diag_impr_int_delivery.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. # -*- coding: utf-8 -*-
  2. """
  3. 生成交付文档:S9 运营指标与智慧看板、运营诊断、运营改善、系统集成
  4. (业务需求描述 / 蓝图设计方案 / 测试用例 / 三联对照表)
  5. 内容对齐:项目合同里的技术规范 2.6 / 2.6.1~2.6.3 / 2.7(数据见 _gen_s9_modules_data.py)。
  6. 输出根目录:交付文档 OneDrive 路径(与 S1 同级)。
  7. """
  8. from __future__ import annotations
  9. from pathlib import Path
  10. from docx import Document
  11. from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
  12. from docx.oxml import OxmlElement
  13. from docx.oxml.ns import qn
  14. from docx.shared import Cm, Pt, RGBColor
  15. from openpyxl import Workbook, load_workbook
  16. from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
  17. from openpyxl.utils import get_column_letter
  18. from _gen_s9_modules_data import MODULES
  19. BASE = Path(r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档")
  20. REPO_DOC = Path(__file__).resolve().parent
  21. ACCEPTANCE_XLSX = REPO_DOC / ".feishu-cache" / "full-acceptance-backup-20260728" / "Ai-DOP全量功能验收对照表_系统166项版.xlsx"
  22. MODULE_OVERVIEW_MD = REPO_DOC / "Ai-DOP系统各模块情况一览表.md"
  23. FONT = "微软雅黑"
  24. BLUE = RGBColor(0, 0, 0)
  25. HEADER_FILL = "D6E3F0"
  26. CN_NUM = "零一二三四五六七八九十"
  27. TODAY = "2026-09-04"
  28. TECH_SPEC_NAME = "项目合同里的技术规范"
  29. thin = Side(style="thin", color="000000")
  30. THIN_BORDER = Border(left=thin, right=thin, top=thin, bottom=thin)
  31. HDR_FILL = PatternFill("solid", fgColor="1F3864")
  32. HDR_FONT = Font(name=FONT, bold=True, color="FFFFFF", size=10)
  33. CELL_FONT = Font(name=FONT, size=10)
  34. WRAP = Alignment(wrap_text=True, vertical="center")
  35. # 模块正文数据:_gen_s9_modules_data.MODULES(对齐技术规范书 2.6 / 2.7)
  36. # ─────────────────────────────────────────────────────────────
  37. # Word 辅助
  38. # ─────────────────────────────────────────────────────────────
  39. def set_run_font(run, *, size=None, bold=None, color=None):
  40. run.font.name = FONT
  41. r_pr = run._element.get_or_add_rPr()
  42. r_fonts = r_pr.rFonts
  43. if r_fonts is None:
  44. r_fonts = OxmlElement("w:rFonts")
  45. r_pr.insert(0, r_fonts)
  46. r_fonts.set(qn("w:ascii"), FONT)
  47. r_fonts.set(qn("w:hAnsi"), FONT)
  48. r_fonts.set(qn("w:eastAsia"), FONT)
  49. if size is not None:
  50. run.font.size = size
  51. if bold is not None:
  52. run.bold = bold
  53. if color is not None:
  54. run.font.color.rgb = color
  55. def add_para(doc, text="", *, size=10.5, bold=False, color=None, align=None, space_after=6, style=None):
  56. p = doc.add_paragraph(style=style) if style else doc.add_paragraph()
  57. if align is not None:
  58. p.alignment = align
  59. p.paragraph_format.space_after = Pt(space_after)
  60. p.paragraph_format.line_spacing = 1.5
  61. run = p.add_run(text)
  62. set_run_font(run, size=Pt(size), bold=bold, color=color)
  63. return p
  64. def add_page_break(doc):
  65. p = doc.add_paragraph()
  66. p.add_run().add_break(WD_BREAK.PAGE)
  67. def shade_cell(cell, fill: str):
  68. tc_pr = cell._element.get_or_add_tcPr()
  69. old = tc_pr.find(qn("w:shd"))
  70. if old is not None:
  71. tc_pr.remove(old)
  72. shd = OxmlElement("w:shd")
  73. shd.set(qn("w:val"), "clear")
  74. shd.set(qn("w:color"), "auto")
  75. shd.set(qn("w:fill"), fill)
  76. tc_pr.append(shd)
  77. def set_cell_text(cell, text, *, bold=False, color=None, size=10.5, center=False):
  78. cell.text = ""
  79. p = cell.paragraphs[0]
  80. if center:
  81. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  82. run = p.add_run(str(text))
  83. set_run_font(run, size=Pt(size), bold=bold, color=color)
  84. def add_info_table(doc, doc_no: str):
  85. table = doc.add_table(rows=5, cols=2)
  86. table.style = "Table Grid"
  87. rows = [
  88. ("文档编号", doc_no),
  89. ("版本", "V0.4"),
  90. ("密级", "内部"),
  91. ("编制单位", "智造易项目组"),
  92. ("编制日期", TODAY),
  93. ]
  94. for i, (k, v) in enumerate(rows):
  95. set_cell_text(table.rows[i].cells[0], k, bold=True, center=True)
  96. set_cell_text(table.rows[i].cells[1], v, center=True)
  97. shade_cell(table.rows[i].cells[0], "D6E3F0")
  98. for row in table.rows:
  99. for cell in row.cells:
  100. cell.width = Cm(7)
  101. def add_version_table(doc):
  102. table = doc.add_table(rows=5, cols=4)
  103. table.style = "Table Grid"
  104. headers = ["日期", "版本", "修订人", "变更说明"]
  105. for i, h in enumerate(headers):
  106. set_cell_text(table.rows[0].cells[i], h, bold=True, color=RGBColor(0, 0, 0), center=True)
  107. shade_cell(table.rows[0].cells[i], HEADER_FILL)
  108. data = [
  109. ("2026-07-30", "V0.1", "智造易项目组", "初版建立"),
  110. ("2026-07-30", "V0.2", "智造易项目组", "对齐项目合同里的技术规范需求与功能要求;统一 REQ/FUNC 编号与版式"),
  111. ("2026-09-04", "V0.3", "智造易项目组", "九宫格含 S9 全场关键指标看板;数据对接服务归入系统集成"),
  112. ("2026-09-04", "V0.4", "智造易项目组", "统一系统名称与文档版式,并修订范围与用词"),
  113. ]
  114. for r, row in enumerate(data, 1):
  115. for c, val in enumerate(row):
  116. set_cell_text(table.rows[r].cells[c], val, center=True)
  117. def add_cover(doc, module_title: str, doc_type: str, doc_no: str):
  118. for _ in range(3):
  119. add_para(doc, "")
  120. add_para(doc, module_title, size=28, bold=True, color=BLUE, align=WD_ALIGN_PARAGRAPH.CENTER, space_after=12)
  121. add_para(doc, doc_type, size=22, bold=True, color=BLUE, align=WD_ALIGN_PARAGRAPH.CENTER, space_after=10)
  122. add_para(doc, "Ai-DOP智慧运营管理系统", size=13, align=WD_ALIGN_PARAGRAPH.CENTER, space_after=18)
  123. add_info_table(doc, doc_no)
  124. add_page_break(doc)
  125. add_para(doc, "版本记录", size=16, bold=True, space_after=12)
  126. add_version_table(doc)
  127. add_page_break(doc)
  128. add_para(doc, "目录", size=16, bold=True, space_after=8)
  129. add_page_break(doc)
  130. def heading1(doc, text: str):
  131. p = doc.add_paragraph(style="Heading 1")
  132. p.clear()
  133. run = p.add_run(text)
  134. set_run_font(run, size=Pt(16), bold=True)
  135. return p
  136. def heading2(doc, text: str):
  137. p = doc.add_paragraph(style="Heading 2")
  138. p.clear()
  139. run = p.add_run(text)
  140. set_run_font(run, size=Pt(14), bold=True)
  141. return p
  142. def heading3(doc, text: str):
  143. p = doc.add_paragraph(style="Heading 3")
  144. p.clear()
  145. run = p.add_run(text)
  146. set_run_font(run, size=Pt(12), bold=True)
  147. return p
  148. def write_brd(mod: dict, out_path: Path):
  149. doc = Document()
  150. section = doc.sections[0]
  151. section.top_margin = Cm(2)
  152. section.bottom_margin = Cm(2)
  153. section.left_margin = Cm(2.5)
  154. section.right_margin = Cm(2.5)
  155. add_cover(doc, mod["module_title"], "业务需求描述", mod["doc_no_brd"])
  156. heading1(doc, "1 范围说明")
  157. add_para(doc, f"本文件描述「{mod['module_title']}」业务需求,系统名称为 Ai-DOP智慧运营管理系统。")
  158. add_para(doc, f"报价范围对齐:{mod['quote_scope']}。")
  159. add_para(doc, f"需求与功能要求以{TECH_SPEC_NAME}对应章节为准,并与报价单、三联对照表一致;不得偏离技术规范已列明的能力边界。")
  160. add_para(doc, "需求编号格式 REQ-S9-nnn;与功能编号、测试编号通过三联对照表串联。")
  161. if mod.get("intro"):
  162. add_para(doc, mod["intro"])
  163. heading1(doc, "2 技术规范追溯")
  164. for t in mod.get("tech_spec", []):
  165. if mod.get("intro") and t == mod["intro"]:
  166. continue
  167. add_para(doc, t)
  168. features = [f for f in mod["features"] if f.get("impl_status") != "未实现"]
  169. for i, feat in enumerate(features, 1):
  170. heading1(doc, f"({CN_NUM[i]}){feat['name']}({feat['req']})")
  171. if feat.get("tech_clause"):
  172. heading2(doc, "技术规范条款")
  173. add_para(doc, feat["tech_clause"])
  174. heading2(doc, "功能说明")
  175. for t in feat["func_desc"]:
  176. add_para(doc, t)
  177. heading2(doc, "业务描述")
  178. for t in feat["biz_desc"]:
  179. add_para(doc, t)
  180. add_para(doc, f"对应功能编号:{feat['func']};系统路径:{feat['path']}。", bold=True)
  181. heading1(doc, f"({CN_NUM[len(features) + 1]})权限管理汇总")
  182. for feat in features:
  183. add_para(doc, f"{feat['name']}:{feat['roles']}")
  184. from _delivery_docx_polish import force_all_text_black, rebuild_static_toc
  185. rebuild_static_toc(doc)
  186. force_all_text_black(doc)
  187. out_path.parent.mkdir(parents=True, exist_ok=True)
  188. doc.save(out_path)
  189. print("BRD", out_path)
  190. def write_bbp(mod: dict, out_path: Path):
  191. doc = Document()
  192. section = doc.sections[0]
  193. section.top_margin = Cm(2)
  194. section.bottom_margin = Cm(2)
  195. section.left_margin = Cm(2.5)
  196. section.right_margin = Cm(2.5)
  197. add_cover(doc, mod["module_title"], "业务蓝图设计方案", mod["doc_no_bbp"])
  198. heading1(doc, "1 总体业务方案")
  199. heading2(doc, "1.1 目标和宗旨")
  200. add_para(doc, f"落实报价范围「{mod['quote_scope']}」,并将{TECH_SPEC_NAME}中对应功能要求转化为可配置、可验收的功能方案。")
  201. if mod.get("intro"):
  202. add_para(doc, mod["intro"])
  203. for t in mod.get("tech_spec", [])[:3]:
  204. if mod.get("intro") and t == mod["intro"]:
  205. continue
  206. add_para(doc, t)
  207. heading2(doc, "1.2 总体业务流程图")
  208. add_para(doc, "总体路径:系统集成数据对接服务采集与回写 → 指标日批计算 → 九宫格总览(含 S9 全场关键指标看板)→ 智慧诊断 → 改善建档派单跟踪与效果复盘闭环。")
  209. heading2(doc, "1.3 方案设计")
  210. add_para(doc, "前端:Vue3 + Element Plus;动态看板与智慧诊断闭环页。后端:Admin.NET + 数据对接服务分层(贴源/标准/明细/指标)+ 出站回写队列;对接方式含数据库直连与 HTTP API。")
  211. for idx, feat in enumerate([f for f in mod["features"] if f.get("impl_status") != "未实现"], 2):
  212. heading1(doc, f"{idx} {feat['name']}({feat['func']})")
  213. heading2(doc, f"{idx}.1 目标/宗旨")
  214. add_para(doc, feat["goal"])
  215. if feat.get("tech_clause"):
  216. add_para(doc, f"规范追溯:{feat['tech_clause']}", bold=True)
  217. heading2(doc, f"{idx}.2 业务流程图")
  218. add_para(doc, "流程步骤见下表(文字流程图)。")
  219. table = doc.add_table(rows=1 + len(feat["flow_steps"]), cols=6)
  220. table.style = "Table Grid"
  221. headers = ["步骤", "活动", "角色", "操作", "输入", "输出"]
  222. for c, h in enumerate(headers):
  223. set_cell_text(table.rows[0].cells[c], h, bold=True, color=RGBColor(0, 0, 0), center=True)
  224. shade_cell(table.rows[0].cells[c], "4472C4")
  225. for r, step in enumerate(feat["flow_steps"], 1):
  226. for c, val in enumerate(step):
  227. set_cell_text(table.rows[r].cells[c], val, size=9)
  228. add_para(doc, "")
  229. heading2(doc, f"{idx}.3 业务流程说明")
  230. for t in feat["biz_desc"]:
  231. add_para(doc, t)
  232. heading2(doc, f"{idx}.4 业务流程规则")
  233. for t in feat["rules"]:
  234. add_para(doc, "• " + t)
  235. heading2(doc, f"{idx}.5 权限管理需求")
  236. add_para(doc, feat["roles"])
  237. heading2(doc, f"{idx}.6 系统接口集成")
  238. add_para(doc, feat["interfaces"])
  239. heading2(doc, f"{idx}.7 报表需求")
  240. add_para(doc, feat["reports"])
  241. add_para(doc, f"对应需求:{feat['req']};路由:{feat['route']}。", bold=True)
  242. from _delivery_docx_polish import force_all_text_black, rebuild_static_toc
  243. rebuild_static_toc(doc)
  244. force_all_text_black(doc)
  245. out_path.parent.mkdir(parents=True, exist_ok=True)
  246. doc.save(out_path)
  247. print("BBP", out_path)
  248. # ─────────────────────────────────────────────────────────────
  249. # Excel:测试用例 / 三联对照
  250. # ─────────────────────────────────────────────────────────────
  251. def style_header(ws, row, cols):
  252. for c in range(1, cols + 1):
  253. cell = ws.cell(row=row, column=c)
  254. cell.fill = HDR_FILL
  255. cell.font = HDR_FONT
  256. cell.alignment = WRAP
  257. cell.border = THIN_BORDER
  258. def autosize(ws, max_width=48):
  259. for col in ws.columns:
  260. letter = get_column_letter(col[0].column)
  261. width = 12
  262. for cell in col:
  263. if cell.value:
  264. width = min(max_width, max(width, len(str(cell.value)) + 2))
  265. ws.column_dimensions[letter].width = width
  266. def write_test_xlsx(mod: dict, out_path: Path):
  267. wb = Workbook()
  268. # 目录
  269. ws = wb.active
  270. ws.title = "目录"
  271. ws["B2"] = f"{mod['module_title']}业务场景目录"
  272. ws["B2"].font = Font(name=FONT, bold=True, size=14)
  273. headers = ["序号", "测试分场景", "TC数量", "关联FUNC", "关联REQ"]
  274. for i, h in enumerate(headers, 2):
  275. ws.cell(row=3, column=i, value=h)
  276. style_header(ws, 3, 6)
  277. for i, feat in enumerate(mod["features"], 1):
  278. ws.cell(row=3 + i, column=2, value=i)
  279. ws.cell(row=3 + i, column=3, value=feat["name"])
  280. ws.cell(row=3 + i, column=4, value=f"{len(feat['tcs'])} 条")
  281. ws.cell(row=3 + i, column=5, value=feat["func"])
  282. ws.cell(row=3 + i, column=6, value=feat["req"])
  283. # 各场景 sheet
  284. tc_headers = [
  285. "用例编号", "测试内容", "测试步骤", "系统功能", "预期结果",
  286. "测试结果1st", "测试结果2nd", "角色", "登录用户名", "相关单据编号",
  287. "备注", "TC编号", "关联FUNC", "关联REQ",
  288. ]
  289. for si, feat in enumerate(mod["features"], 1):
  290. name = f"{si} {feat['name']}"[:31]
  291. w = wb.create_sheet(name)
  292. w["A1"] = "详细测试结果"
  293. w["A1"].font = Font(name=FONT, bold=True, size=12)
  294. w["K3"] = "测试内容"
  295. w["K4"] = feat["name"]
  296. w["M4"] = f"{si}"
  297. for c, h in enumerate(tc_headers, 1):
  298. w.cell(row=10, column=c, value=h)
  299. style_header(w, 10, len(tc_headers))
  300. for r, tc in enumerate(feat["tcs"], 12):
  301. raw, code, typ, title, steps, expect = tc
  302. vals = [
  303. raw, title, steps, title, expect,
  304. "", "", "运营专员/运维", "AIDOPDemo", "",
  305. typ, code, feat["func"], feat["req"],
  306. ]
  307. for c, v in enumerate(vals, 1):
  308. cell = w.cell(row=r, column=c, value=v)
  309. cell.font = CELL_FONT
  310. cell.alignment = WRAP
  311. cell.border = THIN_BORDER
  312. w.row_dimensions[r].height = 60
  313. autosize(w)
  314. autosize(ws)
  315. out_path.parent.mkdir(parents=True, exist_ok=True)
  316. wb.save(out_path)
  317. print("TC ", out_path)
  318. def write_triple_xlsx(mod: dict, out_path: Path):
  319. wb = Workbook()
  320. ws0 = wb.active
  321. ws0.title = "验收范围说明"
  322. ws0["A1"] = "项"
  323. ws0["B1"] = "说明"
  324. style_header(ws0, 1, 2)
  325. rows = [
  326. ("验收范围基线", "产互联 ai-dop 技术规范及现行报价单"),
  327. ("验收编号", "只有纳入技术规范验收的 FUNC 填写同号 AC"),
  328. ("关联TC", "测试用例编号,用于支撑 AC 验收结论"),
  329. ("报价对齐", mod["quote_scope"]),
  330. ("系统扩展功能", "保留原 FUNC/TC 作为系统资料;本表已标注验收范围"),
  331. ]
  332. for i, (a, b) in enumerate(rows, 2):
  333. ws0.cell(row=i, column=1, value=a).font = CELL_FONT
  334. ws0.cell(row=i, column=2, value=b).font = CELL_FONT
  335. ws = wb.create_sheet("TC-FUNC-REQ对照表")
  336. headers = [
  337. "需求编号", "需求名称", "功能编号", "验收编号", "验收范围",
  338. "功能名称", "测试编号", "测试内容", "原始编号", "类型", "实际系统菜单路径",
  339. ]
  340. for c, h in enumerate(headers, 1):
  341. ws.cell(row=1, column=c, value=h)
  342. style_header(ws, 1, len(headers))
  343. r = 2
  344. for feat in mod["features"]:
  345. for tc in feat["tcs"]:
  346. raw, code, typ, title, _steps, _expect = tc
  347. vals = [
  348. feat["req"], feat["name"], feat["func"], feat.get("ac", ""),
  349. feat.get("scope", ""), feat["name"], code, title, raw, typ, feat["path"],
  350. ]
  351. for c, v in enumerate(vals, 1):
  352. cell = ws.cell(row=r, column=c, value=v)
  353. cell.font = CELL_FONT
  354. cell.alignment = WRAP
  355. cell.border = THIN_BORDER
  356. r += 1
  357. ws2 = wb.create_sheet("覆盖度统计")
  358. h2 = ["模块", "REQ", "FUNC", "TC(正常)", "TC(异常)", "TC合计", "实际系统菜单路径"]
  359. for c, h in enumerate(h2, 1):
  360. ws2.cell(row=1, column=c, value=h)
  361. style_header(ws2, 1, len(h2))
  362. total_n = total_e = 0
  363. for i, feat in enumerate(mod["features"], 2):
  364. n = sum(1 for t in feat["tcs"] if t[2] == "正常")
  365. e = sum(1 for t in feat["tcs"] if t[2] == "异常")
  366. total_n += n
  367. total_e += e
  368. vals = [feat["name"], 1, 1, n, e, n + e, feat["path"]]
  369. for c, v in enumerate(vals, 1):
  370. ws2.cell(row=i, column=c, value=v).font = CELL_FONT
  371. last = len(mod["features"]) + 2
  372. ws2.cell(row=last, column=1, value="合计").font = Font(name=FONT, bold=True)
  373. ws2.cell(row=last, column=2, value=len(mod["features"]))
  374. ws2.cell(row=last, column=3, value=len({f["func"] for f in mod["features"]}))
  375. ws2.cell(row=last, column=4, value=total_n)
  376. ws2.cell(row=last, column=5, value=total_e)
  377. ws2.cell(row=last, column=6, value=total_n + total_e)
  378. for sheet in (ws0, ws, ws2):
  379. autosize(sheet)
  380. out_path.parent.mkdir(parents=True, exist_ok=True)
  381. wb.save(out_path)
  382. print("TRI", out_path)
  383. def update_acceptance_xlsx():
  384. if not ACCEPTANCE_XLSX.exists():
  385. print("SKIP acceptance: not found", ACCEPTANCE_XLSX)
  386. return
  387. # 聚合 TC(去重保序)
  388. tc_map: dict[str, list[str]] = {}
  389. req_map: dict[str, str] = {}
  390. meta_map: dict[str, dict] = {}
  391. for mod in MODULES.values():
  392. for feat in mod["features"]:
  393. func = feat["func"]
  394. req_map[func] = feat["req"] if func not in req_map else (
  395. req_map[func] if feat["req"] in req_map[func] else f"{req_map[func]}; {feat['req']}"
  396. )
  397. tc_map.setdefault(func, [])
  398. for tc in feat["tcs"]:
  399. if tc[1] not in tc_map[func]:
  400. tc_map[func].append(tc[1])
  401. meta_map[func] = {
  402. "name": feat["name"] if func not in meta_map else meta_map[func]["name"],
  403. "ac": feat.get("ac", ""),
  404. "route": feat.get("route", feat.get("path", "")),
  405. }
  406. wb = load_workbook(ACCEPTANCE_XLSX)
  407. for sheet_name in ("全量FUNC-AC对照", "S9"):
  408. if sheet_name not in wb.sheetnames:
  409. continue
  410. ws = wb[sheet_name]
  411. headers = {ws.cell(1, c).value: c for c in range(1, ws.max_column + 1)}
  412. col_func = headers.get("功能编号")
  413. col_req = headers.get("关联REQ")
  414. col_tcn = headers.get("TC数量")
  415. col_tc = headers.get("关联TC")
  416. col_name = headers.get("功能名称")
  417. col_ac = headers.get("验收编号")
  418. col_route = headers.get("系统路由/入口")
  419. col_mod = headers.get("模块")
  420. col_modname = headers.get("模块名称")
  421. if not col_func:
  422. continue
  423. existing = set()
  424. last_row = 1
  425. for r in range(2, ws.max_row + 1):
  426. func = ws.cell(r, col_func).value
  427. if func:
  428. existing.add(func)
  429. last_row = r
  430. if func in tc_map:
  431. tcs = tc_map[func]
  432. if col_req:
  433. ws.cell(r, col_req).value = req_map.get(func, "待补")
  434. if col_tcn:
  435. ws.cell(r, col_tcn).value = len(tcs)
  436. if col_tc:
  437. ws.cell(r, col_tc).value = "; ".join(tcs)
  438. # 追加缺失 FUNC(如 ChatBI S9-007)
  439. for func, tcs in tc_map.items():
  440. if func in existing:
  441. continue
  442. last_row += 1
  443. meta = meta_map.get(func, {})
  444. if col_mod:
  445. ws.cell(last_row, col_mod).value = "S9"
  446. if col_modname:
  447. ws.cell(last_row, col_modname).value = "运营指标与平台扩展"
  448. ws.cell(last_row, col_func).value = func
  449. if col_name:
  450. ws.cell(last_row, col_name).value = meta.get("name", "")
  451. if col_ac:
  452. ws.cell(last_row, col_ac).value = meta.get("ac", "")
  453. if col_req:
  454. ws.cell(last_row, col_req).value = req_map.get(func, "")
  455. if col_tcn:
  456. ws.cell(last_row, col_tcn).value = len(tcs)
  457. if col_tc:
  458. ws.cell(last_row, col_tc).value = "; ".join(tcs)
  459. if col_route:
  460. ws.cell(last_row, col_route).value = meta.get("route", "")
  461. wb.save(ACCEPTANCE_XLSX)
  462. print("UPD", ACCEPTANCE_XLSX)
  463. dest = BASE / "Ai-DOP全量功能验收对照表.xlsx"
  464. try:
  465. wb2 = load_workbook(ACCEPTANCE_XLSX)
  466. wb2.save(dest)
  467. print("UPD", dest)
  468. except Exception as e:
  469. print("SKIP write delivery acceptance:", e)
  470. alt = BASE / "Ai-DOP全量功能验收对照表_已补S9诊断改善集成.xlsx"
  471. try:
  472. wb3 = load_workbook(ACCEPTANCE_XLSX)
  473. wb3.save(alt)
  474. print("UPD", alt)
  475. except Exception as e:
  476. print("SKIP alt acceptance:", e)
  477. def update_module_overview():
  478. if not MODULE_OVERVIEW_MD.exists():
  479. return
  480. text = MODULE_OVERVIEW_MD.read_text(encoding="utf-8")
  481. row_updates = {
  482. "S9 运营指标与智慧看板": "| 10 | S9 运营指标与智慧看板 | 完成 | 完成 | 待完成 | 完成 | 完成 | 待完成 | 待完成 | 待完成 | 主体已实现;部门看板/S8预警演示数据待补;文档已对齐实现现状 |",
  483. "运营诊断": "| 11 | 运营诊断 | 完成 | 待完成 | 待完成 | 完成 | 完成 | 待完成 | 待完成 | 待完成 | 交互诊断已实现;正式诊断报告落库/导出待补 |",
  484. "运营改善": "| 12 | 运营改善 | 完成 | 待完成 | 待完成 | 完成 | 完成 | 待完成 | 待完成 | 待完成 | 建档/审批/验证已通;独立派单通道待增强;FUNC并入S9-006 |",
  485. "ChatBI": "| 13 | ChatBI | 部分完成 | 待完成 | 待完成 | 完成 | 完成 | 待完成 | 待完成 | 待完成 | KPI聚合问答MVP已上线;NL2SQL/历史/独立页待补(FUNC-S9-007) |",
  486. "系统集成": "| 16 | 系统集成 | 部分完成 | 待完成 | 待完成 | 完成 | 完成 | 待完成 | 待完成 | 待完成 | 001~003为占位;真实同步在数据对接服务;企业SSO未落地 |",
  487. }
  488. lines = text.splitlines()
  489. out = []
  490. for line in lines:
  491. replaced = False
  492. if line.startswith("|"):
  493. cols = [c.strip() for c in line.split("|")]
  494. if len(cols) > 2:
  495. for key, new_row in row_updates.items():
  496. if cols[2] == key or cols[2].startswith(key):
  497. out.append(new_row)
  498. replaced = True
  499. break
  500. if not replaced:
  501. out.append(line)
  502. text2 = "\n".join(out) + "\n"
  503. # 汇总:需求/蓝图含 ChatBI 后为 13
  504. text2 = text2.replace("| 需求文档 | 12 | 0 | 4 |", "| 需求文档 | 13 | 0 | 3 |")
  505. text2 = text2.replace("| 蓝图设计 | 12 | 0 | 4 |", "| 蓝图设计 | 13 | 0 | 3 |")
  506. text2 = text2.replace("| 需求文档 | 13 | 0 | 3 |", "| 需求文档 | 13 | 0 | 3 |")
  507. MODULE_OVERVIEW_MD.write_text(text2, encoding="utf-8")
  508. print("UPD", MODULE_OVERVIEW_MD)
  509. def main():
  510. BASE.mkdir(parents=True, exist_ok=True)
  511. for mod in MODULES.values():
  512. folder = BASE / mod["folder"]
  513. folder.mkdir(parents=True, exist_ok=True)
  514. write_brd(mod, folder / mod["brd_name"])
  515. write_bbp(mod, folder / mod["bbp_name"])
  516. write_test_xlsx(mod, folder / mod["tc_name"])
  517. write_triple_xlsx(mod, folder / mod["triple_name"])
  518. update_acceptance_xlsx()
  519. update_module_overview()
  520. print("DONE")
  521. if __name__ == "__main__":
  522. main()