wechat-robot-skills/skills/docx/scripts/_patent_spec.py

91 lines
4.1 KiB
Python

"""Map patent content to the shared, validated DOCX creation pipeline."""
from __future__ import annotations
from typing import Any
from _document_builder import expect_list, expect_object
def _text(value: Any, label: str) -> str:
if not isinstance(value, str) or not value.strip():
raise ValueError(f"{label} 必须是非空文本")
return value
def build_patent_spec(data: dict[str, Any]) -> dict[str, Any]:
unknown = set(data) - {"claims", "specification", "abstract", "properties"}
if unknown:
raise ValueError(f"专利说明包含未知字段:{sorted(unknown)}")
blocks: list[dict[str, Any]] = []
sections: list[dict[str, Any]] = []
margin = 2.5 / 2.54
page = {"size": "A4", "margins": dict.fromkeys(("top", "bottom", "left", "right"), margin)}
def start_section(title: str) -> None:
if sections:
blocks.append({"type": "section_break", **page})
sections.append({
"index": len(sections),
"header": {"text": title, "alignment": "center"},
"footer": {"text": "", "alignment": "center", "page_number": True,
"page_number_prefix": "第 ", "page_number_suffix": " 页"},
"page_number_start": 1,
})
blocks.append({"type": "heading", "level": 1, "text": title, "alignment": "center"})
claims = expect_list(data.get("claims", []), "claims")
if claims:
start_section("权利要求书")
items = []
for index, raw in enumerate(claims, 1):
claim = expect_object(raw, "claims[]")
if set(claim) - {"number", "text", "dependent"}:
raise ValueError("claims[] 仅支持 number、text、dependent")
if claim.get("number") != index:
raise ValueError("claims[].number 必须从 1 开始连续编号")
items.append({"text": _text(claim.get("text"), "claims[].text"),
"alignment": "justify", "keep_together": True})
blocks.append({"type": "numbered_list", "items": items})
specification = expect_object(data.get("specification", {}), "specification")
titles = [("field", "技术领域"), ("background", "背景技术"), ("summary", "发明内容"),
("drawings", "附图说明"), ("detailed", "具体实施方式")]
unknown = set(specification) - {key for key, _ in titles}
if unknown:
raise ValueError(f"specification 包含未知字段:{sorted(unknown)}")
if specification:
start_section("说明书")
for key, title in titles:
if key not in specification:
continue
value = specification[key]
paragraphs = value if isinstance(value, list) else [value]
if not paragraphs:
raise ValueError(f"specification.{key} 不能为空")
blocks.append({"type": "heading", "level": 2, "text": title})
blocks.extend({"type": "paragraph", "text": _text(text, f"specification.{key}"),
"alignment": "justify", "first_line_indent": 24 / 72}
for text in paragraphs)
if "abstract" in data:
abstract = _text(data["abstract"], "abstract")
start_section("摘要")
blocks.append({"type": "paragraph", "text": abstract, "alignment": "justify",
"first_line_indent": 24 / 72})
if not blocks:
raise ValueError("专利说明至少需要非空 claims、specification 或 abstract")
return {
"properties": expect_object(data.get("properties", {}), "properties"),
"page": page,
"default_font": {"name": "Liberation Serif", "east_asia": "Noto Serif CJK SC",
"size": 12, "line_spacing": 1.5},
"styles": {
"Heading 1": {"font": "Liberation Sans", "east_asia_font": "Noto Sans CJK SC",
"size": 16, "bold": True, "color": "000000"},
"Heading 2": {"font": "Liberation Sans", "east_asia_font": "Noto Sans CJK SC",
"size": 14, "bold": True, "color": "000000"},
},
"blocks": blocks,
"sections": sections,
}