cnhkmcp 2.3.0__py3-none-any.whl → 2.3.2__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.
- cnhkmcp/__init__.py +1 -1
- cnhkmcp/untracked/APP/Tranformer/parsetab.py +60 -0
- cnhkmcp/untracked/APP/Tranformer/validator.py +78 -4
- cnhkmcp/untracked/APP/static/inspiration.js +46 -4
- cnhkmcp/untracked/APP/templates/index.html +33 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/enhance_template.py +132 -6
- cnhkmcp/untracked/APP/trailSomeAlphas/run_pipeline.py +135 -85
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/SKILL.md +17 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/output_report/GLB_delay1_fundamental72_ideas.md +415 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/final_expressions.json +76 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852468022627100.json +22 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852468554457600.json +14 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852469133324600.json +8 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852469704433900.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852470248911900.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852470805192900.json +8 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852471380158000.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852471944247400.json +22 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852472483548800.json +14 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852473053891800.json +22 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852473617716000.json +22 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852474172815700.json +14 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852474735778500.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852475315478500.json +14 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852475912897000.json +8 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852476474911100.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852978914367200.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852979426164800.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852979945511100.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852980480251500.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852981007315500.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854621979784200.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854622483457900.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854623010559800.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854623572902300.json +5 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854624091016000.json +10 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv +330 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv.bak_1769852868 +330 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv.bak_1769854511 +330 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/ace.log +12 -0
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/fetch_dataset.py +7 -1
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/validator.py +80 -4
- cnhkmcp/untracked/APP/trailSomeAlphas/skills/template_final_enhance/op/321/206/320/220/342/225/227/321/207/342/225/227/320/243.md +24 -18
- 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 +27 -2
- cnhkmcp/untracked/back_up/platform_functions.py +2 -2
- 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/platform_functions.py +2 -2
- cnhkmcp/untracked/platform_functions.py +2 -2
- cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/parsetab.py +60 -0
- cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/validator.py +78 -4
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/METADATA +1 -1
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/RECORD +55 -22
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/WHEEL +0 -0
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/entry_points.txt +0 -0
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/licenses/LICENSE +0 -0
- {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
2026-01-30 01:46:34,276 - ace - ERROR -
|
|
2
2
|
Incorrect email or password
|
|
3
3
|
|
|
4
|
+
2026-01-31 17:23:24,903 - ace - ERROR -
|
|
5
|
+
Incorrect email or password
|
|
6
|
+
|
|
7
|
+
2026-01-31 17:23:25,385 - ace - ERROR -
|
|
8
|
+
Incorrect email or password
|
|
9
|
+
|
|
10
|
+
2026-01-31 17:23:25,859 - ace - ERROR -
|
|
11
|
+
Incorrect email or password
|
|
12
|
+
|
|
13
|
+
2026-01-31 17:23:26,330 - ace - ERROR -
|
|
14
|
+
Incorrect email or password
|
|
15
|
+
|
cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/fetch_dataset.py
CHANGED
|
@@ -32,6 +32,12 @@ def main():
|
|
|
32
32
|
parser.add_argument("--delay", type=int, default=1, help="Delay (default: 1)")
|
|
33
33
|
parser.add_argument("--universe", default="TOP3000", help="Universe (default: TOP3000)")
|
|
34
34
|
parser.add_argument("--instrument-type", default="EQUITY", dest="instrument_type", help="Instrument Type (default: EQUITY)")
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--data-type",
|
|
37
|
+
default="MATRIX",
|
|
38
|
+
choices=["MATRIX", "VECTOR"],
|
|
39
|
+
help="Data type to request from BRAIN datafields (MATRIX or VECTOR). Default: MATRIX",
|
|
40
|
+
)
|
|
35
41
|
|
|
36
42
|
args = parser.parse_args()
|
|
37
43
|
|
|
@@ -83,7 +89,7 @@ def main():
|
|
|
83
89
|
delay=args.delay,
|
|
84
90
|
universe=args.universe,
|
|
85
91
|
instrument_type=args.instrument_type,
|
|
86
|
-
data_type=
|
|
92
|
+
data_type=args.data_type,
|
|
87
93
|
)
|
|
88
94
|
|
|
89
95
|
if df is None or df.empty:
|
cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/validator.py
CHANGED
|
@@ -170,7 +170,10 @@ supported_functions = {
|
|
|
170
170
|
'scale_down': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
|
|
171
171
|
|
|
172
172
|
# Arithmetic 类别函数
|
|
173
|
-
|
|
173
|
+
# add(x, y, ..., filter=false)
|
|
174
|
+
# NOTE: add() is variadic (>=2 terms) with an optional boolean filter flag.
|
|
175
|
+
# We validate it with custom logic in validate_function().
|
|
176
|
+
'add': {'min_args': 2, 'max_args': 101, 'arg_types': ['expression'] * 101},
|
|
174
177
|
'multiply': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 99 + ['boolean'], 'param_names': ['x', 'y', 'filter']}, # multiply(x, y, ..., filter=false)
|
|
175
178
|
'sign': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
|
|
176
179
|
'subtract': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # subtract(x, y, filter=false)
|
|
@@ -501,6 +504,10 @@ class ExpressionValidator:
|
|
|
501
504
|
if not function_info:
|
|
502
505
|
return [f"未知函数: {function_name}"]
|
|
503
506
|
|
|
507
|
+
# Custom validation for variadic functions with optional flags
|
|
508
|
+
if function_name == 'add':
|
|
509
|
+
return self._validate_add(args, is_in_group_arg)
|
|
510
|
+
|
|
504
511
|
errors = []
|
|
505
512
|
|
|
506
513
|
# 检查参数数量
|
|
@@ -591,9 +598,9 @@ class ExpressionValidator:
|
|
|
591
598
|
if arg.node_type != 'number':
|
|
592
599
|
errors.append(f"参数 {arg_index+1} 应该是一个数字,但得到 {arg.node_type}")
|
|
593
600
|
elif expected_type == 'boolean':
|
|
594
|
-
#
|
|
595
|
-
if arg.node_type
|
|
596
|
-
errors.append(f"参数 {arg_index+1} 应该是一个布尔值(0/1),但得到 {arg.node_type}")
|
|
601
|
+
# 布尔值可以是 true/false 或数字(0/1)
|
|
602
|
+
if arg.node_type not in {'boolean', 'number'}:
|
|
603
|
+
errors.append(f"参数 {arg_index+1} 应该是一个布尔值(true/false 或 0/1),但得到 {arg.node_type}")
|
|
597
604
|
elif expected_type == 'field':
|
|
598
605
|
if arg.node_type != 'field' and arg.node_type != 'category':
|
|
599
606
|
# 允许field或category作为字段参数
|
|
@@ -610,6 +617,75 @@ class ExpressionValidator:
|
|
|
610
617
|
# group函数的category参数可以是任何类型(field、category等),不进行类型校验
|
|
611
618
|
|
|
612
619
|
return errors
|
|
620
|
+
|
|
621
|
+
def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
|
|
622
|
+
"""Validate add(x, y, ..., filter=false).
|
|
623
|
+
|
|
624
|
+
Rules:
|
|
625
|
+
- At least 2 positional expression terms.
|
|
626
|
+
- Optional filter flag can be provided as:
|
|
627
|
+
- named argument: filter=<boolean>
|
|
628
|
+
- last positional argument: <boolean>
|
|
629
|
+
"""
|
|
630
|
+
errors: List[str] = []
|
|
631
|
+
|
|
632
|
+
if len(args) < 2:
|
|
633
|
+
return [f"函数 add 需要至少 2 个参数,但只提供了 {len(args)}"]
|
|
634
|
+
|
|
635
|
+
named_filter_nodes: List[ASTNode] = []
|
|
636
|
+
positional_nodes: List[ASTNode] = []
|
|
637
|
+
|
|
638
|
+
for arg in args:
|
|
639
|
+
if isinstance(arg, dict) and arg.get('type') == 'named':
|
|
640
|
+
name = arg.get('name')
|
|
641
|
+
value = arg.get('value')
|
|
642
|
+
if name != 'filter':
|
|
643
|
+
errors.append(f"函数 add 不存在参数 '{name}'")
|
|
644
|
+
continue
|
|
645
|
+
if not hasattr(value, 'node_type'):
|
|
646
|
+
errors.append("函数 add 的参数 filter 格式错误")
|
|
647
|
+
continue
|
|
648
|
+
named_filter_nodes.append(value)
|
|
649
|
+
elif isinstance(arg, dict) and arg.get('type') == 'positional':
|
|
650
|
+
value = arg.get('value')
|
|
651
|
+
if hasattr(value, 'node_type'):
|
|
652
|
+
positional_nodes.append(value)
|
|
653
|
+
else:
|
|
654
|
+
errors.append("函数 add 的位置参数格式错误")
|
|
655
|
+
elif hasattr(arg, 'node_type'):
|
|
656
|
+
positional_nodes.append(arg)
|
|
657
|
+
else:
|
|
658
|
+
errors.append("函数 add 的参数格式错误")
|
|
659
|
+
|
|
660
|
+
if len(named_filter_nodes) > 1:
|
|
661
|
+
errors.append("函数 add 的参数 'filter' 只能出现一次")
|
|
662
|
+
|
|
663
|
+
positional_filter_node: Optional[ASTNode] = None
|
|
664
|
+
# Only infer a positional filter flag when:
|
|
665
|
+
# - no named filter is provided
|
|
666
|
+
# - there are at least 3 positional args (x, y, filter)
|
|
667
|
+
# - the last arg is boolean or numeric 0/1
|
|
668
|
+
if not named_filter_nodes and len(positional_nodes) >= 3:
|
|
669
|
+
last = positional_nodes[-1]
|
|
670
|
+
if last.node_type == 'boolean' or (last.node_type == 'number' and last.value in {0, 1}):
|
|
671
|
+
positional_filter_node = positional_nodes.pop()
|
|
672
|
+
|
|
673
|
+
if len(positional_nodes) < 2:
|
|
674
|
+
errors.append(f"函数 add 需要至少 2 个输入项(不含filter),但只提供了 {len(positional_nodes)}")
|
|
675
|
+
|
|
676
|
+
# Validate all term inputs as expressions (no-op, but keep recursion behavior consistent)
|
|
677
|
+
for idx, node in enumerate(positional_nodes):
|
|
678
|
+
errors.extend(self._validate_arg_type(node, 'expression', idx, 'add', is_in_group_arg))
|
|
679
|
+
|
|
680
|
+
# Validate filter, if present (named takes precedence; if both present, that's an error)
|
|
681
|
+
if positional_filter_node is not None and named_filter_nodes:
|
|
682
|
+
errors.append("函数 add 的 filter 不能同时用位置参数和命名参数传递")
|
|
683
|
+
if positional_filter_node is not None:
|
|
684
|
+
errors.extend(self._validate_arg_type(positional_filter_node, 'boolean', len(positional_nodes), 'add', is_in_group_arg))
|
|
685
|
+
if named_filter_nodes:
|
|
686
|
+
errors.extend(self._validate_arg_type(named_filter_nodes[0], 'boolean', len(positional_nodes), 'add', is_in_group_arg))
|
|
687
|
+
|
|
688
|
+
return errors
|
|
613
689
|
|
|
614
690
|
def validate_ast(self, ast: Optional[ASTNode], is_in_group_arg: bool = False) -> List[str]:
|
|
615
691
|
"""递归验证抽象语法树"""
|
|
@@ -194,27 +194,36 @@ days_from_last_change(A) / last_diff_value(A,d)
|
|
|
194
194
|
场景:
|
|
195
195
|
因子是“事件驱动型”的(评级变动、ESG 评级更新等),只在变动后若干天内才有信号;
|
|
196
196
|
可以构造“距上次变化时间”的信号。
|
|
197
|
-
七、
|
|
197
|
+
七、Vector Operator*
|
|
198
|
+
这些用于处理“向量类型数据”,需要先生成统计特征才能用。
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
补充:常用 Vector Operators(Combo, Regular)
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
> 一句话描述:Vector 字段(例如一只股票一天里的一串向量值)不能直接参与普通的算术/时序/截面运算,通常需要先用 `vec_*` 把它“降维”为标量特征(均值/分位/波动/偏度等),再进入后续流程。
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
- `vec_avg(x)`(base):对向量求均值。例:输入 (2,3,5,6,3,8,10) 输出 37/7=5.29
|
|
205
|
+
- `vec_sum(x)`(base):对向量求和。例:输入 (2,3,5,6,3,8,10) 输出 37
|
|
206
|
+
- `vec_count(x)`(genius):向量元素个数
|
|
207
|
+
- `vec_choose(x, nth=k)`(genius):取向量中第 k 个元素(从 0 开始计数)
|
|
208
|
+
- `vec_max(x)`(genius):向量最大值
|
|
209
|
+
- `vec_min(x)`(genius):向量最小值
|
|
210
|
+
- `vec_range(x)`(genius):向量极差(max-min)
|
|
211
|
+
- `vec_stddev(x)`(genius):向量标准差
|
|
212
|
+
- `vec_ir(x)`(genius):向量信息比率(均值/标准差)
|
|
213
|
+
- `vec_skewness(x)`(genius):向量偏度
|
|
214
|
+
- `vec_kurtosis(x)`(genius):向量峰度
|
|
215
|
+
- `vec_norm(x)`(genius):向量范数(所有元素绝对值之和)
|
|
216
|
+
- `vec_percentage(x, percentage=0.5)`(genius):向量分位数(如 0.5 为中位数)
|
|
217
|
+
- `vec_powersum(x, constant=2)`(genius):向量幂和(对每个元素做幂后求和)
|
|
218
|
+
- `vec_filter(vec, value=nan)`(genius):按值过滤向量(可一次过滤多个值,例如 `"nan 0 10"`)。注意:输出仍然是 VECTOR 类型(还未降维)。
|
|
219
|
+
|
|
220
|
+
注意:
|
|
221
|
+
- Vector type 数据不能直接用;你必须先用一个 `vec_*` 把它变成标量特征后再做后续组合。
|
|
222
|
+
- `vec_*` 只能作用在 VECTOR 类型数据上;普通 MATRIX 字段不能直接喂给 `vec_*`。
|
|
207
223
|
|
|
208
|
-
场景:
|
|
209
|
-
多个不同构造方式的同主题因子(比如多个 ESG 变体)组合,提升稳健性;
|
|
210
|
-
单一指标不够可靠,想自动根据历史 IR 给权重。
|
|
211
|
-
self_corr(A)
|
|
212
224
|
|
|
213
|
-
场景:
|
|
214
|
-
研究同因子在截面上的相关结构,比如 ESG 因子和某个 sector pattern 是否极度重叠;
|
|
215
|
-
更偏研究/调试,不是直接构造单因子。
|
|
216
|
-
小结(对应你说的那些“现实情景”):
|
|
217
225
|
|
|
226
|
+
小结:
|
|
218
227
|
只头部/尾部有信号:
|
|
219
228
|
想起:rank + bucket + 逻辑算子(greater/less/if_else/trade_when)、right_tail / left_tail。
|
|
220
229
|
中间区间是噪音:
|
|
@@ -396,11 +405,8 @@ ts_delta(B,d) / days_from_last_change(B)
|
|
|
396
405
|
例如 volume 爆量那几天放大 A,平时保持中立:
|
|
397
406
|
b_spike = greater(ts_delta(B,1), thresh)
|
|
398
407
|
core = trade_when(b_spike, 1.5*A, A)。
|
|
399
|
-
七、Reduce / Combo 等
|
|
400
408
|
|
|
401
|
-
多数用于多维/多因子的情况,不再一一展开;简单原则:
|
|
402
409
|
|
|
403
|
-
若 B 其实是一组值(如多个 volume 相关字段),先用 reduce_avg/reduce_max 合成一个 B_agg,再按前面逻辑当作单一 B 处理。
|
|
404
410
|
最后给一个总的“使用从属 B 的模板公式”
|
|
405
411
|
|
|
406
412
|
可以抽象成:
|
|
@@ -2376,6 +2376,7 @@ def inspiration_generate():
|
|
|
2376
2376
|
delay = data.get('delay')
|
|
2377
2377
|
universe = data.get('universe')
|
|
2378
2378
|
dataset_id = data.get('datasetId')
|
|
2379
|
+
data_type = data.get('dataType') or 'MATRIX'
|
|
2379
2380
|
|
|
2380
2381
|
try:
|
|
2381
2382
|
import openai
|
|
@@ -2391,10 +2392,13 @@ def inspiration_generate():
|
|
|
2391
2392
|
if not s:
|
|
2392
2393
|
return jsonify({'error': 'Not logged in'}), 401
|
|
2393
2394
|
|
|
2395
|
+
if data_type not in ("MATRIX", "VECTOR"):
|
|
2396
|
+
data_type = "MATRIX"
|
|
2397
|
+
|
|
2394
2398
|
operators_df = get_operators(s)
|
|
2395
2399
|
operators_df = operators_df[operators_df['scope'] == 'REGULAR']
|
|
2396
2400
|
|
|
2397
|
-
datafields_df = get_datafields(s, region=region, delay=int(delay), universe=universe, dataset_id=dataset_id, data_type=
|
|
2401
|
+
datafields_df = get_datafields(s, region=region, delay=int(delay), universe=universe, dataset_id=dataset_id, data_type=data_type)
|
|
2398
2402
|
|
|
2399
2403
|
# count the datatype of the datafields_df, if most of them are VECTOR, then we keep the VECTOR category operators in the operators_df, otherwise we remove them
|
|
2400
2404
|
datatype_counts = datafields_df['type'].value_counts().to_dict()
|
|
@@ -2516,6 +2520,7 @@ def inspiration_run_pipeline():
|
|
|
2516
2520
|
region = data.get('region')
|
|
2517
2521
|
delay = data.get('delay')
|
|
2518
2522
|
universe = data.get('universe')
|
|
2523
|
+
data_type = data.get('dataType') or 'MATRIX'
|
|
2519
2524
|
api_key = data.get('apiKey')
|
|
2520
2525
|
base_url = data.get('baseUrl')
|
|
2521
2526
|
model = data.get('model')
|
|
@@ -2541,6 +2546,11 @@ def inspiration_run_pipeline():
|
|
|
2541
2546
|
|
|
2542
2547
|
def run_process():
|
|
2543
2548
|
try:
|
|
2549
|
+
if data_type not in ("MATRIX", "VECTOR"):
|
|
2550
|
+
dt = "MATRIX"
|
|
2551
|
+
else:
|
|
2552
|
+
dt = str(data_type)
|
|
2553
|
+
|
|
2544
2554
|
cmd = [
|
|
2545
2555
|
sys.executable,
|
|
2546
2556
|
run_pipeline_path,
|
|
@@ -2548,7 +2558,8 @@ def inspiration_run_pipeline():
|
|
|
2548
2558
|
'--region', str(region),
|
|
2549
2559
|
'--delay', str(delay),
|
|
2550
2560
|
'--dataset-id', str(dataset_id),
|
|
2551
|
-
'--universe', str(universe)
|
|
2561
|
+
'--universe', str(universe),
|
|
2562
|
+
'--data-type', dt,
|
|
2552
2563
|
]
|
|
2553
2564
|
|
|
2554
2565
|
if api_key:
|
|
@@ -2672,6 +2683,13 @@ def inspiration_enhance_template():
|
|
|
2672
2683
|
api_key = request.form.get('apiKey')
|
|
2673
2684
|
base_url = request.form.get('baseUrl')
|
|
2674
2685
|
model = request.form.get('model')
|
|
2686
|
+
data_type = request.form.get('dataType') or 'MATRIX'
|
|
2687
|
+
|
|
2688
|
+
session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
|
|
2689
|
+
session_info = brain_sessions.get(session_id) if session_id else None
|
|
2690
|
+
|
|
2691
|
+
if data_type not in ("MATRIX", "VECTOR"):
|
|
2692
|
+
data_type = "MATRIX"
|
|
2675
2693
|
|
|
2676
2694
|
if not idea_files or not api_key:
|
|
2677
2695
|
return jsonify({'success': False, 'error': 'Missing ideaFiles or apiKey'}), 400
|
|
@@ -2711,12 +2729,19 @@ def inspiration_enhance_template():
|
|
|
2711
2729
|
env = os.environ.copy()
|
|
2712
2730
|
env['IDEA_JSON'] = idea_path
|
|
2713
2731
|
env['MOONSHOT_API_KEY'] = api_key
|
|
2732
|
+
env['DATA_TYPE'] = str(data_type)
|
|
2714
2733
|
if base_url:
|
|
2715
2734
|
env['MOONSHOT_BASE_URL'] = base_url
|
|
2716
2735
|
if model:
|
|
2717
2736
|
env['MOONSHOT_MODEL'] = model
|
|
2718
2737
|
env['PYTHONIOENCODING'] = 'utf-8'
|
|
2719
2738
|
|
|
2739
|
+
# Inherit BRAIN auth from the logged-in web session (same as run-pipeline).
|
|
2740
|
+
# Do NOT accept raw credentials from the enhance form.
|
|
2741
|
+
if session_info and session_info.get('username') and session_info.get('password'):
|
|
2742
|
+
env['BRAIN_USERNAME'] = session_info['username']
|
|
2743
|
+
env['BRAIN_PASSWORD'] = session_info['password']
|
|
2744
|
+
|
|
2720
2745
|
log_queue.put(f"=== 开始处理: {name} ({idx}/{total}) ===")
|
|
2721
2746
|
proc = subprocess.Popen(
|
|
2722
2747
|
[sys.executable, enhance_script],
|
|
@@ -423,7 +423,7 @@ class BrainApiClient:
|
|
|
423
423
|
return False
|
|
424
424
|
|
|
425
425
|
async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
|
|
426
|
-
delay: int = 1, universe: str = "TOP3000", theme: str = "
|
|
426
|
+
delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
|
|
427
427
|
"""Get available datasets."""
|
|
428
428
|
await self.ensure_authenticated()
|
|
429
429
|
|
|
@@ -1883,7 +1883,7 @@ async def get_datasets(
|
|
|
1883
1883
|
region: str = "USA",
|
|
1884
1884
|
delay: int = 1,
|
|
1885
1885
|
universe: str = "TOP3000",
|
|
1886
|
-
theme: str = "
|
|
1886
|
+
theme: str = "ALL",
|
|
1887
1887
|
search: Optional[str] = None
|
|
1888
1888
|
) -> Dict[str, Any]:
|
|
1889
1889
|
"""
|
|
@@ -357,7 +357,7 @@ class BrainApiClient:
|
|
|
357
357
|
raise
|
|
358
358
|
|
|
359
359
|
async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
|
|
360
|
-
delay: int = 1, universe: str = "TOP3000", theme: str = "
|
|
360
|
+
delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
|
|
361
361
|
"""Get available datasets."""
|
|
362
362
|
await self.ensure_authenticated()
|
|
363
363
|
|
|
@@ -1761,7 +1761,7 @@ async def get_datasets(
|
|
|
1761
1761
|
region: str = "USA",
|
|
1762
1762
|
delay: int = 1,
|
|
1763
1763
|
universe: str = "TOP3000",
|
|
1764
|
-
theme: str = "
|
|
1764
|
+
theme: str = "ALL",
|
|
1765
1765
|
search: Optional[str] = None,
|
|
1766
1766
|
) -> Dict[str, Any]:
|
|
1767
1767
|
"""
|
|
@@ -357,7 +357,7 @@ class BrainApiClient:
|
|
|
357
357
|
raise
|
|
358
358
|
|
|
359
359
|
async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
|
|
360
|
-
delay: int = 1, universe: str = "TOP3000", theme: str = "
|
|
360
|
+
delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
|
|
361
361
|
"""Get available datasets."""
|
|
362
362
|
await self.ensure_authenticated()
|
|
363
363
|
|
|
@@ -1761,7 +1761,7 @@ async def get_datasets(
|
|
|
1761
1761
|
region: str = "USA",
|
|
1762
1762
|
delay: int = 1,
|
|
1763
1763
|
universe: str = "TOP3000",
|
|
1764
|
-
theme: str = "
|
|
1764
|
+
theme: str = "ALL",
|
|
1765
1765
|
search: Optional[str] = None,
|
|
1766
1766
|
) -> Dict[str, Any]:
|
|
1767
1767
|
"""
|
|
@@ -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',386),
|
|
31
|
+
('expression -> expression EQUAL comparison','expression',3,'p_expression','validator.py',387),
|
|
32
|
+
('expression -> expression NOTEQUAL comparison','expression',3,'p_expression','validator.py',388),
|
|
33
|
+
('expression -> expression GREATER comparison','expression',3,'p_expression','validator.py',389),
|
|
34
|
+
('expression -> expression LESS comparison','expression',3,'p_expression','validator.py',390),
|
|
35
|
+
('expression -> expression GREATEREQUAL comparison','expression',3,'p_expression','validator.py',391),
|
|
36
|
+
('expression -> expression LESSEQUAL comparison','expression',3,'p_expression','validator.py',392),
|
|
37
|
+
('comparison -> term','comparison',1,'p_comparison','validator.py',399),
|
|
38
|
+
('comparison -> comparison PLUS term','comparison',3,'p_comparison','validator.py',400),
|
|
39
|
+
('comparison -> comparison MINUS term','comparison',3,'p_comparison','validator.py',401),
|
|
40
|
+
('term -> factor','term',1,'p_term','validator.py',408),
|
|
41
|
+
('term -> term TIMES factor','term',3,'p_term','validator.py',409),
|
|
42
|
+
('term -> term DIVIDE factor','term',3,'p_term','validator.py',410),
|
|
43
|
+
('factor -> NUMBER','factor',1,'p_factor','validator.py',417),
|
|
44
|
+
('factor -> STRING','factor',1,'p_factor','validator.py',418),
|
|
45
|
+
('factor -> FIELD','factor',1,'p_factor','validator.py',419),
|
|
46
|
+
('factor -> CATEGORY','factor',1,'p_factor','validator.py',420),
|
|
47
|
+
('factor -> IDENTIFIER','factor',1,'p_factor','validator.py',421),
|
|
48
|
+
('factor -> BOOLEAN','factor',1,'p_factor','validator.py',422),
|
|
49
|
+
('factor -> MINUS factor','factor',2,'p_factor','validator.py',423),
|
|
50
|
+
('factor -> LPAREN expression RPAREN','factor',3,'p_factor','validator.py',424),
|
|
51
|
+
('factor -> function_call','factor',1,'p_factor','validator.py',425),
|
|
52
|
+
('function_call -> FUNCTION LPAREN args RPAREN','function_call',4,'p_function_call','validator.py',453),
|
|
53
|
+
('args -> arg_list','args',1,'p_args','validator.py',457),
|
|
54
|
+
('args -> empty','args',1,'p_args','validator.py',458),
|
|
55
|
+
('arg_list -> arg','arg_list',1,'p_arg_list','validator.py',465),
|
|
56
|
+
('arg_list -> arg_list COMMA arg','arg_list',3,'p_arg_list','validator.py',466),
|
|
57
|
+
('arg -> expression','arg',1,'p_arg','validator.py',473),
|
|
58
|
+
('arg -> IDENTIFIER ASSIGN expression','arg',3,'p_arg','validator.py',474),
|
|
59
|
+
('empty -> <empty>','empty',0,'p_empty','validator.py',481),
|
|
60
|
+
]
|
|
@@ -170,7 +170,10 @@ supported_functions = {
|
|
|
170
170
|
'scale_down': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
|
|
171
171
|
|
|
172
172
|
# Arithmetic 类别函数
|
|
173
|
-
|
|
173
|
+
# add(x, y, ..., filter=false)
|
|
174
|
+
# NOTE: add() is variadic (>=2 terms) with an optional boolean filter flag.
|
|
175
|
+
# We validate it with custom logic in validate_function().
|
|
176
|
+
'add': {'min_args': 2, 'max_args': 101, 'arg_types': ['expression'] * 101},
|
|
174
177
|
'multiply': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 99 + ['boolean'], 'param_names': ['x', 'y', 'filter']}, # multiply(x, y, ..., filter=false)
|
|
175
178
|
'sign': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
|
|
176
179
|
'subtract': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # subtract(x, y, filter=false)
|
|
@@ -501,6 +504,10 @@ class ExpressionValidator:
|
|
|
501
504
|
if not function_info:
|
|
502
505
|
return [f"未知函数: {function_name}"]
|
|
503
506
|
|
|
507
|
+
# Custom validation for variadic functions with optional flags
|
|
508
|
+
if function_name == 'add':
|
|
509
|
+
return self._validate_add(args, is_in_group_arg)
|
|
510
|
+
|
|
504
511
|
errors = []
|
|
505
512
|
|
|
506
513
|
# 检查参数数量
|
|
@@ -591,9 +598,9 @@ class ExpressionValidator:
|
|
|
591
598
|
if arg.node_type != 'number':
|
|
592
599
|
errors.append(f"参数 {arg_index+1} 应该是一个数字,但得到 {arg.node_type}")
|
|
593
600
|
elif expected_type == 'boolean':
|
|
594
|
-
#
|
|
595
|
-
if arg.node_type
|
|
596
|
-
errors.append(f"参数 {arg_index+1} 应该是一个布尔值(0/1),但得到 {arg.node_type}")
|
|
601
|
+
# 布尔值可以是 true/false 或数字(0/1)
|
|
602
|
+
if arg.node_type not in {'boolean', 'number'}:
|
|
603
|
+
errors.append(f"参数 {arg_index+1} 应该是一个布尔值(true/false 或 0/1),但得到 {arg.node_type}")
|
|
597
604
|
elif expected_type == 'field':
|
|
598
605
|
if arg.node_type != 'field' and arg.node_type != 'category':
|
|
599
606
|
# 允许field或category作为字段参数
|
|
@@ -610,6 +617,73 @@ class ExpressionValidator:
|
|
|
610
617
|
# group函数的category参数可以是任何类型(field、category等),不进行类型校验
|
|
611
618
|
|
|
612
619
|
return errors
|
|
620
|
+
|
|
621
|
+
def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
|
|
622
|
+
"""Validate add(x, y, ..., filter=false).
|
|
623
|
+
|
|
624
|
+
Rules:
|
|
625
|
+
- At least 2 positional expression terms.
|
|
626
|
+
- Optional filter flag can be provided as:
|
|
627
|
+
- named argument: filter=<boolean>
|
|
628
|
+
- last positional argument: <boolean> or 0/1
|
|
629
|
+
"""
|
|
630
|
+
errors: List[str] = []
|
|
631
|
+
|
|
632
|
+
if len(args) < 2:
|
|
633
|
+
return [f"函数 add 需要至少 2 个参数,但只提供了 {len(args)}"]
|
|
634
|
+
|
|
635
|
+
named_filter_nodes: List[ASTNode] = []
|
|
636
|
+
positional_nodes: List[ASTNode] = []
|
|
637
|
+
|
|
638
|
+
for arg in args:
|
|
639
|
+
if isinstance(arg, dict) and arg.get('type') == 'named':
|
|
640
|
+
name = arg.get('name')
|
|
641
|
+
value = arg.get('value')
|
|
642
|
+
if name != 'filter':
|
|
643
|
+
errors.append(f"函数 add 不存在参数 '{name}'")
|
|
644
|
+
continue
|
|
645
|
+
if not hasattr(value, 'node_type'):
|
|
646
|
+
errors.append("函数 add 的参数 filter 格式错误")
|
|
647
|
+
continue
|
|
648
|
+
named_filter_nodes.append(value)
|
|
649
|
+
elif isinstance(arg, dict) and arg.get('type') == 'positional':
|
|
650
|
+
value = arg.get('value')
|
|
651
|
+
if hasattr(value, 'node_type'):
|
|
652
|
+
positional_nodes.append(value)
|
|
653
|
+
else:
|
|
654
|
+
errors.append("函数 add 的位置参数格式错误")
|
|
655
|
+
elif hasattr(arg, 'node_type'):
|
|
656
|
+
positional_nodes.append(arg)
|
|
657
|
+
else:
|
|
658
|
+
errors.append("函数 add 的参数格式错误")
|
|
659
|
+
|
|
660
|
+
if len(named_filter_nodes) > 1:
|
|
661
|
+
errors.append("函数 add 的参数 'filter' 只能出现一次")
|
|
662
|
+
|
|
663
|
+
positional_filter_node: Optional[ASTNode] = None
|
|
664
|
+
# Only infer a positional filter flag when:
|
|
665
|
+
# - no named filter is provided
|
|
666
|
+
# - there are at least 3 positional args (x, y, filter)
|
|
667
|
+
# - the last arg is boolean or numeric 0/1
|
|
668
|
+
if not named_filter_nodes and len(positional_nodes) >= 3:
|
|
669
|
+
last = positional_nodes[-1]
|
|
670
|
+
if last.node_type == 'boolean' or (last.node_type == 'number' and last.value in {0, 1}):
|
|
671
|
+
positional_filter_node = positional_nodes.pop()
|
|
672
|
+
|
|
673
|
+
if len(positional_nodes) < 2:
|
|
674
|
+
errors.append(f"函数 add 需要至少 2 个输入项(不含filter),但只提供了 {len(positional_nodes)}")
|
|
675
|
+
|
|
676
|
+
for idx, node in enumerate(positional_nodes):
|
|
677
|
+
errors.extend(self._validate_arg_type(node, 'expression', idx, 'add', is_in_group_arg))
|
|
678
|
+
|
|
679
|
+
if positional_filter_node is not None and named_filter_nodes:
|
|
680
|
+
errors.append("函数 add 的 filter 不能同时用位置参数和命名参数传递")
|
|
681
|
+
if positional_filter_node is not None:
|
|
682
|
+
errors.extend(self._validate_arg_type(positional_filter_node, 'boolean', len(positional_nodes), 'add', is_in_group_arg))
|
|
683
|
+
if named_filter_nodes:
|
|
684
|
+
errors.extend(self._validate_arg_type(named_filter_nodes[0], 'boolean', len(positional_nodes), 'add', is_in_group_arg))
|
|
685
|
+
|
|
686
|
+
return errors
|
|
613
687
|
|
|
614
688
|
def validate_ast(self, ast: Optional[ASTNode], is_in_group_arg: bool = False) -> List[str]:
|
|
615
689
|
"""递归验证抽象语法树"""
|