@dazitech/cli 3.0.5 → 3.0.7

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.
@@ -1,401 +1,408 @@
1
- # 节点代码编写指南
2
-
3
- **文档 ID**: `flow/node-code-guide`
4
- **适用**: `项目/<业务名>/流程/flows/<流程名>/节点/<节点名>/code.*`
5
- **前置**: **[流程本地文件规范](./local-files-spec.md)**(必读)、[数据流程项目开发指南](./flow-project-guide.md)、[流程变量系统指南](./variables-guide.md)
6
-
7
- > 设计器「打开代码」依赖 `flow.meta.json` 中该节点的 `dir`+`codeFile`。若打不开,先 `dazi flow project doctor` → `repair-meta`。
8
-
9
- ---
10
-
11
- ## 1. 哪些节点有代码文件
12
-
13
- > 完整 **14 种** 可用 `data.type`(含无代码节点)见 [flows-guide §流程节点组件](./flows-guide.md#流程节点组件)。
14
-
15
- | 业务类型 `data.type` | 本地文件 | 平台存储 |
16
- | -------------------- | ------------------------------ | ---------------------- |
17
- | `sql-query` | `code.sql` | `flow_nodes.code_body` |
18
- | `database-source` | `code.sql` | 同上 |
19
- | `dataspace-source` | `code.sql` | 同上 |
20
- | `python-script` | `code.py` | 同上 |
21
- | `excel-python` | `code.py` | 同上 |
22
- | `condition` | `code.py` | 同上 |
23
- | `data-quality-check` | `code.py`(键 `dqPythonCode`) | 同上 |
24
-
25
- 纯配置节点(如 `database-sink`、`dataspace-sink`、`excel-import`、`delay`、`file-source` 等)**无** `code.*`,只在 `flow.json` 的 `data` 里配置;完整列表见 [flows-guide](./flows-guide.md#流程节点组件)。
26
-
27
- **编辑入口**
28
-
29
- - 资源管理器:右键 `节点/<名>/` → **打开节点代码**
30
- - 设计器:选中节点 → **打开代码文件**
31
- - 直接打开 `code.sql` / `code.py`
32
-
33
- **提交入口**
34
-
35
- - 右键 `code.*` → **提交节点** → `flow node push --node <node_uuid>`
36
- - 或 **提交流程**(批量提交所有脏代码节点)
37
-
38
- ---
39
-
40
- ## 2. 修改代码的标准流程
41
-
42
- ```powershell
43
- # 1. 确定流程目录(推荐绝对路径)
44
- $flowDir = "D:\path\to\dazi-work\项目\<业务名>\流程\flows\MyFlow"
45
-
46
- # 2. 编辑 节点/SQL查询/code.sql 等
47
-
48
- # 3. 查看是否有本地改动
49
- dazi flow project status --dir $flowDir
50
-
51
- # 4. 单节点测试
52
- dazi flow run node-exec --node <node_uuid> --dir $flowDir
53
-
54
- # 5. 提交代码到平台
55
- dazi flow node push --node <node_uuid> --dir $flowDir
56
- ```
57
-
58
- `node_uuid` 在 `flow.meta.json` `nodes.<uuid>`,或设计器属性面板、 `node.info.json` 中查看。
59
- **单节点运行 API 使用语义 `nodeId`(画布 `id` 字段)**,CLI 的 `node-exec` 会用 meta 自动翻译。
60
-
61
- 测试失败时阅读 **`_run/<节点名>.last-error.md`**(含错误分类与修复指引),确认后再交给 AI。
62
-
63
- ---
64
-
65
- ## 3. `sql-query` / `database-source` / `dataspace-source`(SQL)
66
-
67
- **文件**: `code.sql`
68
- **画布配置**(`flow.json` → 节点 `data`):
69
-
70
- | 类型 | 关键字段 |
71
- | ------------------ | ---------------------------------------- |
72
- | `database-source` | `connectionId`、`output_variable_name` |
73
- | `dataspace-source` | `spaceId`、`output_variable_name` |
74
- | `sql-query` | `output_variable_name`(消费上游表变量) |
75
-
76
- > **变量用法**:`sql-query` 在 SQL 里把 **上游 `output_variable_name`** 当作 **表名**;`database-source` / `dataspace-source` 只 **产出** 变量、不消费 Run 内变量。详见 [流程变量系统指南 §6.1–6.2](./variables-guide.md#61-sql-queryduckdb-内存-sql)。
77
-
78
- **约定**
79
-
80
- - SQL 写在 **`code.sql`**,不要塞进 `flow.json` `data.sql`(pull 后会剥离到文件)。
81
- - `connectionId` 为 **`ads_connections` 的字符串 id**,用 `dazi-flow source list` 核对。
82
- - `spaceId` 为 **`ads_dataspaces` 的 id**,用 `dazi-flow dataspace list` 核对。
83
- - 执行结果以 **`output_variable_name`** 为名写入调试 Run(表变量)。
84
-
85
- **模板(database-source:外部库 → 表变量)**
86
-
87
- ```sql
88
- -- output_variable_name = dim_product
89
- SELECT product_id, product_name, category
90
- FROM dim_product
91
- WHERE is_active = 1
92
- LIMIT 50000;
93
- ```
94
-
95
- **模板(dataspace-source:数据空间 DuckDB/ClickHouse 表变量)**
96
-
97
- ```sql
98
- -- spaceId flow.json;SQL 针对空间内已注册表
99
- -- output_variable_name = sales_raw
100
- SELECT *
101
- FROM sales_fact
102
- WHERE dt >= '2025-01-01'
103
- LIMIT 100000;
104
- ```
105
-
106
- **模板(sql-query:消费上游表变量 新表变量)**
107
-
108
- ```sql
109
- -- 上游 output_variable_name = excel_raw
110
- -- 本节点 output_variable_name = sales_agg
111
- SELECT
112
- 产品类别,
113
- SUM(销售金额) AS 合计金额
114
- FROM excel_raw
115
- GROUP BY 产品类别;
116
- ```
117
-
118
- **多表 JOIN(变量名即表名)**
119
-
120
- ```sql
121
- -- 上游已产出 orders、customers
122
- SELECT o.order_id, c.customer_name, o.amount
123
- FROM orders o
124
- JOIN customers c ON o.customer_id = c.customer_id;
125
- ```
126
-
127
- **测试**: 右键 → 测试运行节点;成功后用设计器 📊 或 `flow variable pull --name <output_variable_name>` 查看结果。
128
-
129
- ---
130
-
131
- ## 4. `python-script`(表 → 表)
132
-
133
- **文件**: `code.py`
134
- **画布**: 配置 **`output_variable_name`**(主输出表变量名)。
135
-
136
- > **变量用法**:单节点测试用 **`get_variable('上游 output_variable_name')`**;整图跑时入边 **`df`** 可能有值。完整说明与多表示例见 [流程变量系统指南 §6.3](./variables-guide.md#63-python-script最常用)。
137
-
138
- **运行时注入**
139
-
140
- | 符号 | 说明 |
141
- | ---------------------------- | -------------------------------------------------- |
142
- | `pd` | pandas |
143
- | `df` | 来自入边 Parquet(整图跑时通常非空) |
144
- | `get_variable("名")` | 从调试 Run 读上游表(**单节点测试时优先用这个**) |
145
- | `set_table_output(name, df)` | 多表输出 |
146
- | `result_df` | 主输出 DataFrame(与 `output_variable_name` 对应) |
147
- | `output.print(...)` | 运行日志(**勿用裸 `print`**) |
148
-
149
- **单节点测试要点**
150
-
151
- 整图运行时 `df` 可能有值;**单节点调试**时常无入边 Parquet,`df` 为空,应:
152
-
153
- ```python
154
- # -*- coding: utf-8 -*-
155
- # 上游 sql-query 的 output_variable_name = sales_agg
156
- # 本节点 output_variable_name = py_result
157
- import pandas as pd
158
-
159
- output.print("[python-script] 开始")
160
-
161
- df = get_variable("sales_agg") # 变量名须与上游画布配置完全一致
162
- output.print(f"输入 shape={df.shape}, columns={list(df.columns)}")
163
-
164
- result_df = df.groupby("产品类别", as_index=False)["合计金额"].sum()
165
- output.print(f"输出 shape={result_df.shape}")
166
- ```
167
-
168
- **多表 + 入边回退**
169
-
170
- ```python
171
- import pandas as pd
172
-
173
- if df is None or df.empty:
174
- df = get_variable("sales_agg")
175
- ref = get_variable("dim_product")
176
- result_df = df.merge(ref, on="product_id", how="left")
177
- set_table_output("merged_preview", result_df.head(100)) # 可选附加表
178
- ```
179
-
180
- **日志规范**(与平台约定一致)
181
-
182
- - 开始 / 输入信息 / 关键变换 / 输出结果 / 结束 均用 `output.print`
183
- - `except` 里先 `output.print` `raise`
184
-
185
- ---
186
-
187
- ## 5. `excel-python`(Excel 开发 · **默认首选**)
188
-
189
- > **选型**:文件上传管理中的 **`.xlsx` / `.xls`**(有 `managed_file_id`)**优先本节点**,**禁止**用 `file-source`(不解析 Excel)。仅极简单单 Sheet 零代码时才考虑 `excel-import`。
190
-
191
- **文件**: `code.py`
192
- **画布**(`flow.json` → 节点 `data`,**不是写在 code.py 里**):
193
-
194
- | 字段 | 说明 |
195
- | -------------------------- | ------------------------------------------------------------------------------------------- |
196
- | **`managed_file_id`** | 平台「文件上传管理」登记的 **UUID**(见 `资源/files/<名>_<id8>/文件信息.json` `file_id`) |
197
- | **`output_variable_name`** | 主输出表变量名;**必须与** `set_table_output` 第一个参数 **完全一致** |
198
-
199
- ### 分工(AI 必守)
200
-
201
- | 谁配置 | 配什么 | 禁止 |
202
- | ------------------------ | ----------------------------------- | -------------------------------------------------------------- |
203
- | **`flow.json` / 设计器** | `managed_file_id` = UUID | 不要用文件名、相对路径、data upload `id` |
204
- | **`code.py`** | 用 **`excel_source_path`** 读 Excel | 不要 `open("xxx.xlsx")`、`pd.read_excel("D:/...")`、不要猜路径 |
205
-
206
- 引擎根据 `managed_file_id` 查库得到物理路径,执行前注入:
207
-
208
- | 变量 | 说明 |
209
- | ----------------------------- | ----------------------------------------------------------- |
210
- | **`excel_source_path`** | 服务端 Excel **绝对路径**(代码里唯一正确的读文件方式) |
211
- | **`excel_original_filename`** | 登记显示名(仅用于 `output.print` 日志) |
212
- | `pd` | pandas |
213
- | `set_table_output(name, df)` | 产出表变量(**必须**调用,主表名 = `output_variable_name`) |
214
- | `output.print(...)` | 运行日志 |
215
-
216
- **获取 `managed_file_id`**
217
-
218
- 1. 侧栏 **数据资源 文件上传管理** → 拉取到 `资源/files/.../文件信息.json`
219
- 2. 或 CLI:`dazi flow managed-files list` / `managed-files pull --file-id <uuid>`
220
-
221
- **`flow.json` 示例(画布配置)**
222
-
223
- ```json
224
- {
225
- "id": "n-excel-1",
226
- "type": "custom",
227
- "data": {
228
- "label": "Excel解析",
229
- "type": "excel-python",
230
- "managed_file_id": "51853ede-9e6b-4a3a-aa7a-060cbead0862",
231
- "output_variable_name": "成本统计报表"
232
- }
233
- }
234
- ```
235
-
236
- > `managed_file_id` 是 **UUID 字符串**,与 `Excel成本报表08.xlsx` 等文件名 **无关**;文件名只出现在注释/`excel_original_filename` 日志中。
237
-
238
- ---
239
-
240
- ### 示例 A:单 Sheet、复杂表头(推荐模板)
241
-
242
- 对应真实流程「成本报表数据08」:表头不在第 1 行时用 `header=None` + `skiprows` + `names`。
243
-
244
- ```python
245
- # -*- coding: utf-8 -*-
246
- # 画布:managed_file_id = <UUID>(见 flow.json,勿在代码里写文件名)
247
- # 画布:output_variable_name = 成本统计报表
248
- import pandas as pd
249
-
250
- OUTPUT_VAR = "成本统计报表" # 必须与 output_variable_name 一致
251
- DATA_START_ROW = 4 # 0-based,Excel 第 5 行起为数据
252
-
253
- COLUMN_NAMES = [
254
- "基本信息_组织", "基本信息_年度", "基本信息_月度", "基本信息_项目",
255
- "成本项_管理费", "成本项_间接成本", "成本项_税金",
256
- # ... 按实际列继续
257
- ]
258
-
259
- output.print("[excel-python] 开始")
260
- output.print(f"source={excel_original_filename or excel_source_path}")
261
-
262
- # 正确:读引擎注入的路径
263
- raw = pd.read_excel(
264
- excel_source_path,
265
- sheet_name="Sheet1",
266
- header=None,
267
- skiprows=DATA_START_ROW,
268
- usecols="A:P",
269
- names=COLUMN_NAMES,
270
- )
271
- table = raw.dropna(how="all")
272
-
273
- output.print(f"{OUTPUT_VAR} shape={table.shape}")
274
- set_table_output(OUTPUT_VAR, table) # 主输出,名称必须与 output_variable_name 一致
275
- output.print("[excel-python] 完成")
276
- ```
277
-
278
- ---
279
-
280
- ### 示例 B:多 Sheet 产出多张表变量
281
-
282
- 对应 VS-flow0:一个 Excel 解析出 `销售表`、`产品表`、`规格表`。
283
-
284
- ```python
285
- # -*- coding: utf-8 -*-
286
- # 画布:managed_file_id = <UUID>
287
- # 画布:output_variable_name = 销售表(主输出,须 set_table_output 同名)
288
- import pandas as pd
289
-
290
- SHEETS = {
291
- "销售表": {"sheet_name": "销售表", "header": 0, "usecols": "A:H"},
292
- "产品表": {"sheet_name": "产品表", "header": 1, "usecols": "A:C"},
293
- "规格表": {"sheet_name": "规格表", "header": 1, "usecols": "A:C"},
294
- }
295
-
296
- output.print("[excel-python] 开始")
297
- output.print(f"source={excel_original_filename or excel_source_path}")
298
-
299
- for var_name, kwargs in SHEETS.items():
300
- raw = pd.read_excel(excel_source_path, **kwargs) # 只用 excel_source_path
301
- table = raw.dropna(how="all")
302
- output.print(f"{var_name} shape={table.shape}")
303
- set_table_output(var_name, table)
304
-
305
- output.print("[excel-python] 完成")
306
- ```
307
-
308
- 下游 `sql-query` 可直接 `FROM 销售表 JOIN 产品表 ...`。
309
-
310
- ---
311
-
312
- ### 反例(AI 禁止)
313
-
314
- ```python
315
- # 错误:臆造本地/相对路径
316
- pd.read_excel("Excel成本报表08.xlsx")
317
- pd.read_excel("./uploads/xxx.xlsx")
318
- pd.read_excel(r"D:\src2025\...\成本报表.xlsx")
319
-
320
- # ❌ 错误:在 code.py 里写 managed_file_id 或 open 文件
321
- open(managed_file_id)
322
-
323
- # ❌ 错误:主输出未 set_table_output,或名称与 output_variable_name 不一致
324
- result_df = pd.read_excel(excel_source_path)
325
- # 缺少 set_table_output("与画布同名", result_df)
326
- ```
327
-
328
- ---
329
-
330
- ### 提交与测试
331
-
332
- 1. **`managed_file_id` / `output_variable_name`** → `project push --canvas`
333
- 2. 改 **`code.py`** → `node push` → `node-exec` 或整流程 `flow-exec`
334
- 3. 多表时单节点测试可能只有主表;全表变量用 **`flow-exec --type debug`** 后 `variable sync`
335
-
336
- 详见 [flow-project-guide §6.1 文件上传管理](./flow-project-guide.md#61-文件上传管理excel-等流程数据源)、[流程开发最佳实践 §6.1](./流程开发最佳实践-VS-flow0案例.md#61-excel-多表解析节选)。
337
-
338
- ---
339
-
340
- ## 6. `condition`(条件分支)
341
-
342
- **文件**: `code.py`
343
- 逻辑脚本决定分支;使用 `output.print` 记录判断依据。测试方式同 `python-script`。
344
-
345
- ---
346
-
347
- ## 7. `data-quality-check`(数据质量)
348
-
349
- **文件**: `code.py`(平台键 `dqPythonCode`)
350
-
351
- **注入**
352
-
353
- | 符号 | 说明 |
354
- | -------------------------------- | ------------------------------ |
355
- | `df` | 入边 Parquet;单节点时可能为空 |
356
- | `get_variable("名")` | 调试 Run 中的附加表 |
357
- | `quality_config` | 与画布 `qualityConfig` 对齐 |
358
- | `set_scalar_output(name, value)` | 综合分等标量 |
359
- | `result_df` | 质检报告表 |
360
-
361
- **推荐**:`df.empty` 时用 `get_variable` 补主表(变量名与 `attached_variables` / 上游输出一致)。
362
-
363
- 规则从 `quality_config["rules"]` 读取,避免魔法数。端到端质检案例见 [流程开发最佳实践(VS-flow0 案例)](./流程开发最佳实践-VS-flow0案例.md)。
364
-
365
- ---
366
-
367
- ## 8. 画布配置 vs 代码分工
368
-
369
- | 改什么 | 改哪里 | 怎么提交 |
370
- | ---------------------------------------- | ------------------------ | ----------------------- |
371
- | SQL / Python 正文 | `code.sql` / `code.py` | `node push` |
372
- | 连线、坐标、节点增删 | `flow.json`(设计器) | `project push --canvas` |
373
- | connectionId、表名、output_variable_name | 设计器属性 → `flow.json` | `project push --canvas` |
374
-
375
- **不要**在 `flow.json` 里硬塞大段 `pythonCode`/`sql`:pull 会剥离到 `code.*`,push 代码走 `flow-nodes` 接口。
376
-
377
- ---
378
-
379
- ## 9. 查看输出变量
380
-
381
- 变量模型、`debug_run_id`、本地 **`变量/<名>.json`** 详见 **[流程变量系统指南](./variables-guide.md)**。
382
-
383
- ```powershell
384
- # 拉取单个变量(schema + 前 10 行)到 变量/<name>.json
385
- dazi flow variable pull --name sales_df --dir .
386
-
387
- # 同步全部调试变量
388
- dazi flow variable sync --dir .
389
- ```
390
-
391
- 设计器:选中节点 属性 **`output_variable_name`** 点击 **📊**。
392
-
393
- ---
394
-
395
- ## 10. AI 协作建议
396
-
397
- 1. 让 AI 阅读 **`code.*`** + **`flow.json` 中该节点 `data`** + 失败时的 **`_run/*.last-error.md`**
398
- 2. **Agent 模式**(用户委托改流程):AI 应主动执行「改 `node-exec`/`flow-exec` 读 `last-error.md` 再改 → 再跑」循环(默认最多 3 轮),详见流程目录 **`快速启动_<流程名>.md` §AI 自主运行与改错闭环** 或提示词 **`flow/run-fix-loop`**
399
- 3. **对话模式**(用户仅粘贴错误):用户确认后再改代码 → 测试 → `node push`
400
- 4. 需要查上游表结构时,先 **运行上游** 或 **`variable pull`**,把 `变量/<名>.json` 交给 AI
401
- 5. MCP:`dazi-flow mcp serve`(工具含 `flow_run_node`、`flow_node_get_code`、`flow_node_set_code` 等,写操作需 `--allow-write`)
1
+ # 节点代码编写指南
2
+
3
+ **文档 ID**: `flow/node-code-guide`
4
+ **适用**: `项目/<业务名>/流程/flows/<流程名>/节点/<节点名>/code.*`
5
+ **前置**: **[流程本地文件规范](./local-files-spec.md)**(必读)、[数据流程项目开发指南](./flow-project-guide.md)、[流程变量系统指南](./variables-guide.md)
6
+
7
+ > 设计器「打开代码」依赖 `flow.meta.json` 中该节点的 `dir`+`codeFile`。若打不开,先 `dazi flow project doctor` → `repair-meta`。
8
+
9
+ ---
10
+
11
+ ## 1. 哪些节点有代码文件
12
+
13
+ > 完整 **14 种** 可用 `data.type`(含无代码节点)见 [flows-guide §流程节点组件](./flows-guide.md#流程节点组件)。
14
+
15
+ | 业务类型 `data.type` | 本地文件 | 平台存储 |
16
+ | -------------------- | ------------------------------ | ---------------------- |
17
+ | `sql-query` | `code.sql` | `flow_nodes.code_body` |
18
+ | `database-source` | `code.sql` | 同上 |
19
+ | `dataspace-source` | `code.sql` | 同上 |
20
+ | `python-script` | `code.py` | 同上 |
21
+ | `excel-python` | `code.py` | 同上 |
22
+ | `condition` | `code.py` | 同上 |
23
+ | `data-quality-check` | `code.py`(键 `dqPythonCode`) | 同上 |
24
+
25
+ 纯配置节点(如 `database-sink`、`dataspace-sink`、`excel-import`、`delay`、`file-source` 等)**无** `code.*`,只在 `flow.json` 的 `data` 里配置;完整列表见 [flows-guide](./flows-guide.md#流程节点组件)。
26
+
27
+ **编辑入口**
28
+
29
+ - 资源管理器:右键 `节点/<名>/` → **打开节点代码**
30
+ - 设计器:选中节点 → **打开代码文件**
31
+ - 直接打开 `code.sql` / `code.py`
32
+
33
+ **提交入口**
34
+
35
+ - 右键 `code.*` → **提交节点** → `flow node push --node <node_uuid>`
36
+ - 或 **提交流程**(批量提交所有脏代码节点)
37
+
38
+ ---
39
+
40
+ ## 2. 修改代码的标准流程
41
+
42
+ ```powershell
43
+ # 1. 确定流程目录(推荐绝对路径)
44
+ $flowDir = "D:\path\to\dazi-work\项目\<业务名>\流程\flows\MyFlow"
45
+
46
+ # 2. 编辑 节点/SQL查询/code.sql 等
47
+
48
+ # 3. 查看是否有本地改动
49
+ dazi flow project status --dir $flowDir
50
+
51
+ # 4. 提交代码到平台(必须先于测试;node-exec 跑的是平台已 push 的代码)
52
+ dazi flow node push --node <node_uuid> --dir $flowDir
53
+
54
+ # 5. 单节点测试
55
+ dazi flow run node-exec --node <node_uuid> --dir $flowDir
56
+
57
+ # 6. 若有 output_variable_name:核对输出变量(不能仅凭 node-exec 退出码)
58
+ dazi flow variable pull --name <output_variable_name> --dir $flowDir
59
+ ```
60
+
61
+ `node_uuid` 在 `flow.meta.json` → `nodes.<uuid>`,或设计器属性面板、 `node.info.json` 中查看。
62
+ **单节点运行 API 使用语义 `nodeId`(画布 `id` 字段)**,CLI 的 `node-exec` 会用 meta 自动翻译。
63
+
64
+ > **勿先测后 push**:未 `node push` 时 `node-exec` 仍执行平台旧代码,会造成「本地已改但通过/仍报错」的假象。
65
+
66
+ **成功判据**:`node push` 成功 → `node-exec` 的 JSON `success: true` →(有输出变量时)`variable pull` 后 `变量/<名>.json` 为 ready 且数据合理。
67
+
68
+ 测试失败时阅读 **`_run/<节点名>.last-error.md`**(含错误分类与修复指引),确认后再交给 AI。
69
+
70
+ ---
71
+
72
+ ## 3. `sql-query` / `database-source` / `dataspace-source`(SQL)
73
+
74
+ **文件**: `code.sql`
75
+ **画布配置**(`flow.json` → 节点 `data`):
76
+
77
+ | 类型 | 关键字段 |
78
+ | ------------------ | ---------------------------------------- |
79
+ | `database-source` | `connectionId`、`output_variable_name` |
80
+ | `dataspace-source` | `spaceId`、`output_variable_name` |
81
+ | `sql-query` | `output_variable_name`(消费上游表变量) |
82
+
83
+ > **变量用法**:`sql-query` SQL 里把 **上游 `output_variable_name`** 当作 **表名**;`database-source` / `dataspace-source` 只 **产出** 变量、不消费 Run 内变量。详见 [流程变量系统指南 §6.1–6.2](./variables-guide.md#61-sql-queryduckdb-内存-sql)。
84
+
85
+ **约定**
86
+
87
+ - SQL 写在 **`code.sql`**,不要塞进 `flow.json` 的 `data.sql`(pull 后会剥离到文件)。
88
+ - `connectionId` **`ads_connections` 的字符串 id**,用 `dazi-flow source list` 核对。
89
+ - `spaceId` **`ads_dataspaces` 的 id**,用 `dazi-flow dataspace list` 核对。
90
+ - 执行结果以 **`output_variable_name`** 为名写入调试 Run(表变量)。
91
+
92
+ **模板(database-source:外部库 → 表变量)**
93
+
94
+ ```sql
95
+ -- output_variable_name = dim_product
96
+ SELECT product_id, product_name, category
97
+ FROM dim_product
98
+ WHERE is_active = 1
99
+ LIMIT 50000;
100
+ ```
101
+
102
+ **模板(dataspace-source:数据空间 DuckDB/ClickHouse 表变量)**
103
+
104
+ ```sql
105
+ -- spaceId 在 flow.json;SQL 针对空间内已注册表
106
+ -- output_variable_name = sales_raw
107
+ SELECT *
108
+ FROM sales_fact
109
+ WHERE dt >= '2025-01-01'
110
+ LIMIT 100000;
111
+ ```
112
+
113
+ **模板(sql-query:消费上游表变量 新表变量)**
114
+
115
+ ```sql
116
+ -- 上游 output_variable_name = excel_raw
117
+ -- 本节点 output_variable_name = sales_agg
118
+ SELECT
119
+ 产品类别,
120
+ SUM(销售金额) AS 合计金额
121
+ FROM excel_raw
122
+ GROUP BY 产品类别;
123
+ ```
124
+
125
+ **多表 JOIN(变量名即表名)**
126
+
127
+ ```sql
128
+ -- 上游已产出 orders、customers
129
+ SELECT o.order_id, c.customer_name, o.amount
130
+ FROM orders o
131
+ JOIN customers c ON o.customer_id = c.customer_id;
132
+ ```
133
+
134
+ **测试**: 右键 → 测试运行节点;成功后用设计器 📊 或 `flow variable pull --name <output_variable_name>` 查看结果。
135
+
136
+ ---
137
+
138
+ ## 4. `python-script`(表 → 表)
139
+
140
+ **文件**: `code.py`
141
+ **画布**: 配置 **`output_variable_name`**(主输出表变量名)。
142
+
143
+ > **变量用法**:单节点测试用 **`get_variable('上游 output_variable_name')`**;整图跑时入边 **`df`** 可能有值。完整说明与多表示例见 [流程变量系统指南 §6.3](./variables-guide.md#63-python-script最常用)。
144
+
145
+ **运行时注入**
146
+
147
+ | 符号 | 说明 |
148
+ | ---------------------------- | -------------------------------------------------- |
149
+ | `pd` | pandas |
150
+ | `df` | 来自入边 Parquet(整图跑时通常非空) |
151
+ | `get_variable("名")` | 从调试 Run 读上游表(**单节点测试时优先用这个**) |
152
+ | `set_table_output(name, df)` | 多表输出 |
153
+ | `result_df` | 主输出 DataFrame(与 `output_variable_name` 对应) |
154
+ | `output.print(...)` | 运行日志(**勿用裸 `print`**) |
155
+
156
+ **单节点测试要点**
157
+
158
+ 整图运行时 `df` 可能有值;**单节点调试**时常无入边 Parquet,`df` 为空,应:
159
+
160
+ ```python
161
+ # -*- coding: utf-8 -*-
162
+ # 上游 sql-query 的 output_variable_name = sales_agg
163
+ # 本节点 output_variable_name = py_result
164
+ import pandas as pd
165
+
166
+ output.print("[python-script] 开始")
167
+
168
+ df = get_variable("sales_agg") # 变量名须与上游画布配置完全一致
169
+ output.print(f"输入 shape={df.shape}, columns={list(df.columns)}")
170
+
171
+ result_df = df.groupby("产品类别", as_index=False)["合计金额"].sum()
172
+ output.print(f"输出 shape={result_df.shape}")
173
+ ```
174
+
175
+ **多表 + 入边回退**
176
+
177
+ ```python
178
+ import pandas as pd
179
+
180
+ if df is None or df.empty:
181
+ df = get_variable("sales_agg")
182
+ ref = get_variable("dim_product")
183
+ result_df = df.merge(ref, on="product_id", how="left")
184
+ set_table_output("merged_preview", result_df.head(100)) # 可选附加表
185
+ ```
186
+
187
+ **日志规范**(与平台约定一致)
188
+
189
+ - 开始 / 输入信息 / 关键变换 / 输出结果 / 结束 均用 `output.print`
190
+ - `except` 里先 `output.print` 再 `raise`
191
+
192
+ ---
193
+
194
+ ## 5. `excel-python`(Excel 开发 · **默认首选**)
195
+
196
+ > **选型**:文件上传管理中的 **`.xlsx` / `.xls`**(有 `managed_file_id`)**优先本节点**,**禁止**用 `file-source`(不解析 Excel)。仅极简单单 Sheet 零代码时才考虑 `excel-import`。
197
+
198
+ **文件**: `code.py`
199
+ **画布**(`flow.json` 节点 `data`,**不是写在 code.py 里**):
200
+
201
+ | 字段 | 说明 |
202
+ | -------------------------- | ------------------------------------------------------------------------------------------- |
203
+ | **`managed_file_id`** | 平台「文件上传管理」登记的 **UUID**(见 `资源/files/<名>_<id8>/文件信息.json` `file_id`) |
204
+ | **`output_variable_name`** | 主输出表变量名;**必须与** `set_table_output` 第一个参数 **完全一致** |
205
+
206
+ ### 分工(AI 必守)
207
+
208
+ | 谁配置 | 配什么 | 禁止 |
209
+ | ------------------------ | ----------------------------------- | -------------------------------------------------------------- |
210
+ | **`flow.json` / 设计器** | `managed_file_id` = UUID | 不要用文件名、相对路径、data upload 的 `id` |
211
+ | **`code.py`** | 用 **`excel_source_path`** 读 Excel | 不要 `open("xxx.xlsx")`、`pd.read_excel("D:/...")`、不要猜路径 |
212
+
213
+ 引擎根据 `managed_file_id` 查库得到物理路径,执行前注入:
214
+
215
+ | 变量 | 说明 |
216
+ | ----------------------------- | ----------------------------------------------------------- |
217
+ | **`excel_source_path`** | 服务端 Excel **绝对路径**(代码里唯一正确的读文件方式) |
218
+ | **`excel_original_filename`** | 登记显示名(仅用于 `output.print` 日志) |
219
+ | `pd` | pandas |
220
+ | `set_table_output(name, df)` | 产出表变量(**必须**调用,主表名 = `output_variable_name`) |
221
+ | `output.print(...)` | 运行日志 |
222
+
223
+ **获取 `managed_file_id`**
224
+
225
+ 1. 侧栏 **数据资源 → 文件上传管理** → 拉取到 `资源/files/.../文件信息.json`
226
+ 2. 或 CLI:`dazi flow managed-files list` / `managed-files pull --file-id <uuid>`
227
+
228
+ **`flow.json` 示例(画布配置)**
229
+
230
+ ```json
231
+ {
232
+ "id": "n-excel-1",
233
+ "type": "custom",
234
+ "data": {
235
+ "label": "Excel解析",
236
+ "type": "excel-python",
237
+ "managed_file_id": "51853ede-9e6b-4a3a-aa7a-060cbead0862",
238
+ "output_variable_name": "成本统计报表"
239
+ }
240
+ }
241
+ ```
242
+
243
+ > `managed_file_id` 是 **UUID 字符串**,与 `Excel成本报表08.xlsx` 等文件名 **无关**;文件名只出现在注释/`excel_original_filename` 日志中。
244
+
245
+ ---
246
+
247
+ ### 示例 A:单 Sheet、复杂表头(推荐模板)
248
+
249
+ 对应真实流程「成本报表数据08」:表头不在第 1 行时用 `header=None` + `skiprows` + `names`。
250
+
251
+ ```python
252
+ # -*- coding: utf-8 -*-
253
+ # 画布:managed_file_id = <UUID>(见 flow.json,勿在代码里写文件名)
254
+ # 画布:output_variable_name = 成本统计报表
255
+ import pandas as pd
256
+
257
+ OUTPUT_VAR = "成本统计报表" # 必须与 output_variable_name 一致
258
+ DATA_START_ROW = 4 # 0-based,Excel 第 5 行起为数据
259
+
260
+ COLUMN_NAMES = [
261
+ "基本信息_组织", "基本信息_年度", "基本信息_月度", "基本信息_项目",
262
+ "成本项_管理费", "成本项_间接成本", "成本项_税金",
263
+ # ... 按实际列继续
264
+ ]
265
+
266
+ output.print("[excel-python] 开始")
267
+ output.print(f"source={excel_original_filename or excel_source_path}")
268
+
269
+ # ✅ 正确:读引擎注入的路径
270
+ raw = pd.read_excel(
271
+ excel_source_path,
272
+ sheet_name="Sheet1",
273
+ header=None,
274
+ skiprows=DATA_START_ROW,
275
+ usecols="A:P",
276
+ names=COLUMN_NAMES,
277
+ )
278
+ table = raw.dropna(how="all")
279
+
280
+ output.print(f"{OUTPUT_VAR} shape={table.shape}")
281
+ set_table_output(OUTPUT_VAR, table) # 主输出,名称必须与 output_variable_name 一致
282
+ output.print("[excel-python] 完成")
283
+ ```
284
+
285
+ ---
286
+
287
+ ### 示例 B:多 Sheet 产出多张表变量
288
+
289
+ 对应 VS-flow0:一个 Excel 解析出 `销售表`、`产品表`、`规格表`。
290
+
291
+ ```python
292
+ # -*- coding: utf-8 -*-
293
+ # 画布:managed_file_id = <UUID>
294
+ # 画布:output_variable_name = 销售表(主输出,须 set_table_output 同名)
295
+ import pandas as pd
296
+
297
+ SHEETS = {
298
+ "销售表": {"sheet_name": "销售表", "header": 0, "usecols": "A:H"},
299
+ "产品表": {"sheet_name": "产品表", "header": 1, "usecols": "A:C"},
300
+ "规格表": {"sheet_name": "规格表", "header": 1, "usecols": "A:C"},
301
+ }
302
+
303
+ output.print("[excel-python] 开始")
304
+ output.print(f"source={excel_original_filename or excel_source_path}")
305
+
306
+ for var_name, kwargs in SHEETS.items():
307
+ raw = pd.read_excel(excel_source_path, **kwargs) # ✅ 只用 excel_source_path
308
+ table = raw.dropna(how="all")
309
+ output.print(f"{var_name} shape={table.shape}")
310
+ set_table_output(var_name, table)
311
+
312
+ output.print("[excel-python] 完成")
313
+ ```
314
+
315
+ 下游 `sql-query` 可直接 `FROM 销售表 JOIN 产品表 ...`。
316
+
317
+ ---
318
+
319
+ ### 反例(AI 禁止)
320
+
321
+ ```python
322
+ # ❌ 错误:臆造本地/相对路径
323
+ pd.read_excel("Excel成本报表08.xlsx")
324
+ pd.read_excel("./uploads/xxx.xlsx")
325
+ pd.read_excel(r"D:\src2025\...\成本报表.xlsx")
326
+
327
+ # ❌ 错误:在 code.py 里写 managed_file_id 或 open 文件
328
+ open(managed_file_id)
329
+
330
+ # ❌ 错误:主输出未 set_table_output,或名称与 output_variable_name 不一致
331
+ result_df = pd.read_excel(excel_source_path)
332
+ # 缺少 set_table_output("与画布同名", result_df)
333
+ ```
334
+
335
+ ---
336
+
337
+ ### 提交与测试
338
+
339
+ 1. 改 **`managed_file_id` / `output_variable_name`** → `project push --canvas`
340
+ 2. 改 **`code.py`** `node push` → `node-exec` 或整流程 `flow-exec`
341
+ 3. 多表时单节点测试可能只有主表;全表变量用 **`flow-exec --type debug`** 后 `variable sync`
342
+
343
+ 详见 [flow-project-guide §6.1 文件上传管理](./flow-project-guide.md#61-文件上传管理excel-等流程数据源)、[流程开发最佳实践 §6.1](./流程开发最佳实践-VS-flow0案例.md#61-excel-多表解析节选)。
344
+
345
+ ---
346
+
347
+ ## 6. `condition`(条件分支)
348
+
349
+ **文件**: `code.py`
350
+ 逻辑脚本决定分支;使用 `output.print` 记录判断依据。测试方式同 `python-script`。
351
+
352
+ ---
353
+
354
+ ## 7. `data-quality-check`(数据质量)
355
+
356
+ **文件**: `code.py`(平台键 `dqPythonCode`)
357
+
358
+ **注入**
359
+
360
+ | 符号 | 说明 |
361
+ | -------------------------------- | ------------------------------ |
362
+ | `df` | 入边 Parquet;单节点时可能为空 |
363
+ | `get_variable("")` | 调试 Run 中的附加表 |
364
+ | `quality_config` | 与画布 `qualityConfig` 对齐 |
365
+ | `set_scalar_output(name, value)` | 综合分等标量 |
366
+ | `result_df` | 质检报告表 |
367
+
368
+ **推荐**:`df.empty` 时用 `get_variable` 补主表(变量名与 `attached_variables` / 上游输出一致)。
369
+
370
+ 规则从 `quality_config["rules"]` 读取,避免魔法数。端到端质检案例见 [流程开发最佳实践(VS-flow0 案例)](./流程开发最佳实践-VS-flow0案例.md)。
371
+
372
+ ---
373
+
374
+ ## 8. 画布配置 vs 代码分工
375
+
376
+ | 改什么 | 改哪里 | 怎么提交 |
377
+ | ---------------------------------------- | ------------------------ | ----------------------- |
378
+ | SQL / Python 正文 | `code.sql` / `code.py` | `node push` |
379
+ | 连线、坐标、节点增删 | `flow.json`(设计器) | `project push --canvas` |
380
+ | connectionId、表名、output_variable_name | 设计器属性 → `flow.json` | `project push --canvas` |
381
+
382
+ **不要**在 `flow.json` 里硬塞大段 `pythonCode`/`sql`:pull 会剥离到 `code.*`,push 代码走 `flow-nodes` 接口。
383
+
384
+ ---
385
+
386
+ ## 9. 查看输出变量
387
+
388
+ 变量模型、`debug_run_id`、本地 **`变量/<名>.json`** 详见 **[流程变量系统指南](./variables-guide.md)**。
389
+
390
+ ```powershell
391
+ # 拉取单个变量(schema + 10 行)到 变量/<name>.json
392
+ dazi flow variable pull --name sales_df --dir .
393
+
394
+ # 同步全部调试变量
395
+ dazi flow variable sync --dir .
396
+ ```
397
+
398
+ 设计器:选中节点属性 **`output_variable_name`**点击 **📊**。
399
+
400
+ ---
401
+
402
+ ## 10. AI 协作建议
403
+
404
+ 1. 让 AI 阅读 **`code.*`** + **`flow.json` 中该节点 `data`** + 失败时的 **`_run/*.last-error.md`**
405
+ 2. **Agent 模式**(用户委托改流程):AI 应主动执行「改 `code.*` → **`node push`** → **`node-exec`** →(有输出变量时)**`variable pull`** → 读 `last-error.md` → 再改」循环(默认最多 3 轮),详见 **`快速启动_<流程名>.md` §AI 自主运行与改错闭环** 或 **`flow/run-fix-loop`**
406
+ 3. **对话模式**(用户仅粘贴错误):用户确认后再改代码 → **`node push`** → 测试 → `variable pull`(若适用)
407
+ 4. 需要查上游表结构时,先 **运行上游** 或 **`variable pull`**,把 `变量/<名>.json` 交给 AI
408
+ 5. MCP:`dazi-flow mcp serve`(工具含 `flow_run_node`、`flow_node_get_code`、`flow_node_set_code` 等,写操作需 `--allow-write`)