养老机构星级评定资料包 AI 生成器 —— 按 GB/T 37276-2018 条款 + 客户机构信息,自动生成定制化的制度文档(.docx)与台账表格(.xlsx),支持入住长者名单导入、每日记录类台账 12 月分页成册、每份台账底部自动附结构化填写说明。当用户说"生成资料包""客户资料交付""按订单出资料""生成制度模板""生成台账表格""资料包定制""订单交付""跑一下生成""客户要的制度文件""导入老人名单""台账按月分册"时使用。也适用于把这个生成器接进 Flask 应用、批量出资料的场景。
---
name: anchor-star-template-gen
description: 养老机构星级评定资料包 AI 生成器 —— 按 GB/T 37276-2018 条款 + 客户机构信息,自动生成定制化的制度文档(.docx)与台账表格(.xlsx),支持入住长者名单导入、每日记录类台账 12 月分页成册、每份台账底部自动附结构化填写说明。当用户说"生成资料包""客户资料交付""按订单出资料""生成制度模板""生成台账表格""资料包定制""订单交付""跑一下生成""客户要的制度文件""导入老人名单""台账按月分册"时使用。也适用于把这个生成器接进 Flask 应用、批量出资料的场景。
agent_created: true
---
# 星评资料包 AI 生成器
把「客户订单 + 机构信息 + 国标条款」变成一套**能直接打印使用的 docx / xlsx 文件**。
## 一句话原理
**能被"填"的东西出 xlsx;要被"读"的东西出 docx。**
这条口诀是整个 skill 的骨架。制度、预案、职责、计划 → 人读的,出 Word;
台账、记录表、登记表、考核表 → 人填的,出 Excel。
## 四项核心能力
| 能力 | 开关 | 说明 |
|---|---|---|
| **格式路由** | 默认开 | 自动判定 docx / xlsx,不用人工分类 |
| **长者名单导入** | `--elders` | 传入姓名/性别/年龄,相关台账自动预填真实长者行 |
| **月度分册** | 默认开,`--no-monthly` 关 | 每日记录类台账自动出「年度汇总 + 1月…12月」13 个 sheet |
| **填写说明** | 默认开 | 每份 xlsx 底部自动附结构化填写说明(依据/频次/责任人/要求/异常/保存期) |
## 快速上手
```bash
PY="python"
S="anchor-star-template-gen/scripts"
# 1) 【首次】建模板库索引(模板库有更新时重跑)
"$PY" "$S/build_index.py" \
--root "<你的资料目录>" \
--out "<你的资料目录>"
# 2) 看一眼格式怎么路由(不生成,最快)
"$PY" "$S/build_package.py" --keys "★生活照料服务|制度类" "★医疗护理服务|记录/档案类" --plan-only
# 3) 真生成(走 AI)
DEEPSEEK_API_KEY="sk-xxx" "$PY" "$S/build_package.py" \
--keys "★生活照料服务|制度类" \
--org '{"name":"XX养老院","leader":"张三","departments":[{"name":"护理部","members":["李四"]}]}' \
--star 3 --out "输出目录"
# 4) 用订单 JSON(接 Flask 应用时用这个)
"$PY" "$S/build_package.py" --order "orders/xxx.json" --out "delivery/"
# 5) 不调 AI,直接用模板骨架降级出(断网/省 token/快速验证)
"$PY" "$S/build_package.py" --keys "..." --no-ai --out "输出目录"
# 6) 带入住长者名单(自动预填涉及老人姓名的台账)
"$PY" "$S/build_package.py" --keys "★膳食服务|记录/档案类" \
--elders "入住长者名单.xlsx" --out "输出目录"
# 7) 控制月度分册 / 指定年份 / 跳过名册
"$PY" "$S/build_package.py" --keys "..." --year 2026 --no-monthly --no-roster --out "输出目录"
```
## 脚本分工
| 脚本 | 干什么 | 何时跑 |
|---|---|---|
| `build_index.py` | 扫模板库,抽 docx 章节骨架 + xlsx 字段签名 → `template_index.json` | 模板库更新后 |
| `format_route.py` | 格式路由:资料项 → docx/xlsx | 被主入口调用,也可单独测 |
| `ai_gen.py` | 调 DeepSeek 生成内容 JSON(含 guide、长者简报);失败降级 | 被主入口调用 |
| `gen_docx.py` | docx 渲染引擎(中文字体/公文格式/表格/落款) | 被主入口调用 |
| `gen_xlsx.py` | xlsx 渲染引擎(两级表头/留白行/填写说明/月度分册/打印设置) | 被主入口调用 |
| `build_package.py` | **主入口**:路由 → 取骨架 → AI → 渲染 → 打包 zip | 每次生成 |
## 格式路由规则(`format_route.py`)
判定优先级 **名称关键词 > 台账类型 > 默认 docx**:
| 名称含这些 → xlsx | 名称含这些 → docx |
|---|---|
| 记录表、台账、登记表、统计表、汇总表、检查表、考核表、考勤表、排班表、点检表、观察记录、巡视、巡查、交接班、清单、名册、量化表、评估表、问卷、签到表、申请表 | 制度、规范、标准、规程、职责、说明书、预案、流程、计划、总结、报告、方案、手册、须知、协议、公约、指引、规定、办法 |
台账类型兜底:`制度类/计划类/预案类/评价类/证书报告类/协议类` → docx;`记录档案类/台账/汇总类` → xlsx。
## 内容生成结构(AI 返回的 JSON)
**docx 类**:
```json
{"title":"...","blocks":[
{"t":"h1","text":"一、总则"},
{"t":"h2","text":"(一)适用范围"},
{"t":"p","text":"正文段落……"},
{"t":"li","text":"1. 列表项……"},
{"t":"table","caption":"表1","headers":["a","b"],"rows":[["x","y"]]}
]}
```
**xlsx 类**:
```json
{"title":"...","columns":["日期","姓名"],"subColumns":[null,"子项1/子项2"],
"widths":[13,12],
"sampleRows":[["2025-01-01","张某某"]],
"guide":{
"basis":"GB/T 37276-2018 第 4.3.1.3 条 —— ……",
"freq":"每餐记录一次,每日三餐及加餐均需填写。",
"duty":"当班护理员填写,护士每日审核并签名。",
"steps":["每餐后及时填写,不得空项。","膳食类型按医嘱填写……"],
"abnormal":"发现呛咳、吞咽困难、呕吐时立即停止进食,报告护士。",
"retain":"不少于 3 年。"
}}
```
`guide` 是**结构化填写说明**(不是 `note` 那种一行挤压的注释),渲染后长这样:
```
【填写说明(依据养老服务规范)】
依据:GB/T 37276-2018 第 4.3.1.3、4.3.2.6 条 —— ……
频次:每餐记录一次,每日三餐及加餐均需填写。
责任人:当班护理员填写,护士每日审核并签名。
填写要求:
1. 每餐后及时填写,不得空项;无发生事项填「无」或「/」。
2. 膳食类型按医嘱或营养师要求填写……
…(5-8 条)
异常处理:发现呛咳、吞咽困难、呕吐、误吸时立即停止进食,报告护士……
保存期限:不少于 3 年,供等级评定与主管部门检查调阅。
对应国标条款:4.3.1.3、4.3.2.6
```
## 能力一:入住长者名单导入
**目的**:凡是台账里要填老人姓名的地方,自动带出真实长者信息,省得客户一份份手填。
### 名单文件格式
支持 `.csv` / `.txt` / `.xlsx` / `.json`。列名有别名映射,不用严格对齐:
| 目标字段 | 可接受的列名 |
|---|---|
| 姓名 | 姓名、长者姓名、老人姓名、name |
| 性别 | 性别、sex、gender |
| 年龄 | 年龄、age |
| 床号 | 床号、房间号、床位、room |
| 护理等级 | 护理等级、护理级别、等级、level |
| 备注 | 备注、特殊需求、禁忌、note |
分隔符自动嗅探:`,` / `\t` / `,` / `;` / `|`;表头自动识别(首行若不像数据就当前表头)。
标例 CSV:
```csv
姓名,性别,年龄,床号,护理等级,备注
张桂芳,女,82,201,一级,糖尿病、忌甜食
李长顺,男,76,202,二级,高血压、低盐
```
### 行为
- `needs_elders(item)` 判定该资料项是否与具体长者相关(记录档案类、名册、评估表等)
- 相关项:AI prompt 里注入长者简报(默认最多 60 人),生成真实示例行
- **月度分册下的落法**:AI 产出的 `sampleRows` 会被放到 **1 月页顶部**,
用浅底纹 `FFFDF6E3` 标注为「示范行」,并加一行提示
「以下浅色行为示范(依据「入住长者名单」自动预填),正式填写时请从空白行开始。」
- 最多取 6 行,避免喧宾夺主
- 示范行日期统一改为当年 1 月 1 日(AI 常返回别的年份,会穿帮)
- **2-12 月只留空白行**,不放示范(否则客户会误以为已经填过)
- 单页台账:`sampleRows` 直接接在表头下方
- 自动附带生成《入住长者名册》一份(`--no-roster` 可关)
- **红线**:长者名单只用于预填表格示例行,**不写入任何对外说明文字**;
涉及健康信息的备注不进制度类文档。
## 能力二:每日记录类台账 12 月分册
**目的**:巡视、翻身、交接班、用药、膳食这类要天天填的台账,做成"一年一本"。
### 判定
`needs_monthly(item)` 判定,优先级同格式路由:
| 判定为**月度分册** | 判定为**单页** |
|---|---|
| 翻身、巡视、查房、交接班、用药、膳食、护理记录、观察、点检、巡查、消毒、体温、血压、出入量、培训、考勤 | 名册、一览表、清单、汇总表、档案表、协议、计划表、评估表(一次性) |
**踩过的坑**:资料项名形如 `★医疗护理服务·记录/档案类`,`·` 后半段是"类型"不是"文档名"。
直接匹配会把"档案"当成"档案表"命中 SINGLE 名单 → 整批误判成单页。
必须先 `_strip_type_suffix()` 按 `·` 切掉后缀再匹配。同时 SINGLE 名单里用「档案表」「排班表」
而不是光秃秃的「档案」「排班」。
### 结构
```
xlsx 分册:
├── 年度汇总 # 12 个月甘特式状态行(已记录/未记录)
├── 1月 ← 放全量填写说明
├── 2月 ← 放精简版(依据/频次/责任人/异常处理/保存期限 5 行)
├── 3月 … 12月
└── …
```
- **2 月固定按 29 天**(不用实际天数),避免闰年/平年切换导致模板不一致
- 其余月按实际天数(31/30)
- 首列"日期"类字段自动预填 `YYYY-MM-DD`
- 表头设 `print_title_rows`,打印时每页重复表头
- 每页底部:`本月应记录 N 天;如遇整月无事项,请在首行填「本月无事项」并签字。` + 签字行
**踩过的坑**:精简说明的过滤条件一开始写的是 `str(t)[:5] in keep_tuple`(精确匹配),
但「依据:GB/T 37276-2018...」截断后匹配不上,导致精简版只剩 2 行。
改成 `str(t).lstrip().startswith(keep_prefix)` 前缀匹配后恢复 5 行。
## 能力三:结构化填写说明
见上文 `guide` 字段。要点:
- AI 的 xlsx system prompt 里 **`guide` 为必填**,且 `steps` 要求 5-8 条、每条 15-40 字、
必须是可执行动作而不是空话
- `basis` 必须**点名国标条款号**(从 pricing.json 的 `clauses` 带进 prompt)
- `fallback_content()` 降级时也产 guide,保证断网也有说明可看
- `build_guide_blocks(content, item)` 把 guide 转成渲染行 `[(样式, 文本)]`,
样式 ∈ {`hdr` 小标题 / `line` 说明行 / `step` 编号缩进 / `plain` 灰字附注}
- `XlsxBuilder.add_guide(blocks, col_span, title)` 负责落笔,区块标题用品牌绿 `1F5B42`,底部细边框收口
- 月度分册中:**1 月全量,其余月精简**(`guide_full_only_january=True`)
## 必须知道的坑
1. **urllib 必须绕系统代理**:`ai_gen.py` 里已用 `build_opener(ProxyHandler({}))`。
不绕的话本机请求会被 WorkBuddy 系统代理拦成 502。
2. **中文字体必须设 `w:eastAsia`**:`gen_docx.py` 的 `set_run_font()` 已处理。
只设 `font.name` 中文会回退宋体,制度文件看起来就不像公文了。
3. **模板库里的 .wps/.et/.doc/.xls 是 OLE2 老格式**,python 读写不了,`build_index.py`
会跳过并列进 `skipped`。需要纳入时先用 WPS「另存为」转成 .docx/.xlsx 再重跑索引。
4. **`.docx`/`.xlsx` 扩展名不可信**:`build_index.py` 会验文件头(ZIP=504b0304)。
5. **模板库 `99_待处理-隐私与临时文件/` 整个目录跳过**(含真实长者隐私数据),
索引器已硬编码排除,不要放开。
6. **AI 失败必须降级**:`ai_gen.fallback_content()` 会用模板骨架凑一份能交的,
报错记进 `errors` 但不中断整包。**绝不允许因为一份资料失败导致整包失败**。
7. **AI 返回可能带 ```json 围栏**:`_clean_json()` 已剥。
8. **note 前缀去重**:AI 有时自带「填表说明:」,主入口会剥掉,否则会出现
「填表说明:填表说明:」。改用结构化 `guide` 后此问题基本消失。
9. **AI 是分批调用的**:一份资料一次调用,76 份全量跑约 5-10 分钟。
不要试图一次让 AI 生成多份(输出会被截断)。
10. **月度分册判定要先剥「·类型」后缀**:`★医疗护理服务·记录/档案类` 里的"档案"
会把整批误判成单页。见「能力二」的坑。
11. **2 月固定 29 天**:`cap = 29 if m == 2 else calendar.monthrange(year, m)[1]`,
年度汇总页同样处理。别用 `min(29, monthrange(...))`。
12. **精简说明用前缀匹配**:`startswith(tuple)`,不要用截断后 `in tuple`。
13. **月度分册必须消费 `sampleRows`**:早期版本只写空白天数行,AI 生成的长者示范行
被整段丢弃(表现为「名单导入了但台账里没名字」)。现在放 1 月页顶部 + 浅底纹标注。
14. **AI 返回 JSON 可能被 max_tokens 截断**:制度类内容长,6000 tokens 常不够。
两道防线:① `call_deepseek` 默认提到 `max_tokens=8000`;
② `_repair_json()` 逐字符扫描括号深度 + 字符串状态找最后安全截断点,补齐 `}`/`]`。
**别只用 `json.loads()`**,一截断就整个降级,而制度类是全套里最贵的(¥69.5)。
## 红线(写进 prompt 了,别删)
- 严禁出现「包过」「保过」「保证通过」
- 严禁建议「补造历史记录」
- 不许出现特定地域名(除非机构信息里明确给了)
- 落款日期留空给客户填
- **长者名单只用于预填表格示例行,不写入任何对外说明文字**
- 涉及长者健康信息的备注(疾病/禁忌)不进制度类文档正文
## 接进 Flask 应用
`星评自查工具/app.py` 的 `build_package(order)` 可以替换为:
```python
import os, sys
sys.path.insert(0, r"anchor-star-template-gen/scripts")
os.environ.setdefault("DEEPSEEK_API_KEY", DEEPSEEK_KEY) # 把 key 暴露给 skill
import build_package as bp
def build_package(order):
res = bp.build(order=order, out_dir=out,
index_path=os.path.join(BASE_DIR, "template_index.json"),
pricing_path=PRICING_FILE,
elders=order.get("elders"), # 长者名单(可选)
with_roster=True, # 附出《入住长者名册》
monthly=True, # 每日记录类自动分册
year=order.get("year"))
# 把 res["zipPath"] 移进 DELIVERY_DIR,或直接改 build() 的 out_dir
return os.path.basename(res["zipPath"]), len(res["files"])
```
**注意**:`build()` 会同步阻塞到全部资料生成完(一份约 3-12 秒;76 份 5-10 分钟)。
Web 场景下必须放到后台线程并先返回「已受理」,否则请求必超时。
当前 `app.py` 已保留 `_build_package_fallback(order)` 作为兜底(skill 调用异常时回退 v1 说明文档模式)。
### 推荐的 Web 端参数传递
```python
order = {
"orderId": "AQH20260910-001",
"org": {"name": "康乐养老院", "star": "三星", "manager": "王强"},
"keys": ["★生活照料服务|制度类", "★膳食服务|记录/档案类"],
"elders": [{"name":"张桂芳","gender":"女","age":82,"bed":"201",
"level":"一级","note":"糖尿病、忌甜食"}],
"year": 2026,
}
```
## 输出目录约定
```
输出目录/
├── ★生活照料服务-制度类.docx # 单份资料(人读的)
├── ★膳食服务-记录_档案类.xlsx # 月度分册(13 sheet:年度汇总+12月)
├── 安全管理-台账类.xlsx # 单页台账(底部带填写说明)
├── 入住长者名册.docx # 有 elders 时自动附带
├── 资料包清单.md # 交付清单
└── 星评资料包-机构名-时间戳.zip # 打包
```