@dazitech/cli 3.0.7 → 3.0.9

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/clis/dazi-app.js +1 -1
  3. package/dist/clis/dazi-flow.js +1 -1
  4. package/dist/clis/dazi-onto.js +73 -22
  5. package/dist/clis/dazi.js +266 -171
  6. package/dist/docs/flow/flow-project-guide.md +1 -1
  7. package/dist/docs/guides/quickstart.md +18 -4
  8. package/dist/docs/guides/troubleshooting.md +12 -1
  9. package/dist/docs/guides/workspace-v3.md +43 -23
  10. package/dist/docs/index.json +28 -3
  11. package/dist/docs/onto/action-guide.md +3 -3
  12. package/dist/docs/onto/dazi_script_sdk_reference.md +244 -174
  13. package/dist/docs/onto/dazi_script_seed_data_guide.md +158 -155
  14. package/dist/docs/onto/function-guide.md +82 -27
  15. package/dist/docs/onto/space-management.md +3 -1
  16. package/dist/docs/onto//346/234/254/344/275/223/345/210/206/347/261/273/350/247/204/345/210/222/344/270/216SDK/346/211/251/345/261/225/346/226/271/346/241/210.md +168 -0
  17. package/dist/docs/onto//346/234/254/344/275/223/345/221/275/345/220/215/350/247/204/350/214/203_/347/211/251/347/220/206/350/241/250Cube/344/270/216/345/257/271/350/261/241.md +402 -0
  18. 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 +200 -34
  19. package/dist/docs/onto//346/234/254/344/275/223/350/247/204/345/210/222/346/214/207/345/215/227.md +188 -38
  20. package/dist/docs/onto//350/204/232/346/234/254/350/277/220/350/241/214/347/272/240/351/224/231_/345/225/206/345/212/241/346/210/220/346/234/254/346/226/271/346/241/210/345/274/200/345/217/221/350/277/207/347/250/213.md +213 -0
  21. 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 +620 -0
  22. 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 +680 -541
  23. package/dist/examples/index.json +208 -22
  24. package/dist/examples/onto/README.md +51 -0
  25. package/dist/examples/onto/_templates/ontology_function_template.py +50 -0
  26. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_account_breakdown.py +62 -0
  27. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_budget_vs_actual.py +69 -0
  28. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_cost_center_profit.py +64 -0
  29. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_get_summary.py +61 -0
  30. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_mom_analysis.py +82 -0
  31. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_top_accounts.py +61 -0
  32. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_yoy_analysis.py +79 -0
  33. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/save_test_arguments.ps1 +38 -0
  34. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.account_breakdown.json +1 -0
  35. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.budget_vs_actual.json +1 -0
  36. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.cost_center_profit.json +1 -0
  37. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.get_summary.json +1 -0
  38. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.mom_analysis.json +1 -0
  39. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.top_accounts.json +1 -0
  40. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/test_arguments/profit.fn.yoy_analysis.json +1 -0
  41. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_ontology_init.py +679 -0
  42. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_seed_data.py +216 -0
  43. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_channel_mix.py +89 -0
  44. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_customer_segmentation.py +121 -0
  45. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_get_summary.py +78 -0
  46. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_mom_analysis.py +89 -0
  47. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_region_breakdown.py +84 -0
  48. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_top_products.py +98 -0
  49. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_yoy_analysis.py +87 -0
  50. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/save_test_arguments.ps1 +38 -0
  51. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.channel_mix.json +5 -0
  52. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.customer_segmentation.json +5 -0
  53. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.get_summary.json +5 -0
  54. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.mom_analysis.json +5 -0
  55. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.region_breakdown.json +5 -0
  56. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.top_products.json +5 -0
  57. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.yoy_analysis.json +5 -0
  58. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_ontology_init.py +539 -0
  59. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_seed_data.py +163 -0
  60. package/dist/prompts/index.json +2 -2
  61. package/dist/prompts/onto/action-design.md +4 -1
  62. package/dist/prompts/onto/function-design.md +46 -19
  63. package/dist/prompts/onto/rule-seed.md +5 -1
  64. package/dist/prompts/onto/script-publish-run.md +87 -25
  65. package/package.json +1 -1
  66. package/dist/examples/onto/function/profit_fn_customer_segmentation.py +0 -117
  67. package/dist/examples/onto/function/profit_fn_mom_analysis.py +0 -89
  68. package/dist/examples/onto/function/profit_fn_top_products.py +0 -89
  69. package/dist/examples/onto/function/profit_fn_yoy_analysis.py +0 -89
  70. package/dist/examples/onto/setup/profit_ontology_init.py +0 -388
@@ -0,0 +1,539 @@
1
+ """产品销售本体初始化脚本 — space__misc_01
2
+
3
+ 初始化内容:
4
+ 1. 创建物理表(dim_date + 3 维表 + 1 事实表)
5
+ 2. 注册表到空间
6
+ 3. 注册表间关系(4 条,含 fact → dim_date)
7
+ 4. 注册 Cube(4 个)及派生度量
8
+ 5. 定义对象类型(5 种)、绑定数据源、属性、链接
9
+ 6. 同步指标引用
10
+ 7. 配置 347 对齐分类(ads_categories + 桥表)
11
+
12
+ 放置:资源/examples/onto/销售示例/setup/sales_ontology_init.py(复制到项目 ontos/<实现名>/setup/)
13
+ 发布:dazi onto script publish <item-path>/setup/sales_ontology_init.py --space <space-id> --type setup
14
+ 规划对照:资源/docs/onto/规划示例_产品销售本体规划方案.md
15
+ """
16
+
17
+ import json
18
+
19
+ # 与 规划示例_产品销售本体规划方案.md §2.3、§3.x 对齐:display_name=侧栏显示名,description=业务说明
20
+ TABLE_REGISTRY = {
21
+ "dim_date": {
22
+ "display_name": "日期维表",
23
+ "description": "全空间共享日历,事实表通过 date_key 关联",
24
+ "columns": [
25
+ {"name": "date_key", "display_name": "日期键", "description": "YYYYMMDD,主键"},
26
+ {"name": "calendar_date", "display_name": "自然日"},
27
+ {"name": "year", "display_name": "公历年"},
28
+ {"name": "quarter", "display_name": "季度", "description": "1-4"},
29
+ {"name": "month", "display_name": "月", "description": "1-12"},
30
+ {"name": "week_of_year", "display_name": "周"},
31
+ {"name": "day_of_week", "display_name": "星期"},
32
+ {"name": "is_weekend", "display_name": "是否周末", "description": "0/1"},
33
+ {"name": "year_month", "display_name": "年月", "description": "如 2025-06"},
34
+ ],
35
+ },
36
+ "dim_product": {
37
+ "display_name": "产品维表",
38
+ "description": "可售产品主数据",
39
+ "columns": [
40
+ {"name": "product_id", "display_name": "产品 ID", "description": "主键"},
41
+ {"name": "product_code", "display_name": "产品编码"},
42
+ {"name": "product_name", "display_name": "产品名称"},
43
+ {"name": "product_category", "display_name": "产品大类"},
44
+ {"name": "product_subcategory", "display_name": "产品小类"},
45
+ {"name": "brand", "display_name": "品牌"},
46
+ {"name": "unit", "display_name": "计量单位"},
47
+ {"name": "list_price", "display_name": "挂牌价"},
48
+ {"name": "cost_price", "display_name": "成本单价"},
49
+ {"name": "status", "display_name": "状态", "description": "在售/停售"},
50
+ {"name": "created_at", "display_name": "创建时间"},
51
+ {"name": "updated_at", "display_name": "更新时间"},
52
+ ],
53
+ },
54
+ "dim_customer": {
55
+ "display_name": "客户维表",
56
+ "description": "购买方主数据",
57
+ "columns": [
58
+ {"name": "customer_id", "display_name": "客户 ID", "description": "主键"},
59
+ {"name": "customer_code", "display_name": "客户编码"},
60
+ {"name": "customer_name", "display_name": "客户名称"},
61
+ {"name": "customer_region", "display_name": "销售区域"},
62
+ {"name": "customer_type", "display_name": "客户类型"},
63
+ {"name": "industry", "display_name": "所属行业"},
64
+ {"name": "created_at", "display_name": "创建时间"},
65
+ ],
66
+ },
67
+ "dim_channel": {
68
+ "display_name": "渠道维表",
69
+ "description": "销售渠道主数据",
70
+ "columns": [
71
+ {"name": "channel_id", "display_name": "渠道 ID", "description": "主键"},
72
+ {"name": "channel_code", "display_name": "渠道编码"},
73
+ {"name": "channel_name", "display_name": "渠道名称"},
74
+ {"name": "channel_type", "display_name": "渠道类型"},
75
+ ],
76
+ },
77
+ "fact_sales_order_line": {
78
+ "display_name": "销售订单行事实表",
79
+ "description": "订单行粒度销售流水",
80
+ "columns": [
81
+ {"name": "order_id", "display_name": "订单 ID"},
82
+ {"name": "order_line_id", "display_name": "订单行 ID", "description": "主键组合"},
83
+ {"name": "date_key", "display_name": "日期键", "description": "关联 dim_date"},
84
+ {"name": "order_date", "display_name": "订单日期"},
85
+ {"name": "product_id", "display_name": "产品 ID", "description": "关联 dim_product"},
86
+ {"name": "product_category", "display_name": "产品大类", "description": "冗余"},
87
+ {"name": "customer_id", "display_name": "客户 ID", "description": "关联 dim_customer"},
88
+ {"name": "customer_region", "display_name": "销售区域", "description": "冗余"},
89
+ {"name": "customer_type", "display_name": "客户类型", "description": "冗余"},
90
+ {"name": "channel_id", "display_name": "渠道 ID", "description": "关联 dim_channel"},
91
+ {"name": "quantity", "display_name": "销售数量"},
92
+ {"name": "unit_price", "display_name": "成交单价"},
93
+ {"name": "discount_amount", "display_name": "折扣金额"},
94
+ {"name": "sales_amount", "display_name": "销售金额"},
95
+ {"name": "currency", "display_name": "币种"},
96
+ {"name": "order_status", "display_name": "订单状态"},
97
+ ],
98
+ },
99
+ }
100
+
101
+ # 与 347 + 附录 B 对齐;category 值必须是 347 标准中文名
102
+ CATEGORY_REGISTRY = {
103
+ "table": {
104
+ "时间维": ["dim_date"],
105
+ "维度表": ["dim_product", "dim_customer", "dim_channel"],
106
+ "事实表": ["fact_sales_order_line"],
107
+ },
108
+ "cube": {
109
+ "流程型": ["SalesCube"],
110
+ "主体型": ["ProductSalesCube", "CustomerSalesCube", "ChannelSalesCube"],
111
+ },
112
+ "object": {
113
+ "主数据": ["Product", "Customer"],
114
+ "参考": ["SalesChannel"],
115
+ "事务": ["SalesOrder"],
116
+ "分析": ["SalesAnalysis"],
117
+ },
118
+ "relation": {
119
+ "时间关联": [("fact_sales_order_line", "dim_date")],
120
+ "主数据关联": [
121
+ ("fact_sales_order_line", "dim_product"),
122
+ ("fact_sales_order_line", "dim_customer"),
123
+ ("fact_sales_order_line", "dim_channel"),
124
+ ],
125
+ },
126
+ "link": {
127
+ "归属关系": [
128
+ "order_contains_product",
129
+ "order_belongs_customer",
130
+ "order_via_channel",
131
+ "product_has_orders",
132
+ "customer_places_orders",
133
+ ],
134
+ "分析归因": [
135
+ "analysis_by_product",
136
+ "analysis_by_customer",
137
+ "analysis_by_channel",
138
+ ],
139
+ },
140
+ }
141
+
142
+
143
+ def main():
144
+ space_id = "space__misc_01"
145
+ s = space.get(space_id)
146
+
147
+ output.print("=== 产品销售本体初始化 ===")
148
+ output.print(f"空间: {space_id}")
149
+
150
+ # 1. 创建物理表
151
+ output.print("\n[1/10] 创建物理表...")
152
+
153
+ # dim_date(强制时间维)
154
+ s.sql.execute("""
155
+ CREATE TABLE IF NOT EXISTS dim_date (
156
+ date_key Int32,
157
+ calendar_date Date,
158
+ year Int16,
159
+ quarter Int8,
160
+ month Int8,
161
+ week_of_year Int8,
162
+ day_of_week Int8,
163
+ is_weekend UInt8,
164
+ year_month String
165
+ ) ENGINE = MergeTree()
166
+ ORDER BY (date_key)
167
+ """)
168
+ output.print("OK dim_date")
169
+
170
+ # dim_product
171
+ s.sql.execute("""
172
+ CREATE TABLE IF NOT EXISTS dim_product (
173
+ product_id String,
174
+ product_code String,
175
+ product_name String,
176
+ product_category String,
177
+ product_subcategory String,
178
+ brand String,
179
+ unit String,
180
+ list_price Float64,
181
+ cost_price Float64,
182
+ status String,
183
+ created_at DateTime DEFAULT now(),
184
+ updated_at DateTime DEFAULT now()
185
+ ) ENGINE = MergeTree()
186
+ ORDER BY (product_id)
187
+ """)
188
+ output.print("OK dim_product")
189
+
190
+ # dim_customer
191
+ s.sql.execute("""
192
+ CREATE TABLE IF NOT EXISTS dim_customer (
193
+ customer_id String,
194
+ customer_code String,
195
+ customer_name String,
196
+ customer_region String,
197
+ customer_type String,
198
+ industry String,
199
+ created_at DateTime DEFAULT now()
200
+ ) ENGINE = MergeTree()
201
+ ORDER BY (customer_id)
202
+ """)
203
+ output.print("OK dim_customer")
204
+
205
+ # dim_channel
206
+ s.sql.execute("""
207
+ CREATE TABLE IF NOT EXISTS dim_channel (
208
+ channel_id String,
209
+ channel_code String,
210
+ channel_name String,
211
+ channel_type String
212
+ ) ENGINE = MergeTree()
213
+ ORDER BY (channel_id)
214
+ """)
215
+ output.print("OK dim_channel")
216
+
217
+ # fact_sales_order_line(含 date_key)
218
+ s.sql.execute("""
219
+ CREATE TABLE IF NOT EXISTS fact_sales_order_line (
220
+ order_id String,
221
+ order_line_id String,
222
+ date_key Int32,
223
+ order_date Date,
224
+ product_id String,
225
+ product_category String,
226
+ customer_id String,
227
+ customer_region String,
228
+ customer_type String,
229
+ channel_id String,
230
+ quantity Int32,
231
+ unit_price Float64,
232
+ discount_amount Float64,
233
+ sales_amount Float64,
234
+ currency String,
235
+ order_status String
236
+ ) ENGINE = MergeTree()
237
+ ORDER BY (date_key, order_id, order_line_id)
238
+ """)
239
+ output.print("OK fact_sales_order_line")
240
+
241
+ # 2. 注册表(含 display_name / description)
242
+ output.print("\n[2/10] 注册表到空间...")
243
+
244
+ for tbl_name, meta in TABLE_REGISTRY.items():
245
+ s.tables.register_with_meta(
246
+ table_name=tbl_name,
247
+ display_name=meta["display_name"],
248
+ description=meta.get("description"),
249
+ columns=meta["columns"],
250
+ force_column_meta=True,
251
+ )
252
+ output.print(f"OK {tbl_name} ({meta['display_name']})")
253
+
254
+ # 3. 注册表间关系(4 条,含 fact → dim_date)
255
+ output.print("\n[3/10] 注册表间关系...")
256
+
257
+ table_relationships = [
258
+ {
259
+ "from_table": "fact_sales_order_line",
260
+ "to_table": "dim_date",
261
+ "join_sql": "fact_sales_order_line.date_key = dim_date.date_key",
262
+ "join_keys": [{"from": "date_key", "to": "date_key"}],
263
+ "relationship_type": "many_to_one",
264
+ "description": "销售订单行关联日期",
265
+ },
266
+ {
267
+ "from_table": "fact_sales_order_line",
268
+ "to_table": "dim_product",
269
+ "join_sql": "fact_sales_order_line.product_id = dim_product.product_id",
270
+ "join_keys": [{"from": "product_id", "to": "product_id"}],
271
+ "relationship_type": "many_to_one",
272
+ "description": "销售订单行关联产品",
273
+ },
274
+ {
275
+ "from_table": "fact_sales_order_line",
276
+ "to_table": "dim_customer",
277
+ "join_sql": "fact_sales_order_line.customer_id = dim_customer.customer_id",
278
+ "join_keys": [{"from": "customer_id", "to": "customer_id"}],
279
+ "relationship_type": "many_to_one",
280
+ "description": "销售订单行关联客户",
281
+ },
282
+ {
283
+ "from_table": "fact_sales_order_line",
284
+ "to_table": "dim_channel",
285
+ "join_sql": "fact_sales_order_line.channel_id = dim_channel.channel_id",
286
+ "join_keys": [{"from": "channel_id", "to": "channel_id"}],
287
+ "relationship_type": "many_to_one",
288
+ "description": "销售订单行关联渠道",
289
+ },
290
+ ]
291
+ for rel in table_relationships:
292
+ rid = s.tables.add_relationship(**rel)
293
+ output.print(f"OK {rel['from_table']} -> {rel['to_table']} ({rid})")
294
+
295
+ # 4. 注册 Cube(4 个)
296
+ output.print("\n[4/10] 注册 Cube...")
297
+
298
+ fact = "fact_sales_order_line"
299
+ common_measures = [
300
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
301
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
302
+ {"name": "discount", "col": "discount_amount", "agg": "sum", "title": "折扣金额"},
303
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
304
+ {"name": "line_count", "col": "order_line_id", "agg": "uniq", "title": "订单行数"},
305
+ ]
306
+
307
+ # SalesCube(Process · 销售分析主 Cube)
308
+ s.register_cube(
309
+ name="SalesCube",
310
+ table=fact,
311
+ title="销售分析主Cube",
312
+ measures=common_measures,
313
+ dimensions=[
314
+ {"name": "order_id", "col": "order_id", "type": "string", "title": "订单ID"},
315
+ {"name": "order_line_id", "col": "order_line_id", "type": "string", "title": "订单行ID"},
316
+ {"name": "date_key", "col": "date_key", "type": "int32", "title": "日期键"},
317
+ {"name": "order_date", "col": "order_date", "type": "date", "title": "订单日期"},
318
+ {"name": "product_id", "col": "product_id", "type": "string", "title": "产品ID"},
319
+ {"name": "product_category", "col": "product_category", "type": "string", "title": "产品大类"},
320
+ {"name": "customer_id", "col": "customer_id", "type": "string", "title": "客户ID"},
321
+ {"name": "customer_region", "col": "customer_region", "type": "string", "title": "销售区域"},
322
+ {"name": "customer_type", "col": "customer_type", "type": "string", "title": "客户类型"},
323
+ {"name": "channel_id", "col": "channel_id", "type": "string", "title": "渠道ID"},
324
+ ],
325
+ )
326
+ output.print("OK SalesCube")
327
+
328
+ # ProductSalesCube(Subject · 产品销售)
329
+ s.register_cube(
330
+ name="ProductSalesCube",
331
+ table=fact,
332
+ title="产品销售Cube",
333
+ measures=[
334
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
335
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
336
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
337
+ ],
338
+ dimensions=[
339
+ {"name": "product_id", "col": "product_id", "type": "string", "title": "产品ID"},
340
+ {"name": "product_category", "col": "product_category", "type": "string", "title": "产品大类"},
341
+ ],
342
+ )
343
+ output.print("OK ProductSalesCube")
344
+
345
+ # CustomerSalesCube(Subject · 客户销售)
346
+ s.register_cube(
347
+ name="CustomerSalesCube",
348
+ table=fact,
349
+ title="客户销售Cube",
350
+ measures=[
351
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
352
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
353
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
354
+ ],
355
+ dimensions=[
356
+ {"name": "customer_id", "col": "customer_id", "type": "string", "title": "客户ID"},
357
+ {"name": "customer_region", "col": "customer_region", "type": "string", "title": "销售区域"},
358
+ {"name": "customer_type", "col": "customer_type", "type": "string", "title": "客户类型"},
359
+ ],
360
+ )
361
+ output.print("OK CustomerSalesCube")
362
+
363
+ # ChannelSalesCube(Subject · 渠道销售)
364
+ s.register_cube(
365
+ name="ChannelSalesCube",
366
+ table=fact,
367
+ title="渠道销售Cube",
368
+ measures=[
369
+ {"name": "quantity", "col": "quantity", "agg": "sum", "title": "销售数量"},
370
+ {"name": "sales_amount", "col": "sales_amount", "agg": "sum", "title": "销售金额"},
371
+ {"name": "order_count", "col": "order_id", "agg": "uniq", "title": "订单数"},
372
+ ],
373
+ dimensions=[
374
+ {"name": "channel_id", "col": "channel_id", "type": "string", "title": "渠道ID"},
375
+ ],
376
+ )
377
+ output.print("OK ChannelSalesCube")
378
+
379
+ # 5. 派生度量
380
+ output.print("\n[5/10] 配置派生度量...")
381
+
382
+ s.upsert_derived_measures(
383
+ "SalesCube",
384
+ [
385
+ {
386
+ "name": "avg_unit_price",
387
+ "title": "平均单价",
388
+ "expression": "if(SalesCube.quantity > 0, SalesCube.sales_amount / SalesCube.quantity, 0)",
389
+ "description": "销售金额/销量",
390
+ },
391
+ {
392
+ "name": "avg_order_value",
393
+ "title": "客单价",
394
+ "expression": "if(SalesCube.order_count > 0, SalesCube.sales_amount / SalesCube.order_count, 0)",
395
+ "description": "销售金额/订单数",
396
+ },
397
+ ],
398
+ )
399
+ output.print("OK SalesCube 派生度量")
400
+
401
+ s.upsert_derived_measures(
402
+ "ProductSalesCube",
403
+ [
404
+ {
405
+ "name": "avg_unit_price",
406
+ "title": "平均单价",
407
+ "expression": "if(ProductSalesCube.quantity > 0, ProductSalesCube.sales_amount / ProductSalesCube.quantity, 0)",
408
+ "description": "平均成交单价",
409
+ },
410
+ ],
411
+ )
412
+ output.print("OK ProductSalesCube 派生度量")
413
+
414
+ s.upsert_derived_measures(
415
+ "CustomerSalesCube",
416
+ [
417
+ {
418
+ "name": "avg_order_value",
419
+ "title": "客单价",
420
+ "expression": "if(CustomerSalesCube.order_count > 0, CustomerSalesCube.sales_amount / CustomerSalesCube.order_count, 0)",
421
+ "description": "客户客单价",
422
+ },
423
+ ],
424
+ )
425
+ output.print("OK CustomerSalesCube 派生度量")
426
+
427
+ # 6. 对象类型
428
+ output.print("\n[6/10] 定义对象类型...")
429
+
430
+ object_types = [
431
+ ("Product", "产品", "可售产品业务对象"),
432
+ ("Customer", "客户", "购买方业务对象"),
433
+ ("SalesChannel", "销售渠道", "渠道业务对象"),
434
+ ("SalesOrder", "销售订单", "订单/订单行业务对象"),
435
+ ("SalesAnalysis", "销售分析", "多维度销售指标聚合对象"),
436
+ ]
437
+ for code, name, desc in object_types:
438
+ s.onto.define_object_type(code, name, description=desc)
439
+ output.print(f"OK {code}")
440
+
441
+ # 7. 绑定数据源
442
+ output.print("\n[7/10] 绑定数据源...")
443
+
444
+ bindings = [
445
+ ("Product", "ProductSalesCube"),
446
+ ("Customer", "CustomerSalesCube"),
447
+ ("SalesChannel", "ChannelSalesCube"),
448
+ ("SalesOrder", "SalesCube"),
449
+ ("SalesAnalysis", "SalesCube"),
450
+ ]
451
+ for code, cube in bindings:
452
+ s.onto.bind_source(code, "dazi_cube", config={"cube": cube})
453
+ output.print(f"OK {code} -> {cube}")
454
+
455
+ # 8. 定义属性
456
+ output.print("\n[8/10] 定义属性...")
457
+
458
+ properties = [
459
+ # Product 属性
460
+ ("Product", "id", "产品ID", "dimension", "ProductSalesCube.product_id"),
461
+ ("Product", "category", "产品大类", "dimension", "ProductSalesCube.product_category"),
462
+ ("Product", "quantity", "累计销量", "measure", "ProductSalesCube.quantity"),
463
+ ("Product", "sales_amount", "累计销售额", "measure", "ProductSalesCube.sales_amount"),
464
+ ("Product", "order_count", "订单数", "measure", "ProductSalesCube.order_count"),
465
+ ("Product", "avg_unit_price", "平均单价", "measure", "ProductSalesCube.avg_unit_price"),
466
+ # Customer 属性
467
+ ("Customer", "id", "客户ID", "dimension", "CustomerSalesCube.customer_id"),
468
+ ("Customer", "region", "销售区域", "dimension", "CustomerSalesCube.customer_region"),
469
+ ("Customer", "type", "客户类型", "dimension", "CustomerSalesCube.customer_type"),
470
+ ("Customer", "quantity", "累计销量", "measure", "CustomerSalesCube.quantity"),
471
+ ("Customer", "sales_amount", "累计销售额", "measure", "CustomerSalesCube.sales_amount"),
472
+ ("Customer", "order_count", "订单数", "measure", "CustomerSalesCube.order_count"),
473
+ ("Customer", "avg_order_value", "客单价", "measure", "CustomerSalesCube.avg_order_value"),
474
+ # SalesChannel 属性
475
+ ("SalesChannel", "id", "渠道ID", "dimension", "ChannelSalesCube.channel_id"),
476
+ ("SalesChannel", "quantity", "累计销量", "measure", "ChannelSalesCube.quantity"),
477
+ ("SalesChannel", "sales_amount", "累计销售额", "measure", "ChannelSalesCube.sales_amount"),
478
+ ("SalesChannel", "order_count", "订单数", "measure", "ChannelSalesCube.order_count"),
479
+ # SalesOrder 属性
480
+ ("SalesOrder", "id", "订单ID", "dimension", "SalesCube.order_id"),
481
+ ("SalesOrder", "line_id", "订单行", "dimension", "SalesCube.order_line_id"),
482
+ ("SalesOrder", "date", "订单日期", "dimension", "SalesCube.order_date"),
483
+ ("SalesOrder", "quantity", "数量", "measure", "SalesCube.quantity"),
484
+ ("SalesOrder", "sales_amount", "销售金额", "measure", "SalesCube.sales_amount"),
485
+ ("SalesOrder", "unit_price", "成交单价", "measure", "SalesCube.avg_unit_price"),
486
+ # SalesAnalysis 属性
487
+ ("SalesAnalysis", "total_sales", "总销售额", "measure", "SalesCube.sales_amount"),
488
+ ("SalesAnalysis", "total_quantity", "总销量", "measure", "SalesCube.quantity"),
489
+ ("SalesAnalysis", "order_count", "订单数", "measure", "SalesCube.order_count"),
490
+ ("SalesAnalysis", "avg_order_value", "客单价", "measure", "SalesCube.avg_order_value"),
491
+ ("SalesAnalysis", "avg_unit_price", "平均单价", "measure", "SalesCube.avg_unit_price"),
492
+ ]
493
+ for obj_code, prop_code, title, role, qualified_name in properties:
494
+ s.onto.define_property(obj_code, prop_code, title, semantic_role=role, qualified_name=qualified_name)
495
+ output.print(f"OK {len(properties)} 个属性")
496
+
497
+ # 9. 定义链接类型
498
+ output.print("\n[9/10] 定义链接类型...")
499
+
500
+ link_types = [
501
+ ("order_contains_product", "订单包含产品", "SalesOrder", "Product"),
502
+ ("order_belongs_customer", "订单归属客户", "SalesOrder", "Customer"),
503
+ ("order_via_channel", "订单经渠道成交", "SalesOrder", "SalesChannel"),
504
+ ("product_has_orders", "产品有订单", "Product", "SalesOrder"),
505
+ ("customer_places_orders", "客户下单", "Customer", "SalesOrder"),
506
+ ("analysis_by_product", "分析归因产品", "SalesAnalysis", "Product"),
507
+ ("analysis_by_customer", "分析归因客户", "SalesAnalysis", "Customer"),
508
+ ("analysis_by_channel", "分析归因渠道", "SalesAnalysis", "SalesChannel"),
509
+ ]
510
+ for code, name, from_obj, to_obj in link_types:
511
+ s.onto.define_link_type(code, name, from_object_type_code=from_obj, to_object_type_code=to_obj)
512
+ output.print(f"OK {code}")
513
+
514
+ # 同步指标引用
515
+ s.sync_metric_refs()
516
+ output.print("OK sync_metric_refs")
517
+
518
+ # 10. 347 对齐分类
519
+ output.print("\n[10/10] 配置对象分类(347 对齐)...")
520
+
521
+ cat_counts = s.categories.apply_registry(CATEGORY_REGISTRY, skip_missing=True)
522
+ for kind, cnt in cat_counts.items():
523
+ if cnt:
524
+ output.print(f"OK 分类[{kind}] 挂载 {cnt} 项")
525
+
526
+ summary = {
527
+ "ok": True,
528
+ "space_id": space_id,
529
+ "tables": 5,
530
+ "table_relationships": 4,
531
+ "cubes": 4,
532
+ "object_types": 5,
533
+ "properties": len(properties),
534
+ "link_types": len(link_types),
535
+ "category_mounts": cat_counts,
536
+ }
537
+ output.success("本体初始化完成")
538
+ output.print(f"space_id: {space_id}")
539
+ output.print("__JSON_SUMMARY__" + json.dumps(summary, ensure_ascii=False))