programgarden-core 1.2.0__tar.gz → 1.3.0__tar.gz

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 (71) hide show
  1. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/PKG-INFO +1 -1
  2. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/i18n/locales/en.json +23 -0
  3. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/i18n/locales/ko.json +23 -0
  4. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/edge.py +19 -8
  5. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/__init__.py +3 -2
  6. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/infra.py +104 -0
  7. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/registry/node_registry.py +2 -2
  8. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/pyproject.toml +1 -1
  9. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/README.md +0 -0
  10. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/__init__.py +0 -0
  11. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/__init__.py +0 -0
  12. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/client.py +0 -0
  13. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/components.py +0 -0
  14. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/listener.py +0 -0
  15. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/mixins.py +0 -0
  16. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/products.py +0 -0
  17. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/sql.py +0 -0
  18. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/bases/storage.py +0 -0
  19. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/exceptions.py +0 -0
  20. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/expression/__init__.py +0 -0
  21. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/expression/evaluator.py +0 -0
  22. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/i18n/__init__.py +0 -0
  23. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/i18n/translator.py +0 -0
  24. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/korea_alias.py +0 -0
  25. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/__init__.py +0 -0
  26. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/connection_rule.py +0 -0
  27. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/credential.py +0 -0
  28. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/event.py +0 -0
  29. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/exchange.py +0 -0
  30. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/field_binding.py +0 -0
  31. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/job.py +0 -0
  32. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/plugin_resource.py +0 -0
  33. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/resilience.py +0 -0
  34. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/resource.py +0 -0
  35. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/models/workflow.py +0 -0
  36. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/account_futures.py +0 -0
  37. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/account_stock.py +0 -0
  38. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/ai.py +0 -0
  39. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/backtest.py +0 -0
  40. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/backtest_futures.py +0 -0
  41. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/backtest_stock.py +0 -0
  42. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/base.py +0 -0
  43. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/broker.py +0 -0
  44. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/calculation.py +0 -0
  45. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/condition.py +0 -0
  46. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/data.py +0 -0
  47. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/data_futures.py +0 -0
  48. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/data_stock.py +0 -0
  49. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/display.py +0 -0
  50. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/event.py +0 -0
  51. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
  52. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
  53. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/order.py +0 -0
  54. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/portfolio.py +0 -0
  55. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/realtime_futures.py +0 -0
  56. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/realtime_stock.py +0 -0
  57. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/risk.py +0 -0
  58. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/symbol.py +0 -0
  59. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/symbol_futures.py +0 -0
  60. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/symbol_stock.py +0 -0
  61. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/nodes/trigger.py +0 -0
  62. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/presets/__init__.py +0 -0
  63. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/presets/news_analyst.json +0 -0
  64. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/presets/risk_manager.json +0 -0
  65. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/presets/strategist.json +0 -0
  66. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/presets/technical_analyst.json +0 -0
  67. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/registry/__init__.py +0 -0
  68. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/registry/credential_registry.py +0 -0
  69. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/registry/dynamic_node_registry.py +0 -0
  70. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/registry/plugin_registry.py +0 -0
  71. {programgarden_core-1.2.0 → programgarden_core-1.3.0}/programgarden_core/retry_executor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: programgarden-core
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
5
5
  Author: 프로그램동산
6
6
  Author-email: coding@programgarden.com
@@ -36,6 +36,8 @@
36
36
  "nodes.AIAgentNode.name": "AI Agent",
37
37
  "nodes.AggregateNode.description": "Collect individual items into a list. Use with SplitNode.",
38
38
  "nodes.AggregateNode.name": "Aggregate",
39
+ "nodes.IfNode.description": "Branch execution flow based on a condition (if/else)",
40
+ "nodes.IfNode.name": "If Branch",
39
41
  "nodes.AlertNode.description": "Send notifications (email, SMS, webhook).",
40
42
  "nodes.AlertNode.name": "Alert",
41
43
  "nodes.BacktestEngineNode.description": "Run backtest with signals and historical data, calculate performance metrics.",
@@ -182,6 +184,9 @@
182
184
  "fields.AggregateNode.mode": "Select aggregation mode",
183
185
  "fields.AggregateNode.value_field": "Field name for calculation (for sum/avg/min/max modes)",
184
186
  "fields.AggregateNode.value_field_helper": "Specify a field containing numeric values",
187
+ "fields.IfNode.left": "Left value (comparison target)",
188
+ "fields.IfNode.operator": "Comparison operator",
189
+ "fields.IfNode.right": "Right value (comparison reference)",
185
190
  "fields.AlertNode.credential_id": "Alert service credential",
186
191
  "fields.AlertNode.message_template": "Message template (variables supported)",
187
192
  "fields.BacktestEngineNode.allow_fractional": "Allow fractional shares",
@@ -437,6 +442,9 @@
437
442
  "fieldNames.AggregateNode.filter_field": "Filter Field",
438
443
  "fieldNames.AggregateNode.mode": "Mode",
439
444
  "fieldNames.AggregateNode.value_field": "Value Field",
445
+ "fieldNames.IfNode.left": "Left Value",
446
+ "fieldNames.IfNode.operator": "Operator",
447
+ "fieldNames.IfNode.right": "Right Value",
440
448
  "fieldNames.BacktestEngineNode.allow_fractional": "Allow Fractional",
441
449
  "fieldNames.BacktestEngineNode.allow_short": "Allow Short",
442
450
  "fieldNames.BacktestEngineNode.atr_period": "ATR Period",
@@ -666,6 +674,9 @@
666
674
  "ports.aggregate_count": "Item count",
667
675
  "ports.aggregate_item": "Item to collect",
668
676
  "ports.aggregate_value": "Calculated result (sum/avg/min/max)",
677
+ "ports.if_true": "Output when condition is true",
678
+ "ports.if_false": "Output when condition is false",
679
+ "ports.if_result": "Condition evaluation result (boolean)",
669
680
  "ports.allocated_capital": "Allocated capital",
670
681
  "ports.allocation_weights": "Allocation weights",
671
682
  "ports.approved_symbols": "Approved symbols",
@@ -800,6 +811,18 @@
800
811
  "enums.aggregate_mode.max": "Maximum",
801
812
  "enums.aggregate_mode.min": "Minimum",
802
813
  "enums.aggregate_mode.sum": "Sum",
814
+ "enums.if_operator.eq": "Equals (==)",
815
+ "enums.if_operator.ne": "Not equals (!=)",
816
+ "enums.if_operator.gt": "Greater than (>)",
817
+ "enums.if_operator.gte": "Greater or equal (>=)",
818
+ "enums.if_operator.lt": "Less than (<)",
819
+ "enums.if_operator.lte": "Less or equal (<=)",
820
+ "enums.if_operator.in": "Is in (in)",
821
+ "enums.if_operator.not_in": "Is not in (not in)",
822
+ "enums.if_operator.contains": "Contains",
823
+ "enums.if_operator.not_contains": "Does not contain",
824
+ "enums.if_operator.is_empty": "Is empty",
825
+ "enums.if_operator.is_not_empty": "Is not empty",
803
826
  "enums.allocation_method.custom": "Custom Allocation",
804
827
  "enums.allocation_method.equal": "Equal Weight",
805
828
  "enums.allocation_method.momentum": "Momentum Based",
@@ -36,6 +36,8 @@
36
36
  "nodes.AIAgentNode.name": "AI 에이전트",
37
37
  "nodes.AggregateNode.description": "개별 아이템을 리스트로 수집합니다. SplitNode와 함께 사용하세요.",
38
38
  "nodes.AggregateNode.name": "집계",
39
+ "nodes.IfNode.description": "조건에 따라 실행 흐름을 분기합니다 (if/else)",
40
+ "nodes.IfNode.name": "조건 분기",
39
41
  "nodes.AlertNode.description": "알림 발송 (이메일, SMS, 웹훅).",
40
42
  "nodes.AlertNode.name": "알림",
41
43
  "nodes.BacktestEngineNode.description": "시그널과 과거 데이터로 백테스트 실행 및 성과 지표 계산.",
@@ -182,6 +184,9 @@
182
184
  "fields.AggregateNode.mode": "집계 방식을 선택하세요",
183
185
  "fields.AggregateNode.value_field": "계산에 사용할 필드명 (sum/avg/min/max 모드용)",
184
186
  "fields.AggregateNode.value_field_helper": "숫자 값이 있는 필드를 지정하세요",
187
+ "fields.IfNode.left": "왼쪽 값 (비교 대상)",
188
+ "fields.IfNode.operator": "비교 연산자",
189
+ "fields.IfNode.right": "오른쪽 값 (비교 기준)",
185
190
  "fields.AlertNode.credential_id": "알림 서비스 인증정보",
186
191
  "fields.AlertNode.message_template": "메시지 템플릿 (변수 사용 가능)",
187
192
  "fields.BacktestEngineNode.allow_fractional": "소수점 주식 허용",
@@ -437,6 +442,9 @@
437
442
  "fieldNames.AggregateNode.filter_field": "필터필드",
438
443
  "fieldNames.AggregateNode.mode": "집계방식",
439
444
  "fieldNames.AggregateNode.value_field": "값필드",
445
+ "fieldNames.IfNode.left": "왼쪽 값",
446
+ "fieldNames.IfNode.operator": "연산자",
447
+ "fieldNames.IfNode.right": "오른쪽 값",
440
448
  "fieldNames.BacktestEngineNode.allow_fractional": "소수점매매허용",
441
449
  "fieldNames.BacktestEngineNode.allow_short": "공매도허용",
442
450
  "fieldNames.BacktestEngineNode.atr_period": "ATR기간",
@@ -666,6 +674,9 @@
666
674
  "ports.aggregate_count": "아이템 개수",
667
675
  "ports.aggregate_item": "수집할 아이템",
668
676
  "ports.aggregate_value": "계산 결과 (sum/avg/min/max)",
677
+ "ports.if_true": "조건이 참일 때 출력",
678
+ "ports.if_false": "조건이 거짓일 때 출력",
679
+ "ports.if_result": "조건 평가 결과 (boolean)",
669
680
  "ports.allocated_capital": "배분된 자본",
670
681
  "ports.allocation_weights": "배분 비중",
671
682
  "ports.approved_symbols": "승인된 종목",
@@ -800,6 +811,18 @@
800
811
  "enums.aggregate_mode.max": "최댓값",
801
812
  "enums.aggregate_mode.min": "최솟값",
802
813
  "enums.aggregate_mode.sum": "합계",
814
+ "enums.if_operator.eq": "같음 (==)",
815
+ "enums.if_operator.ne": "다름 (!=)",
816
+ "enums.if_operator.gt": "초과 (>)",
817
+ "enums.if_operator.gte": "이상 (>=)",
818
+ "enums.if_operator.lt": "미만 (<)",
819
+ "enums.if_operator.lte": "이하 (<=)",
820
+ "enums.if_operator.in": "포함됨 (in)",
821
+ "enums.if_operator.not_in": "미포함됨 (not in)",
822
+ "enums.if_operator.contains": "포함함 (contains)",
823
+ "enums.if_operator.not_contains": "미포함함 (not contains)",
824
+ "enums.if_operator.is_empty": "비어있음",
825
+ "enums.if_operator.is_not_empty": "비어있지 않음",
803
826
  "enums.allocation_method.custom": "커스텀 배분",
804
827
  "enums.allocation_method.equal": "균등 배분",
805
828
  "enums.allocation_method.momentum": "모멘텀 기반",
@@ -58,6 +58,10 @@ class Edge(BaseModel):
58
58
  alias="to",
59
59
  description="도착 노드 ID",
60
60
  )
61
+ from_port: Optional[str] = Field(
62
+ default=None,
63
+ description="출발 포트 (예: IfNode의 true/false)",
64
+ )
61
65
  edge_type: EdgeType = Field(
62
66
  default=EdgeType.MAIN,
63
67
  alias="type",
@@ -78,20 +82,27 @@ class Edge(BaseModel):
78
82
  @classmethod
79
83
  def extract_node_ids(cls, values: dict) -> dict:
80
84
  """
81
- from/to 값에서 노드 ID만 추출 (하위호환성)
82
-
83
- "nodeA.portX" → "nodeA"
84
- "nodeA" → "nodeA"
85
+ from/to 값에서 노드 ID만 추출하고 포트 정보를 보존
86
+
87
+ "nodeA.portX" → from="nodeA", from_port="portX"
88
+ "nodeA" → from="nodeA"
85
89
  """
86
90
  if isinstance(values, dict):
87
91
  from_val = values.get('from') or values.get('from_node')
88
92
  to_val = values.get('to') or values.get('to_node')
89
-
93
+
90
94
  if from_val:
91
- values['from'] = from_val.split('.')[0]
95
+ from_str = str(from_val)
96
+ if '.' in from_str:
97
+ parts = from_str.split('.', 1)
98
+ values['from'] = parts[0]
99
+ if not values.get('from_port'):
100
+ values['from_port'] = parts[1]
101
+ else:
102
+ values['from'] = from_str
92
103
  if to_val:
93
- values['to'] = to_val.split('.')[0]
94
-
104
+ values['to'] = str(to_val).split('.')[0]
105
+
95
106
  return values
96
107
 
97
108
  # 하위호환성을 위한 property (기존 코드가 참조하는 경우)
@@ -2,7 +2,7 @@
2
2
  ProgramGarden Core - 노드 타입 정의
3
3
 
4
4
  48개 노드 타입을 카테고리로 분류:
5
- - infra (4): StartNode, ThrottleNode, SplitNode, AggregateNode
5
+ - infra (5): StartNode, ThrottleNode, SplitNode, AggregateNode, IfNode
6
6
  - broker (2): OverseasStockBrokerNode, OverseasFuturesBrokerNode
7
7
  - market (12): OverseasStockMarketDataNode, OverseasFuturesMarketDataNode, OverseasStockHistoricalDataNode, OverseasFuturesHistoricalDataNode,
8
8
  OverseasStockRealMarketDataNode, OverseasFuturesRealMarketDataNode, OverseasStockSymbolQueryNode, OverseasFuturesSymbolQueryNode,
@@ -22,7 +22,7 @@ ProgramGarden Core - 노드 타입 정의
22
22
  """
23
23
 
24
24
  from programgarden_core.nodes.base import BaseNode, NodeCategory, Position
25
- from programgarden_core.nodes.infra import StartNode, ThrottleNode, SplitNode, AggregateNode
25
+ from programgarden_core.nodes.infra import StartNode, ThrottleNode, SplitNode, AggregateNode, IfNode
26
26
  from programgarden_core.nodes.broker import OverseasStockBrokerNode, OverseasFuturesBrokerNode
27
27
  # Market - 상품별 분리 노드
28
28
  from programgarden_core.nodes.data_stock import OverseasStockMarketDataNode
@@ -95,6 +95,7 @@ __all__ = [
95
95
  "ThrottleNode",
96
96
  "SplitNode",
97
97
  "AggregateNode",
98
+ "IfNode",
98
99
  # Broker (상품별 분리)
99
100
  "OverseasStockBrokerNode",
100
101
  "OverseasFuturesBrokerNode",
@@ -6,6 +6,7 @@ Infrastructure nodes:
6
6
  - ThrottleNode: Data flow control
7
7
  - SplitNode: Split list into individual items (item-based execution)
8
8
  - AggregateNode: Aggregate individual items into a list
9
+ - IfNode: Conditional branching (if/else)
9
10
  """
10
11
 
11
12
  from typing import Optional, List, Literal, Dict, TYPE_CHECKING, ClassVar, Any
@@ -309,3 +310,106 @@ class AggregateNode(BaseNode):
309
310
  helper_text="i18n:fields.AggregateNode.value_field_helper",
310
311
  ),
311
312
  }
313
+
314
+
315
+ class IfNode(BaseNode):
316
+ """
317
+ 조건 분기 노드
318
+
319
+ left와 right 값을 operator로 비교하여 true/false 브랜치로 실행 흐름을 분기합니다.
320
+ 조건이 참이면 true 포트로, 거짓이면 false 포트로 데이터가 전달됩니다.
321
+
322
+ Edge에 from_port를 지정하여 분기 경로를 설정합니다:
323
+ - {"from": "if1", "to": "order", "from_port": "true"}
324
+ - {"from": "if1", "to": "notify", "from_port": "false"}
325
+ """
326
+
327
+ type: Literal["IfNode"] = "IfNode"
328
+ category: NodeCategory = NodeCategory.INFRA
329
+ description: str = "i18n:nodes.IfNode.description"
330
+
331
+ _img_url: ClassVar[str] = "https://cdn.programgarden.io/nodes/if.svg"
332
+
333
+ # 비교 연산 필드
334
+ left: Any = Field(default=None, description="왼쪽 피연산자 (표현식 바인딩 가능)")
335
+ operator: Literal[
336
+ "==", "!=", ">", ">=", "<", "<=",
337
+ "in", "not_in",
338
+ "contains", "not_contains",
339
+ "is_empty", "is_not_empty",
340
+ ] = Field(default="==", description="비교 연산자")
341
+ right: Any = Field(default=None, description="오른쪽 피연산자 (표현식 바인딩 가능)")
342
+
343
+ _inputs: List[InputPort] = [
344
+ InputPort(name="trigger", type="signal", description="i18n:ports.trigger"),
345
+ ]
346
+ _outputs: List[OutputPort] = [
347
+ OutputPort(name="true", type="any", description="i18n:ports.if_true"),
348
+ OutputPort(name="false", type="any", description="i18n:ports.if_false"),
349
+ OutputPort(name="result", type="boolean", description="i18n:ports.if_result"),
350
+ ]
351
+
352
+ @classmethod
353
+ def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
354
+ from programgarden_core.models.field_binding import (
355
+ FieldSchema, FieldType, FieldCategory, ExpressionMode,
356
+ )
357
+ return {
358
+ "left": FieldSchema(
359
+ name="left",
360
+ type=FieldType.STRING,
361
+ description="i18n:fields.IfNode.left",
362
+ required=True,
363
+ expression_mode=ExpressionMode.BOTH,
364
+ category=FieldCategory.PARAMETERS,
365
+ placeholder="{{ nodes.account.balance }}",
366
+ example="{{ nodes.account.balance }}",
367
+ expected_type="any",
368
+ ),
369
+ "operator": FieldSchema(
370
+ name="operator",
371
+ type=FieldType.ENUM,
372
+ description="i18n:fields.IfNode.operator",
373
+ default="==",
374
+ enum_values=[
375
+ "==", "!=", ">", ">=", "<", "<=",
376
+ "in", "not_in",
377
+ "contains", "not_contains",
378
+ "is_empty", "is_not_empty",
379
+ ],
380
+ enum_labels={
381
+ "==": "i18n:enums.if_operator.eq",
382
+ "!=": "i18n:enums.if_operator.ne",
383
+ ">": "i18n:enums.if_operator.gt",
384
+ ">=": "i18n:enums.if_operator.gte",
385
+ "<": "i18n:enums.if_operator.lt",
386
+ "<=": "i18n:enums.if_operator.lte",
387
+ "in": "i18n:enums.if_operator.in",
388
+ "not_in": "i18n:enums.if_operator.not_in",
389
+ "contains": "i18n:enums.if_operator.contains",
390
+ "not_contains": "i18n:enums.if_operator.not_contains",
391
+ "is_empty": "i18n:enums.if_operator.is_empty",
392
+ "is_not_empty": "i18n:enums.if_operator.is_not_empty",
393
+ },
394
+ required=True,
395
+ expression_mode=ExpressionMode.FIXED_ONLY,
396
+ category=FieldCategory.PARAMETERS,
397
+ ),
398
+ "right": FieldSchema(
399
+ name="right",
400
+ type=FieldType.STRING,
401
+ description="i18n:fields.IfNode.right",
402
+ required=False,
403
+ expression_mode=ExpressionMode.BOTH,
404
+ category=FieldCategory.PARAMETERS,
405
+ placeholder="1000000",
406
+ example="1000000",
407
+ expected_type="any",
408
+ visible_when={
409
+ "operator": [
410
+ "==", "!=", ">", ">=", "<", "<=",
411
+ "in", "not_in", "contains", "not_contains",
412
+ ],
413
+ },
414
+ ),
415
+ }
@@ -76,7 +76,7 @@ class NodeTypeRegistry:
76
76
  """Register built-in node types"""
77
77
  # 지연 임포트로 순환 참조 방지
78
78
  from programgarden_core.nodes import (
79
- StartNode, ThrottleNode, SplitNode, AggregateNode,
79
+ StartNode, ThrottleNode, SplitNode, AggregateNode, IfNode,
80
80
  OverseasStockBrokerNode, OverseasFuturesBrokerNode,
81
81
  # Market - Stock (해외주식)
82
82
  OverseasStockMarketDataNode, OverseasStockHistoricalDataNode,
@@ -110,7 +110,7 @@ class NodeTypeRegistry:
110
110
 
111
111
  node_classes = [
112
112
  # Infra
113
- StartNode, ThrottleNode, SplitNode, AggregateNode,
113
+ StartNode, ThrottleNode, SplitNode, AggregateNode, IfNode,
114
114
  # Broker (상품별 분리)
115
115
  OverseasStockBrokerNode, OverseasFuturesBrokerNode,
116
116
  # Market - Stock (해외주식)
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden-core"
8
- version = "1.2.0"
8
+ version = "1.3.0"
9
9
  description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
10
10
  readme = "README.md"
11
11