Skill: runkecheng/forces-reanalyze-smart (version 1.0.2)

--- name: forces-reanalyze-smart version: 1.0.2 description: '完成智慧门店工单复盘全流程。触发词：工单复盘、复盘工单、拉取工单数据、创建复盘文档、工单分析、创建复盘。完整覆盖三个阶段：(1)拉取Furcas工单数据并导入多维表格 (2)创建/更新工单复盘总文档 (3)创建开发复盘文档并填充TOP5统计。当用户要求进行工单复盘时使用。' --- # 智慧门店工单复盘全流程 ## 流程概览 一次完整的工单复盘涉及 **3个文档**，按顺序执行： ``` Step 0：复盘跟踪表（前置检查） 检查/创建 目标月份记录 → 落数据基线 阶段一：工单数据表（飞书多维表格） Step 1 → Step 2 → Step 3 → Step 4 → Step 5 → Step 5b 解析月份 → 获取Cookie → 拉取Furcas数据 → 创建/复用月份bitable（模板：J4tawf06GijvEmkBTSCcdyNsnyd）→ 导入CSV → 更新mention-doc链接 阶段二：工单复盘总文档（知识库文档） Step 6 → Step 7 创建文档（模板：GEqawooPYiDXLWkRKZHcJnLcnFc）→ 填充变量 阶段三：开发复盘文档（知识库文档） Step 8 创建文档（模板：Secqwa7deiaRwTkUsG6cJ7xpnzj）→ 填充TOP5统计 ``` ## 关键链接 | 类型 | 链接/Token | |------|-----------| | 工单数据Bitable模板 | `J4tawf06GijvEmkBTSCcdyNsnyd` (table: `tblXtjn9QHTcB5oa`, view: `vew8A2EB9c`) | | 复盘总文档模板 | `GEqawooPYiDXLWkRKZHcJnLcnFc` | | 开发复盘文档模板 | `Secqwa7deiaRwTkUsG6cJ7xpnzj` | | 复盘跟踪表（前置数据源） | `YMQjwQeWCipABCkl8y7ckZlunQe` (table: `tblpygt9n8dqZcsw`，参见 Step 0) | | Bot App ID | `cli_a97b6a0ffc399cc0` | --- # ⚙️ 环境要求与配置 ## 运行时依赖 使用此 skill 需要以下运行时环境（OpenClaw 标准环境已预装 Node.js）： | 依赖 | 用途 | 安装命令 | |------|------|---------| | Python 3 | 运行 fetch_furcas.py & generate_batches.py | 通常已预装 | | requests (Python) | Furcas API 调用 | `pip3 install requests` | | Node.js 18+ | 运行 import_to_bitable.mjs & fill_review_cells.cjs | 通常已预装 | ## 环境变量说明 > **这是此 skill 被其他人使用时最容易出错的部分。** 以下是所有脚本读取的环境变量： ### 必须设置的环境变量 | 环境变量 | 脚本 | 说明 | |---------|------|------| | `FURCAS_COOKIE` | `fetch_furcas.py` | Furcas 登录 Cookie。从 https://furcas.shouqianba.com 登录后 F12 → Network → 复制完整 Cookie 请求头 | | `SENDER_OPEN_ID` | `fill_review_cells.cjs` | 当前用户的飞书 open_id（每人不同）。格式 `ou_xxxxxxxx`。可从飞书开发者后台获取 | ### 可选设置的环境变量 | 环境变量 | 脚本 | 默认值 | 说明 | |---------|------|-------|------| | `FEISHU_UAT_DIR` | `import_to_bitable.mjs`, `fill_review_cells.cjs` | `$XDG_DATA_HOME/openclaw-feishu-uat` | OAuth 加密令牌存储目录。标准部署无需修改，特殊部署需指定 | | `APP_TOKEN` | `import_to_bitable.mjs` | （必填） | 工单数据多维表格的 app_token | | `TABLE_ID` | `import_to_bitable.mjs` | （必填） | 工单数据表的 table_id | | `CSV_PATH` | `import_to_bitable.mjs` | `/workspace/furcas.csv` | CSV 文件路径 | | `BATCH_SIZE` | `import_to_bitable.mjs` | `15` | 每批导入条数 | | `TABLE_NAME` | `import_to_bitable.mjs` | `工单数据` | 目标表名（用于校验） | | `APP_ID` | `fill_review_cells.cjs` | `cli_a97b6a0ffc399cc0` | Bot App ID（团队共用，通常无需修改） | | `DOC_ID` | `fill_review_cells.cjs` | （必填） | 要填充的复盘总文档 ID | | `MONTH` | `fill_review_cells.cjs` | （必填） | 目标月份标签，如 `2026-05` | ## 首次使用前的准备 1. **安装 Python 依赖**：运行一次 `pip3 install requests` 2. **完成飞书 OAuth 授权**：执行 `clawhub install forces-reanalyze-smart` 后，按提示完成飞书授权 3. **获取 Furcas Cookie**：登录 Furcas 后拷贝 Cookie 4. **确认自己的 open_id**：在飞书开放平台获取或咨询管理员 --- # 前置步骤：复盘跟踪表 ## Step 0: 检查/创建跟踪表记录 在创建任何文档之前，先检查复盘跟踪表是否已有目标月份的记录。 ### 跟踪表链接 `https://sqb.feishu.cn/wiki/YMQjwQeWCipABCkl8y7ckZlunQe?table=tblpygt9n8dqZcsw&view=vewme7V2C9` - Bitable App Token: `YMQjwQeWCipABCkl8y7ckZlunQe` - Table ID: `tblpygt9n8dqZcsw` ### ⚠️ 核心规则：必须先查存在，绝不复件 > **这条规则是全局最高优先级。任何创建操作前必须先查，查到已存在则直接复用，绝不允许创建第二条同名记录。** ### 操作步骤 1. **查询已有记录（必须执行，不可跳过）**： ``` 操作：feishu_bitable_app_table_record list 参数： app_token: "YMQjwQeWCipABCkl8y7ckZlunQe" table_id: "tblpygt9n8dqZcsw" filter: { conjunction: "and", conditions: [ {field_name: "复盘范围", operator: "is", value: ["2026-05"]} ] } ``` > **注意 field_name 必须用中文名「复盘范围」（是文本类型的主字段），不要用 field_id。** 2. **判断是否已有目标月份**： - ✅ **找到记录** → **立即停止创建流程**，直接使用已有记录的 `record_id` 及其包含的所有数据（工单总数、复盘工单数、各分类统计等），进入阶段一 - ❌ **没找到** → 执行第 3 步创建新记录 > **绝对禁止**：查到已有记录后，再创建第二条同名或同周期的记录。 3. **新建记录**（仅在确认不存在时执行）： ``` 操作：feishu_bitable_app_table_record create 参数： app_token: "YMQjwQeWCipABCkl8y7ckZlunQe" table_id: "tblpygt9n8dqZcsw" fields: 复盘范围: "{月份标签}" // 整月如 "2026-05"（不要加 "-整" 后缀） 开始时间: {毫秒时间戳} // 范围起始日期 00:00:00 UTC+8 结束时间: {毫秒时间戳} // 范围结束日期 23:59:59 UTC+8 日期: {毫秒时间戳} // 当前操作日期（执行复盘的当天） ``` ### 字段说明 | 字段 | 类型 | 填写规则 | |------|------|---------| | `复盘范围` | 文本 (主字段) | 整月：`2026-05`（**不要写** `2026-05-整`）；上半月：`2026-05-上`；下半月：`2026-05-下` | | `开始时间` | 日期（毫秒时间戳） | 范围起始日 `00:00:00 UTC+8`，如2026年5月整月 → `1777593600000`（对应 `2026-05-01T00:00:00+08:00`） | | `结束时间` | 日期（毫秒时间戳） | 范围结束日 `23:59:59 UTC+8`，如2026年5月整月 → `1780271999000`（对应 `2026-05-31T23:59:59+08:00`） | | `日期` | 日期（毫秒时间戳） | 当前操作日期，即执行复盘的当天，如2026年5月6日 → `1777910400000`（`2026-05-06T00:00:00+08:00`） | > ⏰ **时间戳必须二次确认**：创建记录后立即读取该记录，检查时间戳对应的年份是否与目标年份一致（2026年）。一个常见的错误是毫秒时间戳算成了2025年的日期。如果时间戳不对，删除该记录，重新计算并创建。 > **注意**：其余字段（工单总数、复盘工单、超时工单数、各解决类别统计等）均为 Lookup/Formula 自动计算字段，无需手动填写，数据导入后会自行更新。 ### 创建后操作 - ✅ 确认记录已成功创建 - ✅ **读取刚创建的记录，验证时间戳是否对应正确的日期（2026年不是2025年）** - 记录下 `record_id`，后续阶段二填充文档第三节表格时需要 - 进入阶段一，开始拉取工单数据 --- # 阶段一：工单数据表 ## Step 1: 解析用户意图 用户说"做X月工单复盘"或"复盘X月"时： - 提取目标月份（如 `2026-04`） - 计算开始/结束日期：`2026-04-01` ~ `2026-04-30` - 记录月份标签：`2026-04` 如果用户提到"拉取X到X的工单数据"，直接使用指定的起止日期。 ## Step 2: 获取 Cookie 本阶段需要有效的 Furcas Cookie。 1. 如果已有可用的 `FURCAS_COOKIE` 环境变量，直接使用 2. 如果没有，引导用户： - 登录 `https://furcas.shouqianba.com` - F12 开发者工具 → Network → 复制完整的 Cookie 请求头 - 设置环境变量：`export FURCAS_COOKIE="完整Cookie值"` > ⚠️ **不要**将 Cookie 硬编码写入脚本文件。这是个人凭据，每个使用者必须用自己的 Cookie。 > 环境变量方式可在 session 级别临时设置，不会污染源代码，也便于他人使用。 ## Step 3: 拉取工单数据 ```bash python scripts/fetch_furcas.py -s "2026-04-01" -n "2026-04-30" -o /workspace/furcas.csv ``` 输出字段：问题描述、问题链接、工单状态\|修复情况、问题原因、责任人、解决类别、解决模块、超时时间、超时备注 ## Step 4: 创建/复用月份工单数据 Bitable ⚠️ **必须检查**：先确认目标月份目录下是否已有同名 bitable。用 `feishu_wiki_space_node list` 列出目录内容，看是否已有工单数据表。如有则直接复用，跳过创建。 ### 正确创建方式（仅需一步） 使用 `feishu_wiki_space_node copy` 将模板节点直接复制到目标月份目录： ``` 操作：feishu_wiki_space_node copy 参数： node_token: "J4tawf06GijvEmkBTSCcdyNynsd" // 工单数据表模板 space_id: "7510046829784662017" target_parent_token: "{目标月份目录节点token}" target_space_id: "7510046829784662017" ``` **为什么必须用 wiki node copy？** - ❌ `feishu_bitable_app copy` → 创建独立 bitable（不在 wiki 目录中） - ❌ `feishu_wiki_space_node create` → 创建空白 bitable（表名 "Table"，字段全英文） - ✅ `feishu_wiki_space_node copy` → 直接在 wiki 内创建模板副本，字段/视图全部正确 ### 复制后的处理 1. **重命名**：bitable 名称 → `智慧门店-2026-5月工单数据` 2. **删除模板空记录**：模板自带 5 条空记录（`fields: {}`），用 `batch_delete` 清空 3. **验证字段结构**：应包含以下 9 个字段： - `文本`（Text, primary）、`问题链接`（Url）、`工单状态|修复情况`（Text）、`问题原因`（Text） - `责任人`（Text）、`解决类别`（SingleSelect）、`解决模块`（SingleSelect） - `超时时间`（Text）、`超时备注`（Text） **关键约束**： - `工单状态|修复情况` 含竖线 `|`（CSV中为 `/`，脚本会自动映射） - `问题链接` 是 URL 类型，导入时用 `{"link": "URL", "text": "查看工单"}` - `责任人` 是文本类型（type=1），不是人员类型 ## Step 5: 导入数据到多维表格 **推荐：使用导入脚本** ```bash APP_TOKEN={月份bitable的app_token} \ TABLE_ID={月份bitable的table_id} \ CSV_PATH=/workspace/furcas.csv \ BATCH_SIZE=15 \ node scripts/import_to_bitable.mjs --name "数据表" ``` > ⚠️ **`--name` 参数必须传**：模板复制出来的表名叫 `数据表`，但脚本默认校验名为 `工单数据`。必须用 `--name "数据表"` 或 `TABLE_NAME="数据表"` 让校验通过。不传的话校验会警告但不阻止导入。 脚本自动：校验表名 → 清空旧数据 → 分批创建记录 → 创建看板视图 → 验证条目数 → **校验超时备注** → **去重检查**。 > ⚠️ **`batch_delete` 参数名是 `record_ids`（不是 `records`）**：这是飞书 API 的规范。如果写错，清空静默失败，新数据追加到旧数据上，导致重复。 > ⚠️ **去重检查是最后一道防线**：即使清空步骤因意外失败（如网络超时、API 限流），去重步骤也会自动删除重复工单，保证每个工单 ID 唯一。 > ⚠️ **表名校验**：脚本在导入前会调用 API 读取目标表的实际名称，与 `TABLE_NAME` 环境变量比对。如果名称不匹配会给出警告，防止数据写入错误的表。 **手动替代方案**：使用 `feishu_bitable_app_table_record` 工具 `action=batch_create`，records 参数用 `string="false"`。 --- # 阶段二：工单复盘总文档 ## Step 5b: 更新文档 Mention-Doc 链接 导入数据后，立即更新复盘总文档中的 mention-doc 链接，确保指向当前月份的正确表格和开发复盘文档。 **为什么必须做：** 复盘总文档从模板复制后，mention-doc 链接指向的是模板的占位对象（如旧月份的表格或模板文档）。不更新的话，点击链接会看到错误的数据。 ### 更新步骤 1. **获取复盘总文档的文档 ID**（从 step 6/7 创建或之前创建的文档获取） 2. **找到 mention-doc 所在的 block**： - 遍历文档根级 children，找到包含「月具体工单请查看飞书表格」文本的 block - 该 block 是 type=2（text），其 elements[] 中包含 mention_doc 类型的 element 3. **PATCH 更新该 block 的 elements**： - `工单数据表 mention-doc`：token → 当前月份的 bitable token，obj_type → 8（bitable） - `开发复盘文档 mention-doc`：token → 当前月份的 wiki 节点 token，obj_type → 16（wiki） - **不写 text_run 的 content**（显示文本由文档标题决定） - **obj_type 必须传整数**（8=bitable, 16=wiki），传字符串会报 `99992402` ### 脚本参考 ```javascript // PATCH /docx/v1/documents/{docId}/blocks/{blockId} // Body: { update_text_elements: { elements: [originalElements] } } // 只改 mention_doc.token/obj_type/url，保持 text_run 不变 ``` ### 第 34 个经验（2026-05-07）：Mention-doc 更新后 token 被自动解析 发送 wiki 节点 token 后，飞书 API 会自动解析为实际文档的 obj_token，类型也会从 wiki(16) 变为具体类型（如 docx=22, bitable=8）。修改后 fetch-doc 验证即可。 ## Step 6: 创建工单复盘总文档 ### 前置检查 **在创建之前，先检查目标月度目录下是否已存在同名的文档。** 使用 `feishu_wiki_space_node list` 列出月度目录下的所有节点。如果已有同名文档，直接进入 Step 7 更新即可，**不要重复创建**。 ### 创建步骤 1. **复制模板**：从模板 `GEqawooPYiDXLWkRKZHcJnLcnFc` 复制创建新文档 2. **重命名**：按月份命名，如 `智慧门店-2026-05月-工单复盘` 3. **放入对应目录**：确认文档在正确的年度/月度知识库目录下 复制文档的两种方式： - 使用 `feishu_wiki_space_node` 的 `copy` 操作（如果模板是 wiki 节点），传入 `space_id` 和 `target_parent_token` - 或用 `feishu_create_doc` 从模板创建（传入 wiki 节点 token） - 或用 `feishu_drive_file` 的 `copy` 操作 ## Step 7: 填充复盘总文档变量 模板中有大量 `x` 占位符（橙色高亮），需要按以下规则替换为实际数据。 ### 模板结构要点（非常重要） **不要破坏模板原有的换行和段落结构。** 模板中每个变量的位置和间距都有意义。以下从实际模板 `GEqawooPYiDXLWkRKZHcJnLcnFc` 获取的精确结构： ``` # 一、会议信息 会议主题：智慧门店-<text color="orange">2026-</text><text color="orange">10</text>月工单复盘 会议时间：<text color="orange">2026.05.07（周四） 16:00 - 16:30</text> # 二、会议议程 **复盘时间：**<text color="orange">**2026.05.07**</text> **复盘范围：**<text color="orange">**2026.04.01 00:00:00-2026.04.30 23:59:59**</text> **参与人员：** - **开发：朱栋泉、**<text color="orange">**李文茂(28)、付雷(12)、王佳明(10)、赵杭琪(9)**</text> - **产品：** - **测试：** - **技术支持：崔文思** - **运营：蒋达周** # 三、工单总计<text color="orange">XX</text>个(按工单数统计) [Table: 10行×11列，3个月数据（2026-02/03/04），每33格一组=110格] ⚠️ 2026-04 组（最近一个月）使用整体橙色格式，与 02/03 组的逐字橙色不同 <text color="orange">2026-04</text>月具体工单请查看飞书表格：<mention-doc [REDACTED_SECRET]" type="wiki">2026-xx月-智慧门店工单数据</mention-doc> 开发复盘文档：<mention-doc [REDACTED_SECRET]" type="wiki">开发复盘文档模板</mention-doc> # 四、工单总结 ## 工单数量分析 - <text color="orange">2026-04</text>月共产生工单<text color="orange">646</text>个，本次复盘筛选出<text color="orange">154</text>个工单。同比<text color="orange">2026-03</text>月<text color="orange">增加25.19%</text> - 从**解决类别**看，工单主要集中在：<text color="orange">外部原因-无需技术排查(208)、设计如此-无需优化(160)、外部原因-需技术排查(81)</text> - 从**解决模块**看：工单主要集中在：<text color="orange">收银系统-餐饮(197)、打印机(150)、扫码点单(117)</text> ## 工单时效分析 <text color="orange">90</text>个超时工单（其中产研介入<text color="orange">59</text>个），时效内解决占比<text color="orange">84.67</text>%，较3月<text color="orange">82%</text>有所提高。 主要超时原因： ## 问题总结 立即更改内容跟进： 同步客服&技术支持： # 五、历史跟进事项 <text color="orange">**2026-03**</text>**月跟进事项**： ... <text color="orange">**2026-02**</text>**月跟进事项**： ... ``` #### 📌 模板关键位置标注 | 位置 | 说明 | |------|------| | Section 3 标题 | `# 三、工单总计<text color="orange">XX</text>个(按工单数统计)`，**无 callout 块** | | Section 3 末尾 | **Mention-doc**（工单数据表 bitable + 开发复盘文档 wiki） | | Section 4: 工单数量分析 | 含同比上月数据行 `<text color="orange">同比...增加xx%</text>` | | Section 4: 工单时效分析 | `主要超时原因：`后为填空区域 | | Section 5 | 月份为「目标月-1」和「目标月-2」，如05月复盘则显示04和03月 | | 表格最后一组（最近月） | 使用整体橙色 `<text color="orange">已解决x（产研介x）</text>` 格式，与前面两组不同 | #### 📌 Section 5 月份命名规则 - 表头显示「目标月-1」和「目标月-2」的跟进事项 - 例如：05月复盘 → 显示 `**2026-04**月跟进事项` 和 `**2026-03**月跟进事项` - 月份部分用橙色标签包裹：`<text color="orange">**2026-03**</text>**月跟进事项**：` --- ### 🏷️ 命名规范（必须遵守） 创建文档时**禁止直接使用模板的名称**。每次创建必须按以下规范重命名： | 文档类型 | 模板节点 | 创建后命名规范 | 示例（2026-05月） | |---------|---------|--------------|------------------| | 工单数据跟踪表（多维表格） | `J4tawf06GijvEmkBTSCcdyNynsd` | `智慧门店-2026-xx月-工单数据` | `智慧门店-2026-05月-工单数据` | | 工单复盘文档（知识库文档） | `GEqawooPYiDXLWkRKZHcJnLcnFc` | `智慧门店-2026-xx月-工单复盘` | `智慧门店-2026-05月-工单复盘` | | 开发复盘文档（知识库文档） | `Secqwa7deiaRwTkUsG6cJ7xpnzj` | `智慧门店-2026-xx月-开发复盘文档` | `智慧门店-2026-05月-开发复盘文档` | **重命名方式**（API 不支持，必须手工）： 1. 复制模板创建文档后，在飞书页面打开 2. 右键左侧目录树中的节点 → 选择「重命名」 3. 按上表命名规范输入新名称 4. 标题改完后，mention-doc 的显示文字会自动更新（因为 mention 显示的是文档实际标题） > ⚠️ Section 3 的表格是 `<lark-table>` 格式（带 rowspan/colspan 的复杂表格），**不能用 markdown 表格替代**。 > 使用 `overwrite` 模式重写文档时，表格结构会丢失。因此 Section 3 必须保留原始 `<lark-table>` 代码。 ### ⚠️ ⚠️ ⚠️ 三大数据来源规则（必须遵守，不要再出错） ⚠️ ⚠️ ⚠️ > **以下3条规则已经被用户多次重复强调。每次出错都会导致文档数据混乱。阅读此文件时请逐条仔细核对！** #### 规则1：参与人员 → 取开发复盘文档 TOP5 **复盘总文档**中 `# 二、会议议程` → `参与人员：` → `开发：` 后面的橙色部分，必须填入**开发复盘文档 Section 2 中前5名责任人** 的姓名和工单数量。 - 不要使用模板中的旧数据（如 `李文茂(28)、付雷(12)、王佳明(10)、赵杭琪(9)`） - 不要自行编造 - 准确取本月的开发复盘文档中 Section 2 的5位开发人员及其工单数量 - 格式：`李文茂(5)、蒋达周(3)、赵杭琪(2)、金海轩(2)、王金楠(2)` #### 规则2：工单总计 XX + 第三节表格 → 全部来自复盘跟踪表 **模板中所有带 `x` 的数字占位符**（包括 Section 3 标题 `XX`、表格中所有 `x`）的填充值，必须全部从 **复盘跟踪表** Bitable 获取： ``` 复盘跟踪表: https://sqb.feishu.cn/wiki/YMQjwQeWCipABCkl8y7ckZlunQe?table=tblpygt9n8dqZcsw ``` - **Section 3 标题** `三、工单总计XX个` → X 填复盘跟踪表中当月记录的 `工单总数` 字段 - **表格数据** → 全部填复盘跟踪表中各月份的字段值 - **表格顺序**：必须按时间从上到下排列，**新数据在下方**。例如做 5 月复盘时： - 第一行（最上方）：2026-03（最旧） - 第二行（中间）：2026-04 - 第三行（最下方）：2026-05（最新） - **不要**把最新行放在最上面 #### 规则3：Section 4 工单数量分析 + 工单时效分析 → 全部来自复盘跟踪表 `# 四、工单总结` 下所有带数字的橙色文本，数据来源均为**复盘跟踪表**（不是工单数据表）： - `X月共产生工单X个` → 跟踪表 `工单总数` - `本次复盘筛选出X个工单` → 跟踪表 `复盘工单` - `同比上月增加/减少XX%` → 对比跟踪表中上月的 `工单总数` - `解决类别TOP3` → 跟踪表中各解决类别字段（`外部原因-无需技术排查`、`无需优化`、`外部原因-需技术排查` 等） - `解决模块TOP3` → 跟踪表中各模块字段（`收银系统-餐饮`、`扫码点单`、`打印机` 等） - **超时工单数/产研介入** → 跟踪表 `超时工单数`、`超时工单数-产研介入` - **时效内解决占比** → `1 - 超时工单数/工单总数 × 100%` --- ### 数据来源一览 | 数据 | 来源 | |------|------| | 会议信息、议程 | 用户提供或从上月文档沿用 | | **参与人员（开发TOP5）** | **开发复盘文档 Section 2 的前5名责任人** | | **第三节标题 XX** | **复盘跟踪表 `工单总数`** | | **第三节表格（所有数字）** | **复盘跟踪表（全部字段）** | | **工单数量分析（所有数字）** | **复盘跟踪表** | | **工单时效分析（所有数字）** | **复盘跟踪表** | | 历史跟进事项 | 从上月复盘文档复制 | ### 7.1 填充第三节表格 使用 `scripts/fill_review_cells.cjs` 填充嵌套表格单元格。详细操作见 `references/data_mapping.md`。 配置脚本顶部： ```javascript const DOC_ID = '...'; // 新创建的复盘总文档 ID const MONTH = '2026-04'; // 目标月份 const CELL_DATA = { total: '644', noOpt: '160（产研介入94）', prodOpt: '42（20）', techWork: '0', techCheck:'79（48）', noTech: '208（28）', internal: '138（产研介入87）', solved: '已解决54（27）', deferred: '延期处理66（46）', noRepro: '无法重现：18（14）', timeout: '88（58）', }; ``` 执行：`node scripts/fill_review_cells.cjs` ### 7.2 第三节表格：最近3个月数据 模板中 `# 三、工单总计XX个(按工单数统计)` 下方自带一个 `<lark-table>` 格式的嵌套表格。此表格**必须保留**，不能删除或替换为文本。 #### 表格内容规则 表格内应填充**最近3次工单复盘的数据**（包含目标月份+前2个月）。例如做 2026-05 月复盘时，表格应包含： | 行 | 内容 | |----|------| | 第一行（表头） | 复盘范围、时间、工单总数等列名 | | 第一数据行（最旧） | 2026-03 数据（跟踪表记录） | | 第二数据行（中间） | 2026-04 数据（跟踪表记录） | | 第三数据行（最新，在最下方） | 2026-05 数据（跟踪表记录） | **⚠️ 顺序规则**： - 模板默认把最近月放在第一行（上方），但**正确顺序是旧数据在上、新数据在下** - 因此需要把模板的行标签和数据都修改过来。例如模板中的 `2026-02` 行应改为 `2026-03` 数据，`2026-03` 行改为 `2026-04` 数据，`2026-04` 行改为 `2026-05` 数据 - 是的，需要把所有行的标签和数据都改一遍，不能偷懒只改一行 #### 数据获取 1. 从复盘跟踪表 `list` 全部记录 2. 找到最近3个月的记录（按月份排序 `2026-05` / `2026-04` / `2026-03`） 3. 提取各字段值：工单总数、复盘工单、超时工单数、无需优化、待产品优化、内部原因等 #### 填充方式 使用 `scripts/fill_review_cells.cjs` 逐个填充表格单元格。详细操作见 `references/data_mapping.md`。 配置脚本顶部： ```javascript const DOC_ID = '...'; // 新创建的复盘总文档 ID const MONTH = '2026-05'; // 目标月份（用于查找表格中的对应单元格） const CELL_DATA = { // 以下数据来自复盘跟踪表，key 见 data_mapping.md total: '21', noOpt: '11（产研介入7）', prodOpt: '0', techWork: '0', techCheck: '0', noTech: '0', internal: '10（产研介入3）', solved: '已解决4（1）', deferred: '延期处理6（2）', noRepro: '无法重现：0', timeout: '11（3）', }; ``` 如果目标月份在跟踪表中尚无数据（月未结束），表格中该行留空或填 "-"，用附注说明。 #### 重要：表格数据来自复盘跟踪表 复盘总文档第三节表格的数据**不直接来自工单数据表**，而是来自复盘跟踪表。跟踪表中各字段是 Lookup 公式，会自动根据 Step 0 创建的记录中的时间范围和已导入的工单数据计算汇总值。 因此复盘的正常顺序是： 1. **Step 0** → 创建跟踪表记录 2. **阶段一** → 导入工单数据 3. **之后** 跟踪表各 Lookup 字段会自动计算 4. **Step 7** → 从跟踪表读取计算结果，填入第三节表格 ### 7.3 填充工单总结 **⚠️ 所有数字均来自复盘跟踪表，不是工单数据表** 模板中 Section 4 的每一条文本都有橙色标记的数字占位符。所有数值必须从**复盘跟踪表**获取，不能从刚导入的工单数据表（24条）统计。 **工单数量分析**需要以下统计数据（全部来自复盘跟踪表 filter `复盘范围`=当月）： | 变量 | 跟踪表字段 | 取值方式 | |------|-----------|---------| | 当月工单总数 | `工单总数` | 直接取值 | | 复盘工单数 | `复盘工单` | 直接取值 | | 同比上月增减% | 对比上月 `工单总数` | `(本月-上月)/上月 × 100%` | | 解决类别TOP3 | `外部原因-无需技术排查`、`无需优化`、`待产品优化`、`外部原因-需技术排查` 等 | 按字段值排序取TOP3 | | 解决模块TOP3 | `收银系统-餐饮`、`扫码点单`、`打印机`、`收银系统-零售` 等 | 按字段值排序取TOP3 | **工单时效分析**需要（全部来自跟踪表）： | 变量 | 跟踪表字段 | 取值方式 | |------|-----------|---------| | 超时工单数 | `超时工单数` | 直接取值 | | 超时产研介入 | `超时工单数-产研介入` | 直接取值 | | 时效内解决占比% | `工单总数` + `超时工单数` | `(1 - 超时工单数/工单总数 × 100%)`，保留两位小数 | **获取方式**：`feishu_bitable_app_table_record list` 查询 `复盘范围` = 当月，然后从返回的 fields 中提取所需字段。 **注意**：Section 4 的文本包含大量橙色标记（`<text color="orange">`），无法通过简单的 `replace_range` 或 `selection_with_ellipsis` 更新（因为标记打断了文本连续性）。 - 超时文本块（如 `25个超时工单`）可以通过 block PATCH 更新（type 2 块） - 列表项（type 12）和标题块（type 3）无法通过 block API 更新 - 必须通过 `feishu_update_doc replace_all`（但会破坏 callout）或手动编辑 **问题总结**和**历史跟进事项**从上月文档复制，或由用户填写。 --- # 阶段三：开发复盘文档 ## Step 8: 创建开发复盘文档 ### 前置检查 **同样先检查目标月度目录下是否已有同名文档。** 使用 `feishu_wiki_space_node list` 确认后再操作。 ### 创建步骤 1. **复制模板**：从 `Secqwa7deiaRwTkUsG6cJ7xpnzj` 复制创建 2. **重命名**：按月份命名，如 `智慧门店-2026-05月-开发复盘文档` 3. **放入对应目录** ### ⚠️ 开发复盘文档需要修改/填充的内容（共3项，缺一不可！） **不要破坏模板原有的换行和段落结构。** 完整模板结构如下： ``` # 一、开发复盘文档 - 工单复盘文档：<mention-doc token="..." type="wiki">智慧门店工单复盘模版</mention-doc> ← 需修改 ① - 复盘表格内容：<mention-doc token="..." type="wiki">2026-xx月-智慧门店工单数据</mention-doc> ← 需修改 ② # 二、主要人员总结材料 <text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个) ← 需修改 ③ [callout] ← 保持模板原样！不要改动！ ``` --- #### 修改① 替换「工单复盘文档」mention-doc **目标**：指向本次创建的复盘总文档 wiki 节点 ``` { mention_doc: { [REDACTED_SECRET]", obj_type: 16, // wiki title: "智慧门店-2026-05月-工单复盘", url: "https://sqb.feishu.cn/wiki/{wiki节点token}" } } ``` #### 修改② 替换「复盘表格内容」mention-doc **目标**：指向当前月份的工单数据 bitable ``` { mention_doc: { [REDACTED_SECRET]", obj_type: 8, // bitable title: "智慧门店-2026-5月工单数据", url: "https://sqb.feishu.cn/base/{app_token}?table={table_id}&view={view_id}" } } ``` #### 修改③ 替换橙色高亮区的占位符（开发A~E 和 x） **这是最容易出错的地方！严格按照以下步骤：** 1. 找到文档根 children 中的 **type 2 块**（5个，对应开发A~E） 2. 对每个块的 elements，执行 3 个替换： | 位置 | 原来 | 改为 | 示例（李文茂） | |------|------|------|------| | 第一个 text_run | `开发A(` | `责任人姓名+（` | `李文茂(` | | 中间 text_run（内容为`x`） | `x` | 该人**工单总数** | `5` | | 末尾 text_run（内容为`x`） | `x` | 该人**超时工单数** | `4` | **元素结构参考**（以开发A为例，其余开发B~E结构相同但元素数可能不同）： ```javascript // 开发A 的 elements（8个元素）： [ {text_run: "开发A("}, // 改为人名 {text_run: "工单总"}, // 不动 {text_run: "数"}, // 不动 {text_run: "x"}, // 改为工单总数 {text_run: "个"}, // 不动 {text_run: ")(超时工单"}, // 不动 {text_run: "x"}, // 改为超时工单数 {text_run: "个)"} // 不动 ] // 开发B~E 的 elements（6个元素）： [ {text_run: "开发X("}, // 改为人名 {text_run: "工单总数"}, // 不动 {text_run: "x"}, // 改为工单总数 {text_run: "个)(超时工单"}, // 不动 {text_run: "x"}, // 改为超时工单数 {text_run: "个)"} // 不动 ] // 替换后效果： [ {text_run: "李文茂("}, {text_run: "工单总"}, {text_run: "数"}, {text_run: "5"}, // 原来是 x {text_run: "个"}, {text_run: ")(超时工单"}, {text_run: "4"}, // 原来是 x {text_run: "个)"} ] ``` **✅ 最终橙色高亮区的效果**（模板 → 正确）： ``` <text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个) ↓ 替换为 <text color="orange">李文茂(</text>工单总数<text color="orange">5</text>个)(超时工单<text color="orange">4</text>个) ``` --- ### ❗ 绝对不要做的事（踩坑记录） | ❌ 错误 | 后果 | |---------|------| | 只替换数字 `x`，不把`开发A`改成责任人姓名 | 橙色区显示 "开发A(工单总数5个)" 而非 "李文茂(工单总数5个)" | | 把分析总结填入 callout 内容 | callout 应该保持模板占位符状态，留给用户手动填写 | | 写入多行文本到 callout 块（如 `工单原因分析：\n1. xxx`） | 后续无法一次性清空，需要逐元素删除残留行 | | 用 `feishu_update_doc replace_all` 更新文档内容 | 会破坏 `<callout>` 块结构 | --- ### 责任人的选取规则 - 从工单数据表（阶段一导入的）统计 TOP5 责任人 - **排除** `崔文思` 和 `蒋达周` - 统计每个责任人： - 工单总数：该责任人名下的工单数量 - 超时工单数：该责任人名下 `超时时间` 不为空的工单数 **数据获取方式**： 1. 从工单数据表 `list` 全部记录 2. 按 `责任人` 字段分组计数 3. 排除崔文思、蒋达周 4. 取工单数最多的前5名 5. 对每个负责人统计超时工单数 **填充方式**： - 使用 UAT 调飞书 Docx PATCH API 逐元素更新，不要用 `feishu_update_doc`（会破坏 `<callout>` 块） - 通过 PATCH `update_text_elements` 替换橙色占位符的 `x`，保留 `<text color="orange">` 样式 - 模板已有5个开发者槽位（开发A-E），直接 PATCH 全部5个槽位的占位符，无需 POST 追加 ### 分析总结占位符 每个责任人段落的 callout 区域包含需要用户填写的分析总结（无法自动生成）： ``` 分析总结： 超时工单分析： 工单原因分析： 需更改需跟进事项： 需同步产品： 需同步开发： 需同步测试： 需同步客服： 需同步技术支持： 其他： ``` 在填充数据后告知用户这些区域需要手动填写。 --- # 脚本说明 ## `scripts/fetch_furcas.py` 从 Furcas API 拉取数据。参数：`-s` 开始日期、`-n` 结束日期、`-o` 输出路径。 ## `scripts/import_to_bitable.mjs` CSV → 多维表格导入一体化脚本。环境变量：`APP_TOKEN`, `TABLE_ID`, `CSV_PATH`, `BATCH_SIZE`, `TABLE_NAME`。 ## `scripts/generate_batches.py` CSV → JSON 批处理文件。参数：`--csv`, `--output-dir`, `--batch-size`。 ## `scripts/fill_review_cells.cjs` 填充复盘总文档第三节嵌套表格单元格。直接调飞书 Docx PATCH API，用用户 OAuth UAT。顶部配置区设置月份和数据。 --- # 注意事项 ## 通用 1. **Cookie 时效性**：Furcas Cookie 可能过期，过期后需用户重新拷贝 2. **导入长度限制**：batch_create 每批 ≤ 15 条 3. **看板视图分组**：创建后需显式设置 `kanban_field_id` 4. **表格嵌套单元格**：不能用 markdown flat table 替代，必须用 `fill_review_cells.cjs` 逐个填充 5. **替换 `x` 变量**：开发复盘文档的 `<text color="orange">x</text>` 占位符必须用 UAT PATCH API 逐元素更新（保留橙色样式），**禁止用 `feishu_update_doc replace_all`**（会破坏 `<callout>` 块）。复盘总文档的 `x` 同样用 PATCH API 更新。 6. **责任人排除**：开发复盘 TOP5 统计需排除崔文思、蒋达周 7. **文档排版**：从模板复制后，确认文档结构完整。当前模板（2026-05起）已无 callout 提示块，无需手动删除 ### 模板结构守则 8. **永远不要合并或删除模板中的换行**。模板的每个段落、列表都是精心设计的结构。特别是： - 会议信息的 `会议主题` / `会议时间` 各占一行 - 会议议程的 `**复盘时间：**` / `**复盘范围：**` / `**参与人员：**` 各自独立成段 - 参与人员列表用 `-` 无序列表格式 - 开发复盘文档的 callout 中每个冒号标题独占一行，`>` 前缀必须保留 - ⚠️ 复盘总文档模板（2026-05 版本）**已无 callout 块**，Section 3 标题后直接是 `<lark-table>` 9. **`feishu_update_doc` 禁止用于含表格的文档**：复盘总文档的 Section 3 包含 `<lark-table>`，**任何模式（overwrite/replace_all/append/insert_after/replace_range/delete_range）都会摧毁表格属性**（rowspan、colspan 丢失，cols 数变化）。 - ❌ `feishu_update_doc` 会先序列化文档为 markdown 再反序列化，过程中 lark-table 的 HTML 属性丢失 - ✅ **所有更新必须使用 UAT Token 直接调飞书 PATCH blocks API，逐元素更新** - ✅ 表格单元格：`PATCH /blocks/{cell_child_text_id}` 用 `update_text_elements` - ✅ 文本段落：`PATCH /blocks/{block_id}` 用 `update_text_elements` 10. **`feishu_update_doc` 禁止用于含 callout 块的文档**：开发复盘文档不含 lark-table，但含有 `<callout>` 块（type=19），feishu_update_doc 的 markdown 序列化/反序列化会： - ❌ `<callout>` → `>` blockquote（结构被破坏，callout 框消失） - ❌ `<text color="orange">` → 普通文本（颜色标记丢失） - ❌ 可能引入额外段落（如 `# 三、总结`） - ✅ **正确做法：所有更新必须使用 UAT Token 直接调飞书 PATCH blocks API，逐元素更新** ## 开发复盘文档填充经验 11. **替换开发 A/B/C/D/E 标记**（模板现含5个槽位）： - 模板使用 `<text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个)`（B/C/D/E同理） - 替换时搜索整个带标记的字符串，替换为 `**张姝（工单总数12个）（超时工单5个）**` - 橙色标记在替换时自动去除，改用加粗（** **） 12. **模板已有 5 个开发者槽位（开发A-E）**：最新模板 `Secqwa7deiaRwTkUsG6cJ7xpnzj` 已直接包含 5 组（开发A/B/C/D/E），每组含 `<text color="orange">开发X(工单总数x个)(超时工单x个)</text>` + `<callout>` 块。 - 直接 PATCH 填满全部 5 个槽位即可（无需 POST 追加） - TOP5 开发者按工单数从多到少填入开发A→E - ❌ 模板只有3个槽位时（旧版），才需要 POST `/children` 追加开发D和E ## 含表格文档的 PATCH API 更新（关键） ### ⚠️ 核心规则：feishu_update_doc 不可用于含复杂表格的文档 复盘总文档的 Section 3 包含 `<lark-table>`（带 rowspan/colspan），**feishu_update_doc 的任何模式（replace_all、insert_after、delete_range、replace_range、overwrite）都会摧毁表格属性**。 原因：这些模式先将文档序列化为 markdown 再反序列化，反序列化时 lark-table 的 HTML 属性丢失。 后果示例： - `cols="11"` → `cols="10"`（colspan=2 的合列丢失） - `rowspan="3"` → 消失（每月 3 行不再合并） - 表格从 10 行 11 列 → 7 行 10 列 ### ✅ 正确做法：直接调飞书 Docx PATCH API ``` PATCH /open-apis/docx/v1/documents/{document_id}/blocks/{block_id} Authorization: Bearer {user_access_token} Body: { "update_text_elements": { "elements": [{ "text_run": { "content": "新文本内容", "text_element_style": {} } }] } } ``` ⚠️ `update_text_elements` 会**丢失格式**（bold/orange）：新的 text_run 使用空的 `text_element_style: {}`，所以加粗、橙色标记等格式会丢失。如需保留格式，需在 `text_element_style` 中显式设置 `bold: {}` 或 `inline_code: {color: 25, background_color: 2}`（橙色）。 ### 更新顺序不可逆 ``` 模板拷贝 → 填表（fill_review_cells.cjs）→ 更新文本（PATCH API） ↑ ↑ 必须先做 后做 ``` 填表（表格单元格级别的 block 操作）必须在任何文本替换之前完成。如果先做文本替换再填表，feishu_update_doc 已经破坏了表格结构。 ### Block-type-specific 文本属性 通过 PATCH API 读取/更新文本时，不同 block type 的文本存在不同属性中，不是都在 `block.text` 下： | Block Type | 描述 | 文本所在属性 | |-----------|------|------------| | 2 | 文本段落 | `block.text.elements` | | 3 | 标题1（#） | `block.heading1.elements` | | 4 | 标题2（##） | `block.heading2.elements` | | 5 | 标题3（###） | `block.heading3.elements` | | 12 | 无序列表项（-） | `block.bullet.elements` | | 14 | 有序列表项（1.） | `block.ordered.elements` | **PATCH API 写入统一使用 `update_text_elements`**，不分类型。 ### Heading 块可以有子节点 飞书文档中，标题块（block_type=3/4/5）**可以有** 子 children（如 Section 4 的 `## 工单数量分析` 就包含 bullet 子节点）。Section 4 的文本不是根级节点，而是 heading 的 children： ```javascript // Section 4 的数据需要遍历 heading 的 children 获取： const rootBlocks = getChildren(document_block_id); for (const b of rootBlocks) { // b.block_type === 4 （heading2） // b.txt === '工单数量分析' const sectionKids = getChildren(b.block_id); // ← ✅ 能拿到子节点 // sectionKids[0].block_type === 12 （bullet） // sectionKids[0].txt === '2026-05月共产生工单81个...' } ``` **但是**：表格（type=31）和 callout（type=19）**不是**任何块的 children，它们始终是文档根层的同级节点。即使它们在视觉上位于某个 heading 下方，API 读取时它们和 heading 是平级关系。 ### 权限与加密 PATCH API 需要 User Access Token（UAT），用 OAuth 方式获取。UAT 加密存储，解密方式： ```javascript const d = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'openclaw-feishu-uat'); const enc = fs.readFileSync(path.join(d, '{appId}_{userOpenId}.enc')); const masterKey = fs.readFileSync(path.join(d, 'master.key')); // AES-256-GCM: IV 12 bytes + tag 16 bytes + ciphertext const iv = enc.subarray(0, 12), tag = enc.subarray(12, 28), cipher = enc.subarray(28); const decipher = crypto.createDecipheriv('aes-256-gcm', masterKey, iv); decipher.setAuthTag(tag); const uat = JSON.parse(Buffer.concat([decipher.update(cipher), decipher.final()]).toString('utf8')).accessToken; ``` ### fill_review_cells.cjs 的使用 位置：`scripts/fill_review_cells.cjs` 配置方式（通过环境变量传入，不需要修改脚本）： ```bash # 设置必要的环境变量 export DOC_ID="新文档的obj_token" export SENDER_OPEN_ID="ou_你的飞书open_id" export MONTH="2026-05" export APP_ID="cli_a97b6a0ffc399cc0" # 团队共用，通常已设为默认值 # 运行脚本 node scripts/fill_review_cells.cjs ``` 脚本中的 `CELL_DATA` 由 AI 根据复盘跟踪表数据自动生成并作为环境变量传入，请勿手动修改。 脚本会自动填充**最近 3 个月**（本月 + 前 2 个月）的数据到表格中的对应位置。填写规则见 `references/data_mapping.md`。 ### 文本更新脚本参考 ```javascript // readText 必须检查所有 block-type 属性 function readText(block) { const src = block?.text || block?.heading1 || block?.heading2 || block?.heading3 || block?.bullet || block?.ordered || block?.todo; return src?.elements?.map(e => e.text_run?.content || '').join('') || ''; } // 写入统一用 update_text_elements async function patchBlock(uat, blockId, newText) { const body = { update_text_elements: { elements: [{ text_run: { content: newText, text_element_style: {} } }] } }; const url = `${DOCX_BASE}/${DOC_ID}/blocks/${blockId}`; return apiRetry('PATCH', url, { Authorization: `Bearer ${uat}` }, JSON.stringify(body)); } ``` ## UAT 与 API 调用 13. **UAT 解密方式**： - 加密目录路径（动态解析）：`$FEISHU_UAT_DIR` → `$XDG_DATA_HOME/openclaw-feishu-uat` → `~/.local/share/openclaw-feishu-uat` - 加密文件：`{uatDir}/{appId}_{userOpenId}.enc` - Master key：`{uatDir}/master.key`（**32 字节原始密钥，不需要 SHA256 哈希**） - 解密算法：AES-256-GCM，IV 12 字节 + tag 16 字节 + ciphertext - 解密后的 JSON 字段：`accessToken`（驼峰，不是 `access_token`） - `import_to_bitable.mjs` 和 `fill_review_cells.cjs` 均已正确实现动态路径解析 14. **mention-doc 的 token 解析**： - 当用 `<mention-doc [REDACTED_SECRET]" type="wiki">` 时，飞书 API 会自动解析为实际文档的 obj_token - type 也会自动更新（如 wiki → docx 或 bitable） - 因此如果后续的 replace_all 索引用的原始 wiki token 找不到内容，很可能已被 API 自动替换 - 应在每次更新后重新 fetch-doc 确认实际内容 ## Import 脚本已知 bug 15. **`import_to_bitable.mjs` 看板创建 bug**： - `createKanbanView("按责任人分组")` 创建的看板视图没有返回正确的 view_id（返回 undefined） - 后续设置 `kanban_field_id` 时会因 `view_id=undefined` 而失败 - 变通方案：脚本跑完后，手动创建看板视图或用 UAT 调飞书 API PATCH `/views/{viewId}` 设置 `kanban_field_id` - 正确字段 ID：`责任人` 的 field_id（可通过 list_fields 获取） 16. **FIELD_MAP 硬编码**：CSV 列名 "工单状态/修复情况" → bitable 字段名 "工单状态|修复情况"（竖线）。如果 bitable 字段名用了斜杠，需要修改 FIELD_MAP 或重命名字段。 17. **超时备注字段可能导入后为空（2026-05-15 发现）**： - FIELD_MAP 包含 `"超时备注": "超时备注"` 映射 - `batch_create` 传入纯字符串 `"崔文思->某人: 05-15 10:00；"` 可以正确写入（已验证） - `PUT` 传入纯字符串也可以正确写入（已用此方法补了19条） - **但某些情况下数据会丢失**：猜测与 Cookie 切换后 CSV 列顺序或旧数据残留有关 - **修复**：已加 [5b/6] 校验步骤，导入后自动对比 CSV 与 Bitable 中超时备注数据量，发现不一致则警告 ## 2026-05-06 完整复盘经验总结 以下为 2026-05 月工单复盘全流程中遇到并解决的问题汇总，下次工单复盘前请先通读，避免重复踩坑。 --- ### 一、Furcas 数据拉取 #### ❗ Cookie 过期处理 Furcas API 登录态不可持久化。每次执行 `fetch_furcas.py` 前： 1. 打开浏览器 → F12 → Network → 随便请求 Furcas 页面 2. 从请求头复制 cookie 请求头完整值（含 `acw_tc` 和 `furcas` 字段） 3. 更新脚本中的 `HEADERS["Cookie"]` 变量 4. 不要只更新部分字段，`acw_tc` 每次都会变 #### ❗ 解决类别 ID 映射 拉取数据后 `fetch_furcas.py` 会按解决类别分类写入 CSV，ID 映射如下： | ID | 类别 | |----|------| | 1 | 设计如此-无需优化 | | 2 | 外部原因-无需优化 | | 3 | 内部原因-已解决 | | 4 | 内部原因-延期处理 | | 5 | 内部原因-无法重现 | --- ### 二、复盘跟踪表（bitable 操作） #### ❗ 跟踪表记录创建规范 创建复盘跟踪表记录时： - `app_token = YMQjwQeWCipABCkl8y7ckZlunQe` - `table_id = tblpygt9n8dqZcsw` - 年份字段填 2026（单选） - 月份字段填 05（单选） - `工单总数` = 该月 Furcas 所有工单的总数（从查询结果 count 获取） - `复盘工单` = 筛选后需要复盘的工单数（排除设计如此、外部原因-无需优化后） - 创建后等待 1-2 秒再 PATCH 其他字段 #### ❗ 字段 ID 映射（跟蹤表） | 字段名 | field_id | |--------|----------| | 工单总数 | fldg7k4Zb | | 复盘工单 | fldBxBSm3x | | 无需优化 | fld2Fi7oVD | | 待产品优化 | fldQ45Kqj8 | | 外部原因-技术派工 | fldW8sKSSx | | 外部原因-需技术排查 | fldRerqDIp | | 外部原因-无需技术排查 | fldVefvMG2 | | 内部原因 | fldIor3W9R | | 已解决 | flde2eaOu | | 延期处理 | fldZqWk3G | | 无法复现 | fldYplayCE | | 超时工单数 | fldTQkMXsW | #### ✅ 工单总数来源 `工单总数` 字段值来自 **Furcas API 返回的总数**，不是从导入 bitable 的记录数获取，不是从 CSV 行数获取。导入脚本只导入需要复盘的工单（筛选后的子集），不能当作总数。 --- ### 三、复盘总文档操作（含表格的文档） #### ❗ 含 lark-table 的文档禁止用 feishu_update_doc 的任何模式 `feishu_update_doc` 的 `overwrite`、`replace_all`、`append` 模式**都会损坏 lark-table**（返回 3000/4000515 等错误）。 - ✅ 正确做法：使用 UAT Token 直接调用 PATCH blocks API，逐格更新单元格文本 - ✅ 使用 PATCH 更新文本块（heading、text、bullet 等） - ❌ 禁止 `feishu_update_doc` 的任何模式 #### ❗ 表格单元格偏移（110格布局） 每个复盘表格有 110 个单元格： - 前 11 格 = 表头行 - 之后每 33 格 = 一个月的数据 - Group 0（03月）= cells[11..43] - Group 1（04月）= cells[44..76] - Group 2（05月）= cells[77..109] **每个月份组 33 格，单元格偏移**： | 偏移（组内） | 字段 | 行 | |-------------|------|----| | 0 | 月份标签 | row1 | | 1 | 工单总数 | row1 | | 2-3 | 无需优化（colspan=2） | row1 | | 4 | 待产品优化 | row1 | | 5 | 外部原因-技术派工 | row1 | | 6 | 外部原因-需技术排查 | row1 | | 7 | 外部原因-无需技术排查 | row1 | | 8 | 内部原因 | row1 | | 9 | 已解决 | row1 | | 10 | 超时（rowspan=3） | row1 | | 20 | 延期处理 | row2 | | 31 | 无法复现 | row3 | **数据数组顺序必须匹配 offset 顺序**： ```javascript // 正确顺序：total(1), noOpt(2), prodOpt(4), techWork(5), techCheck(6), noTech(7), internal(8), solved(9), timeout(10), deferred(20), noRepro(31) const data = ['516', '147（产研介入55）', '48（产研介入17）', '3（产研介入3）', '72（产研介入53）', '147（产研介入25）', '96（产研介入56）', '已解决38（产研介入16）', '49（产研介入28）', '延期处理47（产研介入31）', '无法复现：11（产研介入9）']; ``` #### ❗ 月份标签重命名级联 bug（关键！） **不能按顺序逐个改多个月份标签**！ - ❌ 错误做法：依次执行 02→03, 03→04, 04→05 - 改名 02→03 后，原 03 组的标签仍然是 03 - 第二次操作 03→04 会匹配到 **刚改好的那组**，而不是原 03 组 - 导致一个月份被跳过 - ✅ 正确方案 A：从后往前改（04→05, 03→04, 02→03） - ✅ **推荐方案 B**：一次性全部覆盖写入，无视当前值 - 用已知正确数据直接覆盖所有三组，每次重新写全部数据 - 这是最安全的做法 #### ❗ 表头标题行丢前缀 更新 heading1 的 `工单总计XX个(按工单数统计)` 后，前缀 `三、` 会丢失。 - **原因**：`update_text_elements` 完全替换了所有元素，前缀在另一个 element 中 - **修复**：更新时要保留完整标题，用 `# 三、工单总计x个(按工单数统计)` #### ❗ heading 块用 update_text_elements - ❌ `update_heading1` / `update_heading2` / `update_heading3` → 返回 `1770001 invalid param` - ✅ `update_text_elements` → 对 heading1/2/3/text/bullet/ordered 都有效 - **通用原则**：所有文本元素的更新一律用 `update_text_elements: { elements: [...] }` #### ❗ Section 4 子块遍历 Section 4（工单数量分析等）的 bullet 文本是 **heading 的 children**，不是根级块。 需要： 1. 遍历根级块找到 heading（type=4, txt='工单数量分析'） 2. 获取该 heading 的 children（`gC(docId, heading.block_id)`） 3. 逐个更新这些 bullet 块的文本 #### ✅ 文本更新用 element-level 操作 当 template 有 `<text color="orange">x</text>` 样式时，不能简单替换整个文本字符串——会丢失颜色样式。 - ✅ 遍历 `elements[]`，只改 content 不变 style - ✅ 如在会议主题中改 `10`→`05`，保留其余所有 element 的 style 属性 --- ### 四、Callout 块处理 #### ❗ Callout 块不可通过 API 删除 - Feishu Docx API 的 DELETE 端点对 type=19（callout/quote）块返回 404 - ❌ `DELETE /blocks/{block_id}` 对 callout 无效 - ✅ 变通方案：只清空子节点的文本内容（将其 text element content 设为空字符串） - ⚠️ Callout 框本身只能**手动在飞书页面删除**（选中后按 Delete/Backspace） --- ### 五、mention-doc 处理 #### ❗ mention-doc 是内联元素 - mention-doc **不是**独立的 type=33 块（那是独立 mention doc block） - 在文本段落中，mention-doc 是 `elements[]` 数组中的一个 element - 同一 element 可能同时有 `text_run` 和 `mention_doc` 两个属性 #### ❗ 更新 mention-doc token 的正确方式 PATCH 时需传入完整 element，**必须带的字段**： ```json { "text_run": { "content": "显示文字", "text_element_style": {} }, "mention_doc": { "obj_type": 16, // 整數！不能传字符串 "wiki" "text_element_style": {}, "token": "UJdQwNdkPidcvKkXZzbcL1tDnaf", // wiki 节点 token "title": "显示标题", "url": "https://sqb.feishu.cn/wiki/UJdQwNdk..." } } ``` - `obj_type` 是**整数**（16=wiki节点），传字符串会报 `99992402 field validation failed` #### ❗ token 会被自动解析 发送 wiki 节点 token 后，飞书 API 会自动解析为实际文档的 obj_token： - ⚡ token: `UJdQwNdk...` (wiki) → `UtJCdfO5Iotw...` (docx obj_token) - ⚡ type: 16 (wiki) → 22 (docx) - ⚡ url 保持不变（wiki URL） - ⚡ title 会自动从**文档实际标题**获取，发送的 title 字段被忽略 #### ❗ 显示文本无法通过 API 修改 mention 在文档中显示的文字由**文档标题**决定，不是由你写入的 text_run.content 或 title 字段决定。 - ✅ 要改显示文字，必须**改文档标题**（手工右键重命名） - ❌ 通过 PATCH 修改 element 中的 text_run.content 或 mention_doc.title 均无效 --- ### 六、开发复盘文档操作 #### ❗ 旧版模板只有3个开发槽位（已更新为5个） 模板 `Secqwa7deiaRwTkUsG6cJ7xpnzj` 已在 2026-05-07 更新，直接包含开发A-E 五个槽位。旧版只有3个时： 1. 先 PATCH 填满 3 个槽位 2. 再通过 `POST /blocks/{root_doc_id}/children` 添加第 4、5 个 1. 用 UAT PATCH API 更新 3 个槽位的开发者名称和数量（逐元素更新，保留橙色样式） 2. 再通过 `POST /blocks/{root_doc_id}/children` 添加第 4、5 个 #### ❗ insert_after 的正确方式（插入兄弟块） 要插入兄弟块**不是** POST 到目标块，而是 POST 到**父块**（根文档 block_id = doc_id）： ```javascript // ✅ 正确：POST 到父块（文档本身），指定 index POST /documents/{docId}/blocks/{docId}/children { index: 金海轩的索引+1, children: [{ block_type: 2, text: { elements: [...] } }] } // ❌ 错误：POST 到目标块自己（会把新内容作为金海轩的子块） POST /documents/{docId}/blocks/{金海轩的block_id}/children ``` #### ❗ x 是单字母不是 xxx 模板中的占位符是 `x`（如 `工单总数x个`），不是 `xxx`。替换时注意匹配模式。 #### ❗ 排除特定人员 开发复盘 TOP5 必须排除崔文思（技术支持）和蒋达周（运营）。统计开发者时： - 先统计每个开发者的工单数 - 去掉崔文思和蒋达周 - 取前 5 个填入 --- ### 七、文档创建与复制 #### ❗ 模板复制后需验证 obj_token `wiki_space_node action=copy` 返回的 `obj_token` 可能字符错位（如 `UtJCdfO5Iotw` 与 `UtJCdfO5Iotw` 中的 `5` 和 `O` 互换）。复制后： 1. ✅ 立即用 `wiki_space_node action=get` 验证新节点的实际 `obj_token` 2. ✅ 或用 `feishu_fetch_doc` 直接读节点 token 验证文档是否可访问 #### ❗ 文档创建后有一定延迟 复制创建的文档不会立即就绪。如果立即调用 PATCH 操作，可能返回 `4000515 resource not found`。 ✅ 建议做法：创建后等待 2-3 秒再操作。 #### ❗ 标题改不了（API 限制） - ❌ `PATCH /docx/v1/documents/{docId}` → `1770001 invalid param`（不支持改标题） - ❌ `PATCH /wiki/v2/spaces/{spaceId}/nodes/{nodeToken}` → 404（没有 rename 端点） - ✅ **必须手工操作**：在飞书页面右键节点 → 重命名 --- ### 八、UAT 工具链 #### ❗ 标准 UAT 文件路径 ```javascript const LINUX_UAT_DIR = path.join( process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'openclaw-feishu-uat' ); const encFile = path.join(LINUX_UAT_DIR, `${APP_ID}_${OPEN_ID}.enc`); const masterKey = fs.readFileSync(path.join(LINUX_UAT_DIR, 'master.key')); const uatToken = JSON.parse( decrypt(fs.readFileSync(encFile), masterKey) ).accessToken; // 驼峰！不是 access_token ``` - 不要硬编码 `/root/.local/share/`，用 `XDG_DATA_HOME` 环境变量 - 加密文件命名规则：`{appId}_{userOpenId}.enc`（冒号替换为下划线） - `accessToken` 是驼峰，不是下划线格式 - 解密算法：AES-256-GCM，IV=12字节 + tag=16字节 + ciphertext - Master key 是 32 字节原始密钥，**不需要 SHA256 哈希** --- ### 九、复盘范围规范 #### ✅ 复盘范围时间格式 | 复盘类型 | 时间范围 | |---------|---------| | 整月复盘 | 当月1日 00:00:00 - 当月最后一日 23:59:59 | | 上半月复盘 | — 当月15日 23:59:59 | | 下半月复盘 | 当月16日 00:00:00 — | 文档中 color="orange" 的复盘范围文本要保持橙底样式，仅改日期文字。 --- ### ⚠️ 文档命名规范（严格执行，否则重做） #### 三个文档必须从模板创建，命名必须为： | 文档类型 | 模板 | 命名规范（5月示例） | |---------|------|------------------| | 工单数据跟踪表（Bitable） | `J4tawf06GijvEmkBTSCcdyNynsd` | `智慧门店-2026-05月-工单数据` | | 工单复盘总文档 | GEqawooPYiDXLWkRKZHcJnLcnFc (wiki节点) | `智慧门店-2026-05月-工单复盘` | | 开发复盘文档 | Secqwa7deiaRwTkUsG6cJ7xpnzj (wiki节点) | `智慧门店-2026-05月-开发复盘文档` | #### 顺序：先建数据表 → 拉数据 → 建复盘文档 #### 常见错误：命名带"模版"后缀、用"05-整"格式、忘记创建在对应月份目录下 ### 十一、文档填充规则 #### 表格更新（重要） - lark-table文档禁止feishu_update_doc（破坏结构） - 使用docx PATCH API更新text block的`update_text_elements` - 结构：table_cell(31) → 110个type32子节点 → 每个含type2 text block #### 文本更新 - Feishu文本可能拆成多个text_run元素（每个有独立样式） - 判断用`elements.map(e=>e.text_run?.content).join('')`（完整文本），修改用单个element的content - `update_text_elements`对所有block_type通用 - type 12是bullet块，元素在`.bullet.elements`中 - mention_doc不可通过API修改 - **`feishu_update_doc` 禁止用于含 callout(type=19) 的文档**（序列化会破坏callout结构） - callout块的内容需通过PATCH其子block的`update_text_elements`更新，不要替换整个文档 #### 文档标题/命名 - 创建时在copy request的`title`参数设置 - 不能通过API修改已有wiki节点标题 - 需重命名时：copy新文档+删除旧节点 ### 十二、关键常量表 | 项目 | 值 | |------|-----| | 工单数据(含Furcas) app_token | `YMQjwQeWCipABCkl8y7ckZlunQe` | | 工单汇总表 table_id | `tblpygt9n8dqZcsw` | | Furcas工单表 table_id | `tbln4MvksjBRkGGn` | | 工单数据跟踪表模板(Bitable) | `J4tawf06GijvEmkBTSCcdyNynsd` | | 2026目录节点（space 7510046829784662017 下） | `MDK5w5CcGi8RK6kRFMKcuBKLnNg` | | 复盘总文档模板 | wiki 节点 `GEqawooPYiDXLWkRKZHcJnLcnFc` | | 开发复盘文档模板 | wiki 节点 `Secqwa7deiaRwTkUsG6cJ7xpnzj` | | 用户 open_id（需替换为自己的） | `ou_xxx...` | | App ID | `cli_a97b6a0ffc399cc0` | | Space ID | `7510046829784662017` | | 05月目录节点 | `KAXEwRivKinTfTkshDucvSiGn4g` | --- ### 十三、2026-05-07 追加经验 以下为 2026-05-07 修复 4 月工单数据时发现并解决的问题。 #### ❗ `import_to_bitable.mjs` 清空步骤有静默失败 bug（致命！） **bug 描述**：清空旧数据的 `batch_delete` API 参数名写错，用的 `records` 但正确参数是 `record_ids`。且 try-catch 吞掉了错误，导致清空失败时不停机： ```javascript // ❌ 错误（旧代码） await api("POST", `.../records/batch_delete`, { records: ids }); // 清空失败，错误被 catch 吃掉 // 旧数据继续留在表中，新数据追加写入 // → 每条工单出现 2 遍（304条 = 152 × 2） ``` **修复**： 1. 参数名改为 `record_ids` ✅ 2. 去掉 try-catch，让清空失败直接报错停机 ✅ 3. 导入完成后新增去重步骤（最后一道防线）✅ 4. 去重逻辑：按工单 ID 分组，每个工单只保留 1 条记录 ✅ #### ❗ 多条同名空表被创建（`智慧门店-2026-05月-工单数据` × 4） **原因**：首次创建 bitable 时没有检查知识库目录下是否已有同名表格。 **修复方式**：后续复盘时，在创建新 bitable 前必须： 1. 先搜索「工单数据表」目录下是否有同月名称的表格 2. 如果有，直接复用，不创建新的 3. 搜索方式：`feishu_wiki_space_node list` 列出目录下所有节点，检查名称是否包含目标月份 #### ❗ Mention-doc 指向错误的表格 **原因**：从模板复制复盘总文档后，文档内的 mention-doc 指向的是模板的占位对象（4月表或模板），不是当前月份的表。 **修复方式**（已添加 Step 5b 到流程中）： 1. 导入数据后立即更新 mention-doc 链接 2. 将指向前月份的 bitable mention-doc 改为当前月份的 3. 将指向模板的 wiki mention-doc 改为实际开发复盘文档节点 #### ❗ UAT 没有 bitable 读取权限 Raw API 调用 `/bitable/v1/apps/{token}/tables/{table_id}/records` 返回 `99991679 Unauthorized`： - UAT 用户授权不包含 `bitable:app:readonly` 或 `base:record:read` - ✅ **feishu_bitable_app_table_record 工具可以正常读取**（使用 bot 身份或不同授权路径） - ✅ 如果脚本需要用 UAT 读 bitable，需要先 check 权限，否则使用 `feishu_bitable_app_table_record` 工具 #### ❗ 单个 DELETE 大量记录会超时 去重脚本一次删 152 条记录（逐条 DELETE）因超时被 SIGTERM： - 超时阈值约 2 分钟 - ✅ 分批删除 20 条为一个 checkpoint，每次加 `await new Promise(r => setTimeout(r, 50))` 限速 - ✅ 如果超时，再次运行脚本即可继续删剩余的 #### ❗ feishu_update_doc replace_all 破坏 callout 块（生成 "三、总结"） **现象**：使用 `feishu_update_doc replace_all` 更新开发复盘文档后： - `<callout>` 块 → `>` markdown blockquote（callout 框消失） - `<text color="orange">` → 普通文本（橙色丢失） - 多出 `# 三、总结` 等异常段落 **根因**：`feishu_update_doc` 先将文档序列化为 markdown，再反序列化回飞书文档格式。反序列化时： - `<callout>` 被当作 `>` blockquote 处理 - 如果 markdown 末尾包含 `# 三、总结` 等标题，会被当作新段落加入 - **SKILL.md 第10条之前错误地允许了在含 callout 的文档中使用 feishu_update_doc** **修复**： 1. 从模板重新创建文档 ✅（必须用 copy，不能用 feishu_create_doc 写 markdown） 2. 所有更新用 UAT PATCH API 逐元素操作，不用 feishu_update_doc ✅ 3. SKILL.md 第10条已更正，明确禁止 feishu_update_doc 用于含 callout 的文档 ✅ #### ❗ 开发复盘文档模板更新为5个开发者槽位（2026-05-07） 模板 `Secqwa7deiaRwTkUsG6cJ7xpnzj` 在 2026-05-07 更新，"主要人员总结材料"下从3个槽位（开发A/B/C）扩展到5个（开发A/B/C/D/E）。 **影响**： - ✅ 复盘流程不再需要 POST `/children` 追加开发D/E - ✅ 直接 PATCH 填充5个槽位即可 - ⚠️ 旧版3槽位模板已废弃，但SKILL.md保留了旧方法作为参考 - SKILL.md 第11条已改为"替换开发 A/B/C/D/E 标记" - SKILL.md 第12条已更新 #### ❗ 开发复盘文档 Section 1 的 mention-doc 链接需替换（2026-05-07） 复制模板后，开发复盘文档的 Section 1 包含两个指向模板/占位对象的 mention-doc： ``` # 一、开发复盘文档 - 工单复盘文档：<mention-doc [REDACTED_SECRET]" type="wiki">智慧门店工单复盘模版</mention-doc> - 复盘表格内容：<mention-doc [REDACTED_SECRET]" type="wiki">2026-xx月-智慧门店工单数据</mention-doc> ``` **必须替换为：** 1. `工单复盘文档` mention-doc: token → 本次创建的 **复盘总文档** wiki 节点 token 2. `复盘表格内容` mention-doc: token → 当前月份工单数据 **bitable** token（含 table_id 和 view_id） **变更方式**：PATCH 对应 block 的 `update_text_elements`，修改 mention_doc 的 `token`、`obj_type`、`url`。 #### ❗ 创建月份工单数据 bitable 的正确方式（2026-05-07） **错误做法**（之前踩过的坑）： 1. ❌ `feishu_bitable_app copy` 从模板复制 → 创建独立 bitable（不在 wiki 目录中） 2. ❌ `feishu_wiki_space_node create` 传 obj_token → 又新建一个**空白 bitable**（表名 "Table"，字段全英文） → 导致 wiki 链接指向空表，数据在独立表里 **正确做法**（仅需一步）： 1. ✅ `feishu_wiki_space_node copy` 直接将模板节点复制到目标月份目录 - `node_token: "J4tawf06GijvEmkBTSCcdyNynsd"`（工单数据表模板） - `target_parent_token: "{目标月份目录节点token}"` - `space_id: "7510046829784662017"` → 直接在 wiki 内创建副本，bitable 结构、字段、视图全部正确 **复制后的处理**： 1. 重命名 bitable（如 "智慧门店-2026-5月工单数据"） 2. 删除模板自带的 5 条空记录（`batch_delete` 空记录的 `record_ids`） 3. 导入 CSV 数据 **导入脚本注意事项**： - 表名默认校验为 "工单数据"，但模板复制出来的表名是 "数据表" → 用 `--name "数据表"` 参数 - 看板视图创建后的 `view_id` 可能返回 undefined（脚本返回值解析 bug） → 视图实际已创建，可通过 `feishu_bitable_app_table_view list` 获取 `view_id` → 手工设置 `property: { kanban_field_id: "责任人字段ID" }` **开发复盘文档人名占位符规则（2026-05-07）**： - 模板中 `开发A(` `开发B(` 等是**人名占位符**，不是固定标签 - 必须替换为责任人实际姓名（如 `李文茂(`），不是仅替换数字 - 替换后第一个元素内容从 "开发A(" → "李文茂("，保持橙色样式不变 **开发复盘文档 callout 内容规则（2026-05-07）**： - callout 内（分析总结：、超时工单分析：等10个字段）**不要填充任何内容** - callout 应保持模板的占位符状态，留给用户手动填写 - 如果误填了多行文本，清理时需要：1) 把多余元素全删除 2) 只剩单元素 "分析总结：" --- ### 2026-05-15 新增经验 #### ❗ 超时备注导入后校验 FIELD_MAP 已有映射，但首次导入后超时备注字段为空。已在脚本 [5/6] 验证后追加 [5b/6] 校验步骤，自动检测 CSV 中超时备注数据是否成功写入 bitable。 **补救方法**（当超时备注导入失败时）： ```bash # 从 CSV 读取超时备注数据，逐条写入 bitable（通过 UAT PUT API） # 脚本逻辑：遍历 bitable 记录 → 按问题链接匹配 CSV → 对有超时时间的记录填写超时备注 ``` UAT API 的 `batch_create` 和 `PUT` 操作均正常，传入纯字符串即可。 #### ❗ 视图可能隐藏关键字段 4月数据表的视图 `vewadQIK4f` 的 `hidden_fields` 中包含了 `解决类别`、`解决模块`、`责任人` 三个字段，导致用户看不到数据。 **排查方法**：用 `feishu_bitable_app_table_view action=get` 获取视图配置，检查 `property.hidden_fields` 列表 **修复方法**：调用 UAT API PATCH 视图，设置 `property: { }`（空对象或省略 hidden_fields 即可清除） ---


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal:  | SkillSpector: suspicious (CRITICAL)