115 lines
5.4 KiB
Python
115 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||||
|
|
"""客服 Agent 四份交付文档 · 构建器(JSON 配置驱动)
|
|||
|
|
|
|||
|
|
从既有文档抽取「项目 HTML 版式外壳」,再灌入新正文,保证版式与既有体系逐字一致。
|
|||
|
|
用法:python _docbuilder.py <spec.json>
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import re
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
SRC = Path(r"D:\桌面\金融\开发文档\D3.1-客服Agent需求开发文档与设计方案.html")
|
|||
|
|
OUT_DIR = Path(r"D:\桌面\金融\客服agent")
|
|||
|
|
|
|||
|
|
text = SRC.read_text(encoding="utf-8")
|
|||
|
|
|
|||
|
|
HEAD_END = '<div id="toc-container">'
|
|||
|
|
SHELL_HEAD = text[: text.index(HEAD_END) + len(HEAD_END)]
|
|||
|
|
# ⚠️ 结束锚点必须取「正文第一段注释」的位置:若取 `</div><!-- end doc-container -->`,
|
|||
|
|
# 那在文件里是**最后一行**,会把原文档整段正文一并带进来(曾因此产出 4920 行的错件)。
|
|||
|
|
BODY_START = '<!-- ==================== 0 ==================== -->'
|
|||
|
|
SHELL_MID = text[text.index(' </div>\n</nav>'): text.index(BODY_START)]
|
|||
|
|
SHELL_TAIL = text[text.index('</div><!-- end doc-container -->'):]
|
|||
|
|
assert 'doc-header' in SHELL_MID and len(SHELL_MID) < 4000, "SHELL_MID 抽取异常"
|
|||
|
|
assert SHELL_TAIL.startswith('</div><!-- end doc-container -->')
|
|||
|
|
|
|||
|
|
# 备份一份外壳,便于后续单独构建
|
|||
|
|
Path(r"D:\桌面\金融\_shell_head.html").write_text(SHELL_HEAD, encoding="utf-8")
|
|||
|
|
Path(r"D:\桌面\金融\_shell_mid.html").write_text(SHELL_MID, encoding="utf-8")
|
|||
|
|
Path(r"D:\桌面\金融\_shell_tail.html").write_text(SHELL_TAIL, encoding="utf-8")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build(spec: dict) -> str:
|
|||
|
|
head = SHELL_HEAD
|
|||
|
|
head = head.replace(
|
|||
|
|
"<title>XX科技·智能财富管家系统 — 智能客服 Agent 需求开发文档与设计方案</title>",
|
|||
|
|
f"<title>{spec['title']}</title>")
|
|||
|
|
head = head.replace(
|
|||
|
|
'<div class="sidebar-subtitle">客服 Agent 专项方案 v2.3 · 双角色 · 身份分离</div>',
|
|||
|
|
f'<div class="sidebar-subtitle">{spec["subtitle"]}</div>')
|
|||
|
|
|
|||
|
|
parts = []
|
|||
|
|
for idx, grp in enumerate(spec["toc"], start=1):
|
|||
|
|
active = " active" if idx == 1 else ""
|
|||
|
|
arr = " open" if idx == 1 else ""
|
|||
|
|
kids = " open" if idx == 1 else ""
|
|||
|
|
parts.append(f'<div class="toc-group">\n'
|
|||
|
|
f'<button class="toc-toggle{active}" data-group="{idx}">'
|
|||
|
|
f'<span class="arr{arr}">▶</span><span>{grp["g"]}</span></button>\n'
|
|||
|
|
f'<div class="toc-kids{kids}">')
|
|||
|
|
for href, label in grp["items"]:
|
|||
|
|
parts.append(f'<a class="toc-item" href="#{href}">{label}</a>')
|
|||
|
|
parts.append("</div></div>\n")
|
|||
|
|
toc_html = "\n".join(parts)
|
|||
|
|
|
|||
|
|
mid = SHELL_MID
|
|||
|
|
mid = mid.replace(
|
|||
|
|
'<div id="topbar-title">智能财富管家系统 — 智能客服 Agent 需求开发文档与设计方案</div>',
|
|||
|
|
f'<div id="topbar-title">{spec["topbar"]}</div>')
|
|||
|
|
mid = mid.replace('<span id="topbar-badge">v2.3 · 访客 ≠ 角色 · 重构基线</span>',
|
|||
|
|
f'<span id="topbar-badge">{spec["badge"]}</span>')
|
|||
|
|
mid = mid.replace('<div class="doc-eyebrow">专项需求开发文档 · 详细设计方案</div>',
|
|||
|
|
f'<div class="doc-eyebrow">{spec["eyebrow"]}</div>')
|
|||
|
|
mid = mid.replace(
|
|||
|
|
'<h1 class="doc-title">XX科技·智能财富管家系统<br>智能客服 Agent 需求开发文档与设计方案</h1>',
|
|||
|
|
f'<h1 class="doc-title">{spec["doctitle"]}</h1>')
|
|||
|
|
meta_html = "".join(
|
|||
|
|
f' <div class="doc-meta-item">\n'
|
|||
|
|
f' <span class="doc-meta-label">{k}</span>\n'
|
|||
|
|
f' <span class="doc-meta-value">{v}</span>\n'
|
|||
|
|
f' </div>\n' for k, v in spec["meta"])
|
|||
|
|
mid = re.sub(r' <div class="doc-meta">.*?\n </div>\n',
|
|||
|
|
f' <div class="doc-meta">\n{meta_html} </div>\n', mid, flags=re.S)
|
|||
|
|
|
|||
|
|
tail = SHELL_TAIL
|
|||
|
|
tail = tail.replace(
|
|||
|
|
"(<strong>v2.2 双角色 · 重构基线版</strong>)",
|
|||
|
|
f"(<strong>{spec['closing']}</strong>)")
|
|||
|
|
|
|||
|
|
# ⚠️ 版式外壳是从旧版式文档抽取的,会把旧品牌字面一并带进来(曾因此漏改 4 份文档的
|
|||
|
|
# <title>/<h1>/sidebar 品牌)。这里对「外壳」做一次品牌兜底归一,保证任何一次重新生成
|
|||
|
|
# 都不会回退到旧口径。只作用于 head/mid/tail;body 内可能有意保留历史字面(如 P-10 行),不动。
|
|||
|
|
BRAND = (
|
|||
|
|
("XX科技·智能财富管家系统", "南方基金·智能服务系统"),
|
|||
|
|
("XX科技 · 智能金融事业部", "南方基金 · 智能服务事业部"),
|
|||
|
|
("XX科技", "南方基金"),
|
|||
|
|
("智能财富管家系统", "智能服务系统"),
|
|||
|
|
("南方财富管理有限公司", "南方基金管理股份有限公司"),
|
|||
|
|
("南方财富", "南方基金"),
|
|||
|
|
("400-XXX-XXXX", "400-889-8899"),
|
|||
|
|
("400-826-9518", "400-889-8899"),
|
|||
|
|
("nanfangwm.com", "nffund.com"),
|
|||
|
|
)
|
|||
|
|
head, mid, tail = (
|
|||
|
|
__import__("functools").reduce(
|
|||
|
|
lambda s, kv: s.replace(kv[0], kv[1]), BRAND, part)
|
|||
|
|
for part in (head, mid, tail)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
body = Path(spec["body_file"]).read_text(encoding="utf-8")
|
|||
|
|
return head + "\n" + toc_html + mid + body + tail
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> None:
|
|||
|
|
spec = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|||
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|||
|
|
out = OUT_DIR / spec["out_file"]
|
|||
|
|
html = build(spec)
|
|||
|
|
out.write_text(html, encoding="utf-8")
|
|||
|
|
print(f"OK {out} ({html.count(chr(10)) + 1} lines)")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|