← 返回爱琴海个人主页

养老星级资料包生成器

养老机构星级评定资料包 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         # 打包
```
养老星级资料包生成器 · 由 sync_homepage.py 自动生成