cnhkmcp 2.3.4__py3-none-any.whl → 2.3.6__py3-none-any.whl

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 (60) hide show
  1. cnhkmcp/__init__.py +1 -1
  2. cnhkmcp/untracked/AI/321/206/320/231/320/243/321/205/342/225/226/320/265/321/204/342/225/221/342/225/221/BRAIN_AI/321/206/320/231/320/243/321/205/342/225/226/320/265/321/204/342/225/221/342/225/221Mac_Linux/321/207/320/231/320/230/321/206/320/254/320/274.zip +0 -0
  3. cnhkmcp/untracked/AI/321/206/320/231/320/243/321/205/342/225/226/320/265/321/204/342/225/221/342/225/221//321/205/320/237/320/234/321/205/320/227/342/225/227/321/205/320/276/320/231/321/210/320/263/320/225AI/321/206/320/231/320/243/321/205/342/225/226/320/265/321/204/342/225/221/342/225/221_Windows/321/207/320/231/320/230/321/206/320/254/320/274.exe +0 -0
  4. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/main.py +6 -3
  5. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242//321/211/320/266/320/246/321/206/320/274/320/261/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +5 -3
  6. cnhkmcp/untracked/APP/Tranformer/validator.py +149 -32
  7. cnhkmcp/untracked/APP/ace_lib.py +5 -3
  8. cnhkmcp/untracked/APP/blueprints/parsetab.py +60 -0
  9. cnhkmcp/untracked/APP/blueprints/validator.py +1080 -0
  10. cnhkmcp/untracked/APP/requirements.txt +0 -0
  11. cnhkmcp/untracked/APP/static/decoder.js +164 -38
  12. cnhkmcp/untracked/APP/static/simulator.js +15 -15
  13. cnhkmcp/untracked/APP/templates/feature_engineering.html +78 -78
  14. cnhkmcp/untracked/APP/templates/idea_house.html +49 -49
  15. cnhkmcp/untracked/APP/templates/index.html +58 -58
  16. cnhkmcp/untracked/APP/templates/inspiration_house.html +64 -64
  17. cnhkmcp/untracked/APP/templates/paper_analysis.html +21 -21
  18. cnhkmcp/untracked/APP/templates/simulator.html +38 -38
  19. cnhkmcp/untracked/APP/templates/transformer_web.html +24 -24
  20. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/SKILL.md +15 -15
  21. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927658009727000.json +10 -0
  22. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927658519220600.json +10 -0
  23. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659002708800.json +10 -0
  24. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659510920900.json +10 -0
  25. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659982673800.json +10 -0
  26. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/validator.py +153 -32
  27. cnhkmcp/untracked/APP//321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +51 -3
  28. cnhkmcp/untracked/arxiv_api.py +5 -3
  29. cnhkmcp/untracked/back_up/forum_functions.py +5 -3
  30. cnhkmcp/untracked/forum_functions.py +5 -3
  31. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/forum_functions.py +5 -3
  32. cnhkmcp/untracked/platform_functions.py +5 -3
  33. cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/validator.py +149 -32
  34. cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/SKILL.md +8 -2
  35. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/ace.log +0 -0
  36. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/idea_context.json +15 -0
  37. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/__init__.py +0 -0
  38. cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/parse_idea_file.py +33 -1
  39. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/parsetab.py +60 -0
  40. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/validator.py +1086 -0
  41. cnhkmcp/untracked//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +5 -3
  42. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/METADATA +1 -1
  43. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/RECORD +60 -48
  44. /cnhkmcp/untracked/{skills/brain-inspectTemplate-create-Setting → APP/hkSimulator}/ace.log +0 -0
  45. /cnhkmcp/untracked/{skills/brain-inspectTemplate-create-Setting/scripts/__init__.py → APP/hkSimulator/autosim_20260201_172428.log} +0 -0
  46. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/.gitignore +0 -0
  47. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/ace_lib.py +0 -0
  48. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/config.json +0 -0
  49. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/fundamental28_GLB_1_idea_1769874845978315000.json +0 -0
  50. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/helpful_functions.py +0 -0
  51. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/build_alpha_list.py +0 -0
  52. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/fetch_sim_options.py +0 -0
  53. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/load_credentials.py +0 -0
  54. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/process_template.py +0 -0
  55. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/resolve_settings.py +0 -0
  56. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/sim_options_snapshot.json +0 -0
  57. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/WHEEL +0 -0
  58. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/entry_points.txt +0 -0
  59. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/licenses/LICENSE +0 -0
  60. {cnhkmcp-2.3.4.dist-info → cnhkmcp-2.3.6.dist-info}/top_level.txt +0 -0
cnhkmcp/__init__.py CHANGED
@@ -50,7 +50,7 @@ from .untracked.forum_functions import (
50
50
  read_full_forum_post
51
51
  )
52
52
 
53
- __version__ = "2.3.4"
53
+ __version__ = "2.3.6"
54
54
  __author__ = "CNHK"
55
55
  __email__ = "cnhk@example.com"
56
56
 
@@ -14,9 +14,12 @@ import sys
14
14
 
15
15
  # try set gbk problem
16
16
  try:
17
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
18
- except Exception as e:
19
- print(e)
17
+ import io
18
+ if hasattr(sys.stdout, 'reconfigure'):
19
+ sys.stdout.reconfigure(encoding='utf-8')
20
+ elif hasattr(sys.stdout, 'buffer'):
21
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
22
+ except Exception:
20
23
  pass
21
24
 
22
25
  # --- Auto-Install Dependencies ---
@@ -18,9 +18,11 @@ import io
18
18
 
19
19
  # try set gbk problem
20
20
  try:
21
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
22
- except Exception as e:
23
- print(e)
21
+ if hasattr(sys.stdout, 'reconfigure'):
22
+ sys.stdout.reconfigure(encoding='utf-8')
23
+ elif hasattr(sys.stdout, 'buffer'):
24
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
25
+ except Exception:
24
26
  pass
25
27
 
26
28
  # 获取脚本所在目录(项目根目录)
@@ -65,51 +65,65 @@ supported_functions = {
65
65
  'ts_delay': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
66
66
  'ts_corr': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
67
67
  'ts_zscore': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
68
- 'ts_returns': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'mode']},
68
+ 'ts_returns': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'mode'], 'keyword_only': True},
69
69
  'ts_product': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
70
- 'ts_backfill': {'min_args': 2, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'string']},
70
+ # Platform: ts_backfill(x, d)
71
+ 'ts_backfill': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
71
72
  'days_from_last_change': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
72
73
  'last_diff_value': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
73
- 'ts_scale': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
74
- 'ts_entropy': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'd', 'buckets']},
74
+ # Platform: ts_scale(x, d, constant=0)
75
+ 'ts_scale': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
76
+ # Platform: ts_entropy(x, d)
77
+ 'ts_entropy': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
75
78
  'ts_step': {'min_args': 1, 'max_args': 1, 'arg_types': ['number']},
76
79
  'ts_sum': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
77
80
  'ts_co_kurtosis': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
78
81
  'inst_tvr': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
79
- 'ts_decay_exp_window': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'factor']},
82
+ 'ts_decay_exp_window': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'factor'], 'keyword_only': True},
80
83
  'ts_av_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
81
84
  'ts_kurtosis': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
82
- 'ts_min_max_diff': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
85
+ # Platform: ts_min_max_diff(x, d, f=0.5)
86
+ 'ts_min_max_diff': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
83
87
  'ts_arg_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
84
88
  'ts_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
85
- 'ts_min_max_cps': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
86
- 'ts_rank': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
89
+ # Platform: ts_min_max_cps(x, d, f=2)
90
+ 'ts_min_max_cps': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
91
+ # Platform: ts_rank(x, d, constant=0)
92
+ 'ts_rank': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
87
93
  'ts_ir': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
88
94
  'ts_theilsen': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
89
- 'hump_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number']},
90
- 'ts_weighted_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number']},
91
- 'ts_quantile': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'string']},
95
+ # Platform: hump_decay(x, p=0)
96
+ 'hump_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'p'], 'keyword_only': True},
97
+ # Platform: ts_weighted_decay(x, k=0.5)
98
+ 'ts_weighted_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'k'], 'keyword_only': True},
99
+ # Platform: ts_quantile(x, d, driver="gaussian")
100
+ 'ts_quantile': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'string'], 'param_names': ['x', 'd', 'driver'], 'keyword_only': True},
92
101
  'ts_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
93
102
  'ts_count_nans': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
94
103
  'ts_covariance': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
95
104
  'ts_co_skewness': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
96
105
  'ts_min_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
97
- 'ts_decay_linear': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'boolean']},
98
- 'jump_decay': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'number', 'expression', 'number', 'number'], 'param_names': ['x', 'd', 'stddev', 'sensitivity', 'force']},
99
- 'ts_moment': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'k']},
106
+ # Platform: ts_decay_linear(x, d, dense=false)
107
+ 'ts_decay_linear': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'boolean'], 'param_names': ['x', 'd', 'dense'], 'keyword_only': True},
108
+ # Platform: jump_decay(x, d, sensitivity=0.5, force=0.1)
109
+ 'jump_decay': {'min_args': 2, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'd', 'sensitivity', 'force'], 'keyword_only': True},
110
+ # Platform: ts_moment(x, d, k=0)
111
+ 'ts_moment': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'k'], 'keyword_only': True},
100
112
  'ts_arg_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
101
- 'ts_regression': {'min_args': 3, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'lag', 'rettype']},
113
+ 'ts_regression': {'min_args': 3, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'lag', 'rettype'], 'keyword_only': True},
102
114
  'ts_skewness': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
103
115
  'ts_max_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
104
116
  'kth_element': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
105
117
  'hump': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'hump']},
106
118
  'ts_median': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
107
119
  'ts_delta': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
108
- 'ts_poly_regression': {'min_args': 3, 'max_args': 4, 'arg_types': ['expression', 'expression', 'number', 'number']},
109
- 'ts_target_tvr_decay': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr']},
110
- 'ts_target_tvr_delta_limit': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number']},
111
- 'ts_target_tvr_hump': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number']},
112
- 'ts_delta_limit': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
120
+ # Platform: ts_poly_regression(y, x, d, k=1) and k must be keyword if provided
121
+ 'ts_poly_regression': {'min_args': 3, 'max_args': 4, 'arg_types': ['expression', 'expression', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'k'], 'keyword_only': True, 'keyword_only_from': 3},
122
+ 'ts_target_tvr_decay': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
123
+ 'ts_target_tvr_delta_limit': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['x', 'y', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
124
+ 'ts_target_tvr_hump': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
125
+ # Platform: ts_delta_limit(x, y, limit_volume=0.1)
126
+ 'ts_delta_limit': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'y', 'limit_volume'], 'keyword_only': True},
113
127
 
114
128
  # Special 类别函数
115
129
  'inst_pnl': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
@@ -276,6 +290,10 @@ class ExpressionValidator:
276
290
  self.parser = yacc.yacc(module=self, debug=False)
277
291
  # 错误信息存储
278
292
  self.errors = []
293
+ # Cache for unit inference (unit/scalar/category)
294
+ self._unit_cache: Dict[int, str] = {}
295
+ # Cache for derived category detection (bucket/group_cartesian_product outputs)
296
+ self._derived_category_cache: Dict[int, bool] = {}
279
297
 
280
298
  # 词法分析器规则
281
299
  tokens = ('FUNCTION', 'FIELD', 'NUMBER', 'LPAREN', 'RPAREN',
@@ -509,6 +527,12 @@ class ExpressionValidator:
509
527
  return self._validate_add(args, is_in_group_arg)
510
528
 
511
529
  errors = []
530
+
531
+ # Keyword-only enforcement for optional parameters.
532
+ # If enabled, only the required leading arguments can be positional.
533
+ keyword_only_from = function_info.get('keyword_only_from')
534
+ if keyword_only_from is None and function_info.get('keyword_only'):
535
+ keyword_only_from = function_info.get('min_args', 0)
512
536
 
513
537
  # 检查参数数量
514
538
  if len(args) < function_info['min_args']:
@@ -545,12 +569,14 @@ class ExpressionValidator:
545
569
  errors.append(f"函数 {function_name} 不存在参数 '{arg['name']}'")
546
570
  elif arg['type'] == 'positional':
547
571
  # 位置参数(字典形式)
548
- # 对于winsorize函数,第二个参数必须是命名参数
549
- if function_name == 'winsorize' and positional_index == 1:
550
- errors.append(f"函数 {function_name} 的第二个参数必须使用命名参数 'std='")
551
- # 对于ts_moment函数,第三个参数必须是命名参数
552
- elif function_name == 'ts_moment' and positional_index == 2:
553
- errors.append(f"函数 {function_name} 的第三个参数必须使用命名参数 'k='")
572
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
573
+ param_name = None
574
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
575
+ param_name = function_info['param_names'][positional_index]
576
+ if param_name:
577
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
578
+ else:
579
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
554
580
  else:
555
581
  # 验证位置参数的类型
556
582
  if positional_index < len(function_info['arg_types']):
@@ -564,12 +590,14 @@ class ExpressionValidator:
564
590
  positional_index += 1
565
591
  else:
566
592
  # 位置参数(直接ASTNode形式)
567
- # 对于winsorize函数,第二个参数必须是命名参数
568
- if function_name == 'winsorize' and positional_index == 1:
569
- errors.append(f"函数 {function_name} 的第二个参数必须使用命名参数 'std='")
570
- # 对于ts_moment函数,第三个参数必须是命名参数
571
- elif function_name == 'ts_moment' and positional_index == 2:
572
- errors.append(f"函数 {function_name} 的第三个参数必须使用命名参数 'k='")
593
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
594
+ param_name = None
595
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
596
+ param_name = function_info['param_names'][positional_index]
597
+ if param_name:
598
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
599
+ else:
600
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
573
601
  else:
574
602
  # 验证位置参数的类型
575
603
  if positional_index < len(function_info['arg_types']):
@@ -583,6 +611,15 @@ class ExpressionValidator:
583
611
  def _validate_arg_type(self, arg: ASTNode, expected_type: str, arg_index: int, function_name: str, is_in_group_arg: bool = False) -> List[str]:
584
612
  """验证参数类型是否符合预期"""
585
613
  errors = []
614
+
615
+ # Unit compatibility check
616
+ # bucket()/group_cartesian_product() output a derived category (grouping key).
617
+ # It can only be consumed where a category/grouping key is expected.
618
+ if self._is_derived_category(arg) and expected_type != 'category':
619
+ errors.append(
620
+ f"Incompatible unit for input of \"{function_name}\" at index {arg_index}, expected \"Unit[]\", found \"Unit[Group:1]\""
621
+ )
622
+ return errors
586
623
 
587
624
  # 首先检查是否是group类型字段,如果是则只能用于Group类型函数
588
625
  # 但是如果当前函数是group_xxx或在group函数的参数链中,则允许使用
@@ -618,6 +655,83 @@ class ExpressionValidator:
618
655
 
619
656
  return errors
620
657
 
658
+ def _infer_unit(self, node: ASTNode) -> str:
659
+ """Infer the Unit kind of an AST node.
660
+
661
+ Returns:
662
+ 'unit' - regular numeric time-series Unit[]
663
+ 'scalar' - literals (numbers/booleans/strings)
664
+ 'category' - category/grouping keys (industry/sector or derived via bucket/cartesian)
665
+ """
666
+ if node is None:
667
+ return 'unit'
668
+
669
+ cache_key = id(node)
670
+ cached = self._unit_cache.get(cache_key)
671
+ if cached is not None:
672
+ return cached
673
+
674
+ unit = 'unit'
675
+
676
+ if node.node_type in {'number', 'boolean', 'string'}:
677
+ unit = 'scalar'
678
+ elif node.node_type in {'field', 'identifier'}:
679
+ unit = 'unit'
680
+ elif node.node_type == 'category':
681
+ unit = 'category'
682
+ elif node.node_type in {'unop', 'binop'}:
683
+ child_units = [self._infer_unit(child) for child in node.children if hasattr(child, 'node_type')]
684
+ unit = 'category' if 'category' in child_units else 'unit'
685
+ elif node.node_type == 'function':
686
+ fname = node.value
687
+ if fname in {'bucket', 'group_cartesian_product'}:
688
+ unit = 'category'
689
+ else:
690
+ first_arg = None
691
+ for child in node.children:
692
+ if isinstance(child, dict):
693
+ if child.get('type') == 'positional':
694
+ first_arg = child.get('value')
695
+ break
696
+ else:
697
+ first_arg = child
698
+ break
699
+ if hasattr(first_arg, 'node_type'):
700
+ unit = self._infer_unit(first_arg)
701
+ else:
702
+ unit = 'unit'
703
+
704
+ self._unit_cache[cache_key] = unit
705
+ return unit
706
+
707
+ def _is_derived_category(self, node: ASTNode) -> bool:
708
+ """Return True if node is a derived category/grouping key (e.g., bucket/cartesian output)."""
709
+ if node is None:
710
+ return False
711
+
712
+ cache_key = id(node)
713
+ cached = self._derived_category_cache.get(cache_key)
714
+ if cached is not None:
715
+ return cached
716
+
717
+ derived = False
718
+ if node.node_type == 'function' and node.value in {'bucket', 'group_cartesian_product'}:
719
+ derived = True
720
+ elif node.node_type in {'unop', 'binop'}:
721
+ derived = any(
722
+ self._is_derived_category(child)
723
+ for child in node.children
724
+ if hasattr(child, 'node_type')
725
+ )
726
+ elif node.node_type == 'function':
727
+ derived = any(
728
+ self._is_derived_category(child.get('value')) if isinstance(child, dict) else self._is_derived_category(child)
729
+ for child in node.children
730
+ )
731
+
732
+ self._derived_category_cache[cache_key] = derived
733
+ return derived
734
+
621
735
  def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
622
736
  """Validate add(x, y, ..., filter=false).
623
737
 
@@ -883,6 +997,9 @@ class ExpressionValidator:
883
997
  """
884
998
  # 重置错误列表
885
999
  self.errors = []
1000
+ # Reset unit inference cache for this expression
1001
+ self._unit_cache = {}
1002
+ self._derived_category_cache = {}
886
1003
 
887
1004
  try:
888
1005
  expression = expression.strip()
@@ -17,9 +17,11 @@ import sys
17
17
  import io
18
18
  # try set gbk problem
19
19
  try:
20
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
21
- except Exception as e:
22
- print(e)
20
+ if hasattr(sys.stdout, 'reconfigure'):
21
+ sys.stdout.reconfigure(encoding='utf-8')
22
+ elif hasattr(sys.stdout, 'buffer'):
23
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
24
+ except Exception:
23
25
  pass
24
26
  from helpful_functions import (
25
27
  expand_dict_columns,
@@ -0,0 +1,60 @@
1
+
2
+ # parsetab.py
3
+ # This file is automatically generated. Do not edit.
4
+ # pylint: disable=W,C,R
5
+ _tabversion = '3.10'
6
+
7
+ _lr_method = 'LALR'
8
+
9
+ _lr_signature = 'ASSIGN BOOLEAN CATEGORY COMMA DIVIDE EQUAL FIELD FUNCTION GREATER GREATEREQUAL IDENTIFIER LESS LESSEQUAL LPAREN MINUS NOTEQUAL NUMBER PLUS RPAREN STRING TIMESexpression : comparison\n| expression EQUAL comparison\n| expression NOTEQUAL comparison\n| expression GREATER comparison\n| expression LESS comparison\n| expression GREATEREQUAL comparison\n| expression LESSEQUAL comparisoncomparison : term\n| comparison PLUS term\n| comparison MINUS termterm : factor\n| term TIMES factor\n| term DIVIDE factorfactor : NUMBER\n| STRING\n| FIELD\n| CATEGORY\n| IDENTIFIER\n| BOOLEAN\n| MINUS factor\n| LPAREN expression RPAREN\n| function_callfunction_call : FUNCTION LPAREN args RPARENargs : arg_list\n| emptyarg_list : arg\n| arg_list COMMA argarg : expression\n| IDENTIFIER ASSIGN expressionempty :'
10
+
11
+ _lr_action_items = {'NUMBER':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,]),'STRING':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,]),'FIELD':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,]),'CATEGORY':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,]),'IDENTIFIER':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[10,10,10,10,10,10,10,10,10,10,10,10,10,44,44,10,]),'BOOLEAN':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,]),'MINUS':([0,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,20,21,22,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,44,45,46,47,],[4,22,-8,4,-11,-14,-15,-16,-17,-18,-19,4,-22,4,4,4,4,4,4,4,4,4,4,-20,4,22,22,22,22,22,22,-9,-10,-12,-13,-21,-18,-23,4,4,]),'LPAREN':([0,4,12,14,15,16,17,18,19,20,21,22,23,24,27,46,47,],[12,12,12,27,12,12,12,12,12,12,12,12,12,12,12,12,12,]),'FUNCTION':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,]),'$end':([1,2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,45,],[0,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,-23,]),'EQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[15,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,15,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,15,-18,-23,15,]),'NOTEQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[16,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,16,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,16,-18,-23,16,]),'GREATER':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[17,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,17,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,17,-18,-23,17,]),'LESS':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[18,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,18,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,18,-18,-23,18,]),'GREATEREQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[19,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,19,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,19,-18,-23,19,]),'LESSEQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[20,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,20,-18,-23,20,]),'RPAREN':([2,3,5,6,7,8,9,10,11,13,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,48,49,],[-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,38,-30,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,45,-24,-25,-26,-28,-18,-23,-27,-29,]),'COMMA':([2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,40,42,43,44,45,48,49,],[-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,46,-26,-28,-18,-23,-27,-29,]),'PLUS':([2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,44,45,],[21,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,21,21,21,21,21,21,-9,-10,-12,-13,-21,-18,-23,]),'TIMES':([3,5,6,7,8,9,10,11,13,25,34,35,36,37,38,44,45,],[23,-11,-14,-15,-16,-17,-18,-19,-22,-20,23,23,-12,-13,-21,-18,-23,]),'DIVIDE':([3,5,6,7,8,9,10,11,13,25,34,35,36,37,38,44,45,],[24,-11,-14,-15,-16,-17,-18,-19,-22,-20,24,24,-12,-13,-21,-18,-23,]),'ASSIGN':([44,],[47,]),}
12
+
13
+ _lr_action = {}
14
+ for _k, _v in _lr_action_items.items():
15
+ for _x,_y in zip(_v[0],_v[1]):
16
+ if not _x in _lr_action: _lr_action[_x] = {}
17
+ _lr_action[_x][_k] = _y
18
+ del _lr_action_items
19
+
20
+ _lr_goto_items = {'expression':([0,12,27,46,47,],[1,26,43,43,49,]),'comparison':([0,12,15,16,17,18,19,20,27,46,47,],[2,2,28,29,30,31,32,33,2,2,2,]),'term':([0,12,15,16,17,18,19,20,21,22,27,46,47,],[3,3,3,3,3,3,3,3,34,35,3,3,3,]),'factor':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[5,25,5,5,5,5,5,5,5,5,5,36,37,5,5,5,]),'function_call':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,]),'args':([27,],[39,]),'arg_list':([27,],[40,]),'empty':([27,],[41,]),'arg':([27,46,],[42,48,]),}
21
+
22
+ _lr_goto = {}
23
+ for _k, _v in _lr_goto_items.items():
24
+ for _x, _y in zip(_v[0], _v[1]):
25
+ if not _x in _lr_goto: _lr_goto[_x] = {}
26
+ _lr_goto[_x][_k] = _y
27
+ del _lr_goto_items
28
+ _lr_productions = [
29
+ ("S' -> expression","S'",1,None,None,None),
30
+ ('expression -> comparison','expression',1,'p_expression','validator.py',390),
31
+ ('expression -> expression EQUAL comparison','expression',3,'p_expression','validator.py',391),
32
+ ('expression -> expression NOTEQUAL comparison','expression',3,'p_expression','validator.py',392),
33
+ ('expression -> expression GREATER comparison','expression',3,'p_expression','validator.py',393),
34
+ ('expression -> expression LESS comparison','expression',3,'p_expression','validator.py',394),
35
+ ('expression -> expression GREATEREQUAL comparison','expression',3,'p_expression','validator.py',395),
36
+ ('expression -> expression LESSEQUAL comparison','expression',3,'p_expression','validator.py',396),
37
+ ('comparison -> term','comparison',1,'p_comparison','validator.py',403),
38
+ ('comparison -> comparison PLUS term','comparison',3,'p_comparison','validator.py',404),
39
+ ('comparison -> comparison MINUS term','comparison',3,'p_comparison','validator.py',405),
40
+ ('term -> factor','term',1,'p_term','validator.py',412),
41
+ ('term -> term TIMES factor','term',3,'p_term','validator.py',413),
42
+ ('term -> term DIVIDE factor','term',3,'p_term','validator.py',414),
43
+ ('factor -> NUMBER','factor',1,'p_factor','validator.py',421),
44
+ ('factor -> STRING','factor',1,'p_factor','validator.py',422),
45
+ ('factor -> FIELD','factor',1,'p_factor','validator.py',423),
46
+ ('factor -> CATEGORY','factor',1,'p_factor','validator.py',424),
47
+ ('factor -> IDENTIFIER','factor',1,'p_factor','validator.py',425),
48
+ ('factor -> BOOLEAN','factor',1,'p_factor','validator.py',426),
49
+ ('factor -> MINUS factor','factor',2,'p_factor','validator.py',427),
50
+ ('factor -> LPAREN expression RPAREN','factor',3,'p_factor','validator.py',428),
51
+ ('factor -> function_call','factor',1,'p_factor','validator.py',429),
52
+ ('function_call -> FUNCTION LPAREN args RPAREN','function_call',4,'p_function_call','validator.py',457),
53
+ ('args -> arg_list','args',1,'p_args','validator.py',461),
54
+ ('args -> empty','args',1,'p_args','validator.py',462),
55
+ ('arg_list -> arg','arg_list',1,'p_arg_list','validator.py',469),
56
+ ('arg_list -> arg_list COMMA arg','arg_list',3,'p_arg_list','validator.py',470),
57
+ ('arg -> expression','arg',1,'p_arg','validator.py',477),
58
+ ('arg -> IDENTIFIER ASSIGN expression','arg',3,'p_arg','validator.py',478),
59
+ ('empty -> <empty>','empty',0,'p_empty','validator.py',485),
60
+ ]