@dazitech/cli 3.0.8 → 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 (75) 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 +1 -1
  5. package/dist/clis/dazi.js +1 -1
  6. package/dist/docs/flow/flow-project-guide.md +1 -1
  7. package/dist/docs/guides/troubleshooting.md +12 -1
  8. package/dist/docs/index.json +20 -1
  9. package/dist/docs/onto/dazi_script_sdk_reference.md +78 -12
  10. package/dist/docs/onto/function-guide.md +61 -33
  11. 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
  12. 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
  13. 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 +63 -1
  14. package/dist/docs/onto//346/234/254/344/275/223/350/247/204/345/210/222/346/214/207/345/215/227.md +123 -15
  15. 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
  16. 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 +356 -233
  17. 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 +379 -296
  18. package/dist/examples/index.json +23 -17
  19. package/dist/examples/onto/README.md +13 -5
  20. package/dist/examples/onto/_templates/ontology_function_template.py +50 -0
  21. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_account_breakdown.py +62 -0
  22. 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
  23. 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
  24. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_get_summary.py +61 -0
  25. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_mom_analysis.py +82 -0
  26. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_top_accounts.py +61 -0
  27. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/profit_fn_yoy_analysis.py +79 -0
  28. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/functions/save_test_arguments.ps1 +38 -0
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_ontology_init.py +232 -74
  37. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/setup/profit_seed_data.py +16 -13
  38. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_channel_mix.py +19 -16
  39. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_customer_segmentation.py +48 -50
  40. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_get_summary.py +3 -6
  41. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_mom_analysis.py +11 -12
  42. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_region_breakdown.py +6 -7
  43. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_top_products.py +5 -8
  44. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/sales_fn_yoy_analysis.py +3 -6
  45. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/save_test_arguments.ps1 +32 -19
  46. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.channel_mix.json +3 -6
  47. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.customer_segmentation.json +2 -7
  48. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.get_summary.json +2 -5
  49. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.mom_analysis.json +2 -5
  50. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.region_breakdown.json +2 -5
  51. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.top_products.json +2 -7
  52. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/test_arguments/sales.fn.yoy_analysis.json +2 -5
  53. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_ontology_init.py +291 -155
  54. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/sales_seed_data.py +59 -20
  55. package/dist/prompts/index.json +1 -1
  56. package/dist/prompts/onto/function-design.md +42 -22
  57. package/dist/prompts/onto/script-publish-run.md +15 -1
  58. package/package.json +1 -1
  59. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_account_breakdown.py +0 -99
  60. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_budget_vs_actual.py +0 -116
  61. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_cost_center_profit.py +0 -85
  62. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_get_summary.py +0 -76
  63. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_mom_analysis.py +0 -86
  64. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_top_accounts.py +0 -103
  65. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/profit_fn_yoy_analysis.py +0 -86
  66. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/save_test_arguments.ps1 +0 -27
  67. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.account_breakdown.json +0 -10
  68. 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 +0 -10
  69. 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 +0 -9
  70. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.get_summary.json +0 -9
  71. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.mom_analysis.json +0 -9
  72. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.top_accounts.json +0 -11
  73. package/dist/examples/onto//345/210/251/346/266/246/347/244/272/344/276/213/function/test_arguments/profit.fn.yoy_analysis.json +0 -9
  74. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/functions/README.md +0 -25
  75. package/dist/examples/onto//351/224/200/345/224/256/347/244/272/344/276/213/setup/README.md +0 -5
@@ -0,0 +1,79 @@
1
+ """同比分析函数
2
+
3
+ 功能:对比当期与去年同期的利润指标变化
4
+ 参数:start_date, end_date
5
+
6
+ 放置:项目/DAZI_TEST/本体/ontos/利润分析示例/functions/profit_fn_yoy_analysis.py
7
+ 发布:dazi onto script publish 项目/DAZI_TEST/本体/ontos/利润分析示例/functions/profit_fn_yoy_analysis.py --space space__misc_01 --register-function-id profit.fn.yoy_analysis
8
+ """
9
+
10
+ from datetime import datetime, timedelta
11
+ import json
12
+
13
+
14
+ def _ontology_fn_body(p):
15
+ start_date = p.get("start_date", "2025-01-01")
16
+ end_date = p.get("end_date", datetime.now().strftime("%Y-%m-%d"))
17
+
18
+ start_dt = datetime.strptime(start_date, "%Y-%m-%d")
19
+ end_dt = datetime.strptime(end_date, "%Y-%m-%d")
20
+
21
+ last_year_start = (start_dt - timedelta(days=365)).strftime("%Y-%m-%d")
22
+ last_year_end = (end_dt - timedelta(days=365)).strftime("%Y-%m-%d")
23
+
24
+ output.print(f"当期: {start_date} ~ {end_date}")
25
+ output.print(f"去年同期: {last_year_start} ~ {last_year_end}")
26
+
27
+ def get_period_data(sd, ed):
28
+ sql = """
29
+ SELECT
30
+ sumIf(amount_signed, account_type='收入') as revenue,
31
+ sumIf(amount_signed, account_type='成本') as cost,
32
+ sumIf(amount_signed, account_type='费用') as expense
33
+ FROM fact_gl_journal_entry
34
+ WHERE posting_date >= '{sd}' AND posting_date <= '{ed}'
35
+ """.format(sd=sd, ed=ed)
36
+ result = p.space.sql.query_one(sql)
37
+ return {
38
+ "revenue": result.get("revenue", 0) or 0,
39
+ "cost": result.get("cost", 0) or 0,
40
+ "expense": result.get("expense", 0) or 0,
41
+ }
42
+
43
+ current = get_period_data(start_date, end_date)
44
+ last_year = get_period_data(last_year_start, last_year_end)
45
+
46
+ current_profit = current["revenue"] - current["cost"] - current["expense"]
47
+ last_year_profit = last_year["revenue"] - last_year["cost"] - last_year["expense"]
48
+
49
+ def calc_yoy(current, last):
50
+ if last == 0:
51
+ return 0 if current == 0 else None
52
+ return (current - last) / last
53
+
54
+ result = {
55
+ "period": f"{start_date} ~ {end_date}",
56
+ "last_year_period": f"{last_year_start} ~ {last_year_end}",
57
+ "current": {
58
+ "revenue": round(current["revenue"], 2),
59
+ "cost": round(current["cost"], 2),
60
+ "expense": round(current["expense"], 2),
61
+ "operating_profit": round(current_profit, 2),
62
+ },
63
+ "last_year": {
64
+ "revenue": round(last_year["revenue"], 2),
65
+ "cost": round(last_year["cost"], 2),
66
+ "expense": round(last_year["expense"], 2),
67
+ "operating_profit": round(last_year_profit, 2),
68
+ },
69
+ "yoy": {
70
+ "revenue": round(calc_yoy(current["revenue"], last_year["revenue"]), 4) if calc_yoy(current["revenue"], last_year["revenue"]) is not None else None,
71
+ "cost": round(calc_yoy(current["cost"], last_year["cost"]), 4) if calc_yoy(current["cost"], last_year["cost"]) is not None else None,
72
+ "expense": round(calc_yoy(current["expense"], last_year["expense"]), 4) if calc_yoy(current["expense"], last_year["expense"]) is not None else None,
73
+ "operating_profit": round(calc_yoy(current_profit, last_year_profit), 4) if calc_yoy(current_profit, last_year_profit) is not None else None,
74
+ },
75
+ }
76
+
77
+ p.function_result(result)
78
+
79
+
@@ -0,0 +1,38 @@
1
+ # save_test_arguments.ps1
2
+ # 批量保存各函数的 test_arguments
3
+ # 用法:在 dazi-work 根目录执行 .\项目\DAZI_TEST\本体\ontos\利润分析示例\functions\save_test_arguments.ps1
4
+
5
+ $spaceId = "space__misc_01"
6
+ $itemPath = "项目/DAZI_TEST/本体/ontos/利润分析示例/functions"
7
+
8
+ $functions = @(
9
+ @{fn_id="profit.fn.get_summary"; file="profit.fn.get_summary.json"},
10
+ @{fn_id="profit.fn.yoy_analysis"; file="profit.fn.yoy_analysis.json"},
11
+ @{fn_id="profit.fn.mom_analysis"; file="profit.fn.mom_analysis.json"},
12
+ @{fn_id="profit.fn.budget_vs_actual"; file="profit.fn.budget_vs_actual.json"},
13
+ @{fn_id="profit.fn.account_breakdown"; file="profit.fn.account_breakdown.json"},
14
+ @{fn_id="profit.fn.cost_center_profit"; file="profit.fn.cost_center_profit.json"},
15
+ @{fn_id="profit.fn.top_accounts"; file="profit.fn.top_accounts.json"}
16
+ )
17
+
18
+ Write-Host "获取函数列表..."
19
+ $fnList = & dazi onto function list --space $spaceId 2>$null
20
+
21
+ foreach ($fn in $functions) {
22
+ $fnId = $fn.fn_id
23
+ $fileName = $fn.file
24
+ $jsonPath = "$PSScriptRoot/test_arguments/$fileName"
25
+
26
+ $ofnId = $fnList | Where-Object { $_ -match $fnId } | ForEach-Object {
27
+ if ($_ -match 'id:\s*(\S+)') { $matches[1] }
28
+ }
29
+
30
+ if ($ofnId) {
31
+ Write-Host "保存 $fnId (ofnId=$ofnId)..."
32
+ & dazi onto function save-test-arguments $ofnId --space $spaceId --arguments-json-file $jsonPath
33
+ } else {
34
+ Write-Host "函数 $fnId 未找到,请先发布函数"
35
+ }
36
+ }
37
+
38
+ Write-Host "完成"
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30","account_type":"收入"},"object_type_code":"Account"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30"},"object_type_code":"BudgetAnalysis"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30"},"object_type_code":"CostCenter"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30"},"object_type_code":"ProfitAnalysis"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30"},"object_type_code":"ProfitAnalysis"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30","limit":10},"object_type_code":"Account"}
@@ -0,0 +1 @@
1
+ {"v":1,"meta":{"last_source":"admin_panel","last_saved_at":"2026-06-06T01:00:00.000000+00:00","last_input_shape":"json"},"arguments":{"start_date":"2025-01-01","end_date":"2026-06-30"},"object_type_code":"ProfitAnalysis"}
@@ -1,32 +1,176 @@
1
- """利润分析本体初始化脚本 — space__zlj
1
+ """利润分析本体初始化脚本 — space__misc_01
2
2
 
3
3
  初始化内容:
4
- 1. 创建物理表(4 张:科目表、成本中心维表、实际分录表、预算表)
5
- 2. 注册表到空间
6
- 3. 注册表间关系(6 条)
7
- 4. 注册 Cube(5 个)及派生度量
8
- 5. 定义对象类型(6 种)、绑定数据源、属性、链接
4
+ 1. 创建物理表(科目表、成本中心维表、实际分录表、预算表)
5
+ 2. 注册表到空间(含 display_name / description)
6
+ 3. 注册表间关系(7条)
7
+ 4. 注册Cube(5个)及派生度量
8
+ 5. 定义对象类型(6种)、绑定数据源、属性、链接
9
9
  6. 同步指标引用
10
+ 7. 配置 347 对齐分类(ads_categories + 桥表)
10
11
 
11
- 放置:项目/潘达石化/本体/ontos/利润分析本体方案/setup/profit_ontology_init.py
12
- 发布:dazi onto script publish 项目/潘达石化/本体/ontos/利润分析本体方案/setup/profit_ontology_init.py --space space__zlj --type setup
12
+ 放置:资源/examples/onto/利润示例/setup/profit_ontology_init.py(复制到项目 ontos/<实现名>/setup/)
13
+ 发布:dazi onto script publish <item-path>/setup/profit_ontology_init.py --space <space-id> --type setup
14
+ 规划对照:资源/docs/onto/规划示例_利润分析本体方案.md
13
15
  """
14
16
 
15
17
  import json
16
18
 
19
+ # 与 规划示例_利润分析本体方案.md §2.3、§3.x 对齐:display_name=侧栏显示名,description=业务说明
20
+ TABLE_REGISTRY = {
21
+ "dim_account": {
22
+ "display_name": "科目维表",
23
+ "description": "会计科目主数据",
24
+ "columns": [
25
+ {"name": "account_id", "display_name": "科目 ID", "description": "主键"},
26
+ {"name": "account_code", "display_name": "科目编码"},
27
+ {"name": "account_name", "display_name": "科目名称"},
28
+ {"name": "account_type", "display_name": "科目类型", "description": "资产/负债/权益/收入/成本/费用"},
29
+ {"name": "pl_category", "display_name": "损益大类"},
30
+ {"name": "parent_account_id", "display_name": "上级科目"},
31
+ {"name": "account_level", "display_name": "层级"},
32
+ {"name": "is_leaf", "display_name": "末级"},
33
+ {"name": "normal_balance", "display_name": "余额方向", "description": "借/贷"},
34
+ {"name": "status", "display_name": "状态", "description": "启用/停用"},
35
+ {"name": "created_at", "display_name": "创建时间"},
36
+ ],
37
+ },
38
+ "dim_cost_center": {
39
+ "display_name": "成本中心维表",
40
+ "description": "组织/利润中心主数据",
41
+ "columns": [
42
+ {"name": "cost_center_id", "display_name": "成本中心 ID", "description": "主键"},
43
+ {"name": "cost_center_code", "display_name": "编码"},
44
+ {"name": "cost_center_name", "display_name": "名称"},
45
+ {"name": "department", "display_name": "部门"},
46
+ {"name": "company_code", "display_name": "公司代码"},
47
+ {"name": "profit_center", "display_name": "利润中心"},
48
+ {"name": "status", "display_name": "状态"},
49
+ {"name": "created_at", "display_name": "创建时间"},
50
+ ],
51
+ },
52
+ "fact_gl_journal_entry": {
53
+ "display_name": "总账实际分录",
54
+ "description": "凭证行粒度损益流水",
55
+ "columns": [
56
+ {"name": "entry_id", "display_name": "凭证 ID"},
57
+ {"name": "line_id", "display_name": "凭证行 ID"},
58
+ {"name": "date_key", "display_name": "日期键", "description": "关联 dim_date,YYYYMMDD"},
59
+ {"name": "posting_date", "display_name": "记账日期"},
60
+ {"name": "fiscal_year", "display_name": "会计年度"},
61
+ {"name": "fiscal_period", "display_name": "会计期间"},
62
+ {"name": "account_id", "display_name": "科目 ID", "description": "关联 dim_account"},
63
+ {"name": "account_code", "display_name": "科目编码", "description": "冗余"},
64
+ {"name": "account_name", "display_name": "科目名称", "description": "冗余"},
65
+ {"name": "account_type", "display_name": "科目类型", "description": "冗余"},
66
+ {"name": "pl_category", "display_name": "损益大类", "description": "冗余"},
67
+ {"name": "account_level", "display_name": "科目层级", "description": "冗余"},
68
+ {"name": "cost_center_id", "display_name": "成本中心 ID", "description": "关联 dim_cost_center"},
69
+ {"name": "cost_center_name", "display_name": "成本中心", "description": "冗余"},
70
+ {"name": "department", "display_name": "部门", "description": "冗余"},
71
+ {"name": "profit_center", "display_name": "利润中心", "description": "冗余"},
72
+ {"name": "debit_amount", "display_name": "借方"},
73
+ {"name": "credit_amount", "display_name": "贷方"},
74
+ {"name": "amount_signed", "display_name": "损益金额", "description": "收入为正、成本费用为负"},
75
+ {"name": "currency", "display_name": "币种"},
76
+ {"name": "voucher_no", "display_name": "凭证号"},
77
+ {"name": "source_system", "display_name": "来源系统"},
78
+ {"name": "description", "display_name": "摘要", "description": "凭证行摘要文本"},
79
+ {"name": "created_at", "display_name": "创建时间"},
80
+ ],
81
+ },
82
+ "fact_budget_entry": {
83
+ "display_name": "预算明细",
84
+ "description": "预算行粒度编制数据",
85
+ "columns": [
86
+ {"name": "budget_id", "display_name": "预算批次"},
87
+ {"name": "line_id", "display_name": "预算行 ID"},
88
+ {"name": "date_key", "display_name": "日期键", "description": "关联 dim_date"},
89
+ {"name": "budget_version", "display_name": "预算版本"},
90
+ {"name": "fiscal_year", "display_name": "预算年度"},
91
+ {"name": "fiscal_period", "display_name": "预算期间", "description": "1-12"},
92
+ {"name": "account_id", "display_name": "科目 ID", "description": "关联 dim_account"},
93
+ {"name": "account_code", "display_name": "科目编码", "description": "冗余"},
94
+ {"name": "account_name", "display_name": "科目名称", "description": "冗余"},
95
+ {"name": "account_type", "display_name": "科目类型", "description": "冗余"},
96
+ {"name": "pl_category", "display_name": "损益大类", "description": "冗余"},
97
+ {"name": "cost_center_id", "display_name": "成本中心 ID", "description": "关联 dim_cost_center"},
98
+ {"name": "cost_center_name", "display_name": "成本中心", "description": "冗余"},
99
+ {"name": "department", "display_name": "部门", "description": "冗余"},
100
+ {"name": "budget_amount", "display_name": "预算金额"},
101
+ {"name": "currency", "display_name": "币种"},
102
+ {"name": "status", "display_name": "状态", "description": "草稿/已发布"},
103
+ {"name": "created_at", "display_name": "创建时间"},
104
+ ],
105
+ },
106
+ }
107
+
108
+ # 与 347 + 附录 B 对齐;category 值必须是 347 标准中文名
109
+ CATEGORY_REGISTRY = {
110
+ "table": {
111
+ "维度表": ["dim_account", "dim_cost_center"],
112
+ "事实表": ["fact_gl_journal_entry", "fact_budget_entry"],
113
+ },
114
+ "cube": {
115
+ "流程型": ["ActualCube", "BudgetCube"],
116
+ "主体型": ["AccountActualCube", "CostCenterActualCube", "TimeActualCube"],
117
+ },
118
+ "object": {
119
+ "主数据": ["Account", "CostCenter"],
120
+ "事务": ["JournalEntry", "BudgetLine"],
121
+ "分析": ["ProfitAnalysis", "BudgetAnalysis"],
122
+ },
123
+ "relation": {
124
+ "时间关联": [
125
+ ("fact_gl_journal_entry", "dim_date"),
126
+ ("fact_budget_entry", "dim_date"),
127
+ ],
128
+ "主数据关联": [
129
+ ("fact_gl_journal_entry", "dim_account"),
130
+ ("fact_gl_journal_entry", "dim_cost_center"),
131
+ ("fact_budget_entry", "dim_account"),
132
+ ("fact_budget_entry", "dim_cost_center"),
133
+ ],
134
+ "层级自关联": [("dim_account", "dim_account")],
135
+ },
136
+ "link": {
137
+ "归属关系": [
138
+ "entry_belongs_account",
139
+ "entry_belongs_cost_center",
140
+ "budget_for_account",
141
+ "budget_for_cost_center",
142
+ ],
143
+ "分析归因": [
144
+ "analysis_by_account",
145
+ "analysis_by_cost_center",
146
+ "account_contributes_profit",
147
+ "cost_center_contributes_profit",
148
+ ],
149
+ "层级关系": ["account_has_parent"],
150
+ "对比关系": ["budget_compared_to_actual"],
151
+ },
152
+ "function": {
153
+ "总览分析": ["profit.fn.get_summary"],
154
+ "趋势分析": ["profit.fn.yoy_analysis", "profit.fn.mom_analysis"],
155
+ "结构分析": ["profit.fn.account_breakdown", "profit.fn.top_accounts"],
156
+ "预实分析": ["profit.fn.budget_vs_actual"],
157
+ "组织分析": ["profit.fn.cost_center_profit"],
158
+ },
159
+ }
160
+
17
161
 
18
162
  def main():
19
- space_id = "space__zlj"
163
+ space_id = "space__misc_01"
20
164
  s = space.get(space_id)
21
165
 
22
166
  output.print("=== 利润分析本体初始化 ===")
23
167
  output.print(f"空间: {space_id}")
24
168
 
25
169
  # 1. 创建物理表
26
- output.print("\n[1/9] 创建物理表...")
170
+ output.print("\n[1/10] 创建物理表...")
27
171
 
28
172
  s.sql.execute("""
29
- CREATE TABLE IF NOT EXISTS account_master (
173
+ CREATE TABLE IF NOT EXISTS dim_account (
30
174
  account_id String,
31
175
  account_code String,
32
176
  account_name String,
@@ -41,10 +185,10 @@ def main():
41
185
  ) ENGINE = MergeTree()
42
186
  ORDER BY (account_code)
43
187
  """)
44
- output.print("OK account_master")
188
+ output.print("OK dim_account")
45
189
 
46
190
  s.sql.execute("""
47
- CREATE TABLE IF NOT EXISTS cost_center_dimension (
191
+ CREATE TABLE IF NOT EXISTS dim_cost_center (
48
192
  cost_center_id String,
49
193
  cost_center_code String,
50
194
  cost_center_name String,
@@ -56,12 +200,13 @@ def main():
56
200
  ) ENGINE = MergeTree()
57
201
  ORDER BY (cost_center_id)
58
202
  """)
59
- output.print("OK cost_center_dimension")
203
+ output.print("OK dim_cost_center")
60
204
 
61
205
  s.sql.execute("""
62
- CREATE TABLE IF NOT EXISTS actual_journal_entry (
206
+ CREATE TABLE IF NOT EXISTS fact_gl_journal_entry (
63
207
  entry_id String,
64
208
  line_id String,
209
+ date_key Int32,
65
210
  posting_date Date,
66
211
  fiscal_year Int32,
67
212
  fiscal_period Int32,
@@ -84,14 +229,15 @@ def main():
84
229
  description String,
85
230
  created_at DateTime DEFAULT now()
86
231
  ) ENGINE = MergeTree()
87
- ORDER BY (posting_date, entry_id, line_id)
232
+ ORDER BY (date_key, entry_id, line_id)
88
233
  """)
89
- output.print("OK actual_journal_entry")
234
+ output.print("OK fact_gl_journal_entry")
90
235
 
91
236
  s.sql.execute("""
92
- CREATE TABLE IF NOT EXISTS budget_entry (
237
+ CREATE TABLE IF NOT EXISTS fact_budget_entry (
93
238
  budget_id String,
94
239
  line_id String,
240
+ date_key Int32,
95
241
  budget_version String,
96
242
  fiscal_year Int32,
97
243
  fiscal_period Int32,
@@ -110,88 +256,91 @@ def main():
110
256
  ) ENGINE = MergeTree()
111
257
  ORDER BY (fiscal_year, fiscal_period, account_id, line_id)
112
258
  """)
113
- output.print("OK budget_entry")
114
-
115
- # 2. 注册表
116
- output.print("\n[2/9] 注册表到空间...")
117
-
118
- for tbl, label in [
119
- ("account_master", "会计科目主数据表"),
120
- ("cost_center_dimension", "成本中心维表"),
121
- ("actual_journal_entry", "实际分录事实表"),
122
- ("budget_entry", "预算事实表"),
123
- ]:
124
- s.tables.register(tbl, label=label)
125
- s.tables.sync_columns(tbl)
126
- output.print(f"OK {tbl}")
259
+ output.print("OK fact_budget_entry")
260
+
261
+ # 2. 注册表(含 display_name / description)
262
+ output.print("\n[2/10] 注册表到空间...")
263
+
264
+ for tbl_name, meta in TABLE_REGISTRY.items():
265
+ s.tables.register_with_meta(
266
+ table_name=tbl_name,
267
+ display_name=meta["display_name"],
268
+ description=meta.get("description"),
269
+ columns=meta["columns"],
270
+ force_column_meta=True,
271
+ )
272
+ output.print(f"OK {tbl_name} ({meta['display_name']})")
127
273
 
128
274
  # 3. 注册表间关系
129
- output.print("\n[3/9] 注册表间关系...")
275
+ output.print("\n[3/10] 注册表间关系...")
130
276
 
131
277
  table_relationships = [
132
278
  {
133
- "from_table": "actual_journal_entry",
134
- "to_table": "account_master",
135
- "join_sql": "actual_journal_entry.account_id = account_master.account_id",
279
+ "from_table": "fact_gl_journal_entry",
280
+ "to_table": "dim_date",
281
+ "join_sql": "fact_gl_journal_entry.date_key = dim_date.date_key",
282
+ "join_keys": [{"from": "date_key", "to": "date_key"}],
283
+ "relationship_type": "many_to_one",
284
+ "description": "分录关联日历",
285
+ },
286
+ {
287
+ "from_table": "fact_budget_entry",
288
+ "to_table": "dim_date",
289
+ "join_sql": "fact_budget_entry.date_key = dim_date.date_key",
290
+ "join_keys": [{"from": "date_key", "to": "date_key"}],
291
+ "relationship_type": "many_to_one",
292
+ "description": "预算关联日历",
293
+ },
294
+ {
295
+ "from_table": "fact_gl_journal_entry",
296
+ "to_table": "dim_account",
297
+ "join_sql": "fact_gl_journal_entry.account_id = dim_account.account_id",
136
298
  "join_keys": [{"from": "account_id", "to": "account_id"}],
137
299
  "relationship_type": "many_to_one",
138
300
  "description": "实际分录关联会计科目",
139
301
  },
140
302
  {
141
- "from_table": "actual_journal_entry",
142
- "to_table": "cost_center_dimension",
143
- "join_sql": "actual_journal_entry.cost_center_id = cost_center_dimension.cost_center_id",
303
+ "from_table": "fact_gl_journal_entry",
304
+ "to_table": "dim_cost_center",
305
+ "join_sql": "fact_gl_journal_entry.cost_center_id = dim_cost_center.cost_center_id",
144
306
  "join_keys": [{"from": "cost_center_id", "to": "cost_center_id"}],
145
307
  "relationship_type": "many_to_one",
146
308
  "description": "实际分录关联成本中心",
147
309
  },
148
310
  {
149
- "from_table": "budget_entry",
150
- "to_table": "account_master",
151
- "join_sql": "budget_entry.account_id = account_master.account_id",
311
+ "from_table": "fact_budget_entry",
312
+ "to_table": "dim_account",
313
+ "join_sql": "fact_budget_entry.account_id = dim_account.account_id",
152
314
  "join_keys": [{"from": "account_id", "to": "account_id"}],
153
315
  "relationship_type": "many_to_one",
154
316
  "description": "预算关联会计科目",
155
317
  },
156
318
  {
157
- "from_table": "budget_entry",
158
- "to_table": "cost_center_dimension",
159
- "join_sql": "budget_entry.cost_center_id = cost_center_dimension.cost_center_id",
319
+ "from_table": "fact_budget_entry",
320
+ "to_table": "dim_cost_center",
321
+ "join_sql": "fact_budget_entry.cost_center_id = dim_cost_center.cost_center_id",
160
322
  "join_keys": [{"from": "cost_center_id", "to": "cost_center_id"}],
161
323
  "relationship_type": "many_to_one",
162
324
  "description": "预算关联成本中心",
163
325
  },
164
326
  {
165
- "from_table": "account_master",
166
- "to_table": "account_master",
167
- "join_sql": "account_master.parent_account_id = account_master.account_id",
327
+ "from_table": "dim_account",
328
+ "to_table": "dim_account",
329
+ "join_sql": "dim_account.parent_account_id = dim_account.account_id",
168
330
  "join_keys": [{"from": "parent_account_id", "to": "account_id"}],
169
331
  "relationship_type": "many_to_one",
170
332
  "description": "科目上级(树形)",
171
333
  },
172
- {
173
- "from_table": "budget_entry",
174
- "to_table": "actual_journal_entry",
175
- "join_sql": "budget_entry.account_id = actual_journal_entry.account_id AND budget_entry.cost_center_id = actual_journal_entry.cost_center_id AND budget_entry.fiscal_year = actual_journal_entry.fiscal_year AND budget_entry.fiscal_period = actual_journal_entry.fiscal_period",
176
- "join_keys": [
177
- {"from": "account_id", "to": "account_id"},
178
- {"from": "cost_center_id", "to": "cost_center_id"},
179
- {"from": "fiscal_year", "to": "fiscal_year"},
180
- {"from": "fiscal_period", "to": "fiscal_period"},
181
- ],
182
- "relationship_type": "many_to_one",
183
- "description": "预算与实际同维对齐",
184
- },
185
334
  ]
186
335
  for rel in table_relationships:
187
336
  rid = s.tables.add_relationship(**rel)
188
- output.print(f"OK {rel['from_table']} -> {rel['to_table']} ({rid})")
337
+ output.print(f"OK {rel['from_table']} -> {rel['to_table']}")
189
338
 
190
339
  # 4. 注册 Cube
191
- output.print("\n[4/9] 注册 Cube...")
340
+ output.print("\n[4/10] 注册 Cube...")
192
341
 
193
- actual = "actual_journal_entry"
194
- budget = "budget_entry"
342
+ actual = "fact_gl_journal_entry"
343
+ budget = "fact_budget_entry"
195
344
 
196
345
  s.register_cube(
197
346
  name="ActualCube",
@@ -206,6 +355,7 @@ def main():
206
355
  dimensions=[
207
356
  {"name": "entry_id", "col": "entry_id", "type": "string", "title": "凭证ID"},
208
357
  {"name": "line_id", "col": "line_id", "type": "string", "title": "行ID"},
358
+ {"name": "date_key", "col": "date_key", "type": "int", "title": "日期键"},
209
359
  {"name": "posting_date", "col": "posting_date", "type": "date", "title": "记账日期"},
210
360
  {"name": "fiscal_year", "col": "fiscal_year", "type": "int", "title": "会计年度"},
211
361
  {"name": "fiscal_period", "col": "fiscal_period", "type": "int", "title": "会计期间"},
@@ -294,17 +444,16 @@ def main():
294
444
  {"name": "line_count", "col": "line_id", "agg": "count", "title": "分录行数"},
295
445
  ],
296
446
  dimensions=[
447
+ {"name": "date_key", "col": "date_key", "type": "int", "title": "日期键"},
297
448
  {"name": "posting_date", "col": "posting_date", "type": "date", "title": "记账日期"},
298
449
  {"name": "fiscal_year", "col": "fiscal_year", "type": "int", "title": "会计年度"},
299
450
  {"name": "fiscal_period", "col": "fiscal_period", "type": "int", "title": "会计期间"},
300
- {"name": "year_month", "col": "posting_date", "type": "string", "title": "年月"},
301
- {"name": "quarter", "col": "posting_date", "type": "string", "title": "季度"},
302
451
  ],
303
452
  )
304
453
  output.print("OK TimeActualCube")
305
454
 
306
455
  # 5. 派生度量
307
- output.print("\n[5/9] 配置派生度量...")
456
+ output.print("\n[5/10] 配置派生度量...")
308
457
 
309
458
  def _pl_measures(cube_name, full=False):
310
459
  base = [
@@ -375,7 +524,7 @@ def main():
375
524
  output.print("OK 派生度量")
376
525
 
377
526
  # 6. 对象类型
378
- output.print("\n[6/9] 定义对象类型...")
527
+ output.print("\n[6/10] 定义对象类型...")
379
528
 
380
529
  object_types = [
381
530
  ("Account", "会计科目", "会计科目业务对象"),
@@ -390,7 +539,7 @@ def main():
390
539
  output.print(f"OK {code}")
391
540
 
392
541
  # 7. 绑定数据源
393
- output.print("\n[7/9] 绑定数据源...")
542
+ output.print("\n[7/10] 绑定数据源...")
394
543
 
395
544
  bindings = [
396
545
  ("Account", "AccountActualCube"),
@@ -405,7 +554,7 @@ def main():
405
554
  output.print(f"OK {obj} -> {cube}")
406
555
 
407
556
  # 8. 属性
408
- output.print("\n[8/9] 定义属性...")
557
+ output.print("\n[8/10] 定义属性...")
409
558
 
410
559
  def define_props(obj_code, props):
411
560
  for code, name, role, qn in props:
@@ -478,7 +627,7 @@ def main():
478
627
  output.print("OK BudgetAnalysis 属性 (6)")
479
628
 
480
629
  # 9. 链接类型
481
- output.print("\n[9/9] 定义链接与同步指标...")
630
+ output.print("\n[9/10] 定义链接与同步指标...")
482
631
 
483
632
  link_types = [
484
633
  ("entry_belongs_account", "分录归属科目", "JournalEntry", "Account", "分录行对应科目"),
@@ -505,17 +654,26 @@ def main():
505
654
  s.sync_metric_refs()
506
655
  output.print("OK sync_metric_refs")
507
656
 
657
+ # 10. 347 对齐分类
658
+ output.print("\n[10/10] 配置对象分类(347 对齐)...")
659
+
660
+ cat_counts = s.categories.apply_registry(CATEGORY_REGISTRY, skip_missing=True)
661
+ for kind, cnt in cat_counts.items():
662
+ if cnt:
663
+ output.print(f"OK 分类[{kind}] 挂载 {cnt} 项")
664
+
508
665
  summary = {
509
666
  "ok": True,
510
667
  "space_id": space_id,
511
668
  "tables": 4,
512
- "table_relationships": 6,
669
+ "table_relationships": 7,
513
670
  "cubes": 5,
514
671
  "object_types": 6,
515
672
  "properties": 42,
516
673
  "link_types": 10,
674
+ "category_mounts": cat_counts,
517
675
  }
518
676
 
519
677
  output.print("\n=== 利润分析本体初始化完成 ===")
520
678
  output.success("初始化成功")
521
- output.print("__JSON_SUMMARY__" + json.dumps(summary, ensure_ascii=True, default=str))
679
+ output.print("__JSON_SUMMARY__" + json.dumps(summary, ensure_ascii=True, default=str))