医院文档一键排版 Skill — 完整包
> 功能:将医院体系内「制度 / 职责 / 规程 / SOP」类文档草稿,一键套版为符合公文版式的 .docx 文件。
> 核心原则:只改格式,绝不修改任何业务文字。
> 输出:带页眉 LOGO、起草审核表、文件日志、四号仿宋正文、固定行距、四级编号体系的 Word 文档。
> 环境依赖:Python 3.8+、pip install python-docx pyyaml。
目 录
- 排版规范速查
- 安装说明
- 使用方式
- 输入格式说明
- 完整 Python 脚本
- YAML 输入模板
- 常见问题
一、排版规范速查
以下为医院公文排版标准,脚本已全部内置实现:
1.1 页面
| 项 | 取值 |
|---|---|
| 纸张 | A4(21 × 29.7 cm) |
| 上/下边距 | 2.54 cm |
| 左右边距 | 3.18 cm |
| 页眉距边 | 1.5 cm |
| 页脚距边 | 1.75 cm |
1.2 页眉(三栏)
| 左 | 中 | 右 |
|---|---|---|
| 医院 LOGO | 编写部门/科室 | 文件编号 |
- 字体:方正小标宋简体 / Times New Roman
- 字号:小五(9 pt)
- 下方一根黑色横线
1.3 页脚
第 X 页 共 X 页
- 字体:仿宋_GB2312 / Times New Roman
- 字号:小五(9 pt),居中
- 使用 Word PAGE / NUMPAGES 字段(首次打开按 Ctrl+A → F9 刷新)
1.4 文件主标题
| 项 | 值 |
|---|---|
| 字体 | 方正小标宋简体 |
| 字号 | 小二(18 pt) |
| 字重 | 加粗 |
| 对齐 | 居中 |
| 行距 | 固定 36 磅 |
1.5 起草审核表(表头表)
| 起草/修订人 | 起草/修订时间 | 审核人 | 审核日期 | 批准人 | 批准日期 | 发布日期 | 生效日期 |
|---|---|---|---|---|---|---|---|
- 字体:仿宋_GB2312,四号(14 pt),单倍行距
- 表头加粗居中,内容居中
1.6 文件日志表
| 文件编码 | 名称 | 生效日期 | 起草/修订内容 | 起草/修订人 |
|---|---|---|---|---|
- 表头:四号(14 pt)加粗
- 内容:小四(12 pt)
- 行距:单倍
1.7 正文格式
| 项 | 值 |
|---|---|
| 字体 | 仿宋_GB2312 / Times New Roman |
| 字号 | 四号(14 pt) |
| 行距 | 固定 28 磅 |
| 对齐 | 两端对齐 |
| 首行缩进 | 2 字符(≈ 28 pt) |
1.8 编号层级
| 层级 | 写法 | 加粗 |
|---|---|---|
| 第一级(章) | 一、二、三、… | 加粗 |
| 第二级(节) | (一)(二)(三)… | 加粗 |
| 第三级(款) | 1. 2. 3. … | 常规 |
| 第四级(项) | 1.1 1.2 1.3 … | 常规 |
二、安装说明
在终端中执行以下命令安装依赖:
pip install python-docx pyyaml
将本包中的 format_document.py 保存到本地任意目录,即可使用。
目录结构
hospital-doc-formatter/
├── format_document.py ← 主脚本(下面第5节有完整代码)
└── sample-input.yaml ← 输入模板(下面第6节)
三、使用方式
方式 A:命令行(推荐)
# 1) 先写好 sample-input.yaml(按第6节模板填写)
# 2) 运行
python format_document.py -i sample-input.yaml -o 输出.docx
# 可选:指定 LOGO 图片
python format_document.py -i sample-input.yaml -o 输出.docx --logo logo.png
方式 B:Markdown 输入
直接用 Markdown 写文档内容(# 标题 → H1,## → 章节,正文段落自动识别编号层级):
python format_document.py -i my-doc.md -o 输出.docx
> Markdown 模式下,文件标题取 H1 第一行。meta(起草人、日期等)需在生成后手动补充或在代码中传入。
方式 C:纯文本文档
直接传 .txt:
python format_document.py -i my-doc.txt -o 输出.docx
> 纯文本模式会自动把第一行当标题,把"一、""(一)""1.""1.1"识别为不同层级的章节标题。
方式 D:Python 调用
from format_document import format_document
meta = {
"title": "临床药师查房制度",
"doc_no": "YKY-Y-2026-001",
"department": "药剂科",
"drafter": "张三",
"draft_date": "2026-08-01",
"reviewer": "李四",
"review_date": "2026-08-02",
"approver": "王五",
"approve_date": "2026-08-05",
"publish_date": "2026-08-06",
"effective_date": "2026-08-10",
"revision_note": "首次制订",
}
blocks = [
{"type": "heading", "level": 1, "text": "一、目的"},
{"type": "para", "text": "为规范临床药师参与查房工作…"},
{"type": "heading", "level": 1, "text": "二、适用范围"},
{"type": "para", "text": "本制度适用于…"},
# …更多段落…
]
format_document(meta, blocks, "输出.docx")
方式 E:配合 WorkBuddy 对话
把文档正文粘贴给 WorkBuddy,它会按照本 skill 规范读取 → 解析 → 生成 YAML → 调脚本 → 输出 docx。
四、输入格式说明
4.1 meta 字段
| YAML key | 说明 | 必填 |
|---|---|---|
title |
文件主标题 | 是 |
doc_no |
文件编号 | 是 |
department |
编写部门 | 是 |
drafter |
起草/修订人 | 推荐 |
draft_date |
起草/修订时间 (YYYY-MM-DD) | 推荐 |
reviewer |
审核人 | 推荐 |
review_date |
审核日期 | 推荐 |
approver |
批准人 | 推荐 |
approve_date |
批准日期 | 推荐 |
publish_date |
发布日期 | 推荐 |
effective_date |
生效日期 | 推荐 |
revision_note |
修订说明(默认"首次制订") | 选填 |
log_extra_rows |
历史修订记录(5列 list) | 选填 |
4.2 blocks 类型
| type | 说明 | level |
|---|---|---|
heading |
章节标题 | 1=一、 2=(一) 3=1. 4=1.1 |
para |
普通正文段落 | — |
title |
主标题(一般由 meta.title 决定,不需要额外写) | — |
blank |
空行 | — |
4.3 编号自动识别
脚本会自动从行首检测以下模式:
| 模式 | → level |
|---|---|
一、 二、 … |
1 |
(一) (二) … |
2 |
1. 2. 3. … |
3 |
1.1 1.2 … |
4 |
五、完整 Python 脚本
将以下代码保存为 format_document.py:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Hospital Document Formatter — 医院文档排版工具
================================================
根据医院的排版规范,将结构化内容(YAML/JSON/Markdown)格式化为符合规范的 .docx 文件。
规范要点:
- 页眉:[LOGO] | 部门 | 文档号 方正小标宋简体 / Times New Roman / 小五
- 页脚:第 X 页 共 X 页 仿宋_GB2312 / Times New Roman / 小五
- 标题:方正小标宋简体 / 小二 / 加粗 / 居中 / 行距36磅
- 正文:仿宋_GB2312 / 四号 / 行距28磅 / 首行缩进2字符
- 第一级编号:一、二、三...(加粗) 第二级加粗:(一)(二)(三)
- 第三级:1.2.3. 第四级:1.1 1.2 1.3
用法:
# 命令行
python format_document.py --input input.yaml --output output.docx [--logo logo.png]
# Python 调用
from format_document import format_document
format_document(meta=..., blocks=..., output_path=...)
依赖:pip install python-docx pyyaml
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt
# ============================================================
# Format specifications (常量)
# ============================================================
FONT_TITLE_CN = "方正小标宋简体"
FONT_BODY_CN = "仿宋_GB2312"
FONT_HEADER_CN = "方正小标宋简体"
FONT_LATIN = "Times New Roman"
SIZE_TITLE = Pt(18) # 小二
SIZE_BODY = Pt(14) # 四号
SIZE_BODY_SMALL = Pt(12) # 小四
SIZE_HEADER = Pt(9) # 小五
LINE_SPACING_TITLE = 36
LINE_SPACING_BODY = 28
LINE_SPACING_LOG = 1.0
INDENT_FIRST_LINE = Pt(28) # ≈ 2字符(四号下)
# ============================================================
# 低层辅助:字体设置、段落行距、表格边框、页码字段
# ============================================================
def set_run_font(run, cn_font: str, latin_font: str, size: Pt,
bold: bool = False) -> None:
run.font.name = latin_font
run.font.size = size
run.bold = bold
rPr = run._element.get_or_add_rPr()
rFonts = rPr.find(qn("w:rFonts"))
if rFonts is None:
rFonts = OxmlElement("w:rFonts")
rPr.append(rFonts)
rFonts.set(qn("w:eastAsia"), cn_font)
rFonts.set(qn("w:ascii"), latin_font)
rFonts.set(qn("w:hAnsi"), latin_font)
def set_paragraph_line_spacing(paragraph, pt_value: float) -> None:
pf = paragraph.paragraph_format
pf.line_spacing = Pt(pt_value)
pf.line_spacing_rule = WD_LINE_SPACING.EXACTLY
def remove_cell_borders(cell) -> None:
tcPr = cell._tc.get_or_add_tcPr()
tcBorders = OxmlElement("w:tcBorders")
for side in ["top","left","bottom","right","insideH","insideV"]:
e = OxmlElement(f"w:{side}")
e.set(qn("w:val"), "none")
e.set(qn("w:sz"), "0")
tcBorders.append(e)
tcPr.append(tcBorders)
def _add_field(paragraph, code: str) -> None:
fld_run = paragraph.add_run()
set_run_font(fld_run, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
begin = OxmlElement("w:fldChar")
begin.set(qn("w:fldCharType"), "begin")
instr = OxmlElement("w:instrText")
instr.set(qn("xml:space"), "preserve")
instr.text = code
end = OxmlElement("w:fldChar")
end.set(qn("w:fldCharType"), "end")
fld_run._r.append(begin)
fld_run._r.append(instr)
fld_run._r.append(end)
# ============================================================
# 页面
# ============================================================
def setup_page(doc: Document) -> None:
sec = doc.sections[0]
sec.page_height = Cm(29.7)
sec.page_width = Cm(21.0)
sec.top_margin = Cm(2.54)
sec.bottom_margin = Cm(2.54)
sec.left_margin = Cm(3.18)
sec.right_margin = Cm(3.18)
sec.header_distance = Cm(1.5)
sec.footer_distance = Cm(1.75)
style = doc.styles["Normal"]
style.font.name = FONT_LATIN
style.font.size = SIZE_BODY
rPr = style.element.get_or_add_rPr()
rFonts = rPr.find(qn("w:rFonts"))
if rFonts is None:
rFonts = OxmlElement("w:rFonts")
rPr.append(rFonts)
rFonts.set(qn("w:eastAsia"), FONT_BODY_CN)
rFonts.set(qn("w:ascii"), FONT_LATIN)
rFonts.set(qn("w:hAnsi"), FONT_LATIN)
# ============================================================
# 页眉 / 页脚
# ============================================================
def setup_header(doc: Document, department: str, doc_no: str,
logo_path: str | None = None) -> None:
hdr = doc.sections[0].header
if hdr.paragraphs:
hdr.paragraphs[0].clear()
table = hdr.add_table(rows=1, cols=3, width=Cm(14.64))
table.autofit = False
for row in table.rows:
for cell in row.cells:
remove_cell_borders(cell)
# 左 LOGO
c0 = table.cell(0, 0); c0.width = Cm(3)
p = c0.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
set_paragraph_line_spacing(p, 12)
if logo_path and Path(logo_path).exists():
r = p.add_run()
r.add_picture(str(logo_path), height=Cm(1.0))
else:
r = p.add_run("【医院 LOGO】")
set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)
# 中 部门
c1 = table.cell(0, 1); c1.width = Cm(8.64)
p = c1.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, 12)
r = p.add_run(department or "")
set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)
# 右 文件编号
c2 = table.cell(0, 2); c2.width = Cm(3)
p = c2.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
set_paragraph_line_spacing(p, 12)
r = p.add_run(doc_no or "")
set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)
# 横线
p_line = hdr.add_paragraph()
pPr = p_line._p.get_or_add_pPr()
pBdr = OxmlElement("w:pBdr")
bot = OxmlElement("w:bottom")
bot.set(qn("w:val"), "single")
bot.set(qn("w:sz"), "6")
bot.set(qn("w:space"), "1")
bot.set(qn("w:color"), "000000")
pBdr.append(bot)
pPr.append(pBdr)
def setup_footer(doc: Document) -> None:
ftr = doc.sections[0].footer
if ftr.paragraphs:
p = ftr.paragraphs[0]; p.clear()
else:
p = ftr.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, 12)
r = p.add_run("第 "); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
_add_field(p, "PAGE \\* MERGEFORMAT")
r = p.add_run(" 页 共 "); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
_add_field(p, "NUMPAGES \\* MERGEFORMAT")
r = p.add_run(" 页"); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
# ============================================================
# 内容部件
# ============================================================
def add_title(doc: Document, title: str) -> None:
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, LINE_SPACING_TITLE)
r = p.add_run(title or "")
set_run_font(r, FONT_TITLE_CN, FONT_LATIN, SIZE_TITLE, bold=True)
def add_metadata_table(doc: Document, meta: dict) -> None:
hdrs = ["起草/修订人","起草/修订时间","审核人","审核日期",
"批准人","批准日期","发布日期","生效日期"]
vals = [meta.get(k, "") for k in
("drafter","draft_date","reviewer","review_date",
"approver","approve_date","publish_date","effective_date")]
table = doc.add_table(rows=2, cols=8)
table.style = "Table Grid"
table.alignment = WD_ALIGN_PARAGRAPH.CENTER
for i, h in enumerate(hdrs):
c = table.cell(0, i); c.text = ""
p = c.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, 12)
r = p.add_run(h)
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=True)
for i, v in enumerate(vals):
c = table.cell(1, i); c.text = ""
p = c.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, 12)
r = p.add_run(str(v))
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY)
p = doc.add_paragraph(); set_paragraph_line_spacing(p, 12)
def add_log_table(doc: Document, meta: dict,
extra_rows: list[list[str]] | None = None) -> None:
hdrs = ["文件编码","名称","生效日期","起草/修订内容","起草/修订人"]
cur = [meta.get("doc_no",""), meta.get("title",""),
meta.get("effective_date",""),
meta.get("revision_note","") or "首次制订",
meta.get("drafter","")]
rows = [cur]
if extra_rows:
rows.extend(extra_rows)
table = doc.add_table(rows=len(rows)+1, cols=5)
table.style = "Table Grid"
for i, h in enumerate(hdrs):
c = table.cell(0, i); c.text = ""
p = c.paragraphs[0]; p.alignment = WD_ALIGN_PARAGRAPH.CENTER
set_paragraph_line_spacing(p, 12)
r = p.add_run(h)
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=True)
for ri, row in enumerate(rows, 1):
for ci, val in enumerate(row):
c = table.cell(ri, ci); c.text = ""
p = c.paragraphs[0]; p.alignment = WD_ALIGN_PARAGRAPH.LEFT
p.paragraph_format.line_spacing = LINE_SPACING_LOG
r = p.add_run(str(val))
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY_SMALL)
p = doc.add_paragraph(); set_paragraph_line_spacing(p, 12)
def add_section_heading(doc: Document, text: str, level: int) -> None:
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
set_paragraph_line_spacing(p, LINE_SPACING_BODY)
if level >= 2:
p.paragraph_format.first_line_indent = INDENT_FIRST_LINE
bold = level in (1, 2)
r = p.add_run(text or "")
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=bold)
def add_body_paragraph(doc: Document, text: str, indent: bool = True) -> None:
if not text or not text.strip():
return
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
set_paragraph_line_spacing(p, LINE_SPACING_BODY)
if indent:
p.paragraph_format.first_line_indent = INDENT_FIRST_LINE
r = p.add_run(text)
set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY)
# ============================================================
# 编号检测
# ============================================================
LV1_RE = re.compile(r"^([一二三四五六七八九十]+)[、.]")
LV2_RE = re.compile(r"^[((]([一二三四五六七八九十]+)[))]")
LV4_RE = re.compile(r"^(\d+\.\d+)[\s\..、]")
LV3_RE = re.compile(r"^(\d+)[..、]")
def detect_numbering(text: str) -> tuple[int, str, str] | None:
t = text.strip()
m = LV4_RE.match(t)
if m:
return (4, m.group(1), t[m.end():].strip())
m = LV2_RE.match(t)
if m:
return (2, m.group(0), t[m.end():].strip())
m = LV1_RE.match(t)
if m:
cn = m.group(1)
return (1, cn + "、", t[m.end():].strip())
m = LV3_RE.match(t)
if m:
return (3, m.group(1)+".", t[m.end()-1:].strip() if m.end()>=1 else t)
return None
# ============================================================
# 输入解析
# ============================================================
def load_input_file(path: str | Path) -> dict | list:
p = Path(path)
text = p.read_text(encoding="utf-8")
if p.suffix.lower() in (".yaml", ".yml"):
try:
import yaml
return yaml.safe_load(text) or {}
except ImportError:
return _lite_yaml_load(text)
if p.suffix.lower() == ".json":
return json.loads(text)
if p.suffix.lower() in (".md", ".markdown"):
return parse_markdown(text)
return parse_plain_text(text)
def _lite_yaml_load(text: str) -> dict:
"""极简 YAML 解析,仅在没有 PyYAML 时备用。"""
out: dict = {}
cur_key = None
cur_list: list | None = None
cur_sub: dict | None = None
for raw in text.splitlines():
if not raw.strip() or raw.lstrip().startswith("#"):
continue
if raw.startswith(" ") and cur_list is not None and cur_sub is not None:
line = raw.strip()
if ":" in line:
k, v = line.split(":", 1)
cur_sub[k.strip()] = v.strip().strip('"').strip("'")
continue
if raw.startswith(" ") and cur_list is not None:
line = raw.strip()
if line.startswith("- "):
line = line[2:]
if cur_sub is not None and ":" in line:
k, v = line.split(":", 1)
cur_sub[k.strip()] = v.strip().strip('"').strip("'")
continue
if ":" in raw:
k, _, v = raw.partition(":")
k, v = k.strip(), v.strip()
if not v:
cur_key = k; cur_list = []; cur_sub = None
out[cur_key] = cur_list
elif v in ("|", ">"):
out[k] = ""
else:
if (v.startswith('"') and v.endswith('"')) or \
(v.startswith("'") and v.endswith("'")):
v = v[1:-1]
out[k] = v
cur_key = None; cur_list = None; cur_sub = None
return out
def parse_markdown(text: str) -> dict:
meta: dict = {}
blocks: list[dict] = []
title_set = False
for raw in text.splitlines():
line = raw.rstrip()
if not line.strip():
blocks.append({"type": "blank"})
continue
if line.startswith("# ") and not title_set:
meta["title"] = line[2:].strip()
title_set = True
continue
m = re.match(r"^(#{1,6})\s+(.*)$", line)
if m:
hashes, body = m.groups()
level = len(hashes)
numbering = detect_numbering(body)
if numbering:
lvl, _, _ = numbering
level = lvl
blocks.append({"type":"heading","level":level,"text":body})
continue
numbering = detect_numbering(line.strip())
if numbering:
lvl, prefix, body = numbering
blocks.append({"type":"heading","level":lvl,
"text":f"{prefix} {body}".strip()})
continue
blocks.append({"type":"para","text":line.strip()})
return {"meta": meta, "blocks": blocks}
def parse_plain_text(text: str) -> dict:
blocks: list[dict] = []
title_set = False
for raw in text.splitlines():
line = raw.rstrip()
if not line.strip():
blocks.append({"type": "blank"})
continue
if not title_set:
blocks.append({"type":"title","text":line.strip()})
title_set = True
continue
numbering = detect_numbering(line.strip())
if numbering:
lvl, prefix, body = numbering
blocks.append({"type":"heading","level":lvl,
"text":f"{prefix}{body}"})
continue
blocks.append({"type":"para","text":line.strip()})
return blocks
# ============================================================
# 主入口
# ============================================================
def format_document(meta: dict, blocks: list[dict],
output_path: str | Path,
logo_path: str | None = None) -> Path:
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
doc = Document()
setup_page(doc)
setup_header(doc,
meta.get("department",""), meta.get("doc_no",""), logo_path)
setup_footer(doc)
title = meta.get("title","")
if title:
add_title(doc, title)
add_metadata_table(doc, meta)
add_log_table(doc, meta, extra_rows=meta.get("log_extra_rows"))
for block in blocks or []:
btype = block.get("type","para")
text = block.get("text","")
if btype == "title":
if text and text != title:
add_title(doc, text)
elif btype == "heading":
add_section_heading(doc, text, int(block.get("level",1)))
elif btype == "para":
add_body_paragraph(doc, text)
elif btype == "blank":
doc.add_paragraph()
else:
add_body_paragraph(doc, str(block))
doc.save(str(output_path))
return output_path
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(
description="医院文档一键排版 — 生成符合规范的 .docx")
p.add_argument("--input","-i",
help="输入文件 (.yaml/.json/.md/.txt)")
p.add_argument("--output","-o", required=True,
help="输出 .docx 路径")
p.add_argument("--logo", help="可选:LOGO 图片路径")
args = p.parse_args(argv)
if args.input and args.input != "-":
data = load_input_file(args.input)
if isinstance(data, list):
data = {"meta":{}, "blocks":data}
else:
text = sys.stdin.read()
if text.lstrip().startswith("#"):
data = parse_markdown(text)
else:
data = {"meta":{}, "blocks":parse_plain_text(text)}
if "meta" not in data:
data["meta"] = {}
meta = data.get("meta") or {}
blocks = data.get("blocks") or []
out = format_document(meta, blocks, args.output, args.logo)
print(f"OK: {out}")
return 0
if __name__ == "__main__":
sys.exit(main())
六、YAML 输入模板
将以下内容保存为 sample-input.yaml,按实际修改 meta 字段和正文 blocks:
# ============================================================
# 医院文档一键排版 — 输入示例(YAML 格式)
# 用法:python format_document.py -i sample-input.yaml -o 输出.docx
# ============================================================
meta:
title: 临床药师查房与用药干预制度
doc_no: YKY-Y-2026-001
department: 药剂科
drafter: 张三
draft_date: 2026-08-01
reviewer: 李四
review_date: 2026-08-02
approver: 王五
approve_date: 2026-08-05
publish_date: 2026-08-06
effective_date: 2026-08-10
revision_note: 首次制订
# 历史修订记录(不需要可删除整段)
log_extra_rows:
- ["YKY-Y-2024-015", "临床药师查房制度(v1)", "2024-09-01", "首次制订", "王二"]
# ============================================================
# 正文(blocks 列表)
# type: heading(level=1/2/3/4) | para | title | blank
# ============================================================
blocks:
- type: heading
level: 1
text: 一、目的
- type: para
text: 为规范临床药师参与查房与用药干预工作,保障患者用药安全、有效、经济,依据《医疗机构药事管理规定》《处方管理办法》等文件,结合本院实际,制定本制度。
- type: heading
level: 1
text: 二、适用范围
- type: para
text: 本制度适用于本院临床药学科全体临床药师及相关药学技术人员。
- type: heading
level: 1
text: 三、职责
- type: heading
level: 2
text: (一)临床药师的职责
- type: para
text: 临床药师应主动参与临床查房,了解患者用药史、过敏史、肝肾功能等情况。
- type: heading
level: 2
text: (二)临床药学科的职责
- type: para
text: 临床药学科负责制订年度计划、组织疑难病例讨论、定期发布合理用药报告。
- type: heading
level: 1
text: 四、工作内容
- type: heading
level: 2
text: (一)日常查房
- type: para
text: 临床药师每周参与临床科室查房不少于 2 次。
- type: heading
level: 2
text: (二)ADR 监测与上报
- type: para
text: 遇疑似 ADR 时,在规定时限内通过国家 ADR 监测系统上报。
- type: heading
level: 1
text: 五、附件
- type: para
text: 附件 1 临床药师查房记录表。
- type: heading
level: 1
text: 六、参考文献
- type: para
text: "[1] 国家卫生健康委员会. 医疗机构药事管理规定[Z]. 2011."
- type: para
text: "[2] 国家药品监督管理局. 药品不良反应报告和监测管理办法[Z]. 2011."
七、常见问题
Q1:方正小标宋简体字体找不到?
如果电脑未安装「方正小标宋简体」,Word 会自动用默认字体替代。建议安装医院标配字体,或在代码中把 FONT_TITLE_CN 改为你电脑上可用的宋体/黑体变体。
Q2:YAML 报错 expected <block end>
文本里出现 [1]、{xxx} 等被 YAML 当成序列/字典。解决方法:把这行用引号包起来——text: "[1] 国家卫健委. …"
Q3:页脚页码不显示?
输出的 docx 使用了 Word 域字段(PAGE / NUMPAGES),首次打开需按 Ctrl+A → F9 刷新。或者打印预览一次即可。
Q4:怎么批量处理多份文档?
当前脚本每次输出 1 个 docx。批量可以:
- 写一个 shell 循环调用
- 或在 WorkBuddy 中依次/并行提交多个排版任务
Q5:附件里包含真实表格怎么办?
当前脚本把附件作为正文段落处理。如需表格,可在 Word 里手动插入,或后续扩展支持。
Q6:能生成 pdf 吗?
脚本输出 .docx,如需 PDF,在 Word 里「另存为 → PDF」即可,或使用 libreoffice --headless --convert-to pdf 命令行转换。
快速开始(3 步)
# 1. 安装依赖
pip install python-docx pyyaml
# 2. 把上面的 YAML 模板复制到 sample-input.yaml,改好内容
# 3. 运行
python format_document.py -i sample-input.yaml -o 我的制度.docx
打开 我的制度.docx,按 Ctrl+A → F9 刷新页码,完成!
> 核心承诺:排版只动格式,不改任何一个业务文字。你可以放心把草稿丢进去,出来的就是排版规范的正式文档。