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
@@ -0,0 +1,471 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "inputNode",
5
+ "name": "request",
6
+ "type": "inputNode",
7
+ "content": {
8
+ "schema": ""
9
+ },
10
+ "position": {
11
+ "x": 110,
12
+ "y": 193.5
13
+ }
14
+ },
15
+ {
16
+ "id": "marketConditionsTable",
17
+ "name": "marketConditionsAssessment",
18
+ "type": "decisionTableNode",
19
+ "content": {
20
+ "rules": [
21
+ {
22
+ "_id": "rule1",
23
+ "i1-1": "> 30",
24
+ "i1-2": "< -15",
25
+ "o1-1": "'severe'",
26
+ "o1-2": "0.8",
27
+ "o1-3": "'Highly volatile market with significant downward trend'"
28
+ },
29
+ {
30
+ "_id": "rule2",
31
+ "i1-1": "> 25",
32
+ "i1-2": "< -10",
33
+ "o1-1": "'high'",
34
+ "o1-2": "0.7",
35
+ "o1-3": "'Elevated volatility with substantial market decline'"
36
+ },
37
+ {
38
+ "_id": "rule3",
39
+ "i1-1": "> 20",
40
+ "i1-2": "< -5",
41
+ "o1-1": "'moderate'",
42
+ "o1-2": "0.5",
43
+ "o1-3": "'Increased volatility with market decline'"
44
+ },
45
+ {
46
+ "_id": "rule4",
47
+ "i1-1": "> 25",
48
+ "i1-2": "> 5",
49
+ "o1-1": "'neutral'",
50
+ "o1-2": "0.3",
51
+ "o1-3": "'High volatility but positive market trend'"
52
+ },
53
+ {
54
+ "_id": "rule5",
55
+ "i1-1": "> 15",
56
+ "i1-2": "",
57
+ "o1-1": "'elevated'",
58
+ "o1-2": "0.4",
59
+ "o1-3": "'Elevated market volatility'"
60
+ },
61
+ {
62
+ "_id": "rule6",
63
+ "i1-1": "",
64
+ "i1-2": "< -10",
65
+ "o1-1": "'negative'",
66
+ "o1-2": "0.6",
67
+ "o1-3": "'Significant market decline'"
68
+ },
69
+ {
70
+ "_id": "rule7",
71
+ "i1-1": "",
72
+ "i1-2": "",
73
+ "o1-1": "'normal'",
74
+ "o1-2": "0.2",
75
+ "o1-3": "'Normal market conditions'"
76
+ }
77
+ ],
78
+ "inputs": [
79
+ {
80
+ "id": "i1-1",
81
+ "name": "Market Volatility Index",
82
+ "field": "market.volatilityIndex"
83
+ },
84
+ {
85
+ "id": "i1-2",
86
+ "name": "Market Trend Percent",
87
+ "field": "market.trendPercentage"
88
+ }
89
+ ],
90
+ "outputs": [
91
+ {
92
+ "id": "o1-1",
93
+ "name": "Market Condition",
94
+ "field": "assessment.marketCondition"
95
+ },
96
+ {
97
+ "id": "o1-2",
98
+ "name": "Market Risk Factor",
99
+ "field": "assessment.marketRiskFactor"
100
+ },
101
+ {
102
+ "id": "o1-3",
103
+ "name": "Market Assessment",
104
+ "field": "assessment.marketAssessment"
105
+ }
106
+ ],
107
+ "hitPolicy": "first",
108
+ "inputField": null,
109
+ "outputPath": null,
110
+ "passThrough": true,
111
+ "executionMode": "single"
112
+ },
113
+ "position": {
114
+ "x": 430,
115
+ "y": 193.5
116
+ }
117
+ },
118
+ {
119
+ "id": "portfolioExposureTable",
120
+ "name": "portfolioExposureAssessment",
121
+ "type": "decisionTableNode",
122
+ "content": {
123
+ "rules": [
124
+ {
125
+ "_id": "rule1",
126
+ "i1-1": "> 50",
127
+ "i1-2": "assessment.marketCondition == 'severe' or assessment.marketCondition == 'high'",
128
+ "o1-1": "'critical'",
129
+ "o1-2": "0.9"
130
+ },
131
+ {
132
+ "_id": "rule2",
133
+ "i1-1": "> 40",
134
+ "i1-2": "assessment.marketCondition == 'severe' or assessment.marketCondition == 'high'",
135
+ "o1-1": "'high'",
136
+ "o1-2": "0.7"
137
+ },
138
+ {
139
+ "_id": "rule3",
140
+ "i1-1": "> 30",
141
+ "i1-2": "assessment.marketCondition != 'normal'",
142
+ "o1-1": "'elevated'",
143
+ "o1-2": "0.5"
144
+ },
145
+ {
146
+ "_id": "rule4",
147
+ "i1-1": "> 20",
148
+ "i1-2": "",
149
+ "o1-1": "'moderate'",
150
+ "o1-2": "0.3"
151
+ },
152
+ {
153
+ "_id": "rule5",
154
+ "i1-1": "",
155
+ "i1-2": "",
156
+ "o1-1": "'low'",
157
+ "o1-2": "0.1"
158
+ }
159
+ ],
160
+ "inputs": [
161
+ {
162
+ "id": "i1-1",
163
+ "name": "High-Risk Asset Percentage",
164
+ "field": "portfolio.highRiskPercentage"
165
+ },
166
+ {
167
+ "id": "i1-2",
168
+ "name": "Market Condition Check"
169
+ }
170
+ ],
171
+ "outputs": [
172
+ {
173
+ "id": "o1-1",
174
+ "name": "Exposure Level",
175
+ "field": "assessment.exposureLevel"
176
+ },
177
+ {
178
+ "id": "o1-2",
179
+ "name": "Exposure Factor",
180
+ "field": "assessment.exposureFactor"
181
+ }
182
+ ],
183
+ "hitPolicy": "first",
184
+ "inputField": null,
185
+ "outputPath": null,
186
+ "passThrough": true,
187
+ "executionMode": "single"
188
+ },
189
+ "position": {
190
+ "x": 750,
191
+ "y": 193.5
192
+ }
193
+ },
194
+ {
195
+ "id": "portfolioVolatilityTable",
196
+ "name": "portfolioVolatilityAssessment",
197
+ "type": "decisionTableNode",
198
+ "content": {
199
+ "rules": [
200
+ {
201
+ "_id": "rule1",
202
+ "i1-1": "> 30",
203
+ "i1-2": "> 0.6",
204
+ "o1-1": "'high'",
205
+ "o1-2": "0.8"
206
+ },
207
+ {
208
+ "_id": "rule2",
209
+ "i1-1": "> 25",
210
+ "i1-2": "> 0.4",
211
+ "o1-1": "'elevated'",
212
+ "o1-2": "0.6"
213
+ },
214
+ {
215
+ "_id": "rule3",
216
+ "i1-1": "> 20",
217
+ "i1-2": "",
218
+ "o1-1": "'moderate'",
219
+ "o1-2": "0.4"
220
+ },
221
+ {
222
+ "_id": "rule4",
223
+ "i1-1": "> 15",
224
+ "i1-2": "",
225
+ "o1-1": "'low'",
226
+ "o1-2": "0.2"
227
+ },
228
+ {
229
+ "_id": "rule5",
230
+ "i1-1": "",
231
+ "i1-2": "",
232
+ "o1-1": "'minimal'",
233
+ "o1-2": "0.1"
234
+ }
235
+ ],
236
+ "inputs": [
237
+ {
238
+ "id": "i1-1",
239
+ "name": "Portfolio Volatility",
240
+ "field": "portfolio.volatility"
241
+ },
242
+ {
243
+ "id": "i1-2",
244
+ "name": "Market Risk Factor",
245
+ "field": "assessment.marketRiskFactor"
246
+ }
247
+ ],
248
+ "outputs": [
249
+ {
250
+ "id": "o1-1",
251
+ "name": "Volatility Level",
252
+ "field": "assessment.volatilityLevel"
253
+ },
254
+ {
255
+ "id": "o1-2",
256
+ "name": "Volatility Factor",
257
+ "field": "assessment.volatilityFactor"
258
+ }
259
+ ],
260
+ "hitPolicy": "first",
261
+ "inputField": null,
262
+ "outputPath": null,
263
+ "passThrough": true,
264
+ "executionMode": "single"
265
+ },
266
+ "position": {
267
+ "x": 1070,
268
+ "y": 193.5
269
+ }
270
+ },
271
+ {
272
+ "id": "riskScoreCalculation",
273
+ "name": "calculateRiskScore",
274
+ "type": "expressionNode",
275
+ "content": {
276
+ "inputField": null,
277
+ "outputPath": null,
278
+ "expressions": [
279
+ {
280
+ "id": "expr1",
281
+ "key": "marketWeight",
282
+ "value": "0.3"
283
+ },
284
+ {
285
+ "id": "expr2",
286
+ "key": "exposureWeight",
287
+ "value": "0.4"
288
+ },
289
+ {
290
+ "id": "expr3",
291
+ "key": "volatilityWeight",
292
+ "value": "0.3"
293
+ },
294
+ {
295
+ "id": "expr4",
296
+ "key": "riskScore",
297
+ "value": "assessment.marketRiskFactor * $.marketWeight + assessment.exposureFactor * $.exposureWeight + assessment.volatilityFactor * $.volatilityWeight"
298
+ },
299
+ {
300
+ "id": "expr5",
301
+ "key": "portfolioDrift",
302
+ "value": "abs(portfolio.currentAllocation.equity - portfolio.targetAllocation.equity)"
303
+ },
304
+ {
305
+ "id": "expr6",
306
+ "key": "riskCategory",
307
+ "value": "$.riskScore >= 0.7 ? 'critical' : $.riskScore >= 0.5 ? 'high' : $.riskScore >= 0.3 ? 'moderate' : 'low'"
308
+ }
309
+ ],
310
+ "passThrough": true,
311
+ "executionMode": "single"
312
+ },
313
+ "position": {
314
+ "x": 1390,
315
+ "y": 193.5
316
+ }
317
+ },
318
+ {
319
+ "id": "actionDeterminationSwitch",
320
+ "name": "determineAction",
321
+ "type": "switchNode",
322
+ "content": {
323
+ "hitPolicy": "first",
324
+ "statements": [
325
+ {
326
+ "id": "alertAction",
327
+ "condition": "riskCategory == 'moderate' and assessment.marketCondition != 'normal'",
328
+ "isDefault": false
329
+ },
330
+ {
331
+ "id": "rebalanceAction",
332
+ "condition": "portfolioDrift > 10 or (riskCategory == 'high' and portfolio.lastRebalance > 90)",
333
+ "isDefault": false
334
+ },
335
+ {
336
+ "id": "mitigateAction",
337
+ "condition": "riskCategory == 'critical' or (riskCategory == 'high' and assessment.marketCondition == 'severe')",
338
+ "isDefault": false
339
+ },
340
+ {
341
+ "id": "noAction",
342
+ "condition": "",
343
+ "isDefault": true
344
+ }
345
+ ]
346
+ },
347
+ "position": {
348
+ "x": 1710,
349
+ "y": 193.5
350
+ }
351
+ },
352
+ {
353
+ "id": "alertFunction",
354
+ "name": "generateAlert",
355
+ "type": "functionNode",
356
+ "content": {
357
+ "source": "import dayjs from 'dayjs';\nexport const handler = async (input) => {\n const { customer, portfolio, assessment, riskScore, riskCategory } = input;\n const alertDate = dayjs().format('YYYY-MM-DD');\n\n return {\n action: 'alert',\n alertDetails: {\n customerId: customer.id,\n portfolioId: portfolio.id,\n date: alertDate,\n riskScore: riskScore,\n riskCategory: riskCategory,\n message: `Risk alert: Portfolio risk level is ${riskCategory} (${riskScore.toFixed(2)}). Market conditions: ${assessment.marketAssessment}`,\n recommendedAction: 'Review portfolio allocation'\n },\n outcome: { status: 'alert_generated', riskScore: riskScore, timestamp: new Date().toISOString() }\n };\n};"
358
+ },
359
+ "position": {
360
+ "x": 2030,
361
+ "y": 46.5
362
+ }
363
+ },
364
+ {
365
+ "id": "rebalanceFunction",
366
+ "name": "suggestRebalancing",
367
+ "type": "functionNode",
368
+ "content": {
369
+ "source": "import dayjs from 'dayjs';\nexport const handler = async (input) => {\n const { customer, portfolio, assessment, riskScore, riskCategory, portfolioDrift } = input;\n const currentDate = dayjs().format('YYYY-MM-DD');\n const suggestedEquity = portfolio.targetAllocation.equity;\n const suggestedBonds = portfolio.targetAllocation.bonds;\n const suggestedCash = portfolio.targetAllocation.cash;\n const driftPercentage = portfolioDrift.toFixed(1);\n\n return {\n action: 'rebalance',\n rebalanceDetails: {\n customerId: customer.id,\n portfolioId: portfolio.id,\n date: currentDate,\n riskScore: riskScore,\n riskCategory: riskCategory,\n currentAllocation: portfolio.currentAllocation,\n targetAllocation: portfolio.targetAllocation,\n driftPercentage: driftPercentage,\n suggestedChanges: {\n equity: suggestedEquity - portfolio.currentAllocation.equity,\n bonds: suggestedBonds - portfolio.currentAllocation.bonds,\n cash: suggestedCash - portfolio.currentAllocation.cash\n },\n message: `Rebalancing recommended: Portfolio has drifted ${driftPercentage}% from target allocation.`\n },\n outcome: { status: 'rebalance_suggested', riskScore: riskScore, timestamp: new Date().toISOString() }\n };\n};"
370
+ },
371
+ "position": {
372
+ "x": 2030,
373
+ "y": 144.5
374
+ }
375
+ },
376
+ {
377
+ "id": "mitigationFunction",
378
+ "name": "implementRiskMitigation",
379
+ "type": "functionNode",
380
+ "content": {
381
+ "source": "import dayjs from 'dayjs';\nexport const handler = async (input) => {\n const { customer, portfolio, assessment, riskScore, riskCategory } = input;\n const mitigationDate = dayjs().format('YYYY-MM-DD');\n const suggestedEquity = Math.max(portfolio.targetAllocation.equity - 15, 0);\n const suggestedBonds = Math.min(portfolio.targetAllocation.bonds + 10, 100 - suggestedEquity - 5);\n const suggestedCash = 100 - suggestedEquity - suggestedBonds;\n\n return {\n action: 'mitigate',\n mitigationDetails: {\n customerId: customer.id,\n portfolioId: portfolio.id,\n date: mitigationDate,\n riskScore: riskScore,\n riskCategory: riskCategory,\n currentAllocation: portfolio.currentAllocation,\n suggestedAllocation: { equity: suggestedEquity, bonds: suggestedBonds, cash: suggestedCash },\n message: `Risk mitigation required: Market conditions (${assessment.marketCondition}) and portfolio risk (${riskCategory}) indicate immediate action needed.`,\n automaticChanges: assessment.marketCondition === 'severe' && riskCategory === 'critical' && customer.preferences.allowAutomaticAdjustments\n },\n outcome: { status: 'mitigation_implemented', riskScore: riskScore, timestamp: new Date().toISOString() }\n };\n};"
382
+ },
383
+ "position": {
384
+ "x": 2030,
385
+ "y": 242.5
386
+ }
387
+ },
388
+ {
389
+ "id": "noActionRequired",
390
+ "name": "noActionNeeded",
391
+ "type": "expressionNode",
392
+ "content": {
393
+ "inputField": null,
394
+ "outputPath": null,
395
+ "expressions": [
396
+ {
397
+ "id": "expr1",
398
+ "key": "outcome",
399
+ "value": "{ status: 'no_action_required', riskScore: riskScore, timestamp: string(date('now')) }"
400
+ }
401
+ ],
402
+ "passThrough": true,
403
+ "executionMode": "single"
404
+ },
405
+ "position": {
406
+ "x": 2030,
407
+ "y": 340.5
408
+ }
409
+ }
410
+ ],
411
+ "edges": [
412
+ {
413
+ "id": "edge1",
414
+ "type": "edge",
415
+ "sourceId": "inputNode",
416
+ "targetId": "marketConditionsTable"
417
+ },
418
+ {
419
+ "id": "edge2",
420
+ "type": "edge",
421
+ "sourceId": "marketConditionsTable",
422
+ "targetId": "portfolioExposureTable"
423
+ },
424
+ {
425
+ "id": "edge3",
426
+ "type": "edge",
427
+ "sourceId": "portfolioExposureTable",
428
+ "targetId": "portfolioVolatilityTable"
429
+ },
430
+ {
431
+ "id": "edge4",
432
+ "type": "edge",
433
+ "sourceId": "portfolioVolatilityTable",
434
+ "targetId": "riskScoreCalculation"
435
+ },
436
+ {
437
+ "id": "edge5",
438
+ "type": "edge",
439
+ "sourceId": "riskScoreCalculation",
440
+ "targetId": "actionDeterminationSwitch"
441
+ },
442
+ {
443
+ "id": "edge6",
444
+ "type": "edge",
445
+ "sourceId": "actionDeterminationSwitch",
446
+ "targetId": "alertFunction",
447
+ "sourceHandle": "alertAction"
448
+ },
449
+ {
450
+ "id": "edge7",
451
+ "type": "edge",
452
+ "sourceId": "actionDeterminationSwitch",
453
+ "targetId": "rebalanceFunction",
454
+ "sourceHandle": "rebalanceAction"
455
+ },
456
+ {
457
+ "id": "edge8",
458
+ "type": "edge",
459
+ "sourceId": "actionDeterminationSwitch",
460
+ "targetId": "mitigationFunction",
461
+ "sourceHandle": "mitigateAction"
462
+ },
463
+ {
464
+ "id": "edge9",
465
+ "type": "edge",
466
+ "sourceId": "actionDeterminationSwitch",
467
+ "targetId": "noActionRequired",
468
+ "sourceHandle": "noAction"
469
+ }
470
+ ]
471
+ }