@dazitech/cli 3.0.6 → 3.0.8

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 (76) hide show
  1. package/README.md +1 -1
  2. package/dist/clis/dazi-app.js +1 -1
  3. package/dist/clis/dazi-flow.js +43 -24
  4. package/dist/clis/dazi-onto.js +73 -22
  5. package/dist/clis/dazi.js +266 -171
  6. package/dist/docs/flow/ai-workflow-playbook.md +4 -2
  7. package/dist/docs/flow/flow-project-guide.md +9 -5
  8. package/dist/docs/flow/flows-guide.md +2 -2
  9. package/dist/docs/flow/node-code-guide.md +408 -401
  10. package/dist/docs/flow/run-guide.md +13 -6
  11. package/dist/docs/flow/variables-guide.md +407 -406
  12. package/dist/docs/guides/quickstart.md +18 -4
  13. package/dist/docs/guides/troubleshooting.md +1 -1
  14. package/dist/docs/guides/workspace-v3.md +43 -23
  15. package/dist/docs/index.json +9 -3
  16. package/dist/docs/onto/action-guide.md +3 -3
  17. package/dist/docs/onto/dazi_script_sdk_reference.md +178 -174
  18. package/dist/docs/onto/dazi_script_seed_data_guide.md +158 -155
  19. package/dist/docs/onto/function-guide.md +37 -10
  20. package/dist/docs/onto/space-management.md +3 -1
  21. package/dist/docs/onto//346/234/254/344/275/223/350/204/232/346/234/254/347/274/226/345/206/231/346/214/207/345/215/227.md +138 -34
  22. package/dist/docs/onto//346/234/254/344/275/223/350/247/204/345/210/222/346/214/207/345/215/227.md +73 -31
  23. package/dist/docs/onto//350/247/204/345/210/222/347/244/272/344/276/213_/344/272/247/345/223/201/351/224/200/345/224/256/346/234/254/344/275/223/350/247/204/345/210/222/346/226/271/346/241/210.md +497 -0
  24. package/dist/docs/onto//350/247/204/345/210/222/347/244/272/344/276/213_/345/210/251/346/266/246/345/210/206/346/236/220/346/234/254/344/275/223/346/226/271/346/241/210.md +597 -541
  25. package/dist/examples/index.json +202 -22
  26. package/dist/examples/onto/README.md +43 -0
  27. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_account_breakdown.py +99 -0
  28. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_budget_vs_actual.py +116 -0
  29. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_cost_center_profit.py +85 -0
  30. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_get_summary.py +76 -0
  31. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_mom_analysis.py +86 -0
  32. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_top_accounts.py +103 -0
  33. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_yoy_analysis.py +86 -0
  34. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/save_test_arguments.ps1 +27 -0
  35. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.account_breakdown.json +10 -0
  36. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.budget_vs_actual.json +10 -0
  37. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.cost_center_profit.json +9 -0
  38. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.get_summary.json +9 -0
  39. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.mom_analysis.json +9 -0
  40. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.top_accounts.json +11 -0
  41. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.yoy_analysis.json +9 -0
  42. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_ontology_init.py +521 -0
  43. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_seed_data.py +213 -0
  44. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/README.md +25 -0
  45. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_channel_mix.py +86 -0
  46. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_customer_segmentation.py +123 -0
  47. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_get_summary.py +81 -0
  48. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_mom_analysis.py +90 -0
  49. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_region_breakdown.py +85 -0
  50. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_top_products.py +101 -0
  51. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_yoy_analysis.py +90 -0
  52. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/save_test_arguments.ps1 +25 -0
  53. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.channel_mix.json +8 -0
  54. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.customer_segmentation.json +10 -0
  55. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.get_summary.json +8 -0
  56. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.mom_analysis.json +8 -0
  57. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.region_breakdown.json +8 -0
  58. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.top_products.json +10 -0
  59. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.yoy_analysis.json +8 -0
  60. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/README.md +5 -0
  61. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_ontology_init.py +403 -0
  62. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_seed_data.py +124 -0
  63. package/dist/prompts/flow/ai-workflow-playbook.md +4 -2
  64. package/dist/prompts/flow/flow-design.md +12 -9
  65. package/dist/prompts/flow/run-fix-loop.md +37 -22
  66. package/dist/prompts/index.json +2 -2
  67. package/dist/prompts/onto/action-design.md +4 -1
  68. package/dist/prompts/onto/function-design.md +9 -2
  69. package/dist/prompts/onto/rule-seed.md +5 -1
  70. package/dist/prompts/onto/script-publish-run.md +72 -24
  71. package/package.json +1 -1
  72. package/dist/examples/onto/function/profit_fn_customer_segmentation.py +0 -117
  73. package/dist/examples/onto/function/profit_fn_mom_analysis.py +0 -89
  74. package/dist/examples/onto/function/profit_fn_top_products.py +0 -89
  75. package/dist/examples/onto/function/profit_fn_yoy_analysis.py +0 -89
  76. package/dist/examples/onto/setup/profit_ontology_init.py +0 -388
@@ -0,0 +1,403 @@
1
+ """产品销售本体初始化脚本 — space__zlj
2
+
3
+ 初始化内容:
4
+ 1. 创建物理表(4 张:产品表、销售表、客户维表、渠道维表)
5
+ 2. 注册表到空间
6
+ 3. 注册表间关系(3 条:事实表 → 维表)
7
+ 4. 注册 Cube(5 个)及派生度量
8
+ 5. 定义对象类型(5 种)、绑定数据源、属性、链接
9
+ 6. 同步指标引用
10
+
11
+ 放置:项目/潘达石化/本体/ontos/产品销售本体方案/setup/sales_ontology_init.py
12
+ 发布:dazi onto script publish 项目/潘达石化/本体/ontos/产品销售本体方案/setup/sales_ontology_init.py --space space__zlj --type setup
13
+ """
14
+
15
+ import json
16
+
17
+
18
+ def main():
19
+ space_id = "space__zlj"
20
+ s = space.get(space_id)
21
+
22
+ output.print("=== 产品销售本体初始化 ===")
23
+ output.print(f"空间: {space_id}")
24
+
25
+ # 1. 创建物理表
26
+ output.print("\n[1/9] 创建物理表...")
27
+
28
+ s.sql.execute("""
29
+ CREATE TABLE IF NOT EXISTS product_master (
30
+ product_id String,
31
+ product_code String,
32
+ product_name String,
33
+ product_category String,
34
+ product_subcategory String,
35
+ brand String,
36
+ unit String,
37
+ list_price Float64,
38
+ cost_price Float64,
39
+ status String,
40
+ created_at DateTime DEFAULT now(),
41
+ updated_at DateTime DEFAULT now()
42
+ ) ENGINE = MergeTree()
43
+ ORDER BY (product_id)
44
+ """)
45
+ output.print("OK product_master")
46
+
47
+ s.sql.execute("""
48
+ CREATE TABLE IF NOT EXISTS customer_dimension (
49
+ customer_id String,
50
+ customer_code String,
51
+ customer_name String,
52
+ customer_region String,
53
+ customer_type String,
54
+ industry String,
55
+ created_at DateTime DEFAULT now()
56
+ ) ENGINE = MergeTree()
57
+ ORDER BY (customer_id)
58
+ """)
59
+ output.print("OK customer_dimension")
60
+
61
+ s.sql.execute("""
62
+ CREATE TABLE IF NOT EXISTS channel_dimension (
63
+ channel_id String,
64
+ channel_code String,
65
+ channel_name String,
66
+ channel_type String
67
+ ) ENGINE = MergeTree()
68
+ ORDER BY (channel_id)
69
+ """)
70
+ output.print("OK channel_dimension")
71
+
72
+ s.sql.execute("""
73
+ CREATE TABLE IF NOT EXISTS sales_order_fact (
74
+ order_id String,
75
+ order_line_id String,
76
+ order_date Date,
77
+ product_id String,
78
+ product_category String,
79
+ customer_id String,
80
+ customer_region String,
81
+ customer_type String,
82
+ channel_id String,
83
+ quantity Int32,
84
+ unit_price Float64,
85
+ discount_amount Float64,
86
+ sales_amount Float64,
87
+ currency String,
88
+ order_status String
89
+ ) ENGINE = MergeTree()
90
+ ORDER BY (order_date, order_id, order_line_id)
91
+ """)
92
+ output.print("OK sales_order_fact")
93
+
94
+ # 2. 注册表
95
+ output.print("\n[2/9] 注册表到空间...")
96
+
97
+ for tbl, label in [
98
+ ("product_master", "产品主数据表"),
99
+ ("customer_dimension", "客户维度表"),
100
+ ("channel_dimension", "销售渠道表"),
101
+ ("sales_order_fact", "销售订单事实表"),
102
+ ]:
103
+ s.tables.register(tbl, label=label)
104
+ s.tables.sync_columns(tbl)
105
+ output.print(f"OK {tbl}")
106
+
107
+ # 3. 注册表间关系(数据空间「表间关系」,供 JOIN / 侧栏展示)
108
+ output.print("\n[3/9] 注册表间关系...")
109
+
110
+ table_relationships = [
111
+ {
112
+ "from_table": "sales_order_fact",
113
+ "to_table": "product_master",
114
+ "join_sql": "sales_order_fact.product_id = product_master.product_id",
115
+ "join_keys": [{"from": "product_id", "to": "product_id"}],
116
+ "relationship_type": "many_to_one",
117
+ "description": "销售订单行关联产品主数据",
118
+ },
119
+ {
120
+ "from_table": "sales_order_fact",
121
+ "to_table": "customer_dimension",
122
+ "join_sql": "sales_order_fact.customer_id = customer_dimension.customer_id",
123
+ "join_keys": [{"from": "customer_id", "to": "customer_id"}],
124
+ "relationship_type": "many_to_one",
125
+ "description": "销售订单行关联客户",
126
+ },
127
+ {
128
+ "from_table": "sales_order_fact",
129
+ "to_table": "channel_dimension",
130
+ "join_sql": "sales_order_fact.channel_id = channel_dimension.channel_id",
131
+ "join_keys": [{"from": "channel_id", "to": "channel_id"}],
132
+ "relationship_type": "many_to_one",
133
+ "description": "销售订单行关联销售渠道",
134
+ },
135
+ ]
136
+ for rel in table_relationships:
137
+ rid = s.tables.add_relationship(**rel)
138
+ output.print(f"OK {rel['from_table']} -> {rel['to_table']} ({rid})")
139
+
140
+ # 4. 注册 Cube
141
+ output.print("\n[4/9] 注册 Cube...")
142
+
143
+ fact = "sales_order_fact"
144
+ common_measures = [
145
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
146
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
147
+ {"name": "discount", "col": "discount_amount", "agg": "sum", "title": "折扣金额"},
148
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
149
+ {"name": "line_count", "col": "order_line_id", "agg": "uniq", "title": "订单行数"},
150
+ ]
151
+
152
+ s.register_cube(
153
+ name="SalesCube",
154
+ table=fact,
155
+ title="销售分析主Cube",
156
+ measures=common_measures,
157
+ dimensions=[
158
+ {"name": "order_id", "col": "order_id", "type": "string", "title": "订单ID"},
159
+ {"name": "order_line_id", "col": "order_line_id", "type": "string", "title": "订单行ID"},
160
+ {"name": "order_date", "col": "order_date", "type": "date", "title": "订单日期"},
161
+ {"name": "product_id", "col": "product_id", "type": "string", "title": "产品ID"},
162
+ {"name": "product_category", "col": "product_category", "type": "string", "title": "产品大类"},
163
+ {"name": "customer_id", "col": "customer_id", "type": "string", "title": "客户ID"},
164
+ {"name": "customer_region", "col": "customer_region", "type": "string", "title": "销售区域"},
165
+ {"name": "channel_id", "col": "channel_id", "type": "string", "title": "渠道ID"},
166
+ ],
167
+ )
168
+ output.print("OK SalesCube")
169
+
170
+ s.register_cube(
171
+ name="ProductSalesCube",
172
+ table=fact,
173
+ title="产品销售Cube",
174
+ measures=[
175
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
176
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
177
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
178
+ ],
179
+ dimensions=[
180
+ {"name": "product_id", "col": "product_id", "type": "string", "title": "产品ID"},
181
+ {"name": "product_category", "col": "product_category", "type": "string", "title": "产品大类"},
182
+ ],
183
+ )
184
+ output.print("OK ProductSalesCube")
185
+
186
+ s.register_cube(
187
+ name="CustomerSalesCube",
188
+ table=fact,
189
+ title="客户销售Cube",
190
+ measures=[
191
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
192
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
193
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
194
+ ],
195
+ dimensions=[
196
+ {"name": "customer_id", "col": "customer_id", "type": "string", "title": "客户ID"},
197
+ {"name": "customer_region", "col": "customer_region", "type": "string", "title": "销售区域"},
198
+ {"name": "customer_type", "col": "customer_type", "type": "string", "title": "客户类型"},
199
+ ],
200
+ )
201
+ output.print("OK CustomerSalesCube")
202
+
203
+ s.register_cube(
204
+ name="ChannelSalesCube",
205
+ table=fact,
206
+ title="渠道销售Cube",
207
+ measures=[
208
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
209
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
210
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
211
+ ],
212
+ dimensions=[
213
+ {"name": "channel_id", "col": "channel_id", "type": "string", "title": "渠道ID"},
214
+ ],
215
+ )
216
+ output.print("OK ChannelSalesCube")
217
+
218
+ s.register_cube(
219
+ name="TimeSalesCube",
220
+ table=fact,
221
+ title="时间维度销售Cube",
222
+ measures=[
223
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
224
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
225
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
226
+ ],
227
+ dimensions=[
228
+ {"name": "order_date", "col": "order_date", "type": "date", "title": "订单日期"},
229
+ {"name": "year_month", "col": "order_date", "type": "string", "title": "年月"},
230
+ {"name": "quarter", "col": "order_date", "type": "string", "title": "季度"},
231
+ {"name": "year", "col": "order_date", "type": "date", "title": "年份"},
232
+ ],
233
+ )
234
+ output.print("OK TimeSalesCube")
235
+
236
+ # 5. 派生度量
237
+ output.print("\n[5/9] 配置派生度量...")
238
+
239
+ s.upsert_derived_measures(
240
+ "SalesCube",
241
+ [
242
+ {
243
+ "name": "avg_unit_price",
244
+ "title": "平均单价",
245
+ "expression": "if(SalesCube.quantity > 0, SalesCube.sales_amount / SalesCube.quantity, 0)",
246
+ "description": "销售金额/销量",
247
+ },
248
+ {
249
+ "name": "avg_order_value",
250
+ "title": "客单价",
251
+ "expression": "if(SalesCube.order_count > 0, SalesCube.sales_amount / SalesCube.order_count, 0)",
252
+ "description": "销售金额/订单数",
253
+ },
254
+ ],
255
+ )
256
+ s.upsert_derived_measures(
257
+ "ProductSalesCube",
258
+ [
259
+ {
260
+ "name": "avg_unit_price",
261
+ "title": "平均单价",
262
+ "expression": "if(ProductSalesCube.quantity > 0, ProductSalesCube.sales_amount / ProductSalesCube.quantity, 0)",
263
+ "description": "平均成交单价",
264
+ },
265
+ ],
266
+ )
267
+ s.upsert_derived_measures(
268
+ "CustomerSalesCube",
269
+ [
270
+ {
271
+ "name": "avg_order_value",
272
+ "title": "客单价",
273
+ "expression": "if(CustomerSalesCube.order_count > 0, CustomerSalesCube.sales_amount / CustomerSalesCube.order_count, 0)",
274
+ "description": "客户客单价",
275
+ },
276
+ ],
277
+ )
278
+ output.print("OK 派生度量")
279
+
280
+ # 6. 对象类型
281
+ output.print("\n[6/9] 定义对象类型...")
282
+
283
+ object_types = [
284
+ ("Product", "产品", "可售产品业务对象"),
285
+ ("Customer", "客户", "购买方业务对象"),
286
+ ("SalesChannel", "销售渠道", "渠道业务对象"),
287
+ ("SalesOrder", "销售订单", "订单/订单行业务对象"),
288
+ ("SalesAnalysis", "销售分析", "多维度销售指标聚合对象"),
289
+ ]
290
+ for code, name, desc in object_types:
291
+ s.onto.define_object_type(code, name, description=desc)
292
+ output.print(f"OK {code}")
293
+
294
+ # 7. 绑定数据源
295
+ output.print("\n[7/9] 绑定数据源...")
296
+
297
+ bindings = [
298
+ ("Product", "ProductSalesCube"),
299
+ ("Customer", "CustomerSalesCube"),
300
+ ("SalesChannel", "ChannelSalesCube"),
301
+ ("SalesOrder", "SalesCube"),
302
+ ("SalesAnalysis", "SalesCube"),
303
+ ]
304
+ for obj, cube in bindings:
305
+ s.onto.bind_source(obj, "dazi_cube", config={"cube": cube})
306
+ output.print(f"OK {obj} -> {cube}")
307
+
308
+ # 8. 属性
309
+ output.print("\n[8/9] 定义属性...")
310
+
311
+ def define_props(obj_code, props):
312
+ for code, name, role, qn in props:
313
+ s.onto.define_property(obj_code, code, name, semantic_role=role, qualified_name=qn)
314
+
315
+ define_props("Product", [
316
+ ("id", "产品ID", "dimension", "ProductSalesCube.product_id"),
317
+ ("category", "产品大类", "dimension", "ProductSalesCube.product_category"),
318
+ ("quantity", "累计销量", "measure", "ProductSalesCube.quantity"),
319
+ ("sales_amount", "累计销售额", "measure", "ProductSalesCube.sales_amount"),
320
+ ("order_count", "订单数", "measure", "ProductSalesCube.order_count"),
321
+ ("avg_unit_price", "平均单价", "measure", "ProductSalesCube.avg_unit_price"),
322
+ ])
323
+ output.print("OK Product 属性 (6)")
324
+
325
+ define_props("Customer", [
326
+ ("id", "客户ID", "dimension", "CustomerSalesCube.customer_id"),
327
+ ("region", "销售区域", "dimension", "CustomerSalesCube.customer_region"),
328
+ ("type", "客户类型", "dimension", "CustomerSalesCube.customer_type"),
329
+ ("sales_amount", "累计销售额", "measure", "CustomerSalesCube.sales_amount"),
330
+ ("order_count", "订单数", "measure", "CustomerSalesCube.order_count"),
331
+ ("avg_order_value", "客单价", "measure", "CustomerSalesCube.avg_order_value"),
332
+ ])
333
+ output.print("OK Customer 属性 (6)")
334
+
335
+ define_props("SalesChannel", [
336
+ ("id", "渠道ID", "dimension", "ChannelSalesCube.channel_id"),
337
+ ("sales_amount", "销售额", "measure", "ChannelSalesCube.sales_amount"),
338
+ ("order_count", "订单数", "measure", "ChannelSalesCube.order_count"),
339
+ ])
340
+ output.print("OK SalesChannel 属性 (3)")
341
+
342
+ define_props("SalesOrder", [
343
+ ("id", "订单ID", "dimension", "SalesCube.order_id"),
344
+ ("line_id", "订单行", "dimension", "SalesCube.order_line_id"),
345
+ ("date", "订单日期", "dimension", "SalesCube.order_date"),
346
+ ("quantity", "数量", "measure", "SalesCube.quantity"),
347
+ ("sales_amount", "销售金额", "measure", "SalesCube.sales_amount"),
348
+ ("unit_price", "成交单价", "measure", "SalesCube.avg_unit_price"),
349
+ ])
350
+ output.print("OK SalesOrder 属性 (6)")
351
+
352
+ define_props("SalesAnalysis", [
353
+ ("date", "日期", "dimension", "SalesCube.order_date"),
354
+ ("product_category", "产品大类", "dimension", "SalesCube.product_category"),
355
+ ("customer_region", "销售区域", "dimension", "SalesCube.customer_region"),
356
+ ("channel_id", "渠道", "dimension", "SalesCube.channel_id"),
357
+ ("quantity", "销量", "measure", "SalesCube.quantity"),
358
+ ("sales_amount", "销售额", "measure", "SalesCube.sales_amount"),
359
+ ("order_count", "订单数", "measure", "SalesCube.order_count"),
360
+ ("avg_order_value", "客单价", "measure", "SalesCube.avg_order_value"),
361
+ ])
362
+ output.print("OK SalesAnalysis 属性 (8)")
363
+
364
+ # 9. 链接类型
365
+ output.print("\n[9/9] 定义链接与同步指标...")
366
+
367
+ link_types = [
368
+ ("order_contains_product", "订单包含产品", "SalesOrder", "Product", "订单行对应产品"),
369
+ ("order_belongs_customer", "订单归属客户", "SalesOrder", "Customer", "订单归属客户"),
370
+ ("order_via_channel", "订单经渠道成交", "SalesOrder", "SalesChannel", "订单销售渠道"),
371
+ ("product_has_orders", "产品有订单", "Product", "SalesOrder", "产品被哪些订单购买"),
372
+ ("customer_places_orders", "客户下单", "Customer", "SalesOrder", "客户历史订单"),
373
+ ("analysis_by_product", "分析归因产品", "SalesAnalysis", "Product", "指标按产品切片"),
374
+ ("analysis_by_customer", "分析归因客户", "SalesAnalysis", "Customer", "指标按客户切片"),
375
+ ("analysis_by_channel", "分析归因渠道", "SalesAnalysis", "SalesChannel", "指标按渠道切片"),
376
+ ]
377
+ for code, name, from_obj, to_obj, desc in link_types:
378
+ s.onto.define_link_type(
379
+ code=code,
380
+ name=name,
381
+ from_object_type_code=from_obj,
382
+ to_object_type_code=to_obj,
383
+ description=desc,
384
+ )
385
+ output.print(f"OK {code}")
386
+
387
+ s.sync_metric_refs()
388
+ output.print("OK sync_metric_refs")
389
+
390
+ summary = {
391
+ "ok": True,
392
+ "space_id": space_id,
393
+ "tables": 4,
394
+ "table_relationships": 3,
395
+ "cubes": 5,
396
+ "object_types": 5,
397
+ "properties": 29,
398
+ "link_types": 8,
399
+ }
400
+
401
+ output.print("\n=== 产品销售本体初始化完成 ===")
402
+ output.success("初始化成功")
403
+ output.print("__JSON_SUMMARY__" + json.dumps(summary, ensure_ascii=True, default=str))
@@ -0,0 +1,124 @@
1
+ """产品销售演示数据灌入 — space__zlj
2
+
3
+ 前置:先执行 sales_ontology_init.py 建表。
4
+ 幂等:sales_order_fact 已有数据则跳过。
5
+
6
+ 放置:项目/潘达石化/本体/ontos/产品销售本体方案/setup/sales_seed_data.py
7
+ 发布:dazi onto script publish 项目/潘达石化/本体/ontos/产品销售本体方案/setup/sales_seed_data.py --space space__zlj --type data
8
+ """
9
+
10
+ import json
11
+ import random
12
+ from datetime import date, datetime, timedelta
13
+
14
+ _SEED_DT = datetime(2025, 1, 1, 0, 0, 0)
15
+
16
+
17
+ def main():
18
+ space_id = "space__zlj"
19
+ s = space.get(space_id)
20
+
21
+ output.print("=== 产品销售演示数据灌入 ===")
22
+
23
+ try:
24
+ n = int(s.sql.query_one("SELECT count() FROM sales_order_fact") or 0)
25
+ except Exception:
26
+ n = 0
27
+ if n > 0:
28
+ output.print(f"sales_order_fact 已有 {n} 行,跳过灌数")
29
+ output.print("__JSON_SUMMARY__" + json.dumps({"ok": True, "skipped": True, "rows": n}, ensure_ascii=True))
30
+ return
31
+
32
+ products = [
33
+ {"product_id": "P001", "product_code": "OIL-92", "product_name": "92号汽油", "product_category": "成品油", "product_subcategory": "汽油", "brand": "潘达", "unit": "吨", "list_price": 8200.0, "cost_price": 7100.0, "status": "在售"},
34
+ {"product_id": "P002", "product_code": "OIL-0", "product_name": "0号柴油", "product_category": "成品油", "product_subcategory": "柴油", "brand": "潘达", "unit": "吨", "list_price": 7500.0, "cost_price": 6500.0, "status": "在售"},
35
+ {"product_id": "P003", "product_code": "CHEM-LLDPE", "product_name": "线性低密度聚乙烯", "product_category": "化工品", "product_subcategory": "聚乙烯", "brand": "潘达", "unit": "吨", "list_price": 9200.0, "cost_price": 8100.0, "status": "在售"},
36
+ {"product_id": "P004", "product_code": "CHEM-PP", "product_name": "聚丙烯PP", "product_category": "化工品", "product_subcategory": "聚丙烯", "brand": "潘达", "unit": "吨", "list_price": 8800.0, "cost_price": 7800.0, "status": "在售"},
37
+ {"product_id": "P005", "product_code": "LUB-BASE", "product_name": "润滑油基础油", "product_category": "润滑油", "product_subcategory": "基础油", "brand": "潘达", "unit": "吨", "list_price": 6800.0, "cost_price": 5900.0, "status": "在售"},
38
+ {"product_id": "P006", "product_code": "COKE-1", "product_name": "石油焦", "product_category": "副产品", "product_subcategory": "石油焦", "brand": "潘达", "unit": "吨", "list_price": 3200.0, "cost_price": 2600.0, "status": "在售"},
39
+ ]
40
+ for p in products:
41
+ p["created_at"] = _SEED_DT
42
+ p["updated_at"] = _SEED_DT
43
+
44
+ customers = [
45
+ {"customer_id": "C001", "customer_code": "CUS-001", "customer_name": "华东油品经销", "customer_region": "华东", "customer_type": "战略", "industry": "经销"},
46
+ {"customer_id": "C002", "customer_code": "CUS-002", "customer_name": "华北炼化贸易", "customer_region": "华北", "customer_type": "VIP", "industry": "贸易"},
47
+ {"customer_id": "C003", "customer_code": "CUS-003", "customer_name": "华南化工采购", "customer_region": "华南", "customer_type": "VIP", "industry": "化工"},
48
+ {"customer_id": "C004", "customer_code": "CUS-004", "customer_name": "西南区域物流", "customer_region": "西南", "customer_type": "普通", "industry": "物流"},
49
+ {"customer_id": "C005", "customer_code": "CUS-005", "customer_name": "华中制造集团", "customer_region": "华中", "customer_type": "普通", "industry": "制造"},
50
+ ]
51
+ for c in customers:
52
+ c["created_at"] = _SEED_DT
53
+
54
+ channels = [
55
+ {"channel_id": "CH01", "channel_code": "DIR", "channel_name": "直销", "channel_type": "直销"},
56
+ {"channel_id": "CH02", "channel_code": "DIST", "channel_name": "经销渠道", "channel_type": "经销"},
57
+ {"channel_id": "CH03", "channel_code": "B2B", "channel_name": "B2B平台", "channel_type": "线上"},
58
+ ]
59
+
60
+ s.sql.insert_rows("product_master", products)
61
+ s.sql.insert_rows("customer_dimension", customers)
62
+ s.sql.insert_rows("channel_dimension", channels)
63
+ output.print("OK 维表数据")
64
+
65
+ random.seed(627)
66
+ regions = {c["customer_id"]: c["customer_region"] for c in customers}
67
+ ctypes = {c["customer_id"]: c["customer_type"] for c in customers}
68
+ pcats = {p["product_id"]: p["product_category"] for p in products}
69
+ prices = {p["product_id"]: p["list_price"] for p in products}
70
+
71
+ fact_rows = []
72
+ line_seq = 1
73
+ start = date(2025, 1, 1)
74
+ end = date(2026, 6, 30)
75
+ days = (end - start).days + 1
76
+
77
+ for d_offset in range(days):
78
+ order_date = start + timedelta(days=d_offset)
79
+ if random.random() > 0.35:
80
+ continue
81
+ daily_orders = random.randint(2, 8)
82
+ for _ in range(daily_orders):
83
+ order_id = f"O{order_date.strftime('%Y%m%d')}{random.randint(100, 999)}"
84
+ customer_id = random.choice(list(regions.keys()))
85
+ channel_id = random.choice(["CH01", "CH02", "CH03"])
86
+ lines = random.randint(1, 3)
87
+ for _ in range(lines):
88
+ product_id = random.choice(list(pcats.keys()))
89
+ qty = random.randint(5, 200)
90
+ unit_price = round(prices[product_id] * random.uniform(0.92, 1.0), 2)
91
+ discount = round(unit_price * qty * random.uniform(0, 0.05), 2)
92
+ sales_amount = round(unit_price * qty - discount, 2)
93
+ fact_rows.append({
94
+ "order_id": order_id,
95
+ "order_line_id": f"L{line_seq:06d}",
96
+ "order_date": order_date,
97
+ "product_id": product_id,
98
+ "product_category": pcats[product_id],
99
+ "customer_id": customer_id,
100
+ "customer_region": regions[customer_id],
101
+ "customer_type": ctypes[customer_id],
102
+ "channel_id": channel_id,
103
+ "quantity": qty,
104
+ "unit_price": unit_price,
105
+ "discount_amount": discount,
106
+ "sales_amount": sales_amount,
107
+ "currency": "CNY",
108
+ "order_status": random.choice(["已完成", "已完成", "已发货"]),
109
+ })
110
+ line_seq += 1
111
+
112
+ inserted = s.sql.insert_rows("sales_order_fact", fact_rows)
113
+ output.print(f"OK 销售事实表插入 {inserted} 行")
114
+
115
+ summary = {
116
+ "ok": True,
117
+ "space_id": space_id,
118
+ "products": len(products),
119
+ "customers": len(customers),
120
+ "channels": len(channels),
121
+ "fact_inserted": inserted,
122
+ }
123
+ output.success("灌数完成")
124
+ output.print("__JSON_SUMMARY__" + json.dumps(summary, ensure_ascii=True, default=str))
@@ -58,9 +58,11 @@ dazi flow run flow-exec --dir . --type debug
58
58
 
59
59
  - [ ] `doctor` 为 ✅
60
60
  - [ ] 配置变更已 `push --canvas`
61
- - [ ] 代码变更已 `node push`
61
+ - [ ] `code.*` 后已 **`node push`**(先于 `node-exec`)
62
+ - [ ] `node-exec` JSON `success: true`
63
+ - [ ] 有 `output_variable_name` 时已 **`variable pull`** 且 `变量/<名>.json` 合理
62
64
  - [ ] `flow.json` 无代码正文
63
- - [ ] 测试通过后再声称完成
65
+ - [ ] 以上均满足后再声称完成(禁止仅凭 exit code、禁止未 push 就测代码)
64
66
 
65
67
  ---
66
68
 
@@ -66,12 +66,13 @@ dazi flow node new --type <node_type> --dir . --label "<节点名>"
66
66
 
67
67
  # 3) 修改 flow.json(节点配置与连线)+ 修改 节点/<名>/code.*
68
68
 
69
- # 4) 单节点测试
70
- dazi flow run node-exec --node <node_uuid> --dir .
71
-
72
- # 5) 提交(代码可用 node push;拓扑/配置变更必须 canvas push)
69
+ # 4) 提交(先于测试;node-exec 跑的是平台已 push 的代码)
70
+ dazi flow project push --dir . --canvas # 若改了拓扑/配置
73
71
  dazi flow node push --node <node_uuid> --dir .
74
- dazi flow project push --dir . --canvas
72
+
73
+ # 5) 单节点测试 + 核对输出变量
74
+ dazi flow run node-exec --node <node_uuid> --dir .
75
+ dazi flow variable pull --name <output_variable_name> --dir . # 若节点配置了输出变量
75
76
  ```
76
77
 
77
78
  ## 一致性检查(回答末尾必须自检)
@@ -85,7 +86,9 @@ dazi flow project push --dir . --canvas
85
86
 
86
87
  设计/改代码**不能**只交付文件清单;必须进入 **改错循环**(详见 `flow/run-fix-loop` 与流程目录 `快速启动_<流程名>.md` §AI 自主运行与改错闭环):
87
88
 
88
- 1. 对每个代码节点执行 `node-exec`;必要时整流程 `flow-exec --type debug`
89
- 2. 失败时读取 `_run/*.last-error.md`,按错误分类修复后重跑(默认最多 3 轮)
90
- 3. 全部通过后再 `node push` / `project push --canvas`
91
- 4. **禁止**未实际运行就声称「设计已完成/已通过」
89
+ 1. `code.*` 后先 `node push`;改画布/配置先 `project push --canvas`
90
+ 2. 对每个代码节点执行 `node-exec`;有 `output_variable_name` 时 `variable pull` 核对
91
+ 3. 必要时整流程 `flow-exec --type debug`
92
+ 4. 失败时读取 `_run/*.last-error.md`,按错误分类修复后重跑(默认最多 3 轮,详见 `flow/run-fix-loop`)
93
+ 5. **成功判据**:push 成功 → node-exec `success: true` → variable 合理(若适用)
94
+ 6. **禁止**未 push 就测代码;**禁止**未实际运行就声称「设计已完成/已通过」