"""Word 初稿渲染(python-docx)。 初稿结构:封面信息 → 目录骨架 → 六章(3/4/5 章自动成文,1/2/6 章素材要点 + [待补充] 占位)。 写入技能 output/ 目录(用户可取)。python-docx 为技能特有依赖(requirements.txt 声明)。 """ from __future__ import annotations from typing import Any from util.logging_config import get_skill_logger from util.runtime_paths import get_output_dir def _log(): try: return get_skill_logger() except RuntimeError: return None def _check_docx_available() -> None: try: import docx # noqa: F401 except ImportError as exc: raise RuntimeError( "python-docx 未安装(技能依赖安装异常);请重新安装技能,或先用 HTML 预览查看初稿内容。" ) from exc def write_docx(project: dict[str, Any], sections: dict[str, list[str]], batch_no: str) -> str: """生成《结题报告(初稿)》docx,返回文件路径。""" _check_docx_available() import docx from docx.enum.text import WD_COLOR_INDEX from docx.shared import Pt document = docx.Document() log = _log() if log: log.info("docx_render_start project_no=%s stage=docx", project.get("project_no")) title = document.add_heading("结题报告(初稿)", level=0) title.alignment = 1 info_table = document.add_table(rows=0, cols=2) info_table.style = "Table Grid" for label, value in ( ("项目名称", project.get("project_name")), ("项目编号", project.get("project_no")), ("项目负责人", project.get("pi_name")), ("所属学院", project.get("department")), ("项目类型", project.get("program")), ("执行周期", f"{project.get('start_date') or '—'} ~ {project.get('end_date') or '—'}"), ("生成批次", batch_no), ): row = info_table.add_row().cells row[0].text = str(label) row[1].text = str(value if value not in (None, "") else "—") document.add_heading("目录(骨架,终稿请按本单位模板调整)", level=1) for section_title in sections: document.add_paragraph(section_title, style="List Number") for section_title, paragraphs in sections.items(): document.add_heading(section_title, level=1) for paragraph in paragraphs: text = str(paragraph) p = document.add_paragraph() run = p.add_run(text) if text.startswith("[待补充"): run.italic = True run.font.highlight_color = WD_COLOR_INDEX.YELLOW # 占位黄底提示,便于教师定位待写章节 notice = document.add_paragraph() notice_run = notice.add_run( "说明:本初稿由结题报告初稿助手按台账数据自动生成;数字章节(完成情况/决算/成果清单)请逐项核对," "叙事章节仅提供素材要点,须由项目负责人本人撰写终稿;严禁引入台账之外的成果或结论。" ) notice_run.font.size = Pt(9) project_no = str(project.get("project_no") or "未编号") filename = f"结题报告初稿-{project_no}.docx" path = get_output_dir() + "/" + filename document.save(path) if log: log.info("docx_render_done project_no=%s path=%s stage=docx", project_no, path) return path