weaver-e10-builder 1.6.10 → 1.6.11

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 (155) hide show
  1. package/dist/commands/report-admin.d.ts.map +1 -1
  2. package/dist/commands/report-admin.js +378 -2
  3. package/dist/commands/report-admin.js.map +1 -1
  4. package/dist/commands/report-validate.d.ts.map +1 -1
  5. package/dist/commands/report-validate.js +1066 -3
  6. package/dist/commands/report-validate.js.map +1 -1
  7. package/dist/commands/report.d.ts.map +1 -1
  8. package/dist/commands/report.js +341 -3
  9. package/dist/commands/report.js.map +1 -1
  10. package/dist/commands/workflow-after-submit-comment.d.ts +3 -0
  11. package/dist/commands/workflow-after-submit-comment.d.ts.map +1 -0
  12. package/dist/commands/workflow-after-submit-comment.js +56 -0
  13. package/dist/commands/workflow-after-submit-comment.js.map +1 -0
  14. package/dist/commands/workflow-setting.d.ts.map +1 -1
  15. package/dist/commands/workflow-setting.js +2 -67
  16. package/dist/commands/workflow-setting.js.map +1 -1
  17. package/dist/services/report/blueprint.d.ts +109 -0
  18. package/dist/services/report/blueprint.d.ts.map +1 -0
  19. package/dist/services/report/blueprint.js +322 -0
  20. package/dist/services/report/blueprint.js.map +1 -0
  21. package/dist/services/report/build.d.ts.map +1 -1
  22. package/dist/services/report/build.js +8 -3
  23. package/dist/services/report/build.js.map +1 -1
  24. package/dist/services/report/card.d.ts +65 -0
  25. package/dist/services/report/card.d.ts.map +1 -0
  26. package/dist/services/report/card.js +151 -0
  27. package/dist/services/report/card.js.map +1 -0
  28. package/dist/services/report/copy-health.d.ts +31 -0
  29. package/dist/services/report/copy-health.d.ts.map +1 -0
  30. package/dist/services/report/copy-health.js +87 -0
  31. package/dist/services/report/copy-health.js.map +1 -0
  32. package/dist/services/report/dataset-heal.d.ts +64 -0
  33. package/dist/services/report/dataset-heal.d.ts.map +1 -0
  34. package/dist/services/report/dataset-heal.js +89 -0
  35. package/dist/services/report/dataset-heal.js.map +1 -0
  36. package/dist/services/report/dataset-ids.d.ts +70 -0
  37. package/dist/services/report/dataset-ids.d.ts.map +1 -0
  38. package/dist/services/report/dataset-ids.js +149 -0
  39. package/dist/services/report/dataset-ids.js.map +1 -0
  40. package/dist/services/report/dataset.d.ts.map +1 -1
  41. package/dist/services/report/dataset.js +3 -1
  42. package/dist/services/report/dataset.js.map +1 -1
  43. package/dist/services/report/empty-dataset.d.ts +51 -0
  44. package/dist/services/report/empty-dataset.d.ts.map +1 -0
  45. package/dist/services/report/empty-dataset.js +213 -0
  46. package/dist/services/report/empty-dataset.js.map +1 -0
  47. package/dist/services/report/field-binding.d.ts +75 -0
  48. package/dist/services/report/field-binding.d.ts.map +1 -0
  49. package/dist/services/report/field-binding.js +160 -0
  50. package/dist/services/report/field-binding.js.map +1 -0
  51. package/dist/services/report/forms.d.ts +32 -0
  52. package/dist/services/report/forms.d.ts.map +1 -1
  53. package/dist/services/report/forms.js +90 -0
  54. package/dist/services/report/forms.js.map +1 -1
  55. package/dist/services/report/formula-parent.d.ts +25 -0
  56. package/dist/services/report/formula-parent.d.ts.map +1 -0
  57. package/dist/services/report/formula-parent.js +116 -0
  58. package/dist/services/report/formula-parent.js.map +1 -0
  59. package/dist/services/report/locate.d.ts +40 -0
  60. package/dist/services/report/locate.d.ts.map +1 -0
  61. package/dist/services/report/locate.js +122 -0
  62. package/dist/services/report/locate.js.map +1 -0
  63. package/dist/services/report/reports.d.ts +4 -0
  64. package/dist/services/report/reports.d.ts.map +1 -1
  65. package/dist/services/report/reports.js +4 -2
  66. package/dist/services/report/reports.js.map +1 -1
  67. package/dist/services/report/scene.d.ts +22 -0
  68. package/dist/services/report/scene.d.ts.map +1 -0
  69. package/dist/services/report/scene.js +145 -0
  70. package/dist/services/report/scene.js.map +1 -0
  71. package/dist/services/report/types.d.ts +28 -0
  72. package/dist/services/report/types.d.ts.map +1 -1
  73. package/dist/services/report/verify.d.ts +24 -0
  74. package/dist/services/report/verify.d.ts.map +1 -1
  75. package/dist/services/report/verify.js +115 -2
  76. package/dist/services/report/verify.js.map +1 -1
  77. package/dist/services/report/workbook.d.ts +12 -1
  78. package/dist/services/report/workbook.d.ts.map +1 -1
  79. package/dist/services/report/workbook.js +107 -8
  80. package/dist/services/report/workbook.js.map +1 -1
  81. package/dist/services/workflow-after-submit-comment.d.ts +20 -0
  82. package/dist/services/workflow-after-submit-comment.d.ts.map +1 -0
  83. package/dist/services/workflow-after-submit-comment.js +81 -0
  84. package/dist/services/workflow-after-submit-comment.js.map +1 -0
  85. package/ecodecmd/src/build.js +36 -12
  86. package/package.json +1 -1
  87. package/skill-docs/SKILL.md +4 -4
  88. package/skill-docs/_catalog.md +10 -1
  89. package/skill-docs/references/coding/guide-page-code.md +95 -0
  90. package/skill-docs/references/components/ref-component-common.md +98 -0
  91. package/skill-docs/references/report/add-dataset-registration-guide.md +44 -22
  92. package/skill-docs/references/report/advanced-crosstab-tree-guide.md +82 -0
  93. package/skill-docs/references/report/ai-report-workflow.md +100 -100
  94. package/skill-docs/references/report/api-auth-headers.md +129 -0
  95. package/skill-docs/references/report/api-report-build-fast-path.md +274 -275
  96. package/skill-docs/references/report/app-datasource-lookup-guide.md +230 -230
  97. package/skill-docs/references/report/branch-scene-guide.md +293 -0
  98. package/skill-docs/references/report/build-resilience-guide.md +99 -0
  99. package/skill-docs/references/report/card-report-guide.md +110 -0
  100. package/skill-docs/references/report/cell-config-echo-guide.md +44 -9
  101. package/skill-docs/references/report/cell-transform-guide.md +9 -9
  102. package/skill-docs/references/report/column-split-report-guide.md +96 -0
  103. package/skill-docs/references/report/condition-format-guide.md +49 -24
  104. package/skill-docs/references/report/cross-dataset-filter-guide.md +180 -69
  105. package/skill-docs/references/report/crosstab-raw-workbook-guide.md +265 -0
  106. package/skill-docs/references/report/dataset-query-filter-guide.md +231 -235
  107. package/skill-docs/references/report/dataset-sidebar-ops-guide.md +75 -34
  108. package/skill-docs/references/report/dataset-table-field-contract-guide.md +196 -46
  109. package/skill-docs/references/report/datasource-metadata-guide.md +128 -119
  110. package/skill-docs/references/report/datasource-selection-and-parent-filter-guide.md +183 -148
  111. package/skill-docs/references/report/date-range-query-reference.md +97 -102
  112. package/skill-docs/references/report/default-header-style-guide.md +8 -8
  113. package/skill-docs/references/report/designer-blank-prevention-guide.md +8 -8
  114. package/skill-docs/references/report/dossier-report-guide.md +69 -0
  115. package/skill-docs/references/report/drilldown-report-guide.md +59 -0
  116. package/skill-docs/references/report/event-hreflink-guide.md +53 -47
  117. package/skill-docs/references/report/expansion-block-parent-config-guide.md +208 -0
  118. package/skill-docs/references/report/failure-cleanup-and-nonblocking-guide.md +183 -0
  119. package/skill-docs/references/report/field-bind-exact-match-guide.md +208 -208
  120. package/skill-docs/references/report/financial-stats-report-guide.md +89 -0
  121. package/skill-docs/references/report/formula-cell-api-guide.md +325 -258
  122. package/skill-docs/references/report/golden-case-summary-sheet.md +200 -0
  123. package/skill-docs/references/report/guide-report-build.md +6 -1
  124. package/skill-docs/references/report/hidden-row-and-header-merge-guide.md +10 -10
  125. package/skill-docs/references/report/layout-showlocation-freeze-guide.md +39 -10
  126. package/skill-docs/references/report/multi-report-crosstab-casebook.md +263 -0
  127. package/skill-docs/references/report/paged-group-report-guide.md +68 -0
  128. package/skill-docs/references/report/preaggregate-auto-disable-guide.md +24 -13
  129. package/skill-docs/references/report/query-condition-api-guide.md +145 -125
  130. package/skill-docs/references/report/ref-report-commands.md +41 -3
  131. package/skill-docs/references/report/report-baseinfo-defaults-guide.md +117 -117
  132. package/skill-docs/references/report/report-building-guide.md +52 -52
  133. package/skill-docs/references/report/report-case-prompts.md +104 -0
  134. package/skill-docs/references/report/report-config-schema.md +281 -281
  135. package/skill-docs/references/report/report-create-modes-guide.md +73 -35
  136. package/skill-docs/references/report/report-data-model.md +131 -131
  137. package/skill-docs/references/report/report-delivery-contract.md +23 -3
  138. package/skill-docs/references/report/report-designer-feature-map.md +30 -35
  139. package/skill-docs/references/report/report-help-guide.md +304 -0
  140. package/skill-docs/references/report/report-internal-error-guide.md +71 -0
  141. package/skill-docs/references/report/report-knowledge-base.md +27 -27
  142. package/skill-docs/references/report/report-lifecycle-api-guide.md +126 -94
  143. package/skill-docs/references/report/report-list-page-ops-guide.md +54 -8
  144. package/skill-docs/references/report/report-permission-guide.md +99 -102
  145. package/skill-docs/references/report/report-requirement-analysis-template.md +93 -29
  146. package/skill-docs/references/report/report-self-check-guide.md +164 -164
  147. package/skill-docs/references/report/report-verification-discipline-guide.md +203 -0
  148. package/skill-docs/references/report/safe-partial-save-guide.md +151 -151
  149. package/skill-docs/references/report/shard-freeze-report-guide.md +97 -0
  150. package/skill-docs/references/report/snowflake-id-precision-guide.md +84 -84
  151. package/skill-docs/references/report/spec-build-schema.md +71 -71
  152. package/skill-docs/references/report/targeted-fix-guide.md +155 -97
  153. package/skill-docs/references/report/user-request-scope-only-guide.md +23 -4
  154. package/skill-docs/references/workflow/guide-after-submit-comment.md +58 -0
  155. package/skill-docs/references/workflow/guide-workflow.md +6 -0
@@ -1,258 +1,325 @@
1
- # 公式单元格 API 写入规范
2
-
3
- > **适用场景**:通过 API / 脚本写入报表 workbook(`saveReportWithCacheConfig` + `saveReportDevInfo`),或生成含 Formula 单元格的 ReportProps JSON。
4
- > **权威原则**:设计器手动保存的格式为准;文档与脚本冲突时以 `getReportDevInfo` 回读结果为准。
5
-
6
- ---
7
-
8
- ## 一、问题现象
9
-
10
- 设计器里输入 `=IF(J3=0,0,L3/J3)` 能正常保存并展示,但 **API 脚本写入「相同公式」可能导致设计器整页空白**。
11
-
12
- **根因不是 IF 函数被禁用**,而是持久化字段格式与手动保存不一致:
13
-
14
- | 字段 | 设计器手动保存 ✅ | 脚本 API 常见错误 ❌ |
15
- |------|-------------------|----------------------|
16
- | `cellText` / 顶层 `formula` | `IF(J3=0,0,L3/J3)` **无前导 `=`** | `=IF(J3=0,0,L3/J3)` **带 `=`** |
17
- | `countType` | `count` | `none` |
18
- | `displayType` | `Group` | `List` |
19
- | `groupType` | `null` | `none` |
20
- | `style.formatter` | 空字符串 | `0.00%`、`General` 等 |
21
-
22
- 平台源码(`EdcReportDevDesignServiceImpl`)从 workbook 提取 Formula 时:
23
-
24
- ```java
25
- if (field.getCellType() == Formula) {
26
- field.setCellText(cell.getFormula()); // 原样写入 DB
27
- }
28
- ```
29
-
30
- 设计器保存时会去掉 leading `=`;脚本若 workbook 顶层 `formula` 带 `=`,DB 里 `cellText` 也会带 `=`。
31
- 设计器再次加载时 SpreadJS 可能再补 `=` → 变成 `==IF(...)` 或解析失败 → **整页空白**。
32
-
33
- DB 种子数据同样 **不带 `=`**:`'IFERROR(G5/F5,"--")'`、`'SUM(F5)'`。
34
-
35
- ---
36
-
37
- ## 二、Agent 必遵守则(硬约束)
38
-
39
- 生成或修改含公式单元格的报表时,**必须**同时满足:
40
-
41
- 1. **`cellText` 与 workbook 顶层 `formula` 均不带前导 `=`**
42
- - 设计器输入:`=IF(J3=0,0,L3/J3)`
43
- - API 存储:`IF(J3=0,0,L3/J3)`
44
- - 写入前统一:`bare = formula_expr.lstrip("=")`
45
-
46
- 2. **`countType` 必须为 `count`**(勿用 `none`)
47
-
48
- 3. **`displayType` 必须为 `Group`**(分组报表中的公式格;勿用 `List`)
49
-
50
- 4. **`groupType` 必须为 `null`**(JSON 中写 `null`,勿写 `"none"`)
51
-
52
- 5. **`expandType` 为 `NoExpand`**
53
-
54
- 6. **`style.formatter` 为空字符串**(与手动保存对齐;百分比展示依赖公式结果或后续在设计器设格式,脚本勿擅自写 `0.00%`)
55
-
56
- 7. **父格坐标**:公式格与同级 Field 子格一致,指向根格(如 `leftX=2, leftY=5` 对应 F 列责任人,0-based)
57
-
58
- 8. **双保存**:`saveReportWithCacheConfig` + `saveReportDevInfo`,保存后用 `getReportDevInfo` 回读验证
59
-
60
- 9. **除零保护**:占比/达成率类公式推荐 `IF(分母=0,0,分子/分母)`,与设计器手动写法一致
61
-
62
- ---
63
-
64
- ## 三、workbook 单元格结构(dataTable)
65
-
66
- Formula 单元格在 workbook `dataTable[row][col]` 中的标准结构:
67
-
68
- ```json
69
- {
70
- "formula": "IF(J3=0,0,L3/J3)",
71
- "style": {
72
- "hAlign": 1,
73
- "vAlign": 1,
74
- "formatter": "",
75
- "cellType": {
76
- "typeName": "FormulaCellType",
77
- "editorType": 1,
78
- "cellType": "Formula",
79
- "cellText": "IF(J3=0,0,L3/J3)",
80
- "displayType": "Group",
81
- "countType": "count",
82
- "groupType": null,
83
- "expandType": "NoExpand",
84
- "leftX": 2,
85
- "leftY": 5,
86
- "topX": -1,
87
- "topY": -1,
88
- "refX": -1,
89
- "refY": -1,
90
- "subordination": ["", ""],
91
- "formatterType": [],
92
- "orderType": "no",
93
- "isActiveParent": false,
94
- "arrowType": "None",
95
- "textDirection": "FromLeftToRight",
96
- "allowOverflow": true,
97
- "hiddenContext": "0",
98
- "transSelectOptions": [],
99
- "browserType": [],
100
- "thumbnail": "NoDisplay"
101
- }
102
- },
103
- "tag": { "formatterType": ["common"] }
104
- }
105
- ```
106
-
107
- **注意**:顶层 `formula` 与 `style.cellType.cellText` **内容相同且均无前导 `=`**。
108
-
109
- ---
110
-
111
- ## 四、参考实现 `make_formula()`
112
-
113
- ```python
114
- def make_formula(formula_expr, formatter=""):
115
- """构建 Formula 单元格,格式对齐设计器手动保存。"""
116
- bare = formula_expr.lstrip("=")
117
- ct = {
118
- "allowOverflow": True,
119
- "typeName": "FormulaCellType",
120
- "editorType": 1,
121
- "leftX": ROOT_ROW,
122
- "leftY": ROOT_COL,
123
- "topX": -1,
124
- "topY": -1,
125
- "refX": -1,
126
- "refY": -1,
127
- "expandType": "NoExpand",
128
- "displayType": "Group",
129
- "cellType": "Formula",
130
- "cellText": bare,
131
- "transSelectOptions": [],
132
- "browserType": [],
133
- "thumbnail": "NoDisplay",
134
- "hiddenContext": "0",
135
- "orderType": "no",
136
- "formatterType": [],
137
- "subordination": ["", ""],
138
- "countType": "count",
139
- "groupType": None,
140
- "isActiveParent": False,
141
- "arrowType": "None",
142
- "textDirection": "FromLeftToRight",
143
- }
144
- fmt = formatter if formatter is not None else ""
145
- return {
146
- "style": {"hAlign": 1, "vAlign": 1, "formatter": fmt, "cellType": ct},
147
- "formula": bare,
148
- "tag": {"formatterType": ["common"]},
149
- }
150
- ```
151
-
152
- 项目参考:`本地工作目录` 的 `make_formula()`。
153
-
154
- ---
155
-
156
- ## 五、公式表达式规范
157
-
158
- ### 5.1 单元格引用
159
-
160
- - 使用 Excel A1 风格:`J3`、`L3`、`AA3`
161
- - 行号与数据行一致(通常第 3 行即 `x=2` 的数据行写 `...3`)
162
- - 引用同报表内 Field 汇总格,不引用数据集字段名
163
-
164
- ### 5.2 常用模式
165
-
166
- | 场景 | 设计器输入 | API 存储(无前导 `=`) |
167
- |------|-----------|------------------------|
168
- | 达成率 | `=IF(J3=0,0,L3/J3)` | `IF(J3=0,0,L3/J3)` |
169
- | 业绩占比 | `=IF(L3=0,0,M3/L3)` | `IF(L3=0,0,M3/L3)` |
170
- | 合计 | `=SUM(F4:F10)` | `SUM(F4:F10)` |
171
- | 安全除法 | `=IFERROR(G5/F5,"--")` | `IFERROR(G5/F5,"--")` |
172
-
173
- ### 5.3 IF / IFERROR
174
-
175
- - **IF、IFERROR 均允许**通过 API 写入;此前「IF 导致空白」是格式错误,不是函数限制
176
- - 占比类公式 **推荐** `IF(分母=0,0,...)` 而非裸除法
177
-
178
- ### 5.4 禁止写法
179
-
180
- | 禁止 | 原因 |
181
- |------|------|
182
- | `cellText: "=SUM(A1)"` | 双 `=` 导致 SpreadJS 解析失败 |
183
- | `countType: "none"` | 与手动保存不一致,可能引发展示异常 |
184
- | `displayType: "List"` | 公式格应为 `Group` |
185
- | `groupType: "none"` | 应为 `null` |
186
- | 仅写 `style.cellType.cellText` 不写顶层 `formula` | workbook 与设计器可能不同步 |
187
-
188
- ---
189
-
190
- ## 六、验证清单
191
-
192
- 写入公式后,Agent **必须**执行:
193
-
194
- ### 6.1 `getReportDevInfo` 回读
195
-
196
- 对每个 Formula 格(如 K3 → `x=2, y=10`)检查:
197
-
198
- - [ ] `cellType` 为 `Formula`
199
- - [ ] `cellText` **不以 `=` 开头**
200
- - [ ] `countType` 为 `count`
201
- - [ ] `displayType` 为 `Group`
202
- - [ ] `groupType` 为 `null`
203
- - [ ] 公式内容与预期一致(如 `IF(J3=0,0,L3/J3)`)
204
-
205
- ### 6.2 保存接口
206
-
207
- - [ ] `saveReportWithCacheConfig` 返回 200
208
- - [ ] `saveReportDevInfo` 返回 200
209
- - [ ] `checkSave` 返回 200(可选)
210
-
211
- ### 6.3 设计器 smoke test
212
-
213
- - [ ] 刷新设计器 URL 后页面非空白
214
- - [ ] 公式格可点击编辑,显示为 `=IF(...)`(设计器 UI 会自动加 `=`)
215
- - [ ] 预览/解析后公式列有数值
216
-
217
- ### 6.4 本地预检
218
-
219
- ```bash
220
- python e10-cli report validate <配置文件路径>
221
- ```
222
-
223
- 脚本会对 Formula 单元格检查前导 `=`、`countType`、`displayType` 等(见脚本内 `validate_formula_fields`)。
224
-
225
- ---
226
-
227
- ## 七、与父格 / 跨数据集筛选的关系
228
-
229
- - 公式格 **不参与** subordination / leftFilterField(通常 `subordination: ["", ""]`)
230
- - 公式引用的是 **同报表内已配置好的 Field 汇总格**(如 L3 总业绩、J3 指标)
231
- - 被引用的 Field 格若跨数据集,须先按 `cross-dataset-filter-guide.md` 配好筛选;公式本身不再重复配 fieldKey
232
-
233
- ---
234
-
235
- ## 八、EBU 经营报表示例(37 列)
236
-
237
- | 列 | 字段 | 公式(API 存储,无前导 `=`) |
238
- |----|------|------------------------------|
239
- | K | 达成率 | `IF(J3=0,0,L3/J3)` |
240
- | R | 新客户业绩占比 | `IF(L3=0,0,M3/L3)` |
241
- | S | 纯专项业绩占比 | `IF(L3=0,0,N3/L3)` |
242
- | T | 信创客户业绩占比 | `IF(L3=0,0,O3/L3)` |
243
- | X | 新签合同个数占比 | `IF(V3=0,0,W3/V3)` |
244
- | Z | 纯专项合同个数占比 | `IF(V3=0,0,Y3/V3)` |
245
- | AB | 信创合同个数占比 | `IF(V3=0,0,AA3/V3)` |
246
-
247
- 报表 ID 示例:`1304979556231593985`
248
- 验证脚本:`本地工作目录`
249
-
250
- ---
251
-
252
- ## 九、相关文档
253
-
254
- - `SKILL.md` — 能力一步骤 6.2 公式配置
255
- - `ai-report-workflow.md` §六 — 工作流中的公式规则
256
- - `report-config-schema.md` §2.5 — Formula Schema
257
- - `cross-dataset-filter-guide.md` — 跨数据集筛选(与公式格配合)
258
- - `report-building-guide.md` §8.2 — 占比/同环比为前端 SpreadJS 计算
1
+ # 公式单元格 API 写入规范
2
+
3
+ > **适用场景**:通过 API / 脚本写入报表 workbook(`saveReportWithCacheConfig` + `saveReportDevInfo`),或生成含 Formula 单元格的 ReportProps JSON。
4
+ > **权威原则**:设计器手动保存的格式为准;文档与脚本冲突时以 `getReportDevInfo` 回读结果为准。
5
+
6
+ ---
7
+
8
+ ## 一、问题现象
9
+
10
+ 设计器里输入 `=IF(J3=0,0,L3/J3)` 能正常保存并展示,但 **API 脚本写入「相同公式」可能导致设计器整页空白**。
11
+
12
+ **根因不是 IF 函数被禁用**,而是持久化字段格式与手动保存不一致:
13
+
14
+ | 字段 | 设计器手动保存 ✅ | 脚本 API 常见错误 ❌ |
15
+ |------|-------------------|----------------------|
16
+ | `cellText` / 顶层 `formula` | `IF(J3=0,0,L3/J3)` **无前导 `=`** | `=IF(J3=0,0,L3/J3)` **带 `=`** |
17
+ | `countType` | `count` | `none` |
18
+ | `displayType` | `Group` | `List` |
19
+ | `groupType` | `null` | `none` |
20
+ | `style.formatter` | 空字符串 | `0.00%`、`General` 等 |
21
+
22
+ 平台源码(`EdcReportDevDesignServiceImpl`)从 workbook 提取 Formula 时:
23
+
24
+ ```java
25
+ if (field.getCellType() == Formula) {
26
+ field.setCellText(cell.getFormula()); // 原样写入 DB
27
+ }
28
+ ```
29
+
30
+ 设计器保存时会去掉 leading `=`;脚本若 workbook 顶层 `formula` 带 `=`,DB 里 `cellText` 也会带 `=`。
31
+ 设计器再次加载时 SpreadJS 可能再补 `=` → 变成 `==IF(...)` 或解析失败 → **整页空白**。
32
+
33
+ DB 种子数据同样 **不带 `=`**:`'IFERROR(G5/F5,"--")'`、`'SUM(F5)'`。
34
+
35
+ ---
36
+
37
+ ## 二、Agent 必遵守则(硬约束)
38
+
39
+ 生成或修改含公式单元格的报表时,**必须**同时满足:
40
+
41
+ 1. **`cellText` 与 workbook 顶层 `formula` 均不带前导 `=`**
42
+ - 设计器输入:`=IF(J3=0,0,L3/J3)`
43
+ - API 存储:`IF(J3=0,0,L3/J3)`
44
+ - 写入前统一:`bare = formula_expr.lstrip("=")`
45
+
46
+ 2. **`countType` 必须为 `count`**(勿用 `none`)
47
+
48
+ 3. **`displayType` 必须为 `Group`**(分组报表中的公式格;勿用 `List`)
49
+
50
+ 4. **`groupType` 必须为 `null`**(JSON 中写 `null`,勿写 `"none"`)
51
+
52
+ 5. **`expandType` 为 `NoExpand`**
53
+
54
+ 6. **`style.formatter` 为空字符串**(与手动保存对齐;百分比展示依赖公式结果或后续在设计器设格式,脚本勿擅自写 `0.00%`)
55
+
56
+ 7. **★ 父格坐标(用户规则)**:公式格的父格 **通常与它引用的、参与计算的字段父格保持统一**
57
+ —— 两者指向 **同一个单元格**,即被引用字段区域的 **根格**。判定:
58
+ - 先把每个被引用字段的父格 **沿「左父格链」归一到区域根格**(`B3←A3` → A3;`H3←G3←…←A3` → A3);
59
+ - 归一后 **全部落在同一区域** → 取该 **区域根格**(如 A3)。与「合计行左父格指向最前面文本格」同一口径。
60
+ - 归一后 **分属不同区域(跨源)** → 机器判不了,人工指定。
61
+ - ⚠️ **只引用 1 个字段时也必须归一**:`IFERROR(H3-TODAY(),"")` 里 H3 的直接父格是 G3,但 G3 自己挂在 A3 下,
62
+ 真正随记录扩展的锚点是根格 **A3** —— 直接用 G3 会让同一区域的公式因「引用字段个数不同」得到不同父格,规则自相矛盾。
63
+ 实操:把选定父格的 `leftX/leftY` 原样写到公式格上(0-based;例 `leftX=2, leftY=5` 对应 F 列)。
64
+ 父格不一致的后果:行 / 列扩展时公式格与数据**错位**,或整块公式不随行复制。
65
+ 自查 / 对齐:`e10-cli report formula parent check`(只读,exit 3 = 不一致)· `formula parent align [--apply]`。
66
+
67
+ 8. **双保存**:`saveReportWithCacheConfig` + `saveReportDevInfo`,保存后用 `getReportDevInfo` 回读验证
68
+
69
+ 9. **除零保护**:占比/达成率类公式推荐 `IF(分母=0,0,分子/分母)`,与设计器手动写法一致
70
+
71
+ ---
72
+
73
+ ## 三、workbook 单元格结构(dataTable)
74
+
75
+ Formula 单元格在 workbook `dataTable[row][col]` 中的标准结构:
76
+
77
+ ```json
78
+ {
79
+ "formula": "IF(J3=0,0,L3/J3)",
80
+ "style": {
81
+ "hAlign": 1,
82
+ "vAlign": 1,
83
+ "formatter": "",
84
+ "cellType": {
85
+ "typeName": "FormulaCellType",
86
+ "editorType": 1,
87
+ "cellType": "Formula",
88
+ "cellText": "IF(J3=0,0,L3/J3)",
89
+ "displayType": "Group",
90
+ "countType": "count",
91
+ "groupType": null,
92
+ "expandType": "NoExpand",
93
+ "leftX": 2,
94
+ "leftY": 5,
95
+ "topX": -1,
96
+ "topY": -1,
97
+ "refX": -1,
98
+ "refY": -1,
99
+ "subordination": ["", ""],
100
+ "formatterType": [],
101
+ "orderType": "no",
102
+ "isActiveParent": false,
103
+ "arrowType": "None",
104
+ "textDirection": "FromLeftToRight",
105
+ "allowOverflow": true,
106
+ "hiddenContext": "0",
107
+ "transSelectOptions": [],
108
+ "browserType": [],
109
+ "thumbnail": "NoDisplay"
110
+ }
111
+ },
112
+ "tag": { "formatterType": ["common"] }
113
+ }
114
+ ```
115
+
116
+ **注意**:顶层 `formula` 与 `style.cellType.cellText` **内容相同且均无前导 `=`**。
117
+
118
+ ---
119
+
120
+ ## 四、参考实现 `make_formula()`
121
+
122
+ ```python
123
+ def make_formula(formula_expr, formatter=""):
124
+ """构建 Formula 单元格,格式对齐设计器手动保存。"""
125
+ bare = formula_expr.lstrip("=")
126
+ ct = {
127
+ "allowOverflow": True,
128
+ "typeName": "FormulaCellType",
129
+ "editorType": 1,
130
+ "leftX": ROOT_ROW,
131
+ "leftY": ROOT_COL,
132
+ "topX": -1,
133
+ "topY": -1,
134
+ "refX": -1,
135
+ "refY": -1,
136
+ "expandType": "NoExpand",
137
+ "displayType": "Group",
138
+ "cellType": "Formula",
139
+ "cellText": bare,
140
+ "transSelectOptions": [],
141
+ "browserType": [],
142
+ "thumbnail": "NoDisplay",
143
+ "hiddenContext": "0",
144
+ "orderType": "no",
145
+ "formatterType": [],
146
+ "subordination": ["", ""],
147
+ "countType": "count",
148
+ "groupType": None,
149
+ "isActiveParent": False,
150
+ "arrowType": "None",
151
+ "textDirection": "FromLeftToRight",
152
+ }
153
+ fmt = formatter if formatter is not None else ""
154
+ return {
155
+ "style": {"hAlign": 1, "vAlign": 1, "formatter": fmt, "cellType": ct},
156
+ "formula": bare,
157
+ "tag": {"formatterType": ["common"]},
158
+ }
159
+ ```
160
+
161
+
162
+ ---
163
+
164
+ ## 五、公式表达式规范
165
+
166
+ ### 5.1 单元格引用
167
+
168
+ - 使用 Excel A1 风格:`J3`、`L3`、`AA3`
169
+ - 行号与数据行一致(通常第 3 行即 `x=2` 的数据行写 `...3`)
170
+ - 引用同报表内 Field 汇总格,不引用数据集字段名
171
+
172
+ ### 5.2 常用模式
173
+
174
+ | 场景 | 设计器输入 | API 存储(无前导 `=`) |
175
+ |------|-----------|------------------------|
176
+ | 达成率 | `=IF(J3=0,0,L3/J3)` | `IF(J3=0,0,L3/J3)` |
177
+ | 业绩占比 | `=IF(L3=0,0,M3/L3)` | `IF(L3=0,0,M3/L3)` |
178
+ | 合计 | `=SUM(F4:F10)` | `SUM(F4:F10)` |
179
+ | 安全除法 | `=IFERROR(G5/F5,"--")` | `IFERROR(G5/F5,"--")` |
180
+
181
+ ### 5.3 IF / IFERROR
182
+
183
+ - **IF、IFERROR 均允许**通过 API 写入;此前「IF 导致空白」是格式错误,不是函数限制
184
+ - 占比类公式 **推荐** `IF(分母=0,0,...)` 而非裸除法
185
+
186
+ ### 5.4 禁止写法
187
+
188
+ | 禁止 | 原因 |
189
+ |------|------|
190
+ | `cellText: "=SUM(A1)"` | 双 `=` 导致 SpreadJS 解析失败 |
191
+ | `countType: "none"` | 与手动保存不一致,可能引发展示异常 |
192
+ | `displayType: "List"` | 公式格应为 `Group` |
193
+ | `groupType: "none"` | 应为 `null` |
194
+ | 仅写 `style.cellType.cellText` 不写顶层 `formula` | workbook 与设计器可能不同步 |
195
+
196
+ ---
197
+
198
+ ## 六、验证清单
199
+
200
+ 写入公式后,Agent **必须**执行:
201
+
202
+ ### 6.1 `getReportDevInfo` 回读
203
+
204
+ 对每个 Formula 格(如 K3 → `x=2, y=10`)检查:
205
+
206
+ - [ ] `cellType` 为 `Formula`
207
+ - [ ] `cellText` **不以 `=` 开头**
208
+ - [ ] `countType` 为 `count`
209
+ - [ ] `displayType` 为 `Group`
210
+ - [ ] `groupType` 为 `null`
211
+ - [ ] 公式内容与预期一致(如 `IF(J3=0,0,L3/J3)`)
212
+
213
+ ### 6.2 保存接口
214
+
215
+ - [ ] `saveReportWithCacheConfig` 返回 200
216
+ - [ ] `saveReportDevInfo` 返回 200
217
+ - [ ] `checkSave` 返回 200(可选)
218
+
219
+ ### 6.3 设计器 smoke test
220
+
221
+ - [ ] 刷新设计器 URL 后页面非空白
222
+ - [ ] 公式格可点击编辑,显示为 `=IF(...)`(设计器 UI 会自动加 `=`)
223
+ - [ ] 预览/解析后公式列有数值
224
+
225
+ ### 6.4 本地预检
226
+
227
+ ```bash
228
+ e10-cli report py validate <配置文件路径>
229
+ ```
230
+
231
+ 脚本会对 Formula 单元格检查前导 `=`、`countType`、`displayType` 等(见脚本内 `validate_formula_fields`)。
232
+
233
+ ---
234
+
235
+ ## 七、与父格 / 跨数据集筛选的关系
236
+
237
+ - 公式格 **不参与** subordination / leftFilterField(通常 `subordination: ["", ""]`)
238
+ - ★ **父格一致性(用户规则)**:公式格父格应与其**引用的、参与计算的字段**父格 **统一为同一个单元格**。
239
+ 判定:把被引用字段的父格沿左父格链**归一到区域根格**后 —— **同区域** → 取该根格(如 A3,与合计行口径一致);
240
+ **不同区域(跨源)** → 人工指定。只引用 1 个字段时同样要归一(H3 的直接父格 G3 挂在 A3 下 → 取 A3)。
241
+ 父格不一致会导致扩展时错位、整块不随行复制。
242
+ 只读自查:`e10-cli report formula parent check --report-id <id> --app-id <id>`(解析公式引用 A1 坐标 → 比对 leftX/leftY,不一致 exit 3);
243
+ 批量对齐:`e10-cli report formula parent align --report-id <id> --app-id <id> [--apply]`(走构建态通道,Formula 格存活);
244
+ 定点改:`e10-cli report fix parent --set-parent "x,y=L,T"`(**只改左父格**)。
245
+ - ★★★ **扩展数据带内公式格「四件套」—— 缺一即乱码**(注意点 BUILD-46 / BUILD-49)
246
+ 公式格左父格**必须直接挂「块入口格」**(数据带第一格),**禁止链到前一列公式格** —— 否则扩展实例
247
+ **引用解析错位**:**预览第一行正常(或 `#DIV/0!`)、后续行渲染乱码**。
248
+
249
+ | # | 键 | 必须值 | 缺了会怎样(症状 / 注意点) |
250
+ |---|----|--------|--------------------------|
251
+ | ① | `leftX`/`leftY` | 直接挂**块入口格** | 扩展时公式格与数据错位 / 整块不随行复制 |
252
+ | ② | `refX`/`refY` | `(-1,-1)` | 服务端 int 默认补 **0**,回读常带 `0,0` → 被原样复制 → 后续行渲染乱码(**BUILD-46**) |
253
+ | ③ | `expandType` | `NoExpand`(**禁 `Vertical`**) | `Vertical` 让公式格**自成扩展实例** → 引擎重映射错位(**BUILD-49**) |
254
+ | ④ | `subordination` | `["",""]` | API 构建默认 `[]` → 公式引用区间**横跨另一公式格**时依赖链建不起来 → **浏览器端**解析进隐藏模板区 → `p1900rc1900nt` 乱码(**BUILD-49**) |
255
+
256
+ - **④ 是本次最关键的一条**:设计器写过的公式格**恒为 `["",""]`**;`[]` 只在 API 构建里出现。
257
+ 典型引爆点:公式区间(如 `SUM(AE16:AG16)`)里含**另一个公式格**(AG16)→ `[]` 让展开引擎
258
+ 建不起引用依赖链 → 浏览器端渲染出 `p1900rc1900nt` 之类字面量。
259
+ - **落库后处理路径** = `wb.sheets[*].data.dataTable[行][列].style.cellType`(`typeName=FormulaCellType`,
260
+ **不是** `cells`)。构建层的 `normalizeFormulaCellShape()`(构建态出口)与
261
+ `saveReportDevInfoAuto` 防御层 **R7 / R8** 已自动归位 ①②③④(dry-run 安全)。
262
+ - ⚠️ **只对 Formula 格强制**:`Field` 格的 `subordination` 承载**跨数据集绑定**,绝不能动
263
+ (见 `cross-dataset-filter-guide.md`)。
264
+ - **行号规则**(重申):引用**带自身行** = `x+1`;**跨带引用** = **目标带** `x+1`。
265
+ - **`{r}` 占位符藏在两处**:cell 顶层 `formula` 键 + `style.cellType.cellText`。替换必须
266
+ **两处都做**,且用 `split/join` **全局替换**(只 replace 第一处会留下不一致)。
267
+ - **蓝图 diff 方法论**:渲染异常时,与「用户认可的正常报表」`getReportDevInfo` 回读做**逐字段 diff**
268
+ (`left` / `refX` / `refY` / **`subordination`** / **`style.formatter`**)实证定位,**禁止猜测**。
269
+ ★ diff 键清单**必须含 `subordination` 与 `style`** —— 历次两轮乱码定位正是漏了这两个键才误判「两端等价」(BUILD-53)。
270
+ - 体检 / 修复:`e10-cli report formula parent check|align --report-id <id> --app-id <id> [--apply]`(align 顺带归 `refX/refY=-1`)。
271
+ - ★★ **上父格(topX/topY)—— 另一条轴,别和左父格混**
272
+ 左父格管「行(纵向扩展)」,上父格管「列(横向扩展)」。判据 = **该格是否必须随横向扩展列保持相对位置**。
273
+ - 交叉表里位于「**横向扩展列右侧**」的合计 / 汇总列:上父格应**显式指向本列列头格**。
274
+ 验证(`chl测试002` 交叉表 `1324295299769475074`:A 序号 | B 学院 | C 专业 | D 年级(横向扩展) | E 合计):
275
+ `E2`/`E3` 上父格 = `E1`(即 `--set-top-parent "1,4=0,4"` / `"2,4=0,4"`);
276
+ `D2`/`D3` 上父格 = `D1`(`(0,3)`,跟随年级列)。
277
+ - 纯纵向的格(如 `A2` 序号)上父格保持**默认 `-1,-1`** —— 它不参与横向扩展,加了反而错。
278
+ - 症状:列头被横向扩展推走、合计列留在原地 → 与数据列错位。
279
+ 定点改上父格:`e10-cli report formula top --report-id <id> --app-id <id> --set-top-parent "x,y=topX,topY" [--apply]`
280
+ (走构建态通道、Formula 格存活、落库后逐格回读;`dump cells` 可回看每格 top)。
281
+ 坐标口径:`x` = 行(0 基),`y` = 列(0 基)——即 `A1` 记法 `E2` → `(1,4)`。
282
+ - 公式引用的是 **同报表内已配置好的 Field 汇总格**(如 L3 总业绩、J3 指标)
283
+ - 被引用的 Field 格若跨数据集,须先按 `cross-dataset-filter-guide.md` 配好筛选;公式本身不再重复配 fieldKey
284
+
285
+ ### 7.1 设计器保存的规范化副作用(API 复刻设计器状态时必须对齐)
286
+
287
+ 设计器**每次保存**都会把公式格(以及整数列)归一成下面这套值。API 复刻「设计器写过的格」时,
288
+ **必须产出同样的值** —— 否则 diff 会显示差异,渲染路径也可能不同:
289
+
290
+ | 键 | 设计器归一后的值 | API 常见错值 |
291
+ |----|-----------------|-------------|
292
+ | `groupType` | `null`(JSON 里就是 `null`) | `"none"` 或省略 |
293
+ | `countType` | 公式格 / 整数列 → `none`;计数列 → `count` | 一律 `count` |
294
+ | `subordination` | `["",""]` | `[]` |
295
+ | `style.formatter` | 合法格式串(`General` / `0.00%` / `¥#,##0.00` …) | 短码 `common` / `percent` |
296
+ | `expandType` | `NoExpand` | `Vertical` |
297
+
298
+ > ★ 与 `getReportDevInfo` 回读对照时,**这套键是最容易被漏比的部分**(见 §七 diff 键清单)。
299
+ > 脚本侧统一由 构建层的 **R7 / R8 / R9** 归位。
300
+
301
+ ---
302
+
303
+ ## 八、EBU 经营报表示例(37 列)
304
+
305
+ | 列 | 字段 | 公式(API 存储,无前导 `=`) |
306
+ |----|------|------------------------------|
307
+ | K | 达成率 | `IF(J3=0,0,L3/J3)` |
308
+ | R | 新客户业绩占比 | `IF(L3=0,0,M3/L3)` |
309
+ | S | 纯专项业绩占比 | `IF(L3=0,0,N3/L3)` |
310
+ | T | 信创客户业绩占比 | `IF(L3=0,0,O3/L3)` |
311
+ | X | 新签合同个数占比 | `IF(V3=0,0,W3/V3)` |
312
+ | Z | 纯专项合同个数占比 | `IF(V3=0,0,Y3/V3)` |
313
+ | AB | 信创合同个数占比 | `IF(V3=0,0,AA3/V3)` |
314
+
315
+ 报表 ID 示例:`1304979556231593985`
316
+
317
+ ---
318
+
319
+ ## 九、相关文档
320
+
321
+ - `SKILL.md` — 能力一步骤 6.2 公式配置
322
+ - `ai-report-workflow.md` §六 — 工作流中的公式规则
323
+ - `report-config-schema.md` §2.5 — Formula Schema
324
+ - `cross-dataset-filter-guide.md` — 跨数据集筛选(与公式格配合)
325
+ - `report-building-guide.md` §8.2 — 占比/同环比为前端 SpreadJS 计算