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
@@ -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,16 @@ 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 (typically by group_* operators).
618
+ # It must not flow into normal Unit[] inputs like ts_mean's first argument.
619
+ if self._is_derived_category(arg) and expected_type != 'category':
620
+ errors.append(
621
+ f"Incompatible unit for input of \"{function_name}\" at index {arg_index}, expected \"Unit[]\", found \"Unit[Group:1]\""
622
+ )
623
+ return errors
586
624
 
587
625
  # 首先检查是否是group类型字段,如果是则只能用于Group类型函数
588
626
  # 但是如果当前函数是group_xxx或在group函数的参数链中,则允许使用
@@ -618,6 +656,86 @@ class ExpressionValidator:
618
656
 
619
657
  return errors
620
658
 
659
+ def _infer_unit(self, node: ASTNode) -> str:
660
+ """Infer the Unit kind of an AST node.
661
+
662
+ Returns:
663
+ 'unit' - regular numeric time-series Unit[]
664
+ 'scalar' - literals (numbers/booleans/strings)
665
+ 'category' - category/grouping keys (industry/sector or derived via bucket/cartesian)
666
+ """
667
+ if node is None:
668
+ return 'unit'
669
+
670
+ cache_key = id(node)
671
+ cached = self._unit_cache.get(cache_key)
672
+ if cached is not None:
673
+ return cached
674
+
675
+ unit = 'unit'
676
+
677
+ if node.node_type in {'number', 'boolean', 'string'}:
678
+ unit = 'scalar'
679
+ elif node.node_type in {'field', 'identifier'}:
680
+ unit = 'unit'
681
+ elif node.node_type == 'category':
682
+ unit = 'category'
683
+ elif node.node_type in {'unop', 'binop'}:
684
+ child_units = [self._infer_unit(child) for child in node.children if hasattr(child, 'node_type')]
685
+ unit = 'category' if 'category' in child_units else 'unit'
686
+ elif node.node_type == 'function':
687
+ fname = node.value
688
+ # Group-building functions create derived category outputs
689
+ if fname in {'bucket', 'group_cartesian_product'}:
690
+ unit = 'category'
691
+ else:
692
+ # Default: propagate from the first positional argument if available
693
+ first_arg = None
694
+ for child in node.children:
695
+ if isinstance(child, dict):
696
+ if child.get('type') == 'positional':
697
+ first_arg = child.get('value')
698
+ break
699
+ else:
700
+ first_arg = child
701
+ break
702
+
703
+ if hasattr(first_arg, 'node_type'):
704
+ unit = self._infer_unit(first_arg)
705
+ else:
706
+ unit = 'unit'
707
+
708
+ self._unit_cache[cache_key] = unit
709
+ return unit
710
+
711
+ def _is_derived_category(self, node: ASTNode) -> bool:
712
+ """Return True if node is a derived category/grouping key (e.g., bucket/cartesian output)."""
713
+ if node is None:
714
+ return False
715
+
716
+ cache_key = id(node)
717
+ cached = self._derived_category_cache.get(cache_key)
718
+ if cached is not None:
719
+ return cached
720
+
721
+ derived = False
722
+ if node.node_type == 'function' and node.value in {'bucket', 'group_cartesian_product'}:
723
+ derived = True
724
+ elif node.node_type in {'unop', 'binop'}:
725
+ derived = any(
726
+ self._is_derived_category(child)
727
+ for child in node.children
728
+ if hasattr(child, 'node_type')
729
+ )
730
+ elif node.node_type == 'function':
731
+ derived = any(
732
+ self._is_derived_category(child.get('value')) if isinstance(child, dict) else self._is_derived_category(child)
733
+ for child in node.children
734
+ )
735
+
736
+ self._derived_category_cache[cache_key] = derived
737
+ return derived
738
+
621
739
  def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
622
740
  """Validate add(x, y, ..., filter=false).
623
741
 
@@ -885,6 +1003,9 @@ class ExpressionValidator:
885
1003
  """
886
1004
  # 重置错误列表
887
1005
  self.errors = []
1006
+ # Reset unit inference cache for this expression
1007
+ self._unit_cache = {}
1008
+ self._derived_category_cache = {}
888
1009
 
889
1010
  try:
890
1011
  expression = expression.strip()
@@ -11,9 +11,11 @@ import io
11
11
 
12
12
  # try set gbk problem
13
13
  try:
14
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
15
- except Exception as e:
16
- print(e)
14
+ if hasattr(sys.stdout, 'reconfigure'):
15
+ sys.stdout.reconfigure(encoding='utf-8')
16
+ elif hasattr(sys.stdout, 'buffer'):
17
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
18
+ except Exception:
17
19
  pass
18
20
 
19
21
  def install_requirements():
@@ -136,6 +138,7 @@ try:
136
138
  import queue
137
139
  import uuid
138
140
  from datetime import datetime
141
+ from blueprints.validator import ExpressionValidator
139
142
  print("📚 Core packages imported successfully!")
140
143
 
141
144
  # Import ace_lib for simulation options
@@ -415,6 +418,51 @@ def test_simulator_connection():
415
418
  'error': f'Connection failed: {str(e)}'
416
419
  })
417
420
 
421
+ @app.route('/api/validate_expressions', methods=['POST'])
422
+ def validate_expressions():
423
+ """Validate a list of expressions using ExpressionValidator"""
424
+ try:
425
+ data = request.json
426
+ expressions = data.get('expressions', [])
427
+
428
+ if not isinstance(expressions, list):
429
+ return jsonify({'error': 'Expressions must be a list'}), 400
430
+
431
+ validator = ExpressionValidator()
432
+ results = []
433
+ valid_count = 0
434
+ invalid_count = 0
435
+
436
+ # Validate all expressions
437
+ expressions_to_validate = expressions
438
+
439
+ for expr in expressions_to_validate:
440
+ validation_result = validator.check_expression(expr)
441
+ if validation_result['valid']:
442
+ valid_count += 1
443
+ results.append({
444
+ 'expression': expr,
445
+ 'valid': True
446
+ })
447
+ else:
448
+ invalid_count += 1
449
+ results.append({
450
+ 'expression': expr,
451
+ 'valid': False,
452
+ 'errors': validation_result['errors']
453
+ })
454
+
455
+ return jsonify({
456
+ 'results': results,
457
+ 'summary': {
458
+ 'total': len(expressions_to_validate),
459
+ 'valid': valid_count,
460
+ 'invalid': invalid_count
461
+ }
462
+ })
463
+ except Exception as e:
464
+ return jsonify({'error': str(e)}), 500
465
+
418
466
  @app.route('/api/simulator/run', methods=['POST'])
419
467
  def run_simulator_with_params():
420
468
  """Run simulator with user-provided parameters in a new terminal"""
@@ -6,9 +6,11 @@ import argparse
6
6
  import io
7
7
  # try set gbk problem
8
8
  try:
9
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
10
- except Exception as e:
11
- print(e)
9
+ if hasattr(sys.stdout, 'reconfigure'):
10
+ sys.stdout.reconfigure(encoding='utf-8')
11
+ elif hasattr(sys.stdout, 'buffer'):
12
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
13
+ except Exception:
12
14
  pass
13
15
 
14
16
  def search_arxiv(query, max_results=10):
@@ -26,9 +26,11 @@ import io
26
26
 
27
27
  # try set gbk problem
28
28
  try:
29
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
30
- except Exception as e:
31
- print(e)
29
+ if hasattr(sys.stdout, 'reconfigure'):
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ elif hasattr(sys.stdout, 'buffer'):
32
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
33
+ except Exception:
32
34
  pass
33
35
 
34
36
  # Initialize forum MCP server
@@ -19,9 +19,11 @@ import io
19
19
 
20
20
  # try set gbk problem
21
21
  try:
22
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
23
- except Exception as e:
24
- print(e)
22
+ if hasattr(sys.stdout, 'reconfigure'):
23
+ sys.stdout.reconfigure(encoding='utf-8')
24
+ elif hasattr(sys.stdout, 'buffer'):
25
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
26
+ except Exception:
25
27
  pass
26
28
 
27
29
  def log(message: str, level: str = "INFO"):
@@ -19,9 +19,11 @@ import io
19
19
 
20
20
  # try set gbk problem
21
21
  try:
22
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
23
- except Exception as e:
24
- print(e)
22
+ if hasattr(sys.stdout, 'reconfigure'):
23
+ sys.stdout.reconfigure(encoding='utf-8')
24
+ elif hasattr(sys.stdout, 'buffer'):
25
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
26
+ except Exception:
25
27
  pass
26
28
 
27
29
  def log(message: str, level: str = "INFO"):
@@ -21,9 +21,11 @@ from time import sleep
21
21
  import io
22
22
  # try set gbk problem
23
23
  try:
24
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
25
- except Exception as e:
26
- print(e)
24
+ if hasattr(sys.stdout, 'reconfigure'):
25
+ sys.stdout.reconfigure(encoding='utf-8')
26
+ elif hasattr(sys.stdout, 'buffer'):
27
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
28
+ except Exception:
27
29
  pass
28
30
 
29
31
  import requests