@amaster.ai/pi-lark 0.1.2-beta.61 → 0.1.2-beta.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-approval/SKILL.md +2 -2
  3. package/skills/lark-approval/references/lark-approval-instances-initiated.md +5 -0
  4. package/skills/lark-approval/references/lark-approval-tasks-add-sign.md +68 -20
  5. package/skills/lark-approval/references/lark-approval-tasks-query.md +5 -0
  6. package/skills/lark-base/SKILL.md +110 -7
  7. package/skills/lark-base/references/lark-base-data-query.md +2 -6
  8. package/skills/lark-base/references/lark-base-field-extension.md +170 -0
  9. package/skills/lark-base/references/lark-base-field-lookup.md +1 -1
  10. package/skills/lark-base/references/lark-base-filter-condition.md +32 -5
  11. package/skills/lark-base/references/lark-base-form-detail.md +1 -1
  12. package/skills/lark-base/references/lark-base-form-submit.md +2 -2
  13. package/skills/lark-base/references/lark-base-record-history-list.md +1 -1
  14. package/skills/lark-base/references/lark-base-record-query-and-analysis-sop.md +95 -205
  15. package/skills/lark-base/references/lark-base-template-center.md +5 -1
  16. package/skills/lark-calendar/SKILL.md +6 -0
  17. package/skills/lark-calendar/references/lark-calendar-join-event.md +43 -0
  18. package/skills/lark-drive/references/lark-drive-member-remove.md +2 -1
  19. package/skills/lark-im/SKILL.md +14 -1
  20. package/skills/lark-markdown/SKILL.md +1 -1
  21. package/skills/lark-meeting/SKILL.md +6 -2
  22. package/skills/lark-meeting/references/lark-minutes-summary.md +1 -0
  23. package/skills/lark-meeting/references/lark-minutes-todo.md +39 -1
  24. package/skills/lark-meeting/references/lark-minutes-upload.md +6 -0
  25. package/skills/lark-meeting/references/lark-vc-agent-meeting-end.md +26 -0
  26. package/skills/lark-meeting/references/lark-vc-agent-meeting-invite.md +32 -0
  27. package/skills/lark-meeting/references/lark-vc-agent-meeting-join.md +8 -2
  28. package/skills/lark-meeting/references/lark-vc-meeting-countdown.md +103 -0
  29. package/skills/lark-meeting/references/lark-vc-meeting-events.md +1 -0
  30. package/skills/lark-meeting/references/lark-vc-meeting-screenshot.md +34 -0
  31. package/skills/lark-meeting/scenes/create-and-edit-minutes.md +25 -3
  32. package/skills/lark-meeting/scenes/live-meeting-attend.md +63 -6
  33. package/skills/lark-meeting/scenes/live-meeting-interact.md +32 -3
  34. package/skills/lark-sheets/SKILL.md +76 -60
  35. package/skills/lark-sheets/references/lark-sheets-batch-update.md +83 -14
  36. package/skills/lark-sheets/references/lark-sheets-chart.md +296 -159
  37. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +5 -3
  38. package/skills/lark-sheets/references/lark-sheets-filter.md +1 -1
  39. package/skills/lark-sheets/references/lark-sheets-formula-translation.md +78 -65
  40. package/skills/lark-sheets/references/lark-sheets-formula-verify.md +21 -17
  41. package/skills/lark-sheets/references/lark-sheets-pivot-table.md +2 -1
  42. package/skills/lark-sheets/references/lark-sheets-range-operations.md +1 -1
  43. package/skills/lark-sheets/references/lark-sheets-read-data.md +7 -4
  44. package/skills/lark-sheets/references/lark-sheets-search-replace.md +4 -4
  45. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +2 -2
  46. package/skills/lark-sheets/references/lark-sheets-sparkline.md +1 -0
  47. package/skills/lark-sheets/references/lark-sheets-styles-put.md +3 -3
  48. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +6 -4
  49. package/skills/lark-sheets/references/lark-sheets-workbook.md +3 -1
  50. package/skills/lark-sheets/references/lark-sheets-write-cells.md +50 -48
  51. package/skills/lark-sheets/scripts/lark_chart_layout_check.py +472 -0
  52. package/skills/lark-slides/SKILL.md +2 -0
  53. package/skills/lark-slides/references/cli/lark-slides-add-slide.md +6 -6
  54. package/skills/lark-slides/references/cli/lark-slides-delete-slide.md +3 -3
  55. package/skills/lark-slides/references/cli/lark-slides-replace-slide.md +11 -11
  56. package/skills/lark-slides/references/cli/lark-slides-xml-presentation-slide-get.md +1 -1
  57. package/skills/lark-slides/references/cli/lark-slides-xml-presentation-slide-replace.md +2 -2
  58. package/skills/lark-slides/references/workflow/slides-editing.md +11 -11
  59. package/skills/lark-slides/references/xml/slides_xml_schema_definition.xml +2 -0
  60. package/skills/lark-base/references/lark-base-cell-value.md +0 -165
  61. package/skills/lark-base/references/lark-base-data-analysis-pandas.md +0 -93
  62. package/skills/lark-base/references/lark-base-data-analysis-python-stdlib.md +0 -120
  63. package/skills/lark-base/references/lark-base-record-batch-create.md +0 -63
  64. package/skills/lark-base/references/lark-base-record-batch-update.md +0 -57
  65. package/skills/lark-base/references/lark-base-record-query-and-analysis-cloud-sop.md +0 -145
@@ -5,7 +5,7 @@
5
5
  用户出现以下口语指令时,**强制**走 `+cond-format-{create|update|delete}`,**禁止**用 `+cells-set` 写静态背景色 / 字体色代替:
6
6
 
7
7
  - **颜色动作**:"标红 / 标黄 / 标绿 / 上色 / 染色 / 涂色 / 表红色 / 表黄色"
8
- - **视觉强调**:"高亮 / 突出 / 标记 / 标注 / 区分"
8
+ - **视觉强调**:"高亮 / 突出 / 标记 / 标注 / 区分"——**限带条件语义的**(按值 / 规则决定哪些格上色);纯装饰性无条件上色(斑马纹、整行固定底色)不在强制范围,按视觉规范直接设背景色
9
9
  - **条件触发**:"重复的标出来 / 异常的圈出来 / 过期的染红 / 大于 X 的标黄 / 不达标的标红"
10
10
  - **联动语义**:"颜色随数据变 / 联动 / 自动更新 / 改了数据颜色也跟着变"
11
11
  - **数值可视化**:"数据条 / 色阶 / 渐变色 / 进度条样式"
@@ -29,10 +29,12 @@
29
29
 
30
30
  **常见配置错误(必须注意)**:
31
31
  - **创建后必须验证**:条件格式创建后必须调用 `+cond-format-list` 验证规则是否生效。如果验证发现规则未生效或配置不正确,应立即修复并重试
32
+ - **验证要覆盖哨兵格**:不要只确认规则对象存在;还要按用户规则抽查 2-3 个应命中的单元格/行(含边界行、空值、重复值、非图例状态),确认公式、范围、颜色语义能解释这些哨兵。若规则存在但哨兵颜色/命中逻辑不对,继续修正
32
33
  - **范围要精确**:条件格式的应用范围必须精确覆盖用户指定的列/行,不要遗漏
33
- - **`style.back_color` vs `style.fore_color` 的中文语义**:用户中文语境下的"**标红/高亮/染色/标记**"指**单元格背景色**,用 `back_color`;"**文字红/字体红/把字变红**"才用 `fore_color`。默认无说明时选 `back_color`。把过期数据涂红、重复值高亮等都应该是 `back_color: "#FFE6E6"`(或类似浅红)配合可选的 `fore_color` 加深字体
34
+ - **`style.back_color` vs `style.fore_color` 的中文语义**:用户中文语境下的"**标红/染色/标记**"指**单元格背景色**,用 `back_color`;"**文字红/字体红/把字变红**"才用 `fore_color`。默认无说明时选 `back_color`。用户说"**标红**"用标准红 `back_color: "#FF0000"`;说"**高亮/突出**"才用浅色底(如 `#FFE6E6`)配合可选的 `fore_color` 加深字体——把"标红"做成浅粉会被认为没按要求标色
34
35
  - **日期/空值比较必须防空**:用户说"过期的标红"时,除了 `TODAY()`,公式必须排除空单元格,否则空白格也会被误判为"早于今天"而全表标红。正确公式:`=AND(E1<>"", E1<=TODAY())`;错误公式:`=E1<=TODAY()`(空值会被当作 0 判为过期)
35
36
  - **公式条件注意引用方式**:自定义公式条件中的单元格引用需要根据实际场景选择相对/绝对引用(如 `=E1<=TODAY()` 而非 `=$E$1<=TODAY()`,后者只比较一个格)
37
+ - **`duplicateValues` 只按单列判重**:用户说的"多字段完全相同才算重复"无法直接表达——先建辅助键列(把参与判重的列用分隔符拼成一个键),再用引用该键列的 `expression`(如 `COUNTIF` 键列 >1)把规则应用到数据区整行;只想标记键列本身时才用 `duplicateValues`
36
38
 
37
39
  ⚠️ **用户明确要求"辅助列+条件格式"两步走时,禁止用 `expression` 绕过**:当用户说以下任意一种表达时,必须按两步走(先建辅助列 → 再基于辅助列做条件格式),**禁止**直接用一个 `rule_type: "expression"` 公式一步完成:
38
40
 
@@ -172,7 +174,7 @@ lark-cli sheets +cond-format-create --url "..." --sheet-id "$SID" \
172
174
  lark-cli sheets +cond-format-delete --url "..." --sheet-id "$SID" --rule-id "$RULE_ID" --yes
173
175
  ```
174
176
 
175
- > 一次只删一个 `--rule-id`。要删**多个**条件格式时,先 `+cond-format-list` 拿到各 `rule-id`,再用 `+batch-update` 把多个 `+cond-format-delete` 合并为单次批量提交(fail-fast、不回滚),不要逐个调用。
177
+ > 一次只删一个 `--rule-id`。要删**多个**条件格式时,先 `+cond-format-list` 拿到各 `rule-id`,再用 `+batch-update` 把多个 `+cond-format-delete` 合并为单次批量提交(fail-fast,失败处置见 `lark-sheets-batch-update`),不要逐个调用。
176
178
 
177
179
  ### Validate / DryRun / Execute 约束
178
180
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 真对象硬约束 + 数量校验
4
4
 
5
- 1. **真对象**:当用户要求"筛选 / 只看 / 仅保留 X"时,**必须**通过 `+filter-{create|update|delete}` 创建真实的筛选器对象。**禁止**用"删除不符合条件的行" / "新建子表只放符合条件的行" / 用 `+cells-set` 覆盖原表来代替——这些做法会让原数据丢失或不可恢复。
5
+ 1. **先判产物形态,再选做法**:**要裁列 / 要另存结果表**("只保留某几列""筛出来放新表",含用户显式要求把筛选结果做成独立新表的行级筛选)→ 另建结果 sheet 物化符合条件的行与列,**原表原样保留**;**仅行级、不裁列**("筛选 / 只看 / 仅保留符合条件的行")→ **必须**通过 `+filter-{create|update|delete}` 创建真实的筛选器对象,**禁止**用"删除不符合条件的行" / "新建子表只放符合条件的行" / 用 `+cells-set` 覆盖原表来代替——这些做法会让原数据丢失或不可恢复。
6
6
  2. **筛选数量必校**:执行筛选后**必须**回读,断言 `len(visible_rows) == expected_count`。`expected_count` 来自先用本地脚本在源数据上独立复现该筛选条件得到的结果数。两者不一致时禁止交付,需排查筛选条件 / 数据列类型问题。
7
7
  3. **混合文本列禁止字面比较**:筛选 key 是公式文本(如 `1000+200=1200`)或带单位的混合文本时,先在辅助列里抽出纯数值再筛选;不能直接用文本比较。
8
8
 
@@ -1,23 +1,32 @@
1
1
  # 飞书表格公式生成规则
2
2
 
3
- > **本文定位**:飞书公式正确性的**唯一权威**——书写任何飞书公式、或把 Excel 公式迁移到飞书前,先读本文。涵盖公式书写约定(绝对引用、范围语法)、投影 vs spill、ARRAYFORMULA / 数组语义、高风险引用函数、日期差、不支持函数清单。
4
- > **边界**:本文只讲"公式怎么写对";公式**怎么写入表格**(`+cells-set` / 模板单元格 + `--copy-to-range` / 容错回读)见 `lark-sheets-write-cells`。**公式写入完成后的强制收尾**见 `lark-sheets-formula-verify`:不要把"翻译对了"误当成"已经交付完成"。本文不含 shortcut,通用编辑准则见主 SKILL.md「飞书表格编辑准则」。
3
+ > **本文定位**:飞书公式正确性的**唯一权威**——书写任何飞书公式、或把 Excel 公式迁移到飞书前,先读本文。涵盖公式书写约定(绝对引用、范围语法)、投影 vs spill、`ARRAYFORMULA` / 数组语义与逐行填充、高风险引用函数、日期差、不支持函数清单。
4
+ > **边界**:本文只讲"公式怎么写对";公式**怎么写入表格**(`+cells-set` / 模板单元格 + `--copy-to-range` / 容错回读)见 `lark-sheets-write-cells`。公式写入完成后可用 `lark-sheets-formula-verify` 做诊断;不要把"翻译对了"误当成"结果一定正确"。本文不含 shortcut,通用编辑准则见主 SKILL.md「飞书表格编辑准则」。
5
5
 
6
- **核心原则:飞书不像 Excel 365 那样默认 spill(溢出展开)。飞书普通公式遇到区域时默认"投影"(只取当前行/列对应的单个值),必须显式使用 `ARRAYFORMULA` 或原生数组函数才能逐项展开。**
6
+ **核心原则一:飞书不像 Excel 365 那样默认 spill(溢出展开)。** 某个参数要求单值、实际传入的却是区域时,飞书默认取"投影"(按公式所在行/列取对应的那一个值);只有当求值处于**数组公式上下文内部**——最外层套了 `ARRAYFORMULA`,或公式里已有原生数组函数(`FILTER` / `XLOOKUP` / `SORT` 等,见下方清单)——才逐项展开。`ARRAYFORMULA` 与"逐行标量公式 + `--copy-to-range` 填充"导出后都保真,按需要选:前者一条公式覆盖整片、写起来短;后者每格是独立公式,导出后在 Excel 里能单格编辑。
7
+
8
+ **核心原则二:`LAMBDA` 系高阶函数(`MAP` / `REDUCE` / `SCAN` / `BYROW` / `BYCOL` / `MAKEARRAY`)在飞书内算得对,但导出 `.xlsx` 会静默算错。** 导出时飞书只把 LAMBDA 体内联展开成普通数组表达式,**不保留高阶语义**,全程不报错:
9
+
10
+ - `REDUCE(0,A2:A6,LAMBDA(acc,x,acc+x))`(归约求和)→ `=0+A2:A6`,归约整个丢失
11
+ - `BYROW(A2:B6,LAMBDA(r,SUM(r)))`(逐行求和)→ `=SUM(A2:B6)`,5 个结果塌成 1 个
12
+ - `MAP(A2:A6,LAMBDA(x,IF(x>0,x,0)))` → `=A2:A6>0`,`IF` 整个消失
13
+
14
+ 唯一例外是 `MAP` 且 LAMBDA 体为**纯运算符或单参函数**时展开恰好等价(`LAMBDA(a,b,a*b)` → `=A2:A6*B2:B6` ✓)。**除此之外一律改走 `ARRAYFORMULA` / 逐行填充 / 辅助列**——同样的逐项逻辑写成 `=ARRAYFORMULA(IF(A2:A6>0,A2:A6,0))` 导出后完整保留,写成 `MAP(...LAMBDA(...IF...))` 就丢。这类错误公式不报错、飞书里回读也是对的,只有导出后才暴露,靠事后检查发现不了。
7
15
 
8
16
  ## 公式书写约定(写任何公式都先满足)
9
17
 
10
18
  - **绝对引用 `$`**:向下 / 向右填充前判断哪些引用要锁定——用户指定的固定 cell(`$C$3`)、要固定的数据范围(`$A$2:$B$5`)、锁列不锁行(`$A2`)、锁行不锁列(`B$1`)。填充前检查是否需固定汇率 / 税率 / 查找表 / 权重表,以及同列 / 同行公式结构是否一致。
11
19
  - **公式字符串用飞书范围语法**:写 `H:H`、`A2:B5`,**禁止** `H2:H` / `2:2`。要在公式里引用整行,用显式范围(如 `$A2:$Z2`)替代禁用的 `2:2`。这与 CLI 工具参数(如 `--range` / `--copy-to-range`)的 A1 表示法写法不同:参数侧合法的 `D3:D`、`1:1`、`3:6` 在公式串里反而非法。**公式串 ≠ CLI 参数**,两套规则别互相照搬,混用会导致调用失败或公式报错。
20
+ - **产物要导出 xlsx 交付时优先 Excel 兼容函数**:同一计算能用 Excel 兼容函数(SUMIFS / TEXT / MID / FIND 等)表达就不用飞书特有函数(MAP / REGEXEXTRACT / ARRAYFORMULA 等)——特有函数在导出后的 xlsx 里可能无法重算;确需使用时,导出后核对重算正常再交付。
12
21
 
13
- ## 翻译后必做:代码复现校验
22
+ ## 翻译后建议:代码复现校验
14
23
 
15
- 公式语法翻译完之后,**必须**用本地脚本在源数据上独立复现一份"等价计算结果"再写入。流程:
24
+ 公式语法翻译完之后,建议用本地脚本在源数据上独立复现一份"等价计算结果"再写入。流程:
16
25
 
17
26
  1. **挑 3-5 个代表性输入行**(首行 / 中段 / 末行 / 含空值 / 含异常格式各一)
18
27
  2. **用 Python 复现 Excel 原公式的语义**(不是飞书译文的语义,而是用户原本想要的结果)
19
28
  3. **写入飞书译文公式后回读这几行的实际值**
20
- 4. **三方对照**:`Excel 原公式语义 == Python 复现 == 飞书译文回读值`,全部一致才交付;不一致先排查(数组语义?日期差?范围引用?)
29
+ 4. **三方对照**:`Excel 原公式语义 == Python 复现 == 飞书译文回读值`;不一致时优先排查(数组语义?日期差?范围引用?),无法修完时在交付说明标明风险。
21
30
 
22
31
  **理由**:Excel→飞书的语法翻译很容易在 spill / 数组 / 日期差 / 范围引用上出现等价性偏差,仅靠语法转换通过不足以保证业务结果正确。
23
32
 
@@ -26,39 +35,43 @@
26
35
  本文解决的是"公式怎么写对",不是"写进表里后一定能零错误运行"。因此:
27
36
 
28
37
  1. 按本文完成公式改写后,用 `lark-sheets-write-cells` / `lark-sheets-batch-update` 把公式真实写入表格。
29
- 2. 公式一旦落表,就默认进入 `lark-sheets-formula-verify` 的收尾阶段。
30
- 3. 最终必须跑 `+formula-verify` 收敛到 `status='success'`;`errors_found` / `partial` 都不算完成。
38
+ 2. 公式一旦落表,可进入 `lark-sheets-formula-verify` 做诊断。
39
+ 3. `+formula-verify` 的 `errors_found` / `partial` 是风险信号;关键输出区优先修复,非关键区可在交付说明记录。
40
+
41
+ **静态值改公式("让统计表跟随源数据变化"类任务)额外一步**:改写前先快照原静态值,公式写完后逐格与快照 diff。不一致时先尝试口径变体(`>` / `>=`、取整方式、匹配列)逼近原值;仍不一致不算失败——原静态值可能对应旧数据或含未声明口径——但必须在交付说明中给出 diff 表与所用口径的解释,禁止不声明差异直接交付。
31
42
 
32
43
  ## 决策流程
33
44
 
34
- 1. 最终结果是**标量**(单值)→ 通常不需要 `ARRAYFORMULA`
45
+ 1. 最终结果是**标量**(单值)→ 直接写普通公式
35
46
  2. 最终结果是**一维或二维数组**:
36
- - 公式中**包含**飞书原生数组函数(如 FILTER、XLOOKUP、MAP 等)→ 无需加 `ARRAYFORMULA`,数组语义会自动传播到整个公式,包括原生数组函数外层接的标量运算(如 `+1`、`*100`)
37
- - 公式中**不包含**任何原生数组函数,但在对区域做标量计算 → 加 `ARRAYFORMULA(<整个表达式>)`
38
- 3. Excel 依赖 `ROW(range)` 逐项驱动 `SUBTOTAL/INDIRECT/OFFSET` → 改用 `MAP(ARRAYFORMULA(ROW(...)), LAMBDA(r, ...))`
39
- 4. 内层 `INDEX/INDIRECT/OFFSET` 返回范围,外层 `SUMIF/COUNTIF/SUMIFS` 还要继续吃这些范围 → 改用 `MAP(..., LAMBDA(...))` 或 `REDUCE(..., LAMBDA(...))`
40
- 5. 公式意图是"对多个区域分别计算再汇总"(例如用 INDIRECT/OFFSET 对每行生成一个范围,再对所有范围聚合)→ 飞书不能直接返回"区域的列表",必须明确降维:用 `VSTACK` 垂直合并、`HSTACK` 水平合并、`TOCOL/TOROW` 展平,或 `REDUCE` 归约成标量
47
+ - 公式中**包含**飞书原生数组函数(如 FILTER、XLOOKUP、MAP 等)→ 直接写,数组语义会自动传播到整个公式,包括原生数组函数外层接的标量运算(如 `+1`、`*100`)
48
+ - 公式中**不包含**任何原生数组函数,只是在对区域做标量计算 → 用 `ARRAYFORMULA` 包住整个表达式,或写成**单行标量公式再向下 / 向右填充**(`--copy-to-range`)
49
+ 3. Excel 依赖 `ROW(range)` 逐项驱动 `SUBTOTAL/INDIRECT/OFFSET` → 拆成辅助列:辅助列每行写单行标量式(`=SUBTOTAL(103,INDIRECT("E"&ROW(E16)))`)向下填充,再对辅助列做聚合;但结果要随筛选联动时保持单条 `MAP(...LAMBDA(...))`,见下方「Excel 隐式逐项求值」
50
+ 4. 内层 `INDEX/INDIRECT/OFFSET` 返回范围,外层 `SUMIF/COUNTIF/SUMIFS` 还要继续吃这些范围 → 同样拆辅助列逐行算,再聚合
51
+ 5. 公式意图是"对多个区域分别计算再汇总"(例如用 INDIRECT/OFFSET 对每行生成一个范围,再对所有范围聚合)→ 飞书不能直接返回"区域的列表",必须明确降维:用 `VSTACK` 垂直合并、`HSTACK` 水平合并、`TOCOL/TOROW` 展平,或先把各段结果落到辅助区域再用普通聚合函数汇总
41
52
  6. 算日期差 → 不要写 `DAY(end-start)`,用 `DAYS`、`DATEDIF` 或直接 `end-start`
42
53
 
43
54
  ## 飞书的投影行为(不是默认 spill)
44
55
 
45
- 飞书普通公式对引用区域默认"投影"而不是"spill":
56
+ 触发条件是**参数要求单值、实际传入的却是区域**,此时飞书取"投影"而不是"spill":
46
57
 
47
58
  - 单列区域 → 按当前公式所在行取值
48
59
  - 单行区域 → 按当前公式所在列取值
49
60
  - 二维区域 → 只有当前公式位置能映射到该区域时才取值,否则报错
50
61
  - 数组常量 `{...}` 或函数返回矩阵,在普通标量上下文里通常只取左上角
51
62
 
52
- 因此:
63
+ **例外是数组公式上下文内部**:最外层套了 `ARRAYFORMULA`、或公式里已有原生数组函数时,同一个区域会逐项展开,不再投影。
64
+
65
+ 因此(以下均指普通公式,即不在数组公式上下文里):
53
66
  - `=A1:A2` 在飞书普通公式里不会 spill,只会投影到当前行
54
- - `=ABS(A2:B2)` 不会得到一整行,要写 `=ARRAYFORMULA(ABS(A2:B2))`
67
+ - `=ABS(A2:B2)` 不会得到一整行,要写 `=ARRAYFORMULA(ABS(A2:B2))`,或在 A、B 两格分别写 `=ABS(A2)` / `=ABS(B2)`
55
68
  - `=TRUNC({1.1111,2.222},{1,2})` 要得到一整行,写 `=ARRAYFORMULA(TRUNC({1.1111,2.222},{1,2}))`
56
69
 
57
- ## ARRAYFORMULA 使用规则
70
+ ## 没有原生数组函数时:ARRAYFORMULA 或逐行填充
58
71
 
59
- **前提:以下规则适用于公式中没有任何原生数组函数的情况。** 若公式中已有原生数组函数(如 FILTER、XLOOKUP、MAP 等),数组语义会自动传播到整个公式的求值过程,后续标量运算无需额外包 `ARRAYFORMULA`(见下一节)。
72
+ **前提:本节适用于公式中没有任何原生数组函数的情况。** 若公式中已有原生数组函数(如 FILTER、XLOOKUP、MAP 等),数组语义会自动传播到整个公式的求值过程(见下一节)。
60
73
 
61
- 需要加 `ARRAYFORMULA` 的典型场景(公式中无原生数组函数时):
74
+ 以下运算与函数**只按标量求值**,直接喂整段区域不会逐项展开:
62
75
 
63
76
  - 算术运算:`+ - * / ^ %`
64
77
  - 比较运算:`= <> > >= < <=`
@@ -68,38 +81,39 @@
68
81
  - 条件函数:`IF IFS IFERROR IFNA NOT ISNUMBER ISTEXT ISBLANK ...`
69
82
  - 引用函数(高风险):`INDEX OFFSET COLUMN ROW MATCH`
70
83
 
84
+ **两条等价做法,导出 `.xlsx` 后都保真,按需要选一条:**
85
+
86
+ - **`ARRAYFORMULA(<整个表达式>)`**:一条公式覆盖整片,写起来短。`=ARRAYFORMULA(A2:A100*B2:B100)` ✓、`=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))` ✓
87
+ - **逐行标量式 + 填充**:首行写 `=A2*B2` / `=IF(A2>0,B2,"")`,再用 `--copy-to-range` 铺到整列,引用随行递增。每格是独立公式,导出后在 Excel 里能单格编辑
88
+
89
+ `MAP` 只在 LAMBDA 体是**纯运算符或单参函数**时可用(如 `=MAP(A2:A100,B2:B100,LAMBDA(a,b,a*b))`);体内出现 `IF`、多参函数或字符串拼接就改用上面两条路,理由见开头核心原则二。
90
+
71
91
  ### 公式中有原生数组函数时,整个公式已进入数组模式
72
92
 
73
93
  飞书的数组语义会在整个公式求值过程中累积传播:一旦某个原生数组函数运行,后续所有运算符和函数也会自动逐元素处理,无论它们出现在哪一层。
74
94
 
75
- 因此,以下写法**无需**额外包 `ARRAYFORMULA`:
95
+ 因此以下写法直接成立,不必再包 `ARRAYFORMULA`、也不必拆成逐行填充:
76
96
 
77
97
  - `=FILTER(A2:A10,B2:B10="x")+1` ✓
78
98
  - `=XLOOKUP(E2:E10,A2:A10,B2:B10)*100` ✓
79
99
  - `=ABS(FILTER(A2:A10,B2:B10>0))` ✓
80
100
  - `=MAP(A2:A10,LAMBDA(x,x*2))-1` ✓
81
101
 
82
- 对比:**没有原生数组函数**时必须加:
83
-
84
- - `=A2:A100*B2:B100` → `=ARRAYFORMULA(A2:A100*B2:B100)` ✓
85
- - `=IF(A2:A100>0,B2:B100,"")` → `=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))` ✓
102
+ ## 原生数组函数清单
86
103
 
87
- ## 飞书原生数组函数清单
104
+ 以下函数按数组语义工作,可直接返回整片结果,不必拆成逐行填充;且它们在 Excel 侧同样存在,可安全使用:
88
105
 
89
- 以下函数按数组语义工作,通常**不需要额外包 `ARRAYFORMULA`**:
106
+ `CELL` `CHOOSECOLS` `CHOOSEROWS` `DROP` `EXPAND` `FILTER` `FREQUENCY` `GROWTH` `HSTACK` `LINEST` `LOGEST` `LOOKUP` `MINVERSE` `MMULT` `MUNIT` `RANDARRAY` `SEQUENCE` `SORT` `SORTBY` `SUMPRODUCT` `SWITCH` `TAKE` `TEXTSPLIT` `TOCOL` `TOROW` `TRANSPOSE` `TREND` `UNIQUE` `VSTACK` `WRAPCOLS` `WRAPROWS` `XLOOKUP`
90
107
 
91
- `ARRAYFORMULA` `ARRAY_CONSTRAIN` `BYCOL` `BYROW` `CELL` `CHOOSECOLS` `CHOOSEROWS` `DROP` `EXPAND` `FILTER` `FLATTEN` `FREQUENCY` `GROWTH` `HSTACK` `IMPORTDATA` `IMPORTFEED` `IMPORTHTML` `IMPORTRANGE` `IMPORTXML` `LINEST` `LOGEST` `LOOKUP` `MAKEARRAY` `MAP` `MINVERSE` `MMULT` `MUNIT` `QUERY` `RANDARRAY` `REDUCE` `REGEXEXTRACT` `SCAN` `SEQUENCE` `SORT` `SORTBY` `SORTN` `SPLIT` `SUMPRODUCT` `SWITCH` `TAKE` `TEXTSPLIT` `TOCOL` `TOROW` `TRANSPOSE` `TREND` `UNIQUE` `VSTACK` `WRAPCOLS` `WRAPROWS` `XLOOKUP`
108
+ `BYCOL` `BYROW` `MAKEARRAY` `MAP` `REDUCE` `SCAN` 同样是原生数组函数,但受核心原则二约束——导出 `.xlsx` 会丢高阶语义,默认改走 `ARRAYFORMULA` / 逐行填充 / 辅助列。
92
109
 
93
- > **注意:`SWITCH` 在飞书里被当作原生数组函数处理,这与 Excel 行为不同,不需要额外包 `ARRAYFORMULA`。**
110
+ `ARRAYFORMULA` 不在上面这份清单里——它的作用是给**本来只按标量求值**的表达式套上数组语义,而不是自己返回数组。导出 `.xlsx` 时它会被翻译成 Excel 原生数组公式(`=ARRAYFORMULA(IF(A2:A6>2,B2:B6,""))` → `=IF(A2:A6>2,B2:B6,"")`,作用范围覆盖整片),语义完整保留,可安全使用。
94
111
 
95
- ## IMPORTRANGE 跨工作簿引用限制
112
+ > **注意:`SWITCH` 在飞书里被当作原生数组函数处理,这与 Excel 行为不同——把区域喂给它会逐项展开。**
96
113
 
97
- 用 `IMPORTRANGE` 跨电子表格引用数据时有两条硬上限:
114
+ ## 跨电子表格取数不要用公式
98
115
 
99
- - **嵌套最多 5 层**:被引用的表里若又用 `IMPORTRANGE` 继续引下一张表,整条引用链最多 5 层。
100
- - **每个工作表最多 100 个 `IMPORTRANGE` 引用**。
101
-
102
- 超限会让引用失效或报错。设计大量跨表汇总前先估算引用数,必要时先把数据落地到本表再计算。
116
+ 飞书公式没有跨工作簿引用的通用写法(Excel 的外部链接迁过来也不成立)。需要另一份电子表格的数据时,先把那份数据读出来(`+csv-get` 等)落到本表的一张子表,再在本表内用普通引用计算——既避开跨表引用限制,也保证导出后公式仍可用。
103
117
 
104
118
  ## INDEX / OFFSET / COLUMN / ROW / MATCH 是高风险函数
105
119
 
@@ -111,18 +125,13 @@
111
125
  - 结果本来应该是一行或一块二维区域
112
126
  - 外层还有算术、比较、`IF` 等继续处理它
113
127
 
114
- 更稳的写法:
115
-
116
- - `=ARRAYFORMULA(INDEX(...))`
117
- - `=ARRAYFORMULA(OFFSET(...))`
118
- - `=ARRAYFORMULA(COLUMN(...))`
119
- - `=ARRAYFORMULA(ROW(...))`
128
+ 更稳的写法:整体包一层 `=ARRAYFORMULA(INDEX(...))` / `=ARRAYFORMULA(ROW(...))`;或退回**当前行的标量式再向下填充**——首行写 `=INDEX($A$2:$A$100,ROW(A1))`,向下填充时 `ROW(A1)` 自动递增为 1、2、3…
120
129
 
121
- **例外:** 如果返回值只是立刻交给聚合函数消费,不需要额外包:
130
+ **例外:** 如果返回值只是立刻交给聚合函数消费,直接写即可:
122
131
 
123
132
  - `=SUM(INDEX(A1:B2,0,1))` ✓
124
133
 
125
- ## Excel 隐式逐项求值,飞书里要显式写 MAP
134
+ ## Excel 隐式逐项求值,飞书里要拆辅助列
126
135
 
127
136
  **典型特征:**
128
137
 
@@ -133,18 +142,25 @@
133
142
 
134
143
  同类本质也包括:`INDEX/INDIRECT/OFFSET` 先返回范围,外层再把这些范围交给 `SUMIF`、`COUNTIF`、`AVERAGEIF`、`SUMIFS` 等范围感知函数 —— 飞书里这些外层函数不会自动二次展开内层范围。
135
144
 
136
- 这时不要只会补 `ARRAYFORMULA`,要显式写"遍历"。最常用模板:
145
+ 这时要把"遍历"落到**辅助列**上,分两步:
137
146
 
138
147
  ```excel
139
- =SUMPRODUCT(
140
- MAP(
141
- ARRAYFORMULA(ROW(目标范围)),
142
- LAMBDA(r, 单行计算逻辑)
143
- )
144
- )
148
+ 辅助列首行(如 Z16):=单行计算逻辑 # 例:=SUBTOTAL(103,INDIRECT("E"&ROW(E16)))
149
+ 用 --copy-to-range 铺满 Z16:Z387(引用随行递增)
150
+ 汇总格: =SUM(Z16:Z387) # 需要时可隐藏辅助列
145
151
  ```
146
152
 
147
- 同类场景也优先考虑 `MAP`:
153
+ 辅助列全是普通标量公式,导出 `.xlsx` 后逐格原样保留,也避开了 `LAMBDA` 系高阶函数的导出陷阱。
154
+
155
+ **例外:结果要随筛选联动时,保持单条公式。** `SUBTOTAL` 的意义就在于筛选变化后重新计算,这类需求写成
156
+
157
+ ```excel
158
+ =SUMPRODUCT(MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(row,SUBTOTAL(103,INDIRECT("E"&row)))))
159
+ ```
160
+
161
+ 筛选状态本身导出 `.xlsx` 就不会保留,所以这个场景是飞书内专用,不受核心原则二的导出约束。
162
+
163
+ 其余同类场景走辅助列:
148
164
 
149
165
  - `INDIRECT("A"&ROW(...))`
150
166
  - `OFFSET(...,ROW(...)-ROW(...),...)`
@@ -160,10 +176,7 @@
160
176
 
161
177
  ### 多层范围不能自动二次展开
162
178
 
163
- 内层 `INDEX/INDIRECT/OFFSET` 返回的是二维范围,外层还想继续对这些范围做范围计算时,不要假设飞书会"再展开一层"。改用:
164
-
165
- - `MAP(..., LAMBDA(...))` 显式逐项算
166
- - `REDUCE(..., LAMBDA(...))` 显式累加/归约
179
+ 内层 `INDEX/INDIRECT/OFFSET` 返回的是二维范围,外层还想继续对这些范围做范围计算时,不要假设飞书会"再展开一层"。改用辅助列逐行算再聚合(见上一节),别把二次展开压进单条数组公式。
167
180
 
168
181
  ### 真正的三维或更高维结果不能直接返回
169
182
 
@@ -177,7 +190,7 @@
177
190
  - 左右拼接:`=HSTACK(slice1, slice2, slice3)`
178
191
  - 压成单列:`=TOCOL(...)`
179
192
  - 压成单行:`=TOROW(...)`
180
- - 只保留聚合值:`=REDUCE(slice1, {slice2,slice3}, LAMBDA(acc,x,acc+x))`
193
+ - 只保留聚合值:把各 slice 分别落到辅助区域,再用 `SUM` / `SUMPRODUCT` 等普通聚合函数汇总(`REDUCE` 受核心原则二约束,不要用)
181
194
 
182
195
  不要替用户"偷定"第三维展示方式;如果用户没有明确说明怎么展示,至少先把结果改写成可见的二维形状。
183
196
 
@@ -199,7 +212,7 @@ Excel:`=A1#`(引用 A1 公式溢出的整片区域)
199
212
  飞书没有此语法,迁移方式:
200
213
 
201
214
  - spill 区域已知 → 改成明确范围
202
- - spill 区域未知 → 回到源公式重写,或用 `TAKE` / `DROP` / `ARRAY_CONSTRAIN`
215
+ - spill 区域未知 → 回到源公式重写,或用 `TAKE` / `DROP` 截取
203
216
 
204
217
  ### 结构化引用
205
218
 
@@ -211,7 +224,7 @@ Excel:`=SUM(Table1[Amount])`
211
224
 
212
225
  Excel:`{=A1:A10*B1:B10}`(Ctrl+Shift+Enter 输入)
213
226
 
214
- 飞书改为:`=ARRAYFORMULA(A1:A10*B1:B10)`
227
+ 飞书改为:`=ARRAYFORMULA(A1:A10*B1:B10)`——导出 `.xlsx` 后正好还原成 Excel 的 CSE 数组公式;或首行写 `=A1*B1` 再向下填充
215
228
 
216
229
  ## 日期序列与日期差
217
230
 
@@ -242,16 +255,16 @@ Excel:`{=A1:A10*B1:B10}`(Ctrl+Shift+Enter 输入)
242
255
  - `INFO`、`RTD` — 系统信息 / 实时数据函数,飞书不支持
243
256
  - `PIVOT` — 用 `+pivot-{create|update|delete}` 透视表对象替代
244
257
  - `AMORDEGRC`、`PHONETIC`、`DETECTLANGUAGE` — 飞书不支持
245
- - `LET`、命名自定义函数(名称管理器里定义的 LAMBDA)、独立调用的 `LAMBDA`(如 `=LAMBDA(x,x+1)(5)`)— 会报 `#NAME?`;改用嵌套 IF / 辅助列。**例外**:`LAMBDA` 作为 `MAP` / `REDUCE` / `BYROW` / `BYCOL` / `SCAN` / `MAKEARRAY` 的内联参数时**支持**(见上方「飞书原生数组函数清单」)
258
+ - `LET`、命名自定义函数(名称管理器里定义的 LAMBDA)、独立调用的 `LAMBDA`(如 `=LAMBDA(x,x+1)(5)`)— 会报 `#NAME?`;改用嵌套 IF / 辅助列。**例外**:`LAMBDA` 作为 `MAP` / `REDUCE` / `BYROW` / `BYCOL` / `SCAN` / `MAKEARRAY` 的内联参数时飞书**支持**,但受核心原则二约束(导出 `.xlsx` 丢高阶语义),默认仍走逐行填充 / 辅助列
246
259
 
247
260
  ## 代表性改写示例
248
261
 
249
262
  - 基础逐项计算
250
263
  - Excel: `=A2:A100*B2:B100`
251
- - 飞书: `=ARRAYFORMULA(A2:A100*B2:B100)`
264
+ - 飞书: `=ARRAYFORMULA(A2:A100*B2:B100)`;或首行 `=A2*B2` + `--copy-to-range` 向下填充
252
265
  - 条件判断
253
266
  - Excel: `=IF(A2:A100>0,B2:B100,"")`
254
- - 飞书: `=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))`
267
+ - 飞书: `=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))`;或首行 `=IF(A2>0,B2,"")` + 向下填充(LAMBDA 体含 `IF`,不能用 `MAP`)
255
268
  - 原生数组函数(无需改动)
256
269
  - Excel: `=FILTER(A2:C100,B2:B100="East")`
257
270
  - 飞书: `=FILTER(A2:C100,B2:B100="East")`
@@ -260,17 +273,17 @@ Excel:`{=A1:A10*B1:B10}`(Ctrl+Shift+Enter 输入)
260
273
  - 飞书: `=XLOOKUP(E2:E10,A2:A10,B2:B10)*100`
261
274
  - 高风险引用函数
262
275
  - Excel: `=INDEX(A1:D2,{2,1},0)`
263
- - 飞书: `=ARRAYFORMULA(INDEX(A1:D2,{2,1},0))`
276
+ - 飞书: `=ARRAYFORMULA(INDEX(A1:D2,{2,1},0))`(`col_num=0` 取整行必须包在 `ARRAYFORMULA` 里才成立,裸写会报 `#VALUE!`)
264
277
  - 日期差
265
278
  - 错误: `=DAY(B2-A2)`
266
279
  - 推荐: `=DAYS(B2,A2)` 或 `=DATEDIF(A2,B2,"D")` 或 `=B2-A2`
267
280
  - Excel 隐式逐项求值
268
281
  - Excel: `=SUMPRODUCT(SUBTOTAL(103,INDIRECT("E"&ROW($E$16:$E$387))))`
269
- - 飞书: `=SUMPRODUCT(MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(row,SUBTOTAL(103,INDIRECT("E"&row)))))`
282
+ - 飞书: `=SUMPRODUCT(MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(row,SUBTOTAL(103,INDIRECT("E"&row)))))`(`SUBTOTAL` 要随筛选联动,保持单条公式)
270
283
  - 多层范围 / 二次展开
271
284
  - 错误思路: `=SUMIF(INDIRECT("E"&ROW($E$16:$E$387)),">0")`
272
- - 飞书: `=MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(r,SUMIF(INDIRECT("E"&r),">0")))`
285
+ - 飞书: 辅助列 `Z16` 写 `=SUMIF(INDIRECT("E"&ROW(E16)),">0")` 向下填充到 `Z387`
273
286
  - 三维降二维(保留所有层)
274
287
  - 飞书: `=VSTACK(slice1,slice2,slice3)` 或 `=HSTACK(slice1,slice2,slice3)`
275
288
  - 三维降二维(只保留聚合值)
276
- - 飞书: `=REDUCE(slice1,{slice2,slice3},LAMBDA(acc,x,acc+x))`
289
+ - 飞书: 各 slice 落到辅助区域后 `=SUM(辅助区域)`(不要用 `REDUCE`)
@@ -1,8 +1,8 @@
1
1
  # Lark Sheet Formula Verify(+formula-verify)
2
2
 
3
- > **本文定位**:飞书表格"公式写入后是否真的零错误"的自检入口,也是所有写公式任务的**强制收尾步骤**。公式的书写规则与 Excel→飞书迁移的语义规则一律以 `lark-sheets-formula-translation` 为唯一权威,本文不重复;本文聚焦"写完了之后怎么用一次调用确认 zero-error"。
3
+ > **本文定位**:飞书表格"公式写入后是否真的零错误"的诊断入口。公式的书写规则与 Excel→飞书迁移的语义规则一律以 `lark-sheets-formula-translation` 为唯一权威,本文不重复;本文聚焦"写完之后如何用一次调用发现公式错误"。
4
4
  >
5
- > **边界**:本文不讲公式怎么写(去 `lark-sheets-formula-translation`),也不讲公式怎么写入表格(去 `lark-sheets-write-cells` / `lark-sheets-batch-update`)。本文只讲一件事:**只要任务里发生了公式落表、批量填充公式、`--copy-to-range` 扩展公式、导入含公式 workbook,收尾就必须用 `+formula-verify` 自检到 zero-error 才能交付**。
5
+ > **边界**:本文不讲公式怎么写(去 `lark-sheets-formula-translation`),也不讲公式怎么写入表格(去 `lark-sheets-write-cells` / `lark-sheets-batch-update`)。本文只讲一件事:任务里发生公式落表、批量填充公式、`--copy-to-range` 扩展公式、导入含公式 workbook 时,如何用 `+formula-verify` 做诊断并决定是否修复。
6
6
 
7
7
  ## 为什么需要自检
8
8
 
@@ -11,7 +11,7 @@
11
11
  - 公式编译失败 → 单元格落成文本(写入类 shortcut 返回的 `formula_errors[]` 是**编译失败**信号)。
12
12
  - 公式编译成功但**运行时错误**:`#REF!` / `#DIV/0!` / `#VALUE!` / `#NAME?` / `#NULL!` / `#NUM!` / `#N/A`——这一类只看 `formula_errors[]` 看不到,必须扫单元格值。
13
13
 
14
- `+formula-verify` 把两路信号合并成一份统一 JSON:一次调用聚合全表错误清单 + 编译失败清单 + 每类错误的定位与样本,AI 一眼就能定位修复,链路也能据 `status` 强制收敛到 `success`。
14
+ `+formula-verify` 把两路信号合并成一份统一 JSON:一次调用聚合全表错误清单 + 编译失败清单 + 每类错误的定位与样本,AI 一眼就能定位修复。G2 分支中,它提供诊断信号,不再作为默认交付前置条件。
15
15
 
16
16
  ## 调用契约
17
17
 
@@ -34,9 +34,9 @@
34
34
  - `compile_errors[]`——合并最近一次写入留下的编译失败清单,与运行时错误并存时同时出现。
35
35
  - `warning_message`——仅在 `has_more=true` 时出现,告知调用方需要缩小 `--range` / 拆 `--sheet-id` 续读。
36
36
 
37
- ## 写入收尾收敛规则
37
+ ## 写入后诊断规则
38
38
 
39
- 任何批量公式 / 含公式列写入完成后调用 `+formula-verify` 直到 `status='success'` 才能交付。不要等用户显式说"校验一下公式"才想到这里;**只要任务动作包含写公式,这一步默认就该做**。触发场景:
39
+ 任何批量公式 / 含公式列写入完成后,都可以调用 `+formula-verify` 做一次诊断。不要等用户显式说"校验一下公式"才想到这里;只要任务动作包含写公式,这一步就有较高价值。触发场景:
40
40
 
41
41
  - `+cells-set` / `+csv-put`
42
42
  - `+cells-set --copy-to-range` / 模板单元格向整列或整块扩展公式
@@ -45,27 +45,31 @@
45
45
  - `+table-put`(任意列含公式时)
46
46
  - `+workbook-import`(导入的 xlsx 含公式时)
47
47
 
48
- 收敛规则:
48
+ 处置规则:
49
49
 
50
- 1. `status='success'` → 通过;可以把链路标完成。
51
- 2. `status='partial'` → 扫描被内部上限截断。先缩小 `--range` 或拆 `--sheet-id` 续扫,**不允许**把 `partial` 当作 `success`。
52
- 3. `status='errors_found'` 且 `compile_errors[]` 非空 → **先解决编译失败**:根据 `compile_errors[].reason` 修正公式语法(飞书函数名 / 范围语法 / 引用样式),用 `+cells-set` 重写后再调一次 `+formula-verify`。
53
- 4. `status='errors_found'` 且只剩运行时错误 → 按 `error_summary` 的 `samples[].formula` + `depends_on` 排查根因(零除?空值参与运算?引用越界?日期差写法?数组语义?),修复后重新自检。
54
- 5. 同一处错误连续修复 3 次仍未通过 → 改用 `IFERROR` 包裹兜底,或退回纯值写入;不要在 `errors_found` 状态下扩展 `+cells-set --copy-to-range`、追加批量写入。
50
+ 1. `status='success'` → 记录诊断通过。
51
+ 2. `status='partial'` → 扫描被内部上限截断。若该公式区是关键输出,可缩小 `--range` 或拆 `--sheet-id` 续扫;否则在交付说明中标明诊断覆盖不完整。
52
+ 3. `status='errors_found'` 且 `compile_errors[]` 非空 → 根据 `compile_errors[].reason` 修正公式语法(飞书函数名 / 范围语法 / 引用样式),或在成本过高时降级为静态值并说明原因。
53
+ 4. `status='errors_found'` 且只剩运行时错误 → 按 `error_summary` 的 `samples[].formula` + `depends_on` 排查根因(零除?空值参与运算?引用越界?日期差写法?数组语义?),优先修复关键输出区。
54
+ 5. 同一处错误连续修复 3 次仍未通过 → 改用 `IFERROR` 包裹兜底,或退回纯值写入,并在交付说明写清不随源数据更新。
55
55
 
56
56
  注意:
57
57
 
58
- - 在 `status='errors_found'` 的状态下调用 `+cells-set --copy-to-range` 继续扩展会把错误复制放大。
58
+ - 在 `status='errors_found'` 的状态下调用 `+cells-set --copy-to-range` 继续扩展会把错误复制放大,建议先处理关键错误。
59
59
  - "编译失败但运行时无报错"不是 zero-error(编译失败的单元格此刻是文本不是公式,源数据一变就再也算不出值)。
60
- - 跳过自检直接交付、靠肉眼读首末 5 行确认是不可靠的——表中段、隐藏行、合并区里的错误这样根本看不到。
60
+ - 只靠肉眼读首末 5 行确认不可靠——表中段、隐藏行、合并区里的错误这样根本看不到;`+formula-verify` 可补充这一诊断视角。
61
+ - 只验证写入区首行不够:批量填公式后同时抽查首行、中段、尾部和汇总行;目标是发现“只填到前 N 行”“把明细公式写进合计行”“尾部仍是空/错误值”这类问题。
62
+ - 修公式时先定位根因格,再看下游链路。不要把被上游错误污染的下游格全部重写;同型公式优先从相邻正确单元格复制/改引用,写完回读下游关键格是否仍有 `#VALUE!` / `#REF!`。
63
+ - 查找/匹配公式必须有错误处理:不要裸写 `VLOOKUP` / `XLOOKUP`。未匹配时返回明确文本(如“未匹配到”),不要静默空串,除非用户明确要求空值。
64
+ - 排名/排序公式要处理空值、0 值和不参与排名项;这些项应保持空/0,而不是进入通用排名公式得到正整数名次。
61
65
 
62
66
  ## 截断与续读
63
67
 
64
68
  后端有一个内部硬上限对总扫描单元格数做截断(不暴露给调用方),超过后立即返回 `has_more=true` + `warning_message`,`error_summary` / `compile_errors` 仅覆盖已扫描部分。处理路径:
65
69
 
66
- - 把工作簿按 `--sheet-id` / `--sheet-name` 拆成多次调用。
67
- - 同 sheet 内按 `--range` 切片(如先 `A1:Z200` 再 `AA1:AZ200`),逐块自检。
68
- - 每块都跑到 `has_more=false` 且 `status='success'` 才算通过。
70
+ - 关键输出区优先按 `--sheet-id` / `--sheet-name` 拆成多次调用。
71
+ - 同 sheet 内按 `--range` 切片(如先 `A1:Z200` 再 `AA1:AZ200`),逐块诊断。
72
+ - 如时间不足,说明已诊断范围和未覆盖范围。
69
73
 
70
74
  ## 常见陷阱
71
75
 
@@ -73,5 +77,5 @@
73
77
  |---|---|
74
78
  | 错误字符串本地化 | 后端按内部 `error_kind` / `compute_status` 字段识别错误类别,不走字符串匹配;调用方拿到的 7 类英文错误代码由后端统一规范输出,与 locale 无关。 |
75
79
  | `formatted_value` 可能隐藏错误 | 某些条件格式 / 自定义数字格式会把 `#DIV/0!` 显示成空白。后端直接读 cell `error_kind`,不依赖 `formatted_value`,绕开此类被遮蔽。 |
76
- | 把 `partial` 当 `success` | `partial` 仅表示**已扫描部分**无错误,剩余区域未知。必须续扫直到 `has_more=false` 且 `status='success'` 才能算通过。 |
80
+ | 把 `partial` 当全量健康 | `partial` 仅表示**已扫描部分**无错误,剩余区域未知。关键公式区应继续缩小范围诊断;非关键区可在交付说明标明覆盖不足。 |
77
81
  | 编译失败 vs 运行时错误 | 同一份报告里 `compile_errors[]` 与 `error_summary` 并存。语义层先解决 `compile_errors[]`、再做运行时自检。 |
@@ -33,6 +33,7 @@
33
33
  - **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误
34
34
  - **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(`summarize_by: "sum"`)。不要把行列字段搞反
35
35
  - **聚合类型要匹配**:用户说"统计数量"→ `summarize_by: "count"`;"统计总额"→ `"sum"`;"统计平均"→ `"average"`。完整合法值:`sum` / `count` / `average` / `max` / `min` / `product` / `countNums` / `stdDev` / `stdDevp` / `var` / `varp` / `distinct` / `median`。按用户意图选聚合方式,不要拿 `count` 顶替 `sum`
36
+ - **`--properties` 还原生支持**:计算字段 `calculated_fields[].summarize_by ∈ {sum, custom}`、重复行标签 `repeat_row_labels: true`——别因速查表没列就判"不支持"绕路
36
37
  - **参数长度限制**:如果透视表配置 JSON 过长(数据源范围跨越大量行列),可能导致工具调用失败。此时应先确认数据范围的精确边界,避免传入过大的 range
37
38
  - **落点不能覆盖任何已有数据(不只是 `--source` 范围)**:透视表创建后会向右下**展开**,展开区域哪怕只盖到一个已有单元格(即便已避开源数据),也会报「目标位置不能与数据源重叠」并产生 `#REF!`。创建前无法精确预知展开尺寸,故**强烈优先默认策略**(不传 `--target-sheet-id/-name` 与 `--target-position`/`--range`,后端自动新建空白子表),零覆盖风险;非要落到已有子表,必须挑一片足够大的纯空白区
38
39
  - **创建后必须校验(用 `info` 读取展开后的真实占用区域)**:创建后调用 `+pivot-list` 读 `info.error_state` 与 `info.content_range`/`page_range`——`error_state` 非 `None`(如 `Cover` 盖到其它内容 / `Shrink` 展不开)说明落点冲突,应删除后重建到空白区;`content_range`/`page_range` 是展开后**实际占用区域**,可用 `+csv-get` 抽查其边缘外有没有盖掉原有数据,确认结构正确
@@ -159,7 +160,7 @@ lark-cli sheets +pivot-create --url "..." \
159
160
  ### `+pivot-delete`
160
161
 
161
162
  ```bash
162
- lark-cli sheets +pivot-delete --url "..." --sheet-id "$SID" --pivot-table-id "$PID" --yes
163
+ lark-cli sheets +pivot-delete --url "..." --sheet-id "$SHEET_ID" --pivot-table-id "$PIVOT_TABLE_ID" --yes
163
164
  ```
164
165
 
165
166
  ### Validate / DryRun / Execute 约束
@@ -54,7 +54,7 @@
54
54
  5. **新增合并时数据保护**:合并前确认目标区域只有左上角有数据,其余单元格为空,否则合并会导致非左上角的数据丢失。
55
55
  6. **批量取消合并一次调用即可**:当一个范围(整列 `A:A`、整行 `3:3`、矩形 `A1:D100`)内存在多个合并区域,直接调一次 `+cells-unmerge` 传入这个大范围,会一次性取消该范围内所有合并区域;**不要**为每个合并区域单独调用 unmerge,也不要用 `+batch-update` 拆成多次 unmerge。
56
56
 
57
- **⚠️ 多区域合并不要逐个调用**:对**多个**不同区域执行 `+cells-merge` 时,写成一份 `+styles-put --styles` 的 `cell_merges` 一次交付(合并与样式 / 行高列宽 / 冻结同属一份声明式规格,见 `lark-sheets-styles-put`);只有当合并夹在**跨类型、有顺序依赖**的操作链里(如插列 → 合并 → 写表头)才用 `+batch-update`(fail-fast、不回滚,入参格式见 `lark-sheets-batch-update`)。行高列宽同理**不需要** `+batch-update`:多行 / 多列不同尺寸直接用 `+rows-resize --heights` / `+cols-resize --widths` 的 map 形态,一次调用完成。
57
+ **⚠️ 多区域合并不要逐个调用**:对**多个**不同区域执行 `+cells-merge` 时,写成一份 `+styles-put --styles` 的 `cell_merges` 一次交付(合并与样式 / 行高列宽 / 冻结同属一份声明式规格,见 `lark-sheets-styles-put`);只有当合并夹在**跨类型、有顺序依赖**的操作链里(如插列 → 合并 → 写表头)才用 `+batch-update`(fail-fast,失败处置与入参格式见 `lark-sheets-batch-update`)。行高列宽同理**不需要** `+batch-update`:多行 / 多列不同尺寸直接用 `+rows-resize --heights` / `+cols-resize --widths` 的 map 形态,一次调用完成。
58
58
 
59
59
  **唯一例外**:`+cells-unmerge` 原生支持传一个大 range 一次性取消其中所有合并区域,应直接单次调用,**不要**拆进 `+batch-update`。
60
60
 
@@ -22,7 +22,7 @@
22
22
  | 读取目的 | 用这个 shortcut | 数据去向 | 说明 |
23
23
  |---------|----------------|---------|------|
24
24
  | 快速查看纯值数据、批量处理 | `+csv-get` | 对话上下文 | 返回 CSV 文本(每行带 `[row=N]` 前缀);大表请按 `--range` 行窗口分批读(截断时看 `has_more`) |
25
- | 按列类型结构化读出(喂 DataFrame / round-trip 回 `+table-put`) | `+table-get` | 对话上下文 | 返回 typed 协议(`columns:[列名]` + `data` + `dtypes`/`formats` + `range`),输出形状对齐 pandas split;可一行 `pd.DataFrame(sheet["data"], columns=sheet["columns"]).astype(sheet["dtypes"])` 还原 DataFrame,或直接 round-trip 回 `+table-put`。不带 `--range` 时读**完整 used range**(跨过表中部空行 / 空列),每个子表回传实际读取范围 `range` 供完整性校验;被 `max_chars` 裁掉时该子表还会带 `truncated: true` 与 `truncation_warning`,**先看这两个字段再用数据**。注意这与下文 `current_region` "遇表中部空行截断"不矛盾:`+table-get` 读的是子表物理 used range(飞书记录的已用矩形,含中间空行),`current_region` 是从锚点连通扩展、遇整行空行就断 |
25
+ | 按列类型结构化读出(喂 DataFrame / round-trip 回 `+table-put`) | `+table-get` | 对话上下文 | 返回 typed 协议(`columns:[列名]` + `data` + `dtypes`/`formats` + `range`),输出形状对齐 pandas split;可一行 `pd.DataFrame(sheet["data"], columns=sheet["columns"]).astype(sheet["dtypes"])` 还原 DataFrame,或直接 round-trip 回 `+table-put`。不带 `--range` 时读**完整 used range**(跨过表中部空行 / 空列),每个子表回传读取范围 `range`;被 `max_chars` 裁掉时**该子表**带 `truncated: true` 与 `truncation_warning`,预算耗尽导致后续整表未读时**顶层**也带同组字段,`--output-path` 落盘模式另看 stdout 回执的 `complete` / `truncated`——**先看截断字段再用数据;三层都没报也不等于逻辑读全**,仍要用返回数据实际行数、关键末行与源数据交叉核对(详见下文)。注意这与下文 `current_region` "遇表中部空行截断"不矛盾:`+table-get` 读的是子表物理 used range(飞书记录的已用矩形,含中间空行),`current_region` 是从锚点连通扩展、遇整行空行就断 |
26
26
  | 查看公式、样式、批注、数据验证 | `+cells-get` | 对话上下文 | 返回单元格完整信息,token 开销较大 |
27
27
  | 查看某区域的下拉框(数据验证)选项 | `+dropdown-get` | 对话上下文 | 返回该 A1 范围已配置的下拉列表选项 |
28
28
 
@@ -228,8 +228,9 @@ lark-cli sheets +csv-get --spreadsheet-token shtXXX --sheet-name "销售明细"
228
228
  - `annotated_csv` — 含 `[row=N]` 前缀的 CSV 主入口
229
229
  - `col_indices` / `row_indices` — 列字母 / 行号映射数组
230
230
  - `current_region` — 从锚点扩展到被空行空列包围的连续区域的 A1 范围。⚠️ **它不是整表真实边界**:遇表中部整行空行 / 整列空列会截断、可能小于真实数据范围;表尾的汇总 / 签名 / 脚注又可能让它大于纯数据范围。判断整表是否读全须拿 `+workbook-info` 的物理 `row_count` 当上界交叉核对(见上方「`row_count` 与 `current_region` 都不能单独定末行」)
231
+ - `actual_range` — **本次实际读到的 A1 范围**。续读 / 校验覆盖度一律以它为准:`actual_range` 小于请求范围时,哪怕 `has_more=false` 也说明只拿到部分窗口,不能把 `row_count` 当成"已读全"
231
232
  - `row_count` / `col_count` — **本次返回的行 / 列数**(= `actual_range` 的尺寸,随 `--range` 变),**不是整表物理总行列数**;整表物理尺寸取 `+workbook-info`
232
- - `has_more` — 当前 `--range` 是否因 `--max-chars` 被截断(截断后续读接着用 `--range`);它**只反映本次 range 内是否读完**,`has_more=false` **不代表整表已读全**(range 之外的数据不在判断内)
233
+ - `has_more` — 当前 `--range` 是否因 `--max-chars` 被截断(截断后续读接着用 `--range`);它**只反映本次 range 内是否还有后续页**,`has_more=false` **不代表整表或该窗口已读全**——仍要结合 `actual_range` 看实际覆盖到哪里
233
234
 
234
235
  > 要按列类型结构化读出(喂 DataFrame、或 round-trip 回 `+table-put`)用 `+table-get`(见下);`+csv-get` 给的是带 `[row=N]` 前缀的纯值快照,下游需要行号/列坐标时直接从前缀与 `col_indices` 取。
235
236
 
@@ -249,7 +250,7 @@ lark-cli sheets +cells-get --url "https://example.feishu.cn/sheets/shtXXX" --she
249
250
 
250
251
  `+table-put`(写入侧,见 write-cells reference)的镜像:把表格读回与 `--sheets` 完全同构的 typed 协议(`sheets[]` + `columns:[列名]` + `data:[[行]]` + `dtypes:{列名:pandas_dtype}` + `formats?:{列名:number_format}` + `range`),可直接喂回 `+table-put` 或一行还原 DataFrame。
251
252
 
252
- **默认(不带 `--range`)读取整张子表的完整 used range**:会跨过表中部的整行空行 / 整列空列,覆盖到真实数据边界。每个子表都回传实际读取的 `range`(如 `A1:F10`)——`+table-get` 不返回分页 / 截断标志,这个 `range` 是判断是否读全的唯一信号:拿它和源 xlsx 行列数、关键末行 / 末日期交叉核对,确认读取完整。仍要精确控制范围时显式传 `--range`。
253
+ **默认(不带 `--range`)读取整张子表的完整 used range**:会跨过表中部的整行空行 / 整列空列,覆盖到真实数据边界。每个子表都回传实际读取的 `range`(如 `A1:F10`)。**截断信号分三层**:① 子表数据被 `max_chars` 裁掉时,该子表带 `truncated: true` + `truncation_warning`;② 字符预算耗尽导致后续整表一行未读时,**顶层**也带同组字段(按提示改用 `--sheet-name` 单表重跑或提高 `--max-chars`);③ `--output-path` 落盘模式以 stdout 回执的 `complete`(命中上限时另有 `truncated`)判断文件完整。**任何一层都没报截断,也不等于逻辑读全**——used range 探测在特殊布局(大段整空行 / 空列)下可能偏窄:拿 `range` 连同返回 `data` 的实际行数、关键末行 / 末日期,与源数据行列数(`+workbook-info` / 源 xlsx)交叉核对,确认覆盖真实边界。仍要精确控制范围时显式传 `--range`;分段续读时配 `--no-header`,表头行与各段 dtypes 需自行拼接对齐。
253
254
 
254
255
  列类型从每列 `number_format` 推断(日期格式→`date`/`datetime64[ns]`、数值→`number`/`float64`、bool→`bool`),`date` 列的序列号转回 ISO `yyyy-mm-dd`——日期、数字往返不丢类型。**列类型只在该列所有非空值一致时才定(`number` / `date` / `bool`);一列混了类型(如数字列混入「暂无」、日期列混入裸数字)会降为 `string`(dtypes 输出 `object`),让 `dtypes` 与 `data` 里每个值自洽——能 round-trip 回 `+table-put`、不让 pandas `astype` 崩。降级是无损的(脏值原样保留为文本);若要把零星脏值转成数值列,交给调用方在 pandas 侧做(`to_numeric(errors='coerce')`),那里原始值仍在、可追溯。** 默认读所有子表、第一行当表头(`--no-header` 把首行当数据、列名取 `col1` / `col2` …)。
255
256
 
@@ -262,9 +263,10 @@ lark-cli sheets +table-get --url "<表URL>" --sheet-name "销售"
262
263
 
263
264
  #### 输出 → DataFrame(用 `sheet_to_df` helper)
264
265
 
265
- 输出形状对齐 pandas split:`columns` 是列名数组、`data` 是二维数据、`dtypes` 是 `{列名: pandas_dtype_str}` 映射。直接喂给 `pd.DataFrame(...).astype(...)` 就能一次性还原所有列类型(不必逐列 `to_datetime` / `to_numeric`)。本 skill 把这段 2 行 helper 打包成可 import 的 [`scripts/sheets_df.py`](../scripts/sheets_df.py)(含 `df_to_sheet` 和 `sheet_to_df`,写入 / 读回成对):
266
+ 输出形状对齐 pandas split:`columns` 是列名数组、`data` 是二维数据、`dtypes` 是 `{列名: pandas_dtype_str}` 映射。直接喂给 `pd.DataFrame(...).astype(...)` 就能一次性还原所有列类型(不必逐列 `to_datetime` / `to_numeric`)。本 skill 把这段 2 行 helper 打包成可 import 的 [`scripts/sheets_df.py`](../scripts/sheets_df.py)(含 `df_to_sheet` 和 `sheet_to_df`,写入 / 读回成对;它在本 skill 的 `scripts/` 目录下,运行目录不在该目录时先把它加入 `sys.path` 再 import):
266
267
 
267
268
  ```python
269
+ import sys; sys.path.insert(0, "scripts") # helper 在 skill 根的 scripts/ 下;cwd 不在 skill 根时填该目录的实际路径
268
270
  from sheets_df import sheet_to_df
269
271
 
270
272
  # 单 sheet
@@ -283,6 +285,7 @@ df_sales = sheets["销售"]
283
285
 
284
286
  ```python
285
287
  import json, subprocess
288
+ import sys; sys.path.insert(0, "scripts") # 同上:sheets_df 在 skill 的 scripts/ 目录
286
289
  from sheets_df import df_to_sheet, sheet_to_df
287
290
 
288
291
  # 1. 读
@@ -1,11 +1,11 @@
1
1
  # Lark Sheet Search & Replace
2
2
 
3
- ## 替换前 dry-run + 范围明确(替换前必做)
3
+ ## 替换前 dry-run + 范围明确(替换前建议)
4
4
 
5
5
  `+cells-replace` 的副作用是不可逆的(除非另写代码回滚)。执行前必须:
6
6
 
7
- 1. **明确替换范围**:必须显式说明"只替换 X 列 / X 区域,还是全表替换"。**禁止**默认全表替换——容易误改无关列。范围应由用户指令决定,模糊时主动询问。
8
- 2. **dry-run 命中数量**:先用 `+cells-search` 在同一范围、同一关键词、同一匹配选项(大小写 / 精确 / 正则)下统计命中数量。把数量和**期望命中数**(用户明示的或基于业务理解推断的)对照——一致才进入 `+cells-replace`,不一致先排查(关键词太宽?范围太大?)。
7
+ 1. **明确替换范围**:建议显式说明"只替换 X 列 / X 区域,还是全表替换"。避免默认全表替换——容易误改无关列。范围应由用户指令决定,模糊时主动询问。
8
+ 2. **dry-run 命中数量**:先用 `+cells-search` 在同一范围、同一关键词、同一匹配选项(大小写 / 精确 / 正则)下统计命中数量。把数量和**期望命中数**(用户明示的或基于业务理解推断的)对照;不一致先排查(关键词太宽?范围太大?)。
9
9
  3. **替换后回读校验**:执行后再次 `+cells-search` 旧关键词,预期为 0;并对替换后的若干代表性单元格回读确认值符合预期。
10
10
 
11
11
  ## 使用场景
@@ -17,7 +17,7 @@
17
17
  | 搜索/定位文本 | `+cells-search` | 返回匹配的单元格位置,支持正则、精确匹配等 |
18
18
  | 查找并替换文本 | `+cells-replace` | 批量替换文本;`--regex` 模式下 `--replacement` 可用 `$1`、`$2` 引用 `--find` 的捕获组 |
19
19
 
20
- **常见配置错误(必须注意)**:
20
+ **常见配置错误(注意)**:
21
21
  - **不要把操作动词当搜索词**:用户说"汇总金额"是一个操作动作(求和),不是要搜索"汇总金额"这个文本。只有当确实需要定位某个文本值的位置时才用 `+cells-search`
22
22
  - **不要用搜索来了解表格结构**:要了解表头和数据结构时,应使用 `+csv-get` 读取前几行,而不是用 `+cells-search` 逐个猜测字段名
23
23
  - **注意正则特殊字符**:使用正则匹配时,`.`、`*`、`(`、`)` 等特殊字符需要转义
@@ -87,7 +87,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
87
87
  | Flag | Type | 必填 | 说明 |
88
88
  | --- | --- | --- | --- |
89
89
  | `--range` | string | xor | 要删除的行/列闭区间;行用 1-based 数字如 `3:7` 或单行 `5`,列用字母如 `C:F` 或单列 `C`。与 `--ranges` 二选一 |
90
- | `--ranges` | string + File + Stdin(简单 JSON) | xor | 要删除的多个行/列区间 JSON 数组(最多 100 个,如 `["5:5","8:8","11:13"]` 或 `["C:C","F:G"]`),全行或全列不可混用,区间不可重叠;与 `--range` 二选一。CLI 按位置**从大到小逆序**合成一次批量删除(fail-fast、不回滚)——正序删除会因前面的行/列被删导致后续索引前移错位,逆序由 CLI 代劳,无需自行排序 |
90
+ | `--ranges` | string + File + Stdin(简单 JSON) | xor | 要删除的多个行/列区间 JSON 数组(最多 100 个,如 `["5:5","8:8","11:13"]` 或 `["C:C","F:G"]`),全行或全列不可混用,区间不可重叠;与 `--range` 二选一。CLI 按位置**从大到小逆序**合成一次批量删除(fail-fast,失败后先回读再补发)——正序删除会因前面的行/列被删导致后续索引前移错位,逆序由 CLI 代劳,无需自行排序 |
91
91
 
92
92
  ### `+dim-hide`
93
93
 
@@ -170,7 +170,7 @@ lark-cli sheets +dim-delete --url "..." --sheet-id "$SID" --range "5:7" --yes
170
170
  # 删除 D-F 列
171
171
  lark-cli sheets +dim-delete --url "..." --sheet-id "$SID" --range "D:F" --yes
172
172
 
173
- # 删除多个散布区间(如按查重结果删行):--ranges 一次批量交付(fail-fast、不回滚,CLI 逆序保索引)。
173
+ # 删除多个散布区间(如按查重结果删行):--ranges 一次批量交付(fail-fast,失败后先回读再补发;CLI 逆序保索引)。
174
174
  # CLI 自动按位置从大到小逆序执行——正序会因前面的行被删导致后续索引前移错位;
175
175
  # 无需自行排序,也不要为此拼 +batch-update 的子操作数组
176
176
  lark-cli sheets +dim-delete --url "..." --sheet-id "$SID" --ranges '["5:5","8:8","11:13"]' --yes
@@ -18,6 +18,7 @@
18
18
  **常见配置错误(必须注意)**:
19
19
  - **数据源范围要精确**:迷你图的数据源范围必须与实际数据行列精确对应,范围偏移会导致图形展示错误
20
20
  - **不要与 SPARKLINE() 公式混淆**:飞书表格的 `SPARKLINE()` 公式函数已被禁用,迷你图只能通过 `+sparkline-{create|update|delete}` 的对象方式创建
21
+ - **胜负 / count 迷你图原生支持**:`config.type="win_loss"`——别因速查表没列就判"不支持"绕路
21
22
  - **创建后必须验证**:调用 `+sparkline-list` 确认迷你图配置正确
22
23
 
23
24
  ## Shortcuts