_gen_bid_tech.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # -*- coding: utf-8 -*-
  2. """生成《附件五 技术方案》《附件六 实施与服务方案》各章 Word(格式自拟部分)。
  3. 用法:
  4. python doc/_gen_bid_tech.py 生成全部已就绪章节(单章成文)
  5. python doc/_gen_bid_tech.py 五2 六1 只生成指定章节
  6. python doc/_gen_bid_tech.py --merge 生成并入响应文件用的改号版,
  7. 输出到 _bid_merge_parts/
  8. """
  9. import importlib
  10. import os
  11. import re
  12. import shutil
  13. import sys
  14. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  15. import _bid_docx_kit as kit # noqa: E402
  16. from _bid_docx_kit import Counter, h1, new_doc, para, render # noqa: E402
  17. HERE = os.path.dirname(os.path.abspath(__file__))
  18. SYNC_DIR = r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目招投标"
  19. MERGE_DIR = os.path.join(HERE, "_bid_merge_parts")
  20. # (附件, 章号, 章标题, 分值, 内容模块, 模块内变量名, 并入后的六(三)序号)
  21. CHAPTERS = [
  22. ("五", 1, "项目需求理解及综合分析", 9, ("_bid_ch1_a", "_bid_ch1_b"),
  23. ("BLOCKS_A", "BLOCKS_B"), 4),
  24. ("五", 2, "系统功能展示", 6, ("_bid_ch2",), ("BLOCKS_CH2",), 5),
  25. ("五", 3, "系统建设方案", 3, ("_bid_ch3",), ("BLOCKS_CH3",), 6),
  26. ("五", 4, "智能诊断与分析方案", 3, ("_bid_ch4",), ("BLOCKS_CH4",), 7),
  27. ("五", 5, "UAT 测试与质量保障", 3, ("_bid_ch5",), ("BLOCKS_CH5",), 8),
  28. ("六", 1, "质量与工期保障", 2, ("_bid_att6",), ("BLOCKS_CH6",), 9),
  29. ("六", 2, "应急支撑方案", 2, ("_bid_att6",), ("BLOCKS_CH7",), 10),
  30. ("六", 3, "售后服务方案", 3, ("_bid_att6",), ("BLOCKS_CH8",), 11),
  31. ("六", 4, "项目培训方案", 3, ("_bid_att6",), ("BLOCKS_CH9",), 12),
  32. ]
  33. # 并入总册后的大纲级别(0 起算):章标题→3 级,小节→4 级,
  34. # 更细的层次压到 5、6 级,使总目录只显示到「x.y」。
  35. MERGE_OUTLINE = {1: 2, 2: 3, 3: 4, 4: 5}
  36. LEAD = {
  37. ("五", 1): "本章依据询比文件第五章采购需求与评审办法中技术部分“项目需求理解及综合分析”"
  38. "的评分要素编写,逐一阐述我方对项目现状、应用环境、体系结构需求、功能需求、"
  39. "性能要求、实施要求的理解,对制造业核心业务场景、智能诊断场景、闭环管理逻辑与"
  40. "数据分析需求的理解,并在此基础上输出本项目的总体设计蓝图。",
  41. }
  42. PH = re.compile(r"【[^】]*】|待填|TODO|待补|xxx|XXX")
  43. def _flatten(x):
  44. if isinstance(x, (list, tuple)):
  45. for i in x:
  46. yield from _flatten(i)
  47. elif x is not None:
  48. yield x
  49. def check(blocks):
  50. bad = []
  51. for kind, payload in blocks:
  52. text = " ".join(str(s) for s in _flatten(payload))
  53. for m in PH.finditer(text):
  54. bad.append((kind, m.group(0), text[:48]))
  55. return bad
  56. def stats(doc):
  57. n = sum(len(p.text) for p in doc.paragraphs)
  58. for tb in doc.tables:
  59. for row in tb.rows:
  60. for c in row.cells:
  61. n += len(c.text)
  62. return n
  63. def build(entry, merge=False):
  64. att, no, title, score, mods, names, mno = entry
  65. blocks = []
  66. for mod, var in zip(mods, names):
  67. try:
  68. m = importlib.import_module(mod)
  69. except ModuleNotFoundError:
  70. return None
  71. blocks += getattr(m, var)
  72. bad = check(blocks)
  73. if bad:
  74. for kind, tok, ctx in bad:
  75. print(f" !残留占位 {tok!r} [{kind}] {ctx}")
  76. raise SystemExit(f"附件{att}第 {no} 章存在未填写内容,已中止")
  77. # 改号与大纲级别只影响并入总册的那一份,单章成文的编号保持原样
  78. kit.set_renum(no if merge else None, mno)
  79. kit.set_outline(MERGE_OUTLINE if merge else None)
  80. doc = new_doc()
  81. cnt = Counter(chapter=mno if merge else no)
  82. h1(doc, f"{mno}、{title}" if merge else f"第 {no} 章 {title}")
  83. lead = LEAD.get((att, no))
  84. if lead:
  85. para(doc, lead)
  86. render(doc, cnt, blocks)
  87. kit.set_renum(None, None)
  88. kit.set_outline(None)
  89. kind = "技术方案" if att == "五" else "实施与服务方案"
  90. if merge:
  91. os.makedirs(MERGE_DIR, exist_ok=True)
  92. out = os.path.join(MERGE_DIR, f"六3-{mno:02d} {title}.docx")
  93. else:
  94. out = os.path.join(HERE, f"附件{att}-{kind}-第{no}章 {title}.docx")
  95. doc.save(out)
  96. tag = f"六(三){mno}、" if merge else f"第{no}章 "
  97. print(f"[完成] {tag}{title}({score} 分)"
  98. f" 约 {stats(doc):,} 字 图 {cnt.fig} 张 表 {cnt.tbl} 张")
  99. if not merge and os.path.isdir(SYNC_DIR):
  100. shutil.copy2(out, os.path.join(SYNC_DIR, os.path.basename(out)))
  101. return out
  102. def main():
  103. args = sys.argv[1:]
  104. merge = "--merge" in args
  105. want = [a for a in args if a != "--merge"]
  106. done = skipped = 0
  107. for e in CHAPTERS:
  108. tag = f"{e[0]}{e[1]}"
  109. if want and tag not in want:
  110. continue
  111. if build(e, merge=merge) is None:
  112. print(f"[待写] 附件{e[0]} 第{e[1]}章 {e[2]}({e[3]} 分)")
  113. skipped += 1
  114. else:
  115. done += 1
  116. mode = "并入版" if merge else "单章版"
  117. print(f"\n生成 {done} 章({mode}),待写 {skipped} 章")
  118. if __name__ == "__main__":
  119. main()