# -*- coding: utf-8 -*- """生成《附件五 技术方案》《附件六 实施与服务方案》各章 Word(格式自拟部分)。 用法: python doc/_gen_bid_tech.py 生成全部已就绪章节(单章成文) python doc/_gen_bid_tech.py 五2 六1 只生成指定章节 python doc/_gen_bid_tech.py --merge 生成并入响应文件用的改号版, 输出到 _bid_merge_parts/ """ import importlib import os import re import shutil import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import _bid_docx_kit as kit # noqa: E402 from _bid_docx_kit import Counter, h1, new_doc, para, render # noqa: E402 HERE = os.path.dirname(os.path.abspath(__file__)) SYNC_DIR = r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目招投标" MERGE_DIR = os.path.join(HERE, "_bid_merge_parts") # (附件, 章号, 章标题, 分值, 内容模块, 模块内变量名, 并入后的六(三)序号) CHAPTERS = [ ("五", 1, "项目需求理解及综合分析", 9, ("_bid_ch1_a", "_bid_ch1_b"), ("BLOCKS_A", "BLOCKS_B"), 4), ("五", 2, "系统功能展示", 6, ("_bid_ch2",), ("BLOCKS_CH2",), 5), ("五", 3, "系统建设方案", 3, ("_bid_ch3",), ("BLOCKS_CH3",), 6), ("五", 4, "智能诊断与分析方案", 3, ("_bid_ch4",), ("BLOCKS_CH4",), 7), ("五", 5, "UAT 测试与质量保障", 3, ("_bid_ch5",), ("BLOCKS_CH5",), 8), ("六", 1, "质量与工期保障", 2, ("_bid_att6",), ("BLOCKS_CH6",), 9), ("六", 2, "应急支撑方案", 2, ("_bid_att6",), ("BLOCKS_CH7",), 10), ("六", 3, "售后服务方案", 3, ("_bid_att6",), ("BLOCKS_CH8",), 11), ("六", 4, "项目培训方案", 3, ("_bid_att6",), ("BLOCKS_CH9",), 12), ] # 并入总册后的大纲级别(0 起算):章标题→3 级,小节→4 级, # 更细的层次压到 5、6 级,使总目录只显示到「x.y」。 MERGE_OUTLINE = {1: 2, 2: 3, 3: 4, 4: 5} LEAD = { ("五", 1): "本章依据询比文件第五章采购需求与评审办法中技术部分“项目需求理解及综合分析”" "的评分要素编写,逐一阐述我方对项目现状、应用环境、体系结构需求、功能需求、" "性能要求、实施要求的理解,对制造业核心业务场景、智能诊断场景、闭环管理逻辑与" "数据分析需求的理解,并在此基础上输出本项目的总体设计蓝图。", } PH = re.compile(r"【[^】]*】|待填|TODO|待补|xxx|XXX") def _flatten(x): if isinstance(x, (list, tuple)): for i in x: yield from _flatten(i) elif x is not None: yield x def check(blocks): bad = [] for kind, payload in blocks: text = " ".join(str(s) for s in _flatten(payload)) for m in PH.finditer(text): bad.append((kind, m.group(0), text[:48])) return bad def stats(doc): n = sum(len(p.text) for p in doc.paragraphs) for tb in doc.tables: for row in tb.rows: for c in row.cells: n += len(c.text) return n def build(entry, merge=False): att, no, title, score, mods, names, mno = entry blocks = [] for mod, var in zip(mods, names): try: m = importlib.import_module(mod) except ModuleNotFoundError: return None blocks += getattr(m, var) bad = check(blocks) if bad: for kind, tok, ctx in bad: print(f" !残留占位 {tok!r} [{kind}] {ctx}") raise SystemExit(f"附件{att}第 {no} 章存在未填写内容,已中止") # 改号与大纲级别只影响并入总册的那一份,单章成文的编号保持原样 kit.set_renum(no if merge else None, mno) kit.set_outline(MERGE_OUTLINE if merge else None) doc = new_doc() cnt = Counter(chapter=mno if merge else no) h1(doc, f"{mno}、{title}" if merge else f"第 {no} 章 {title}") lead = LEAD.get((att, no)) if lead: para(doc, lead) render(doc, cnt, blocks) kit.set_renum(None, None) kit.set_outline(None) kind = "技术方案" if att == "五" else "实施与服务方案" if merge: os.makedirs(MERGE_DIR, exist_ok=True) out = os.path.join(MERGE_DIR, f"六3-{mno:02d} {title}.docx") else: out = os.path.join(HERE, f"附件{att}-{kind}-第{no}章 {title}.docx") doc.save(out) tag = f"六(三){mno}、" if merge else f"第{no}章 " print(f"[完成] {tag}{title}({score} 分)" f" 约 {stats(doc):,} 字 图 {cnt.fig} 张 表 {cnt.tbl} 张") if not merge and os.path.isdir(SYNC_DIR): shutil.copy2(out, os.path.join(SYNC_DIR, os.path.basename(out))) return out def main(): args = sys.argv[1:] merge = "--merge" in args want = [a for a in args if a != "--merge"] done = skipped = 0 for e in CHAPTERS: tag = f"{e[0]}{e[1]}" if want and tag not in want: continue if build(e, merge=merge) is None: print(f"[待写] 附件{e[0]} 第{e[1]}章 {e[2]}({e[3]} 分)") skipped += 1 else: done += 1 mode = "并入版" if merge else "单章版" print(f"\n生成 {done} 章({mode}),待写 {skipped} 章") if __name__ == "__main__": main()