planar 0.5.0__py3-none-any.whl → 0.8.0__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 (211) hide show
  1. planar/_version.py +1 -1
  2. planar/ai/agent.py +155 -283
  3. planar/ai/agent_base.py +170 -0
  4. planar/ai/agent_utils.py +7 -0
  5. planar/ai/pydantic_ai.py +638 -0
  6. planar/ai/test_agent_serialization.py +1 -1
  7. planar/app.py +64 -20
  8. planar/cli.py +39 -27
  9. planar/config.py +45 -36
  10. planar/db/db.py +2 -1
  11. planar/files/storage/azure_blob.py +343 -0
  12. planar/files/storage/base.py +7 -0
  13. planar/files/storage/config.py +70 -7
  14. planar/files/storage/s3.py +6 -6
  15. planar/files/storage/test_azure_blob.py +435 -0
  16. planar/logging/formatter.py +17 -4
  17. planar/logging/test_formatter.py +327 -0
  18. planar/registry_items.py +2 -1
  19. planar/routers/agents_router.py +3 -1
  20. planar/routers/files.py +11 -2
  21. planar/routers/models.py +14 -1
  22. planar/routers/test_agents_router.py +1 -1
  23. planar/routers/test_files_router.py +49 -0
  24. planar/routers/test_routes_security.py +5 -7
  25. planar/routers/test_workflow_router.py +270 -3
  26. planar/routers/workflow.py +95 -36
  27. planar/rules/models.py +36 -39
  28. planar/rules/test_data/account_dormancy_management.json +223 -0
  29. planar/rules/test_data/airline_loyalty_points_calculator.json +262 -0
  30. planar/rules/test_data/applicant_risk_assessment.json +435 -0
  31. planar/rules/test_data/booking_fraud_detection.json +407 -0
  32. planar/rules/test_data/cellular_data_rollover_system.json +258 -0
  33. planar/rules/test_data/clinical_trial_eligibility_screener.json +437 -0
  34. planar/rules/test_data/customer_lifetime_value.json +143 -0
  35. planar/rules/test_data/import_duties_calculator.json +289 -0
  36. planar/rules/test_data/insurance_prior_authorization.json +443 -0
  37. planar/rules/test_data/online_check_in_eligibility_system.json +254 -0
  38. planar/rules/test_data/order_consolidation_system.json +375 -0
  39. planar/rules/test_data/portfolio_risk_monitor.json +471 -0
  40. planar/rules/test_data/supply_chain_risk.json +253 -0
  41. planar/rules/test_data/warehouse_cross_docking.json +237 -0
  42. planar/rules/test_rules.py +750 -6
  43. planar/scaffold_templates/planar.dev.yaml.j2 +6 -6
  44. planar/scaffold_templates/planar.prod.yaml.j2 +9 -5
  45. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  46. planar/security/auth_context.py +21 -0
  47. planar/security/{jwt_middleware.py → auth_middleware.py} +70 -17
  48. planar/security/authorization.py +9 -15
  49. planar/security/tests/test_auth_middleware.py +162 -0
  50. planar/sse/proxy.py +4 -9
  51. planar/test_app.py +92 -1
  52. planar/test_cli.py +81 -59
  53. planar/test_config.py +17 -14
  54. planar/testing/fixtures.py +325 -0
  55. planar/testing/planar_test_client.py +5 -2
  56. planar/utils.py +41 -1
  57. planar/workflows/execution.py +1 -1
  58. planar/workflows/orchestrator.py +5 -0
  59. planar/workflows/serialization.py +12 -6
  60. planar/workflows/step_core.py +3 -1
  61. planar/workflows/test_serialization.py +9 -1
  62. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/METADATA +30 -5
  63. planar-0.8.0.dist-info/RECORD +166 -0
  64. planar/.__init__.py.un~ +0 -0
  65. planar/._version.py.un~ +0 -0
  66. planar/.app.py.un~ +0 -0
  67. planar/.cli.py.un~ +0 -0
  68. planar/.config.py.un~ +0 -0
  69. planar/.context.py.un~ +0 -0
  70. planar/.db.py.un~ +0 -0
  71. planar/.di.py.un~ +0 -0
  72. planar/.engine.py.un~ +0 -0
  73. planar/.files.py.un~ +0 -0
  74. planar/.log_context.py.un~ +0 -0
  75. planar/.log_metadata.py.un~ +0 -0
  76. planar/.logging.py.un~ +0 -0
  77. planar/.object_registry.py.un~ +0 -0
  78. planar/.otel.py.un~ +0 -0
  79. planar/.server.py.un~ +0 -0
  80. planar/.session.py.un~ +0 -0
  81. planar/.sqlalchemy.py.un~ +0 -0
  82. planar/.task_local.py.un~ +0 -0
  83. planar/.test_app.py.un~ +0 -0
  84. planar/.test_config.py.un~ +0 -0
  85. planar/.test_object_config.py.un~ +0 -0
  86. planar/.test_sqlalchemy.py.un~ +0 -0
  87. planar/.test_utils.py.un~ +0 -0
  88. planar/.util.py.un~ +0 -0
  89. planar/.utils.py.un~ +0 -0
  90. planar/ai/.__init__.py.un~ +0 -0
  91. planar/ai/._models.py.un~ +0 -0
  92. planar/ai/.agent.py.un~ +0 -0
  93. planar/ai/.agent_utils.py.un~ +0 -0
  94. planar/ai/.events.py.un~ +0 -0
  95. planar/ai/.files.py.un~ +0 -0
  96. planar/ai/.models.py.un~ +0 -0
  97. planar/ai/.providers.py.un~ +0 -0
  98. planar/ai/.pydantic_ai.py.un~ +0 -0
  99. planar/ai/.pydantic_ai_agent.py.un~ +0 -0
  100. planar/ai/.pydantic_ai_provider.py.un~ +0 -0
  101. planar/ai/.step.py.un~ +0 -0
  102. planar/ai/.test_agent.py.un~ +0 -0
  103. planar/ai/.test_agent_serialization.py.un~ +0 -0
  104. planar/ai/.test_providers.py.un~ +0 -0
  105. planar/ai/.utils.py.un~ +0 -0
  106. planar/ai/providers.py +0 -1088
  107. planar/ai/test_agent.py +0 -1298
  108. planar/ai/test_providers.py +0 -463
  109. planar/db/.db.py.un~ +0 -0
  110. planar/files/.config.py.un~ +0 -0
  111. planar/files/.local.py.un~ +0 -0
  112. planar/files/.local_filesystem.py.un~ +0 -0
  113. planar/files/.model.py.un~ +0 -0
  114. planar/files/.models.py.un~ +0 -0
  115. planar/files/.s3.py.un~ +0 -0
  116. planar/files/.storage.py.un~ +0 -0
  117. planar/files/.test_files.py.un~ +0 -0
  118. planar/files/storage/.__init__.py.un~ +0 -0
  119. planar/files/storage/.base.py.un~ +0 -0
  120. planar/files/storage/.config.py.un~ +0 -0
  121. planar/files/storage/.context.py.un~ +0 -0
  122. planar/files/storage/.local_directory.py.un~ +0 -0
  123. planar/files/storage/.test_local_directory.py.un~ +0 -0
  124. planar/files/storage/.test_s3.py.un~ +0 -0
  125. planar/human/.human.py.un~ +0 -0
  126. planar/human/.test_human.py.un~ +0 -0
  127. planar/logging/.__init__.py.un~ +0 -0
  128. planar/logging/.attributes.py.un~ +0 -0
  129. planar/logging/.formatter.py.un~ +0 -0
  130. planar/logging/.logger.py.un~ +0 -0
  131. planar/logging/.otel.py.un~ +0 -0
  132. planar/logging/.tracer.py.un~ +0 -0
  133. planar/modeling/.mixin.py.un~ +0 -0
  134. planar/modeling/.storage.py.un~ +0 -0
  135. planar/modeling/orm/.planar_base_model.py.un~ +0 -0
  136. planar/object_config/.object_config.py.un~ +0 -0
  137. planar/routers/.__init__.py.un~ +0 -0
  138. planar/routers/.agents_router.py.un~ +0 -0
  139. planar/routers/.crud.py.un~ +0 -0
  140. planar/routers/.decision.py.un~ +0 -0
  141. planar/routers/.event.py.un~ +0 -0
  142. planar/routers/.file_attachment.py.un~ +0 -0
  143. planar/routers/.files.py.un~ +0 -0
  144. planar/routers/.files_router.py.un~ +0 -0
  145. planar/routers/.human.py.un~ +0 -0
  146. planar/routers/.info.py.un~ +0 -0
  147. planar/routers/.models.py.un~ +0 -0
  148. planar/routers/.object_config_router.py.un~ +0 -0
  149. planar/routers/.rule.py.un~ +0 -0
  150. planar/routers/.test_object_config_router.py.un~ +0 -0
  151. planar/routers/.test_workflow_router.py.un~ +0 -0
  152. planar/routers/.workflow.py.un~ +0 -0
  153. planar/rules/.decorator.py.un~ +0 -0
  154. planar/rules/.runner.py.un~ +0 -0
  155. planar/rules/.test_rules.py.un~ +0 -0
  156. planar/security/.jwt_middleware.py.un~ +0 -0
  157. planar/sse/.constants.py.un~ +0 -0
  158. planar/sse/.example.html.un~ +0 -0
  159. planar/sse/.hub.py.un~ +0 -0
  160. planar/sse/.model.py.un~ +0 -0
  161. planar/sse/.proxy.py.un~ +0 -0
  162. planar/testing/.client.py.un~ +0 -0
  163. planar/testing/.memory_storage.py.un~ +0 -0
  164. planar/testing/.planar_test_client.py.un~ +0 -0
  165. planar/testing/.predictable_tracer.py.un~ +0 -0
  166. planar/testing/.synchronizable_tracer.py.un~ +0 -0
  167. planar/testing/.test_memory_storage.py.un~ +0 -0
  168. planar/testing/.workflow_observer.py.un~ +0 -0
  169. planar/workflows/.__init__.py.un~ +0 -0
  170. planar/workflows/.builtin_steps.py.un~ +0 -0
  171. planar/workflows/.concurrency_tracing.py.un~ +0 -0
  172. planar/workflows/.context.py.un~ +0 -0
  173. planar/workflows/.contrib.py.un~ +0 -0
  174. planar/workflows/.decorators.py.un~ +0 -0
  175. planar/workflows/.durable_test.py.un~ +0 -0
  176. planar/workflows/.errors.py.un~ +0 -0
  177. planar/workflows/.events.py.un~ +0 -0
  178. planar/workflows/.exceptions.py.un~ +0 -0
  179. planar/workflows/.execution.py.un~ +0 -0
  180. planar/workflows/.human.py.un~ +0 -0
  181. planar/workflows/.lock.py.un~ +0 -0
  182. planar/workflows/.misc.py.un~ +0 -0
  183. planar/workflows/.model.py.un~ +0 -0
  184. planar/workflows/.models.py.un~ +0 -0
  185. planar/workflows/.notifications.py.un~ +0 -0
  186. planar/workflows/.orchestrator.py.un~ +0 -0
  187. planar/workflows/.runtime.py.un~ +0 -0
  188. planar/workflows/.serialization.py.un~ +0 -0
  189. planar/workflows/.step.py.un~ +0 -0
  190. planar/workflows/.step_core.py.un~ +0 -0
  191. planar/workflows/.sub_workflow_runner.py.un~ +0 -0
  192. planar/workflows/.sub_workflow_scheduler.py.un~ +0 -0
  193. planar/workflows/.test_concurrency.py.un~ +0 -0
  194. planar/workflows/.test_concurrency_detection.py.un~ +0 -0
  195. planar/workflows/.test_human.py.un~ +0 -0
  196. planar/workflows/.test_lock_timeout.py.un~ +0 -0
  197. planar/workflows/.test_orchestrator.py.un~ +0 -0
  198. planar/workflows/.test_race_conditions.py.un~ +0 -0
  199. planar/workflows/.test_serialization.py.un~ +0 -0
  200. planar/workflows/.test_suspend_deserialization.py.un~ +0 -0
  201. planar/workflows/.test_workflow.py.un~ +0 -0
  202. planar/workflows/.tracing.py.un~ +0 -0
  203. planar/workflows/.types.py.un~ +0 -0
  204. planar/workflows/.util.py.un~ +0 -0
  205. planar/workflows/.utils.py.un~ +0 -0
  206. planar/workflows/.workflow.py.un~ +0 -0
  207. planar/workflows/.workflow_wrapper.py.un~ +0 -0
  208. planar/workflows/.wrappers.py.un~ +0 -0
  209. planar-0.5.0.dist-info/RECORD +0 -289
  210. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/WHEEL +0 -0
  211. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/entry_points.txt +0 -0
planar/rules/models.py CHANGED
@@ -4,7 +4,7 @@ import json
4
4
  from typing import Annotated, Literal, Type
5
5
  from uuid import uuid4
6
6
 
7
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel, ConfigDict, Field
8
8
 
9
9
  from planar.modeling.field_helpers import JsonSchema
10
10
  from planar.object_config.object_config import (
@@ -12,14 +12,18 @@ from planar.object_config.object_config import (
12
12
  )
13
13
 
14
14
 
15
- class Position(BaseModel):
15
+ class JDMBaseModel(BaseModel):
16
+ model_config = ConfigDict(extra="forbid")
17
+
18
+
19
+ class Position(JDMBaseModel):
16
20
  """Represents the x,y coordinates of a node in the graph"""
17
21
 
18
- x: int
19
- y: int
22
+ x: int | float
23
+ y: int | float
20
24
 
21
25
 
22
- class InputOutputNodeContent(BaseModel):
26
+ class InputOutputNodeContent(JDMBaseModel):
23
27
  """Content structure for input or output nodes containing JSON schema"""
24
28
 
25
29
  schema_: str = Field(
@@ -27,15 +31,15 @@ class InputOutputNodeContent(BaseModel):
27
31
  ) # JSON string representation of the input schema
28
32
 
29
33
 
30
- class RuleTableInput(BaseModel):
34
+ class RuleTableInput(JDMBaseModel):
31
35
  """Represents an input column in a decision table"""
32
36
 
33
37
  id: str
34
- field: str # The field name from the input schema
38
+ field: str | None = None # The field name from the input schema
35
39
  name: str
36
40
 
37
41
 
38
- class RuleTableOutput(BaseModel):
42
+ class RuleTableOutput(JDMBaseModel):
39
43
  """Represents an output column in a decision table"""
40
44
 
41
45
  id: str
@@ -43,23 +47,11 @@ class RuleTableOutput(BaseModel):
43
47
  name: str
44
48
 
45
49
 
46
- class DecisionRule(BaseModel):
47
- """Represents a single rule row in a decision table with dynamic values"""
48
-
49
- rule_id: str = Field(
50
- alias="_id"
51
- ) # Using alias to maintain compatibility with _id field
52
- # Note: Additional dynamic keys will be present based on input/output column IDs
53
- # These are handled using model_extra="allow" configuration
54
-
55
- model_config = {"extra": "allow", "populate_by_name": True}
56
-
57
-
58
- class DecisionTableNodeContent(BaseModel):
50
+ class DecisionTableNodeContent(JDMBaseModel):
59
51
  """Content structure for decision table nodes"""
60
52
 
61
- hitPolicy: Literal["first"] # Policy for rule evaluation
62
- rules: list[DecisionRule] # List of rule rows
53
+ hitPolicy: Literal["first"] | Literal["collect"] # Policy for rule evaluation
54
+ rules: list[dict[str, str]] # List of rule rows
63
55
  inputs: list[RuleTableInput] # Input column definitions
64
56
  outputs: list[RuleTableOutput] # Output column definitions
65
57
 
@@ -73,7 +65,7 @@ class DecisionTableNodeContent(BaseModel):
73
65
  executionMode: Literal["single", "loop"] | None # Execution mode for the table
74
66
 
75
67
 
76
- class InputNode(BaseModel):
68
+ class InputNode(JDMBaseModel):
77
69
  """Represents an input node in the JDM graph"""
78
70
 
79
71
  id: str
@@ -83,7 +75,7 @@ class InputNode(BaseModel):
83
75
  position: Position
84
76
 
85
77
 
86
- class DecisionTableNode(BaseModel):
78
+ class DecisionTableNode(JDMBaseModel):
87
79
  """Represents a decision table node in the JDM graph"""
88
80
 
89
81
  id: str
@@ -93,7 +85,7 @@ class DecisionTableNode(BaseModel):
93
85
  position: Position
94
86
 
95
87
 
96
- class OutputNode(BaseModel):
88
+ class OutputNode(JDMBaseModel):
97
89
  """Represents an output node in the JDM graph"""
98
90
 
99
91
  id: str
@@ -103,13 +95,13 @@ class OutputNode(BaseModel):
103
95
  position: Position
104
96
 
105
97
 
106
- class FunctionNodeContent(BaseModel):
98
+ class FunctionNodeContent(JDMBaseModel):
107
99
  """Content structure for a function node containing JavaScript source code."""
108
100
 
109
101
  source: str
110
102
 
111
103
 
112
- class FunctionNode(BaseModel):
104
+ class FunctionNode(JDMBaseModel):
113
105
  """Represents a JavaScript function node in the JDM graph."""
114
106
 
115
107
  id: str
@@ -119,11 +111,13 @@ class FunctionNode(BaseModel):
119
111
  position: Position
120
112
 
121
113
 
122
- # class LegacyFunctionNode(BaseModel):
114
+ # The gorules site sometimes uses this legacy node in their (presumably outdated) examples.
115
+ # so safe to ignore since our go rules editor frontend won't generate this node.
116
+ #
117
+ # class LegacyFunctionNode(JDMBaseModel):
123
118
  # """Represents a legacy JavaScript function node in the JDM graph.
124
119
  # The GoRules examples (such as https://editor.gorules.io/?template=shipping-fees use a legacy fn node)
125
120
  # """
126
-
127
121
  # id: str
128
122
  # type: Literal["functionNode"]
129
123
  # name: str
@@ -131,21 +125,22 @@ class FunctionNode(BaseModel):
131
125
  # position: Position
132
126
 
133
127
 
134
- class SwitchStatement(BaseModel):
128
+ class SwitchStatement(JDMBaseModel):
135
129
  """Represents a statement in a switch node."""
136
130
 
137
131
  id: str
138
132
  condition: str
133
+ isDefault: bool
139
134
 
140
135
 
141
- class SwitchNodeContent(BaseModel):
136
+ class SwitchNodeContent(JDMBaseModel):
142
137
  """Content structure for a switch node."""
143
138
 
144
139
  hitPolicy: Literal["collect", "first"]
145
140
  statements: list[SwitchStatement]
146
141
 
147
142
 
148
- class SwitchNode(BaseModel):
143
+ class SwitchNode(JDMBaseModel):
149
144
  """Represents a switch node in the JDM graph."""
150
145
 
151
146
  id: str
@@ -155,7 +150,7 @@ class SwitchNode(BaseModel):
155
150
  position: Position
156
151
 
157
152
 
158
- class Expression(BaseModel):
153
+ class Expression(JDMBaseModel):
159
154
  """Represents a single expression in an expression node."""
160
155
 
161
156
  id: str
@@ -163,7 +158,7 @@ class Expression(BaseModel):
163
158
  value: str
164
159
 
165
160
 
166
- class ExpressionNodeContent(BaseModel):
161
+ class ExpressionNodeContent(JDMBaseModel):
167
162
  """Content structure for an expression node."""
168
163
 
169
164
  expressions: list[Expression]
@@ -175,7 +170,7 @@ class ExpressionNodeContent(BaseModel):
175
170
  )
176
171
 
177
172
 
178
- class ExpressionNode(BaseModel):
173
+ class ExpressionNode(JDMBaseModel):
179
174
  """Represents an expression node in the JDM graph."""
180
175
 
181
176
  id: str
@@ -198,21 +193,23 @@ JDMNode = (
198
193
  JDMNodeWithType = Annotated[JDMNode, Field(discriminator="type")]
199
194
 
200
195
 
201
- class JDMEdge(BaseModel):
196
+ class JDMEdge(JDMBaseModel):
202
197
  """Represents an edge connecting nodes in the JDM graph"""
203
198
 
204
199
  id: str
205
200
  type: Literal["edge"]
206
201
  sourceId: str # ID of the source node
207
202
  targetId: str # ID of the target node
203
+ sourceHandle: str | None = None
208
204
 
209
205
 
210
- class JDMGraph(BaseModel):
206
+ class JDMGraph(JDMBaseModel):
211
207
  """
212
208
  Complete JDM (JSON Decision Model) graph structure.
213
209
  This represents a GoRules decision flow with input, processing, and output nodes.
214
210
  """
215
211
 
212
+ contentType: Literal["application/vnd.gorules.decision"] | None = None
216
213
  nodes: list[JDMNodeWithType] # All nodes in the graph
217
214
  edges: list[JDMEdge] # All edges connecting the nodes
218
215
 
@@ -287,7 +284,7 @@ def create_jdm_graph(rule: Rule) -> JDMGraph:
287
284
  name="decisionTable1",
288
285
  content=DecisionTableNodeContent(
289
286
  hitPolicy="first",
290
- rules=[DecisionRule(_id=str(uuid4()), **rule_values)],
287
+ rules=[{"_id": str(uuid4()), **rule_values}],
291
288
  inputs=[
292
289
  RuleTableInput(
293
290
  id=input_column_id,
@@ -0,0 +1,223 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "type": "inputNode",
5
+ "content": {
6
+ "schema": ""
7
+ },
8
+ "id": "ip1",
9
+ "name": "request",
10
+ "position": {
11
+ "x": 110,
12
+ "y": 173.5
13
+ }
14
+ },
15
+ {
16
+ "type": "expressionNode",
17
+ "content": {
18
+ "expressions": [
19
+ {
20
+ "id": "e1-1",
21
+ "key": "daysSinceLastActivity",
22
+ "value": "date('now') - date(lastActivityDate)"
23
+ },
24
+ {
25
+ "id": "e1-2",
26
+ "key": "daysToThreshold",
27
+ "value": "dormancyThreshold - $.daysSinceLastActivity"
28
+ }
29
+ ],
30
+ "passThrough": true,
31
+ "inputField": null,
32
+ "outputPath": null,
33
+ "executionMode": "single"
34
+ },
35
+ "id": "ex1",
36
+ "name": "calculate_dormancy",
37
+ "position": {
38
+ "x": 430,
39
+ "y": 173.5
40
+ }
41
+ },
42
+ {
43
+ "type": "decisionTableNode",
44
+ "content": {
45
+ "hitPolicy": "first",
46
+ "rules": [
47
+ {
48
+ "_id": "r1-1",
49
+ "i1-1": "< 0",
50
+ "o1-1": "'dormant'"
51
+ },
52
+ {
53
+ "_id": "r1-2",
54
+ "i1-1": "< 30",
55
+ "o1-1": "'at_risk'"
56
+ },
57
+ {
58
+ "_id": "r1-3",
59
+ "i1-1": "< 60",
60
+ "o1-1": "'warning'"
61
+ },
62
+ {
63
+ "_id": "r1-4",
64
+ "i1-1": "",
65
+ "o1-1": "'active'"
66
+ }
67
+ ],
68
+ "inputs": [
69
+ {
70
+ "id": "i1-1",
71
+ "name": "Days To Threshold",
72
+ "field": "daysToThreshold"
73
+ }
74
+ ],
75
+ "outputs": [
76
+ {
77
+ "id": "o1-1",
78
+ "name": "Status",
79
+ "field": "dormancyStatus"
80
+ }
81
+ ],
82
+ "passThrough": true,
83
+ "inputField": null,
84
+ "outputPath": null,
85
+ "executionMode": "single"
86
+ },
87
+ "id": "dt1",
88
+ "name": "dormancy_status",
89
+ "position": {
90
+ "x": 750,
91
+ "y": 173.5
92
+ }
93
+ },
94
+ {
95
+ "type": "switchNode",
96
+ "content": {
97
+ "hitPolicy": "first",
98
+ "statements": [
99
+ {
100
+ "id": "s1-1",
101
+ "condition": "dormancyStatus != 'active'",
102
+ "isDefault": false
103
+ },
104
+ {
105
+ "id": "s1-2",
106
+ "condition": "",
107
+ "isDefault": true
108
+ }
109
+ ]
110
+ },
111
+ "id": "sw1",
112
+ "name": "route_by_status",
113
+ "position": {
114
+ "x": 1070,
115
+ "y": 173.5
116
+ }
117
+ },
118
+ {
119
+ "type": "decisionTableNode",
120
+ "content": {
121
+ "hitPolicy": "first",
122
+ "rules": [
123
+ {
124
+ "_id": "r2-1",
125
+ "i2-1": "'dormant'",
126
+ "i2-2": "'premium'",
127
+ "o2-1": "'fee_waiver'",
128
+ "o2-2": "'high'"
129
+ },
130
+ {
131
+ "_id": "r2-2",
132
+ "i2-1": "'dormant'",
133
+ "i2-2": "",
134
+ "o2-1": "'auto_close'",
135
+ "o2-2": "'medium'"
136
+ },
137
+ {
138
+ "_id": "r2-3",
139
+ "i2-1": "'at_risk'",
140
+ "i2-2": "'premium'",
141
+ "o2-1": "'account_review'",
142
+ "o2-2": "'high'"
143
+ },
144
+ {
145
+ "_id": "r2-4",
146
+ "i2-1": "'at_risk'",
147
+ "i2-2": "",
148
+ "o2-1": "'final_notice'",
149
+ "o2-2": "'high'"
150
+ },
151
+ {
152
+ "_id": "r2-5",
153
+ "i2-1": "'warning'",
154
+ "i2-2": "",
155
+ "o2-1": "'notification'",
156
+ "o2-2": "'medium'"
157
+ }
158
+ ],
159
+ "inputs": [
160
+ {
161
+ "id": "i2-1",
162
+ "name": "Status",
163
+ "field": "dormancyStatus"
164
+ },
165
+ {
166
+ "id": "i2-2",
167
+ "name": "Customer Tier",
168
+ "field": "customerTier"
169
+ }
170
+ ],
171
+ "outputs": [
172
+ {
173
+ "id": "o2-1",
174
+ "name": "Action",
175
+ "field": "recommendedAction"
176
+ },
177
+ {
178
+ "id": "o2-2",
179
+ "name": "Priority",
180
+ "field": "actionPriority"
181
+ }
182
+ ],
183
+ "passThrough": false,
184
+ "inputField": null,
185
+ "outputPath": null,
186
+ "executionMode": "single"
187
+ },
188
+ "id": "dt2",
189
+ "name": "determine_action",
190
+ "position": {
191
+ "x": 1390,
192
+ "y": 173.5
193
+ }
194
+ }
195
+ ],
196
+ "edges": [
197
+ {
198
+ "id": "ed1",
199
+ "sourceId": "ip1",
200
+ "targetId": "ex1",
201
+ "type": "edge"
202
+ },
203
+ {
204
+ "id": "ed2",
205
+ "sourceId": "ex1",
206
+ "targetId": "dt1",
207
+ "type": "edge"
208
+ },
209
+ {
210
+ "id": "ed3",
211
+ "sourceId": "dt1",
212
+ "targetId": "sw1",
213
+ "type": "edge"
214
+ },
215
+ {
216
+ "id": "ed4",
217
+ "sourceId": "sw1",
218
+ "targetId": "dt2",
219
+ "sourceHandle": "s1-1",
220
+ "type": "edge"
221
+ }
222
+ ]
223
+ }
@@ -0,0 +1,262 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "type": "inputNode",
5
+ "content": {
6
+ "schema": ""
7
+ },
8
+ "id": "ip1",
9
+ "name": "request",
10
+ "position": {
11
+ "x": 110,
12
+ "y": 114
13
+ }
14
+ },
15
+ {
16
+ "type": "decisionTableNode",
17
+ "content": {
18
+ "hitPolicy": "first",
19
+ "rules": [
20
+ {
21
+ "_id": "r1-1",
22
+ "i1-1": "'First'",
23
+ "o1-1": "3"
24
+ },
25
+ {
26
+ "_id": "r1-2",
27
+ "i1-1": "'Business'",
28
+ "o1-1": "2"
29
+ },
30
+ {
31
+ "_id": "r1-3",
32
+ "i1-1": "'Premium Economy'",
33
+ "o1-1": "1.5"
34
+ },
35
+ {
36
+ "_id": "r1-4",
37
+ "i1-1": "'Economy Plus'",
38
+ "o1-1": "1.25"
39
+ },
40
+ {
41
+ "_id": "r1-5",
42
+ "i1-1": "",
43
+ "o1-1": "1"
44
+ }
45
+ ],
46
+ "inputs": [
47
+ {
48
+ "id": "i1-1",
49
+ "name": "Fare Class",
50
+ "field": "booking.fareClass"
51
+ }
52
+ ],
53
+ "outputs": [
54
+ {
55
+ "id": "o1-1",
56
+ "name": "Fare Multiplier",
57
+ "field": "points.fareMultiplier"
58
+ }
59
+ ],
60
+ "passThrough": true,
61
+ "inputField": null,
62
+ "outputPath": null,
63
+ "executionMode": "single"
64
+ },
65
+ "id": "dt1",
66
+ "name": "fareClassMultiplier",
67
+ "position": {
68
+ "x": 430,
69
+ "y": 114
70
+ }
71
+ },
72
+ {
73
+ "type": "decisionTableNode",
74
+ "content": {
75
+ "hitPolicy": "first",
76
+ "rules": [
77
+ {
78
+ "_id": "r2-1",
79
+ "i2-1": "'Domestic'",
80
+ "i2-2": "< 500",
81
+ "o2-1": "250"
82
+ },
83
+ {
84
+ "_id": "r2-2",
85
+ "i2-1": "'Domestic'",
86
+ "i2-2": "[500..1000]",
87
+ "o2-1": "500"
88
+ },
89
+ {
90
+ "_id": "r2-3",
91
+ "i2-1": "'Domestic'",
92
+ "i2-2": "> 1000",
93
+ "o2-1": "750"
94
+ },
95
+ {
96
+ "_id": "r2-4",
97
+ "i2-1": "'International'",
98
+ "i2-2": "< 2000",
99
+ "o2-1": "1000"
100
+ },
101
+ {
102
+ "_id": "r2-5",
103
+ "i2-1": "'International'",
104
+ "i2-2": "[2000..5000]",
105
+ "o2-1": "2000"
106
+ },
107
+ {
108
+ "_id": "r2-6",
109
+ "i2-1": "'International'",
110
+ "i2-2": "> 5000",
111
+ "o2-1": "3000"
112
+ },
113
+ {
114
+ "_id": "r2-7",
115
+ "i2-1": "",
116
+ "i2-2": "",
117
+ "o2-1": "500"
118
+ }
119
+ ],
120
+ "inputs": [
121
+ {
122
+ "id": "i2-1",
123
+ "name": "Route Type",
124
+ "field": "booking.routeType"
125
+ },
126
+ {
127
+ "id": "i2-2",
128
+ "name": "Distance (miles)",
129
+ "field": "booking.distance"
130
+ }
131
+ ],
132
+ "outputs": [
133
+ {
134
+ "id": "o2-1",
135
+ "name": "Base Points",
136
+ "field": "points.basePoints"
137
+ }
138
+ ],
139
+ "passThrough": true,
140
+ "inputField": null,
141
+ "outputPath": null,
142
+ "executionMode": "single"
143
+ },
144
+ "id": "dt2",
145
+ "name": "routeBasePoints",
146
+ "position": {
147
+ "x": 750,
148
+ "y": 114
149
+ }
150
+ },
151
+ {
152
+ "type": "decisionTableNode",
153
+ "content": {
154
+ "hitPolicy": "first",
155
+ "rules": [
156
+ {
157
+ "_id": "r3-1",
158
+ "i3-1": "'Platinum'",
159
+ "o3-1": "2"
160
+ },
161
+ {
162
+ "_id": "r3-2",
163
+ "i3-1": "'Gold'",
164
+ "o3-1": "1.5"
165
+ },
166
+ {
167
+ "_id": "r3-3",
168
+ "i3-1": "'Silver'",
169
+ "o3-1": "1.25"
170
+ },
171
+ {
172
+ "_id": "r3-4",
173
+ "i3-1": "",
174
+ "o3-1": "1"
175
+ }
176
+ ],
177
+ "inputs": [
178
+ {
179
+ "id": "i3-1",
180
+ "name": "Member Status",
181
+ "field": "member.status"
182
+ }
183
+ ],
184
+ "outputs": [
185
+ {
186
+ "id": "o3-1",
187
+ "name": "Status Multiplier",
188
+ "field": "points.statusMultiplier"
189
+ }
190
+ ],
191
+ "passThrough": true,
192
+ "inputField": null,
193
+ "outputPath": null,
194
+ "executionMode": "single"
195
+ },
196
+ "id": "dt3",
197
+ "name": "memberStatusMultiplier",
198
+ "position": {
199
+ "x": 1070,
200
+ "y": 114
201
+ }
202
+ },
203
+ {
204
+ "type": "expressionNode",
205
+ "content": {
206
+ "expressions": [
207
+ {
208
+ "id": "e1-1",
209
+ "key": "seasonalPromotion",
210
+ "value": "booking.isSeasonalPromotion == true ? 1.5 : 1"
211
+ },
212
+ {
213
+ "id": "e1-2",
214
+ "key": "calculatedPoints",
215
+ "value": "floor(points.basePoints * points.fareMultiplier * points.statusMultiplier * $.seasonalPromotion)"
216
+ },
217
+ {
218
+ "id": "e1-3",
219
+ "key": "totalPoints",
220
+ "value": "$.calculatedPoints"
221
+ }
222
+ ],
223
+ "passThrough": false,
224
+ "inputField": null,
225
+ "outputPath": null,
226
+ "executionMode": "single"
227
+ },
228
+ "id": "ex1",
229
+ "name": "calculateTotalPoints",
230
+ "position": {
231
+ "x": 1390,
232
+ "y": 114
233
+ }
234
+ }
235
+ ],
236
+ "edges": [
237
+ {
238
+ "id": "ed1",
239
+ "sourceId": "ip1",
240
+ "targetId": "dt1",
241
+ "type": "edge"
242
+ },
243
+ {
244
+ "id": "ed2",
245
+ "sourceId": "dt1",
246
+ "targetId": "dt2",
247
+ "type": "edge"
248
+ },
249
+ {
250
+ "id": "ed3",
251
+ "sourceId": "dt2",
252
+ "targetId": "dt3",
253
+ "type": "edge"
254
+ },
255
+ {
256
+ "id": "ed4",
257
+ "sourceId": "dt3",
258
+ "targetId": "ex1",
259
+ "type": "edge"
260
+ }
261
+ ]
262
+ }