# Param XLSX Sync

> HPS3/HPC 工作站 param 表更新工作流：把手动整理的 xlsx（含删除线标记的待删记录）转换为项目里的 param_*.md 数据文件，与 workstation-review 等规格文档逐条比对差异，整理成更新清单交用户审批，审批通过后才写入 param 表，并把变更过程同步到本地与飞书的 param_change_history 文档。触发关键词：param更新 / xlsx转md / 更新param表 / 对比workstation-review / param version history。

- Skill: `cookieshaha/param-xlsx-sync` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cookieshaha/param-xlsx-sync`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cookieshaha/param-xlsx-sync/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: CookiesHaha (https://skillmd.com/u/cookieshaha)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cookieshaha/param-xlsx-sync

---


# param 表更新工作流（xlsx → md）

## 适用场景

- "把这个 xlsx 转成 md，更新 param_xxx.md"
- "对比一下 param 表和 workstation-review，看有没有不一致"
- "我在 xlsx 里用删除线标了要删的记录，帮我处理"
- "把这次改动记录到 param_change_history.md"

## 定位

处理**结构化设备参数表**（机器人/容器/货架/充电桩/工作站等）从 Excel 到 Markdown 数据文件的同步，典型用于 HPS3/HPC 等产品线选型算法的数据源维护（如 `business-flow-config-design.md` 类文档 §4 会直接引用这些 param 表）。不负责 PRD 正文撰写，那是 write-a-prd / lark-workflow-prd-sync 的职责——如果 param 更新最终要反映到 PRD 正文，本 skill 只把结论准备好，落到哪份规格文档由用户决定。

## 前置

- Python3 + `openpyxl`（读取带样式的 xlsx，用于识别删除线）+ `pandas`（转 Markdown 表格，`to_markdown` 依赖 `tabulate`）
- 目标 `.md` 文件已存在且是「## sheet名」分节结构（按 sheet 名对齐替换整个分节，不做逐行 diff 合并）
- lark-cli（Step 4 同步飞书 `param_change_history` 文档时需要；参考 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md) 做认证）
- 项目里需要知道 `param_change_history` 对应的飞书文档 token/URL（通常记在本地 `param_change_history.md` 的 frontmatter，或直接问用户要）

## 固定四步流程

```
Step 1: xlsx → md（清除删除线记录）
    │
    ▼
Step 2: 与规格文档（workstation-review 等）逐条比对差异
    │
    ▼
Step 3: 整理更新清单 → 用户审批 ──[驳回/修改]──┐
    │                                          │
   [批准]                                      │
    │◄─────────────────────────────────────────┘
    ▼
    写入 param_hps3.md / param_hps5.md（或对应 HPC 文件）
    │
    ▼
Step 4: 变更记录写入本地 param_change_history.md + 同步 append 到飞书对应文档
```

**关键约束：Step 1/2 只做只读分析，不修改任何 param_*.md。只有 Step 3 用户明确批准后，才允许写入。** 这是与 v1.0 版本最大的区别——旧版本在生成差异后即直接写入，新版本强制过一道人工审批闸门再落盘。

---

## Step 1 — xlsx → md：清除所有带删除线的记录

不能只用 `pandas.read_excel`（会丢失删除线等格式信息）。用 `openpyxl.load_workbook(path, data_only=True)` 逐 sheet、逐 cell 读值，同时读 `cell.font.strike`：

```python
import openpyxl
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
for name in wb.sheetnames:
    ws = wb[name]
    header = [c.value for c in ws[1]]
    rows = []
    for row in ws.iter_rows(min_row=2):
        values = [c.value for c in row]
        strikes = [bool(c.font and c.font.strike) for c in row]
        rows.append((values, strikes))
```

**处理规则：只要一行里存在带删除线的非空单元格，这一行就整体从转换结果里删除**（不再区分"整行删除线"与"单元格级删除线"两种情况——这是本版本相对 v1.0 的简化：v1.0 曾对单元格级删除线做"保留整行仅备注废弃"的特殊处理，但实践中容易和用户的真实意图产生分歧，v2.0 统一按用户可见的删除线视觉信号执行删除，删除范围以什么记录为准由 Step 2/3 的清单向用户说明并接受审批）。

判断为空需要小心：一行里如果所有非空单元格都没有删除线，正常保留；只要出现至少一个非空单元格带删除线，整行标记为待删除，不写入转换结果。

**转 Markdown 时的格式坑：**
- xlsx 整数被 pandas 读成 `float64` 会在 md 里多出 `.0`（如 `600.0`）——转换前对照目标 md 文件已有格式，必要时对列显式转型（如 `.astype('Int64')`）。
- JSON/富文本字段（如 `chargerRatio` 里的 JSON 数组字符串）不要被自动转义或截断，原样保留。
- 每个 sheet 对应目标 md 文件里的「## sheet_name」二级标题分节，替换时只换表格内容，保留标题。

**Step 1 产出（暂存，不写入正式文件）**：转换后的候选 md 内容 + 一份"本次删除了哪些记录"清单（sheet 名、productNumber/productName、简要说明），供 Step 3 呈现给用户。

## Step 2 — 与规格文档逐条比对差异

如果项目里有一份「人可读」的规格梳理文档（如本项目的 `prd/2026/7/workstation-review.md`），把 Step 1 的候选 md 内容与之逐字段核对：

- 数值型字段（切箱时间、效率区间、部署距离等）：直接比大小，标出具体不一致的数字
- 覆盖范围：param 表有的记录，规格文档是否体现了对应档位/场景（反之亦然）
- **先排除「假差异」**：若按单一 key 去重后比对，同一 key（如 `productNumber`）对应多行（国内版/CE 版各一条）会被误判成新增或差异。比对前先确认 key 是否唯一，不唯一就按完整字段集比对。
- **警惕来源版本落后的情况**：如果新 xlsx 是基于比当前 md 更早的版本整理的（比如仍带着上一轮已清理掉的冗余记录），不要整表覆盖——只提取真正变化的字段/记录，避免把之前已确认的清理/归类工作撤销。判断方法：对比新旧行数与已知的历史清理记录，如果候选转换结果的行数明显多于当前 md 且多出来的行正是历史记录里标注过要删除的，就要提醒用户"这份 xlsx 疑似基于旧版本整理"。

## Step 3 — 整理更新清单，交用户审批

把 Step 1（删除了什么）+ Step 2（比对出的差异/待更新字段）合并成一份结构化清单，格式建议：

```
删除的记录（N 处）：
| Sheet | productNumber/productName | 说明 |

新增/变更的字段（M 处）：
| Sheet | productNumber | 字段名 | 旧值 | 新值 |

未变化部分：xxx
```

**呈现清单后停下来，明确请求用户批准**（"以上改动是否批准写入 param 表？"），不要自行决定写入。任何字面表述可能有歧义的地方（比如用户说"删除工作站"但删除线其实也覆盖了货架/充电桩记录）都要在清单里单独标注出来问清楚，不要自己判断按哪种理解处理。

用户批准后（可能是全部批准，也可能是部分调整后批准）：
- 按最终确认的内容写入目标 `param_hps3.md` / `param_hps5.md`（或项目里对应的其他 HPC/HPS param 文件）
- 只替换涉及改动的「## sheet」分节，其余分节保持原样不动

## Step 4 — 变更记录：本地 + 飞书双写

### 4.1 本地

在项目的变更历史文档（如 `.raw/param_change_history.md`；不存在则先创建）里追加新版本块（版本号在已有文件基础上递增），固定包含：来源、删除记录表（如有）、新增/变更字段表（如有）、归类/结构调整说明（如有）、未变化部分。如规格文档（workstation-review 等）也因本轮变更同步修改，在它自己的变更记录章节里另加一条，两处变更记录不要合并成一份。

### 4.2 飞书

`param_change_history.md` 若已有对应的飞书文档（token/URL 记在 frontmatter 或由用户提供），把本轮新增的版本块同步过去：

```bash
# 用 markdown 格式 append 到文档末尾，避免 overwrite 抹掉之前的历史记录
lark-cli docs +update --api-version v2 \
  --doc "DOC_TOKEN" \
  --command append \
  --doc-format markdown \
  --content @_v{N}_content.md \
  --as user
```

**必须用 `append`，不能用 `overwrite`**——overwrite 会清空之前版本的记录、评论等内容。写入内容前留意 Markdown 转义规则（`-`、`#`、`|`、`~` 等符号在特定位置需要转义，尤其 productNumber 里常见的 `-`；具体规则见 lark-cli 内置的 `skills read lark-doc references/lark-doc-md.md`）。

`--content @file` 只接受当前工作目录下的相对路径，写临时文件时放在 cwd 下（如 `./_v{N}_content.md`），append 成功后删除。

若项目里还没有 `param_change_history` 对应的飞书文档，先问用户是否需要新建（可用飞书文档导入类工具把本地 md 首次导入生成飞书文档，并把返回的 token 记录到本地 md 的 frontmatter 里，供以后 append 使用）。

## Step 5（可选）— 输出总结

给用户一份简短总结："本轮删除了 N 条记录、更新了 M 个字段、已写入 param 表、变更记录已同步本地与飞书"，附关键差异表格。

## 参考实现

本 skill 总结自 `pst/layout` 项目 2026-07-28/29/30 的多轮实际迭代（该项目 `.raw/param_change_history.md` 记录了完整过程：v1 新增字段 → v2 规格核对修正 → v3 双循环归类清理 → v4 单字段精确更新），可作为字段命名、判断阈值等细节的参照案例。

