crca 1.4.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 (501) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.md +65 -0
  2. .github/ISSUE_TEMPLATE/feature_request.md +41 -0
  3. .github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. .github/workflows/publish-manual.yml +61 -0
  5. .github/workflows/publish.yml +64 -0
  6. .gitignore +214 -0
  7. CRCA.py +4156 -0
  8. LICENSE +201 -0
  9. MANIFEST.in +43 -0
  10. PKG-INFO +5035 -0
  11. README.md +4959 -0
  12. __init__.py +17 -0
  13. branches/CRCA-Q.py +2728 -0
  14. branches/crca_cg/corposwarm.py +9065 -0
  15. branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
  16. branches/crca_cg/package.json +5 -0
  17. branches/crca_cg/test_bolt_integration.py +446 -0
  18. branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
  19. branches/crca_cg/test_new_features.py +163 -0
  20. branches/crca_sd/__init__.py +149 -0
  21. branches/crca_sd/crca_sd_core.py +770 -0
  22. branches/crca_sd/crca_sd_governance.py +1325 -0
  23. branches/crca_sd/crca_sd_mpc.py +1130 -0
  24. branches/crca_sd/crca_sd_realtime.py +1844 -0
  25. branches/crca_sd/crca_sd_tui.py +1133 -0
  26. crca-1.4.0.dist-info/METADATA +5035 -0
  27. crca-1.4.0.dist-info/RECORD +501 -0
  28. crca-1.4.0.dist-info/WHEEL +4 -0
  29. crca-1.4.0.dist-info/licenses/LICENSE +201 -0
  30. docs/CRCA-Q.md +2333 -0
  31. examples/config.yaml.example +25 -0
  32. examples/crca_sd_example.py +513 -0
  33. examples/data_broker_example.py +294 -0
  34. examples/logistics_corporation.py +861 -0
  35. examples/palantir_example.py +299 -0
  36. examples/policy_bench.py +934 -0
  37. examples/pridnestrovia-sd.py +705 -0
  38. examples/pridnestrovia_realtime.py +1902 -0
  39. prompts/__init__.py +10 -0
  40. prompts/default_crca.py +101 -0
  41. pyproject.toml +151 -0
  42. requirements.txt +76 -0
  43. schemas/__init__.py +43 -0
  44. schemas/mcpSchemas.py +51 -0
  45. schemas/policy.py +458 -0
  46. templates/__init__.py +38 -0
  47. templates/base_specialized_agent.py +195 -0
  48. templates/drift_detection.py +325 -0
  49. templates/examples/causal_agent_template.py +309 -0
  50. templates/examples/drag_drop_example.py +213 -0
  51. templates/examples/logistics_agent_template.py +207 -0
  52. templates/examples/trading_agent_template.py +206 -0
  53. templates/feature_mixins.py +253 -0
  54. templates/graph_management.py +442 -0
  55. templates/llm_integration.py +194 -0
  56. templates/module_registry.py +276 -0
  57. templates/mpc_planner.py +280 -0
  58. templates/policy_loop.py +1168 -0
  59. templates/prediction_framework.py +448 -0
  60. templates/statistical_methods.py +778 -0
  61. tests/sanity.yml +31 -0
  62. tests/sanity_check +406 -0
  63. tests/test_core.py +47 -0
  64. tests/test_crca_excel.py +166 -0
  65. tests/test_crca_sd.py +780 -0
  66. tests/test_data_broker.py +424 -0
  67. tests/test_palantir.py +349 -0
  68. tools/__init__.py +38 -0
  69. tools/actuators.py +437 -0
  70. tools/bolt.diy/Dockerfile +103 -0
  71. tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
  72. tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
  73. tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
  74. tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
  75. tools/bolt.diy/app/components/@settings/index.ts +12 -0
  76. tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
  77. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
  78. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
  79. tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
  80. tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
  81. tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
  82. tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
  83. tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
  84. tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
  85. tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
  86. tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
  87. tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
  88. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
  89. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
  90. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
  91. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
  92. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
  93. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
  94. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
  95. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
  96. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
  97. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
  98. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
  99. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
  100. tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
  101. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
  102. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
  103. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
  104. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
  105. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
  106. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
  107. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
  108. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
  109. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
  110. tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
  111. tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
  112. tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
  113. tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
  114. tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
  115. tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
  116. tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
  117. tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
  118. tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
  119. tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
  120. tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
  121. tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
  122. tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
  123. tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
  124. tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
  125. tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
  126. tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
  127. tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
  128. tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
  129. tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
  130. tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
  131. tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
  132. tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
  133. tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
  134. tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
  135. tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
  136. tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
  137. tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
  138. tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
  139. tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
  140. tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
  141. tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
  142. tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
  143. tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
  144. tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
  145. tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
  146. tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
  147. tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
  148. tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
  149. tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
  150. tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
  151. tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
  152. tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
  153. tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
  154. tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
  155. tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
  156. tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
  157. tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
  158. tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
  159. tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
  160. tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
  161. tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
  162. tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
  163. tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
  164. tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
  165. tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
  166. tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
  167. tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
  168. tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
  169. tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
  170. tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
  171. tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
  172. tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
  173. tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
  174. tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
  175. tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
  176. tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
  177. tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
  178. tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
  179. tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
  180. tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
  181. tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
  182. tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
  183. tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
  184. tools/bolt.diy/app/components/header/Header.tsx +42 -0
  185. tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
  186. tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
  187. tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
  188. tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
  189. tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
  190. tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
  191. tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
  192. tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
  193. tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
  194. tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
  195. tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
  196. tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
  197. tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
  198. tools/bolt.diy/app/components/txt +1 -0
  199. tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
  200. tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
  201. tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
  202. tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
  203. tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
  204. tools/bolt.diy/app/components/ui/Button.tsx +46 -0
  205. tools/bolt.diy/app/components/ui/Card.tsx +55 -0
  206. tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
  207. tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
  208. tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
  209. tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
  210. tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
  211. tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
  212. tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
  213. tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
  214. tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
  215. tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
  216. tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
  217. tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
  218. tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
  219. tools/bolt.diy/app/components/ui/Input.tsx +22 -0
  220. tools/bolt.diy/app/components/ui/Label.tsx +20 -0
  221. tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
  222. tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
  223. tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
  224. tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
  225. tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
  226. tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
  227. tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
  228. tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
  229. tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
  230. tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
  231. tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
  232. tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
  233. tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
  234. tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
  235. tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
  236. tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
  237. tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
  238. tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
  239. tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
  240. tools/bolt.diy/app/components/ui/index.ts +38 -0
  241. tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
  242. tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
  243. tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
  244. tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
  245. tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
  246. tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
  247. tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
  248. tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
  249. tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
  250. tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
  251. tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
  252. tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
  253. tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
  254. tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
  255. tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
  256. tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
  257. tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
  258. tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
  259. tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
  260. tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
  261. tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
  262. tools/bolt.diy/app/entry.client.tsx +7 -0
  263. tools/bolt.diy/app/entry.server.tsx +80 -0
  264. tools/bolt.diy/app/root.tsx +156 -0
  265. tools/bolt.diy/app/routes/_index.tsx +175 -0
  266. tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
  267. tools/bolt.diy/app/routes/api.chat.ts +463 -0
  268. tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
  269. tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
  270. tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
  271. tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
  272. tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
  273. tools/bolt.diy/app/routes/api.git-info.ts +69 -0
  274. tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
  275. tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
  276. tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
  277. tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
  278. tools/bolt.diy/app/routes/api.github-template.ts +242 -0
  279. tools/bolt.diy/app/routes/api.github-user.ts +287 -0
  280. tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
  281. tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
  282. tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
  283. tools/bolt.diy/app/routes/api.health.ts +8 -0
  284. tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
  285. tools/bolt.diy/app/routes/api.mandate.ts +351 -0
  286. tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
  287. tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
  288. tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
  289. tools/bolt.diy/app/routes/api.models.ts +90 -0
  290. tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
  291. tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
  292. tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
  293. tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
  294. tools/bolt.diy/app/routes/api.supabase.ts +56 -0
  295. tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
  296. tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
  297. tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
  298. tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
  299. tools/bolt.diy/app/routes/api.update.ts +21 -0
  300. tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
  301. tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
  302. tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
  303. tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
  304. tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
  305. tools/bolt.diy/app/routes/git.tsx +25 -0
  306. tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
  307. tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
  308. tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
  309. tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
  310. tools/bolt.diy/app/styles/animations.scss +49 -0
  311. tools/bolt.diy/app/styles/components/code.scss +9 -0
  312. tools/bolt.diy/app/styles/components/editor.scss +135 -0
  313. tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
  314. tools/bolt.diy/app/styles/components/terminal.scss +3 -0
  315. tools/bolt.diy/app/styles/components/toast.scss +23 -0
  316. tools/bolt.diy/app/styles/diff-view.css +72 -0
  317. tools/bolt.diy/app/styles/index.scss +73 -0
  318. tools/bolt.diy/app/styles/variables.scss +255 -0
  319. tools/bolt.diy/app/styles/z-index.scss +37 -0
  320. tools/bolt.diy/app/types/GitHub.ts +182 -0
  321. tools/bolt.diy/app/types/GitLab.ts +103 -0
  322. tools/bolt.diy/app/types/actions.ts +85 -0
  323. tools/bolt.diy/app/types/artifact.ts +5 -0
  324. tools/bolt.diy/app/types/context.ts +26 -0
  325. tools/bolt.diy/app/types/design-scheme.ts +93 -0
  326. tools/bolt.diy/app/types/global.d.ts +13 -0
  327. tools/bolt.diy/app/types/mandate.ts +333 -0
  328. tools/bolt.diy/app/types/model.ts +25 -0
  329. tools/bolt.diy/app/types/netlify.ts +94 -0
  330. tools/bolt.diy/app/types/supabase.ts +54 -0
  331. tools/bolt.diy/app/types/template.ts +8 -0
  332. tools/bolt.diy/app/types/terminal.ts +9 -0
  333. tools/bolt.diy/app/types/theme.ts +1 -0
  334. tools/bolt.diy/app/types/vercel.ts +67 -0
  335. tools/bolt.diy/app/utils/buffer.ts +29 -0
  336. tools/bolt.diy/app/utils/classNames.ts +65 -0
  337. tools/bolt.diy/app/utils/constants.ts +147 -0
  338. tools/bolt.diy/app/utils/debounce.ts +13 -0
  339. tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
  340. tools/bolt.diy/app/utils/diff.spec.ts +11 -0
  341. tools/bolt.diy/app/utils/diff.ts +117 -0
  342. tools/bolt.diy/app/utils/easings.ts +3 -0
  343. tools/bolt.diy/app/utils/fileLocks.ts +96 -0
  344. tools/bolt.diy/app/utils/fileUtils.ts +121 -0
  345. tools/bolt.diy/app/utils/folderImport.ts +73 -0
  346. tools/bolt.diy/app/utils/formatSize.ts +12 -0
  347. tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
  348. tools/bolt.diy/app/utils/githubStats.ts +9 -0
  349. tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
  350. tools/bolt.diy/app/utils/logger.ts +162 -0
  351. tools/bolt.diy/app/utils/markdown.ts +155 -0
  352. tools/bolt.diy/app/utils/mobile.ts +4 -0
  353. tools/bolt.diy/app/utils/os.ts +4 -0
  354. tools/bolt.diy/app/utils/path.ts +19 -0
  355. tools/bolt.diy/app/utils/projectCommands.ts +197 -0
  356. tools/bolt.diy/app/utils/promises.ts +19 -0
  357. tools/bolt.diy/app/utils/react.ts +6 -0
  358. tools/bolt.diy/app/utils/sampler.ts +49 -0
  359. tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
  360. tools/bolt.diy/app/utils/shell.ts +384 -0
  361. tools/bolt.diy/app/utils/stacktrace.ts +27 -0
  362. tools/bolt.diy/app/utils/stripIndent.ts +23 -0
  363. tools/bolt.diy/app/utils/terminal.ts +11 -0
  364. tools/bolt.diy/app/utils/unreachable.ts +3 -0
  365. tools/bolt.diy/app/vite-env.d.ts +2 -0
  366. tools/bolt.diy/assets/entitlements.mac.plist +25 -0
  367. tools/bolt.diy/assets/icons/icon.icns +0 -0
  368. tools/bolt.diy/assets/icons/icon.ico +0 -0
  369. tools/bolt.diy/assets/icons/icon.png +0 -0
  370. tools/bolt.diy/bindings.js +78 -0
  371. tools/bolt.diy/bindings.sh +33 -0
  372. tools/bolt.diy/docker-compose.yaml +145 -0
  373. tools/bolt.diy/electron/main/index.ts +201 -0
  374. tools/bolt.diy/electron/main/tsconfig.json +30 -0
  375. tools/bolt.diy/electron/main/ui/menu.ts +29 -0
  376. tools/bolt.diy/electron/main/ui/window.ts +54 -0
  377. tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
  378. tools/bolt.diy/electron/main/utils/constants.ts +4 -0
  379. tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
  380. tools/bolt.diy/electron/main/utils/reload.ts +35 -0
  381. tools/bolt.diy/electron/main/utils/serve.ts +71 -0
  382. tools/bolt.diy/electron/main/utils/store.ts +3 -0
  383. tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
  384. tools/bolt.diy/electron/main/vite.config.ts +44 -0
  385. tools/bolt.diy/electron/preload/index.ts +22 -0
  386. tools/bolt.diy/electron/preload/tsconfig.json +7 -0
  387. tools/bolt.diy/electron/preload/vite.config.ts +31 -0
  388. tools/bolt.diy/electron-builder.yml +64 -0
  389. tools/bolt.diy/electron-update.yml +4 -0
  390. tools/bolt.diy/eslint.config.mjs +57 -0
  391. tools/bolt.diy/functions/[[path]].ts +12 -0
  392. tools/bolt.diy/icons/angular.svg +1 -0
  393. tools/bolt.diy/icons/astro.svg +8 -0
  394. tools/bolt.diy/icons/chat.svg +1 -0
  395. tools/bolt.diy/icons/expo-brand.svg +1 -0
  396. tools/bolt.diy/icons/expo.svg +4 -0
  397. tools/bolt.diy/icons/logo-text.svg +1 -0
  398. tools/bolt.diy/icons/logo.svg +4 -0
  399. tools/bolt.diy/icons/mcp.svg +1 -0
  400. tools/bolt.diy/icons/nativescript.svg +1 -0
  401. tools/bolt.diy/icons/netlify.svg +10 -0
  402. tools/bolt.diy/icons/nextjs.svg +1 -0
  403. tools/bolt.diy/icons/nuxt.svg +1 -0
  404. tools/bolt.diy/icons/qwik.svg +1 -0
  405. tools/bolt.diy/icons/react.svg +1 -0
  406. tools/bolt.diy/icons/remix.svg +24 -0
  407. tools/bolt.diy/icons/remotion.svg +1 -0
  408. tools/bolt.diy/icons/shadcn.svg +21 -0
  409. tools/bolt.diy/icons/slidev.svg +60 -0
  410. tools/bolt.diy/icons/solidjs.svg +1 -0
  411. tools/bolt.diy/icons/stars.svg +1 -0
  412. tools/bolt.diy/icons/svelte.svg +1 -0
  413. tools/bolt.diy/icons/typescript.svg +1 -0
  414. tools/bolt.diy/icons/vite.svg +1 -0
  415. tools/bolt.diy/icons/vue.svg +1 -0
  416. tools/bolt.diy/load-context.ts +9 -0
  417. tools/bolt.diy/notarize.cjs +31 -0
  418. tools/bolt.diy/package.json +218 -0
  419. tools/bolt.diy/playwright.config.preview.ts +35 -0
  420. tools/bolt.diy/pre-start.cjs +26 -0
  421. tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
  422. tools/bolt.diy/public/apple-touch-icon.png +0 -0
  423. tools/bolt.diy/public/favicon.ico +0 -0
  424. tools/bolt.diy/public/favicon.svg +4 -0
  425. tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
  426. tools/bolt.diy/public/icons/Anthropic.svg +4 -0
  427. tools/bolt.diy/public/icons/Cohere.svg +4 -0
  428. tools/bolt.diy/public/icons/Deepseek.svg +5 -0
  429. tools/bolt.diy/public/icons/Default.svg +4 -0
  430. tools/bolt.diy/public/icons/Google.svg +4 -0
  431. tools/bolt.diy/public/icons/Groq.svg +4 -0
  432. tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
  433. tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
  434. tools/bolt.diy/public/icons/LMStudio.svg +5 -0
  435. tools/bolt.diy/public/icons/Mistral.svg +4 -0
  436. tools/bolt.diy/public/icons/Ollama.svg +4 -0
  437. tools/bolt.diy/public/icons/OpenAI.svg +4 -0
  438. tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
  439. tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
  440. tools/bolt.diy/public/icons/Perplexity.svg +4 -0
  441. tools/bolt.diy/public/icons/Together.svg +4 -0
  442. tools/bolt.diy/public/icons/xAI.svg +5 -0
  443. tools/bolt.diy/public/inspector-script.js +292 -0
  444. tools/bolt.diy/public/logo-dark-styled.png +0 -0
  445. tools/bolt.diy/public/logo-dark.png +0 -0
  446. tools/bolt.diy/public/logo-light-styled.png +0 -0
  447. tools/bolt.diy/public/logo-light.png +0 -0
  448. tools/bolt.diy/public/logo.svg +15 -0
  449. tools/bolt.diy/public/social_preview_index.jpg +0 -0
  450. tools/bolt.diy/scripts/clean.js +45 -0
  451. tools/bolt.diy/scripts/electron-dev.mjs +181 -0
  452. tools/bolt.diy/scripts/setup-env.sh +41 -0
  453. tools/bolt.diy/scripts/update-imports.sh +7 -0
  454. tools/bolt.diy/scripts/update.sh +52 -0
  455. tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
  456. tools/bolt.diy/services/execution-governor/config.ts +42 -0
  457. tools/bolt.diy/services/execution-governor/index.ts +683 -0
  458. tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
  459. tools/bolt.diy/services/execution-governor/package.json +31 -0
  460. tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
  461. tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
  462. tools/bolt.diy/services/execution-governor/types.ts +145 -0
  463. tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
  464. tools/bolt.diy/services/headless-executor/executor.ts +210 -0
  465. tools/bolt.diy/services/headless-executor/index.ts +323 -0
  466. tools/bolt.diy/services/headless-executor/package.json +27 -0
  467. tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
  468. tools/bolt.diy/services/headless-executor/types.ts +38 -0
  469. tools/bolt.diy/test-workflows.sh +240 -0
  470. tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
  471. tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
  472. tools/bolt.diy/tests/mandates/complex.json +53 -0
  473. tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
  474. tools/bolt.diy/tests/mandates/simple.json +35 -0
  475. tools/bolt.diy/tsconfig.json +37 -0
  476. tools/bolt.diy/types/istextorbinary.d.ts +15 -0
  477. tools/bolt.diy/uno.config.ts +279 -0
  478. tools/bolt.diy/vite-electron.config.ts +76 -0
  479. tools/bolt.diy/vite.config.ts +112 -0
  480. tools/bolt.diy/worker-configuration.d.ts +22 -0
  481. tools/bolt.diy/wrangler.toml +6 -0
  482. tools/code_generator.py +461 -0
  483. tools/file_operations.py +465 -0
  484. tools/mandate_generator.py +337 -0
  485. tools/mcpClientUtils.py +1216 -0
  486. tools/sensors.py +285 -0
  487. utils/Agent_types.py +15 -0
  488. utils/AnyToStr.py +0 -0
  489. utils/HHCS.py +277 -0
  490. utils/__init__.py +30 -0
  491. utils/agent.py +3627 -0
  492. utils/aop.py +2948 -0
  493. utils/canonical.py +143 -0
  494. utils/conversation.py +1195 -0
  495. utils/doctrine_versioning +230 -0
  496. utils/formatter.py +474 -0
  497. utils/ledger.py +311 -0
  498. utils/out_types.py +16 -0
  499. utils/rollback.py +339 -0
  500. utils/router.py +929 -0
  501. utils/tui.py +1908 -0
@@ -0,0 +1,1130 @@
1
+ """
2
+ CRCA-SD MPC: Model-Predictive Control, Objectives, Scenarios, Stability, Estimation
3
+
4
+ This module implements:
5
+ - Multi-objective optimization with CVaR risk
6
+ - Rolling horizon MPC solver
7
+ - Scenario generation (Gaussian, Student-t, structured shocks)
8
+ - Stability enforcement (rate limits, smoothing)
9
+ - Pareto frontier extraction
10
+ - State estimation (EKF/UKF) for partial observability
11
+ """
12
+
13
+ from typing import Dict, List, Optional, Tuple, Any, Union, Callable
14
+ import numpy as np
15
+ import time
16
+ from dataclasses import dataclass
17
+ from loguru import logger
18
+
19
+ try:
20
+ import cvxpy as cp
21
+ CVXPY_AVAILABLE = True
22
+ except ImportError:
23
+ CVXPY_AVAILABLE = False
24
+ logger.warning("cvxpy not available, MPC will use scipy.optimize fallback")
25
+
26
+ try:
27
+ from scipy.optimize import minimize
28
+ from scipy.stats import t as student_t
29
+ SCIPY_AVAILABLE = True
30
+ except ImportError:
31
+ SCIPY_AVAILABLE = False
32
+ logger.warning("scipy not available, some features will be limited")
33
+
34
+ from crca_sd.crca_sd_core import StateVector, ControlVector, DynamicsModel, ConstraintChecker
35
+
36
+
37
+ class ObjectiveVector:
38
+ """
39
+ Multi-objective cost vector.
40
+
41
+ Computes objective vector J = [J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk]:
42
+ - J_U: Sum of unemployment over horizon
43
+ - J_ℓ: Negative sum of literacy (maximize literacy)
44
+ - J_Y: Negative sum of output (maximize output)
45
+ - J_ineq: Inequality measure (placeholder)
46
+ - J_C: Sum of ecological damage increase
47
+ - J_risk: CVaR on collapse proxy
48
+
49
+ Args:
50
+ horizon: Planning horizon for objective computation
51
+ """
52
+
53
+ def __init__(self, horizon: int = 10) -> None:
54
+ """Initialize objective vector computer."""
55
+ self.horizon = horizon
56
+
57
+ def compute(
58
+ self,
59
+ x_trajectory: List[StateVector],
60
+ u_trajectory: List[ControlVector]
61
+ ) -> np.ndarray:
62
+ """
63
+ Compute all objectives from trajectory.
64
+
65
+ Args:
66
+ x_trajectory: List of state vectors
67
+ u_trajectory: List of control vectors
68
+
69
+ Returns:
70
+ np.ndarray: Objective vector [J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk]
71
+ """
72
+ if len(x_trajectory) < 2:
73
+ return np.zeros(6)
74
+
75
+ # J_U: Sum of unemployment
76
+ J_U = sum(x.U for x in x_trajectory[1:])
77
+
78
+ # J_ℓ: Negative sum of literacy (maximize literacy = minimize negative)
79
+ J_ℓ = -sum(x.literacy for x in x_trajectory[1:])
80
+
81
+ # J_Y: Negative sum of output (maximize output = minimize negative)
82
+ J_Y = -sum(x.Y for x in x_trajectory[1:])
83
+
84
+ # J_ineq: Inequality measure (simplified: wage variance proxy)
85
+ wages = [x.W for x in x_trajectory[1:]]
86
+ if len(wages) > 1:
87
+ J_ineq = np.std(wages) # Higher variance = more inequality
88
+ else:
89
+ J_ineq = 0.0
90
+
91
+ # J_C: Sum of ecological damage increase
92
+ if len(x_trajectory) > 1:
93
+ C_initial = x_trajectory[0].C
94
+ C_final = x_trajectory[-1].C
95
+ J_C = C_final - C_initial
96
+ else:
97
+ J_C = 0.0
98
+
99
+ # J_risk: CVaR on collapse proxy (computed separately)
100
+ collapse_proxy = self._compute_collapse_proxy(x_trajectory)
101
+ J_risk = collapse_proxy # Will be replaced by actual CVaR in aggregate
102
+
103
+ return np.array([J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk])
104
+
105
+ def aggregate(
106
+ self,
107
+ scenarios: List[Tuple[List[StateVector], List[ControlVector]]],
108
+ weights: Optional[np.ndarray] = None
109
+ ) -> np.ndarray:
110
+ """
111
+ Aggregate objectives across scenarios.
112
+
113
+ Args:
114
+ scenarios: List of (trajectory, controls) tuples
115
+ weights: Scenario weights (default: uniform)
116
+
117
+ Returns:
118
+ np.ndarray: Expected objective vector
119
+ """
120
+ if not scenarios:
121
+ return np.zeros(6)
122
+
123
+ if weights is None:
124
+ weights = np.ones(len(scenarios)) / len(scenarios)
125
+
126
+ objective_vectors = []
127
+ for traj, controls in scenarios:
128
+ obj = self.compute(traj, controls)
129
+ objective_vectors.append(obj)
130
+
131
+ objective_matrix = np.array(objective_vectors)
132
+ expected_objectives = np.average(objective_matrix, axis=0, weights=weights)
133
+
134
+ return expected_objectives
135
+
136
+ def _compute_collapse_proxy(self, trajectory: List[StateVector]) -> float:
137
+ """
138
+ Compute collapse proxy (badness metric).
139
+
140
+ Args:
141
+ trajectory: State trajectory
142
+
143
+ Returns:
144
+ float: Collapse proxy (higher = worse)
145
+ """
146
+ if not trajectory:
147
+ return 0.0
148
+
149
+ # Collapse indicators
150
+ high_unemployment = sum(1 for x in trajectory if x.U > 0.15)
151
+ low_stability = sum(1 for x in trajectory if x.S < 0.4)
152
+ food_crisis = sum(1 for x in trajectory if x.F_stock < x.P * 0.01)
153
+ energy_crisis = sum(1 for x in trajectory if x.E_stock < 1000.0)
154
+
155
+ collapse_score = (
156
+ high_unemployment * 0.3 +
157
+ low_stability * 0.3 +
158
+ food_crisis * 0.2 +
159
+ energy_crisis * 0.2
160
+ ) / len(trajectory)
161
+
162
+ return collapse_score
163
+
164
+
165
+ class CVaRComputer:
166
+ """
167
+ Conditional Value-at-Risk (CVaR) computer.
168
+
169
+ CVaR measures expected loss in worst-case scenarios (tail risk).
170
+
171
+ Args:
172
+ alpha: Confidence level (default: 0.05, i.e., worst 5%)
173
+ """
174
+
175
+ def __init__(self, alpha: float = 0.05) -> None:
176
+ """Initialize CVaR computer."""
177
+ if not (0 < alpha < 1):
178
+ raise ValueError(f"Alpha must be in (0, 1), got {alpha}")
179
+ self.alpha = alpha
180
+
181
+ def compute_cvar(self, z_scores: np.ndarray) -> float:
182
+ """
183
+ Compute CVaR from z-scores (badness values).
184
+
185
+ Args:
186
+ z_scores: Array of badness scores (higher = worse)
187
+
188
+ Returns:
189
+ float: CVaR value
190
+ """
191
+ if len(z_scores) == 0:
192
+ return 0.0
193
+
194
+ # Sort in descending order (worst first)
195
+ sorted_scores = np.sort(z_scores)[::-1]
196
+
197
+ # Number of worst-case scenarios
198
+ n_worst = max(1, int(np.ceil(len(z_scores) * self.alpha)))
199
+
200
+ # CVaR = average of worst scenarios
201
+ cvar = np.mean(sorted_scores[:n_worst])
202
+
203
+ return float(cvar)
204
+
205
+ def collapse_proxy(self, x_trajectory: List[StateVector]) -> float:
206
+ """
207
+ Compute collapse proxy from trajectory.
208
+
209
+ Args:
210
+ x_trajectory: State trajectory
211
+
212
+ Returns:
213
+ float: Collapse proxy (badness metric)
214
+ """
215
+ if not x_trajectory:
216
+ return 0.0
217
+
218
+ # Use same logic as ObjectiveVector
219
+ high_unemployment = sum(1 for x in x_trajectory if x.U > 0.15)
220
+ low_stability = sum(1 for x in x_trajectory if x.S < 0.4)
221
+ food_crisis = sum(1 for x in x_trajectory if x.F_stock < x.P * 0.01)
222
+ energy_crisis = sum(1 for x in x_trajectory if x.E_stock < 1000.0)
223
+
224
+ collapse_score = (
225
+ high_unemployment * 0.3 +
226
+ low_stability * 0.3 +
227
+ food_crisis * 0.2 +
228
+ energy_crisis * 0.2
229
+ ) / len(x_trajectory)
230
+
231
+ return collapse_score
232
+
233
+
234
+ class ScenarioGenerator:
235
+ """
236
+ Scenario generator for disturbance sequences.
237
+
238
+ Generates:
239
+ - Gaussian noise for benign uncertainty
240
+ - Student-t for heavy-tailed disasters
241
+ - Structured events (trade embargo, drought, etc.)
242
+ - Causal scenarios via CRCA (if available)
243
+
244
+ Args:
245
+ rng: Random number generator (default: new generator)
246
+ crca_agent: Optional CRCAAgent for causal scenario generation
247
+ """
248
+
249
+ def __init__(
250
+ self,
251
+ rng: Optional[np.random.Generator] = None,
252
+ crca_agent: Optional[Any] = None
253
+ ) -> None:
254
+ """Initialize scenario generator."""
255
+ if rng is None:
256
+ rng = np.random.default_rng()
257
+ self.rng = rng
258
+ self.crca_agent = crca_agent
259
+
260
+ def generate_gaussian(
261
+ self,
262
+ n_scenarios: int,
263
+ horizon: int,
264
+ mean: Optional[Dict[str, float]] = None,
265
+ cov: Optional[np.ndarray] = None
266
+ ) -> List[List[Dict[str, float]]]:
267
+ """
268
+ Generate Gaussian disturbance scenarios.
269
+
270
+ Args:
271
+ n_scenarios: Number of scenarios
272
+ horizon: Time horizon
273
+ mean: Mean disturbance vector (default: zeros)
274
+ cov: Covariance matrix (default: identity)
275
+
276
+ Returns:
277
+ List[List[Dict[str, float]]]: List of scenario sequences
278
+ """
279
+ if mean is None:
280
+ mean = {
281
+ "demand_shock": 0.0,
282
+ "trade_shock": 0.0,
283
+ "productivity_shock": 1.0,
284
+ "disaster_shock": 0.0,
285
+ "labor_shock": 0.0,
286
+ "unemployment_shock": 0.0,
287
+ "energy_import": 0.0,
288
+ "food_import": 0.0,
289
+ }
290
+
291
+ # Default covariance (diagonal, small)
292
+ if cov is None:
293
+ n_vars = len(mean)
294
+ cov = np.eye(n_vars) * 0.01
295
+
296
+ scenarios = []
297
+ for _ in range(n_scenarios):
298
+ scenario = []
299
+ for _ in range(horizon):
300
+ # Sample from multivariate Gaussian
301
+ disturbance_vec = self.rng.multivariate_normal(
302
+ mean=list(mean.values()),
303
+ cov=cov
304
+ )
305
+ disturbance = {
306
+ key: float(val)
307
+ for key, val in zip(mean.keys(), disturbance_vec)
308
+ }
309
+ scenario.append(disturbance)
310
+ scenarios.append(scenario)
311
+
312
+ return scenarios
313
+
314
+ def generate_student_t(
315
+ self,
316
+ n_scenarios: int,
317
+ horizon: int,
318
+ df: float = 3.0, # Degrees of freedom (lower = heavier tails)
319
+ scale: float = 0.1
320
+ ) -> List[List[Dict[str, float]]]:
321
+ """
322
+ Generate Student-t disturbance scenarios (heavy-tailed).
323
+
324
+ Args:
325
+ n_scenarios: Number of scenarios
326
+ horizon: Time horizon
327
+ df: Degrees of freedom (default: 3.0 for heavy tails)
328
+ scale: Scale parameter
329
+
330
+ Returns:
331
+ List[List[Dict[str, float]]]: List of scenario sequences
332
+ """
333
+ if not SCIPY_AVAILABLE:
334
+ logger.warning("scipy not available, using Gaussian fallback")
335
+ return self.generate_gaussian(n_scenarios, horizon)
336
+
337
+ scenarios = []
338
+ for _ in range(n_scenarios):
339
+ scenario = []
340
+ for _ in range(horizon):
341
+ # Sample from Student-t
342
+ disturbance = {
343
+ "demand_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
344
+ "trade_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
345
+ "productivity_shock": 1.0 + float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
346
+ "disaster_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
347
+ "labor_shock": float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
348
+ "unemployment_shock": float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
349
+ "energy_import": float(student_t.rvs(df, scale=scale * 100, random_state=self.rng)),
350
+ "food_import": float(student_t.rvs(df, scale=scale * 100, random_state=self.rng)),
351
+ }
352
+ scenario.append(disturbance)
353
+ scenarios.append(scenario)
354
+
355
+ return scenarios
356
+
357
+ def generate_structured_shock(
358
+ self,
359
+ event_type: str,
360
+ magnitude: float,
361
+ timing: int,
362
+ horizon: int
363
+ ) -> List[Dict[str, float]]:
364
+ """
365
+ Generate structured shock event (trade embargo, drought, etc.).
366
+
367
+ Args:
368
+ event_type: Type of shock ("trade_embargo", "drought", "productivity_crash")
369
+ magnitude: Shock magnitude
370
+ timing: Time step when shock occurs
371
+ horizon: Time horizon
372
+
373
+ Returns:
374
+ List[Dict[str, float]]: Single scenario sequence
375
+ """
376
+ scenario = [{}] * horizon
377
+
378
+ if event_type == "trade_embargo":
379
+ # Complete trade cutoff at timing
380
+ for t in range(timing, horizon):
381
+ scenario[t] = {
382
+ "trade_shock": 1.0, # Complete cutoff
383
+ "energy_import": -magnitude * 1000,
384
+ "food_import": -magnitude * 500,
385
+ }
386
+
387
+ elif event_type == "drought":
388
+ # Food production crash
389
+ for t in range(timing, min(timing + 5, horizon)):
390
+ scenario[t] = {
391
+ "disaster_shock": magnitude,
392
+ "food_import": -magnitude * 300,
393
+ }
394
+
395
+ elif event_type == "productivity_crash":
396
+ # Productivity shock
397
+ for t in range(timing, min(timing + 3, horizon)):
398
+ scenario[t] = {
399
+ "productivity_shock": 1.0 - magnitude, # Reduce productivity
400
+ }
401
+
402
+ else:
403
+ logger.warning(f"Unknown event type: {event_type}, using generic shock")
404
+ if timing < horizon:
405
+ scenario[timing] = {"disaster_shock": magnitude}
406
+
407
+ return scenario
408
+
409
+ def generate_causal_scenarios(
410
+ self,
411
+ n_scenarios: int,
412
+ horizon: int,
413
+ current_state: Optional[StateVector] = None,
414
+ target_variables: Optional[List[str]] = None
415
+ ) -> List[List[Dict[str, float]]]:
416
+ """
417
+ Generate scenarios using CRCA causal reasoning (if available).
418
+
419
+ Uses CRCAAgent to generate counterfactual scenarios based on causal relationships.
420
+ Falls back to Gaussian if CRCA not available.
421
+
422
+ Args:
423
+ n_scenarios: Number of scenarios
424
+ horizon: Time horizon
425
+ current_state: Current state vector (for counterfactual analysis)
426
+ target_variables: Variables to focus on (default: ["Y", "U", "S"])
427
+
428
+ Returns:
429
+ List[List[Dict[str, float]]]: Causal scenarios
430
+ """
431
+ if self.crca_agent is None or current_state is None:
432
+ # Fallback to Gaussian
433
+ logger.debug("CRCA not available or no state provided, using Gaussian scenarios")
434
+ return self.generate_gaussian(n_scenarios, horizon)
435
+
436
+ try:
437
+ # Convert state to dict for CRCA
438
+ state_dict = {
439
+ "P": current_state.P,
440
+ "L": current_state.L,
441
+ "U": current_state.U,
442
+ "W": current_state.W,
443
+ "S": current_state.S,
444
+ "Y": current_state.Y,
445
+ "K": current_state.K,
446
+ "I": current_state.I,
447
+ "literacy": current_state.literacy,
448
+ "Ecap": current_state.Ecap,
449
+ "Hcap": current_state.Hcap,
450
+ }
451
+
452
+ if target_variables is None:
453
+ target_variables = ["Y", "U", "S"] # GDP, unemployment, stability
454
+
455
+ # Use CRCA to generate counterfactual scenarios
456
+ crca_result = self.crca_agent.run(
457
+ initial_state=state_dict,
458
+ target_variables=target_variables,
459
+ max_steps=horizon
460
+ )
461
+
462
+ # Extract counterfactual scenarios
463
+ counterfactuals = crca_result.get("counterfactual_scenarios", [])
464
+
465
+ if not counterfactuals:
466
+ logger.debug("No counterfactuals from CRCA, using Gaussian scenarios")
467
+ return self.generate_gaussian(n_scenarios, horizon)
468
+
469
+ # Convert counterfactuals to scenario format
470
+ scenarios = []
471
+ for cf in counterfactuals[:n_scenarios]:
472
+ scenario = []
473
+ interventions = cf.interventions
474
+
475
+ # Create scenario as list of disturbance dicts
476
+ for step in range(horizon):
477
+ # Disturbances based on counterfactual interventions
478
+ disturbances = {}
479
+ for var, value in interventions.items():
480
+ if var in state_dict:
481
+ # Convert intervention to disturbance
482
+ current_val = state_dict.get(var, 0.0)
483
+ # Scale down to reasonable disturbance magnitude
484
+ disturbances[var] = (value - current_val) * 0.1
485
+
486
+ scenario.append(disturbances)
487
+
488
+ scenarios.append(scenario)
489
+
490
+ # Pad to n_scenarios if needed
491
+ while len(scenarios) < n_scenarios:
492
+ scenarios.extend(self.generate_gaussian(1, horizon))
493
+
494
+ logger.debug(f"Generated {len(scenarios)} causal scenarios via CRCA")
495
+ return scenarios[:n_scenarios]
496
+
497
+ except Exception as e:
498
+ logger.warning(f"CRCA scenario generation failed: {e}, using Gaussian scenarios")
499
+ return self.generate_gaussian(n_scenarios, horizon)
500
+
501
+
502
+ class StabilityEnforcer:
503
+ """
504
+ Stability enforcer to prevent oscillations and planner thrash.
505
+
506
+ Implements:
507
+ - Budget change limits: ||b_t - b_{t-1}||_1 <= Δ_b
508
+ - Investment smoothing: I_inv_t = β * I_inv_{t-1} + (1-β) * I_hat_t
509
+ - Logistics ramp constraints
510
+
511
+ Args:
512
+ max_budget_change: Maximum L1 norm change in budget shares (default: 0.2)
513
+ investment_smoothing: Smoothing factor β (default: 0.7)
514
+ max_flow_change: Maximum change in logistics flows (default: 0.3)
515
+ """
516
+
517
+ def __init__(
518
+ self,
519
+ max_budget_change: float = 0.2,
520
+ investment_smoothing: float = 0.7,
521
+ max_flow_change: float = 0.3,
522
+ ) -> None:
523
+ """Initialize stability enforcer."""
524
+ self.max_budget_change = max_budget_change
525
+ self.investment_smoothing = investment_smoothing
526
+ self.max_flow_change = max_flow_change
527
+
528
+ def apply_rate_limits(
529
+ self,
530
+ u_t: ControlVector,
531
+ u_prev: Optional[ControlVector],
532
+ limits: Optional[Dict[str, float]] = None
533
+ ) -> ControlVector:
534
+ """
535
+ Apply rate limits to control vector.
536
+
537
+ Args:
538
+ u_t: Proposed control vector
539
+ u_prev: Previous control vector (None if first step)
540
+ limits: Custom limits (default: use instance defaults)
541
+
542
+ Returns:
543
+ ControlVector: Adjusted control vector
544
+ """
545
+ if u_prev is None:
546
+ return u_t
547
+
548
+ if limits is None:
549
+ max_change = self.max_budget_change
550
+ else:
551
+ max_change = limits.get("max_budget_change", self.max_budget_change)
552
+
553
+ # Compute L1 norm of budget change
554
+ budget_change = {}
555
+ for cat in set(list(u_t.budget_shares.keys()) + list(u_prev.budget_shares.keys())):
556
+ prev_val = u_prev.budget_shares.get(cat, 0.0)
557
+ curr_val = u_t.budget_shares.get(cat, 0.0)
558
+ budget_change[cat] = abs(curr_val - prev_val)
559
+
560
+ total_change = sum(budget_change.values())
561
+
562
+ if total_change > max_change:
563
+ # Scale down changes proportionally
564
+ scale = max_change / total_change
565
+ adjusted_shares = {}
566
+ for cat in u_t.budget_shares:
567
+ prev_val = u_prev.budget_shares.get(cat, 0.0)
568
+ curr_val = u_t.budget_shares[cat]
569
+ change = (curr_val - prev_val) * scale
570
+ adjusted_shares[cat] = prev_val + change
571
+
572
+ # Renormalize to ensure simplex
573
+ total = sum(adjusted_shares.values())
574
+ if total > 0:
575
+ adjusted_shares = {k: v / total for k, v in adjusted_shares.items()}
576
+ else:
577
+ adjusted_shares = u_prev.budget_shares.copy()
578
+
579
+ u_adjusted = ControlVector(
580
+ budget_shares=adjusted_shares,
581
+ allocations=u_t.allocations.copy(),
582
+ flows=u_t.flows.copy(),
583
+ )
584
+ return u_adjusted
585
+
586
+ return u_t
587
+
588
+ def smooth_investment(
589
+ self,
590
+ I_hat: float,
591
+ I_prev: float,
592
+ beta: Optional[float] = None
593
+ ) -> float:
594
+ """
595
+ Smooth investment using exponential moving average.
596
+
597
+ Args:
598
+ I_hat: Proposed investment
599
+ I_prev: Previous investment
600
+ beta: Smoothing factor (default: use instance default)
601
+
602
+ Returns:
603
+ float: Smoothed investment
604
+ """
605
+ if beta is None:
606
+ beta = self.investment_smoothing
607
+
608
+ I_smooth = beta * I_prev + (1 - beta) * I_hat
609
+ return I_smooth
610
+
611
+
612
+ class ParetoExtractor:
613
+ """
614
+ Pareto frontier extractor using non-dominated sorting.
615
+
616
+ Finds all Pareto-efficient policies (non-dominated solutions).
617
+ """
618
+
619
+ @staticmethod
620
+ def extract_pareto_frontier(
621
+ candidate_policies: List[ControlVector],
622
+ objectives: np.ndarray
623
+ ) -> Tuple[List[ControlVector], np.ndarray]:
624
+ """
625
+ Extract Pareto-efficient policies.
626
+
627
+ Args:
628
+ candidate_policies: List of candidate policies
629
+ objectives: Objective matrix (n_policies x n_objectives)
630
+
631
+ Returns:
632
+ Tuple[List[ControlVector], np.ndarray]: (pareto_policies, pareto_objectives)
633
+ """
634
+ if len(candidate_policies) == 0:
635
+ return [], np.array([])
636
+
637
+ if objectives.ndim == 1:
638
+ objectives = objectives.reshape(1, -1)
639
+
640
+ n_policies = len(candidate_policies)
641
+ n_objectives = objectives.shape[1]
642
+
643
+ # Non-dominated sorting
644
+ is_pareto = np.ones(n_policies, dtype=bool)
645
+
646
+ for i in range(n_policies):
647
+ for j in range(n_policies):
648
+ if i == j:
649
+ continue
650
+
651
+ # Check if j dominates i
652
+ # j dominates i if: all objectives of j <= i, and at least one <
653
+ obj_i = objectives[i]
654
+ obj_j = objectives[j]
655
+
656
+ # For minimization: j dominates i if obj_j <= obj_i (element-wise)
657
+ # and at least one obj_j < obj_i
658
+ if np.all(obj_j <= obj_i) and np.any(obj_j < obj_i):
659
+ is_pareto[i] = False
660
+ break
661
+
662
+ pareto_indices = np.where(is_pareto)[0]
663
+ pareto_policies = [candidate_policies[i] for i in pareto_indices]
664
+ pareto_objectives = objectives[pareto_indices]
665
+
666
+ return pareto_policies, pareto_objectives
667
+
668
+
669
+ class MPCSolver:
670
+ """
671
+ Model-Predictive Control (MPC) solver.
672
+
673
+ Solves rolling-horizon optimization problem:
674
+ minimize: weighted_sum(objectives)
675
+ subject to:
676
+ - dynamics constraints
677
+ - hard constraints
678
+ - rate limits (if stability enabled)
679
+
680
+ Args:
681
+ dynamics: Dynamics model
682
+ constraint_checker: Constraint checker
683
+ objective_computer: Objective vector computer
684
+ horizon: Planning horizon (default: 10)
685
+ stability_enforcer: Stability enforcer (optional)
686
+ """
687
+
688
+ def __init__(
689
+ self,
690
+ dynamics: DynamicsModel,
691
+ constraint_checker: ConstraintChecker,
692
+ objective_computer: ObjectiveVector,
693
+ horizon: int = 10,
694
+ stability_enforcer: Optional[StabilityEnforcer] = None,
695
+ ) -> None:
696
+ """Initialize MPC solver."""
697
+ self.dynamics = dynamics
698
+ self.constraint_checker = constraint_checker
699
+ self.objective_computer = objective_computer
700
+ self.horizon = horizon
701
+ self.stability_enforcer = stability_enforcer
702
+
703
+ def solve(
704
+ self,
705
+ x_t: StateVector,
706
+ scenarios: List[List[Dict[str, float]]],
707
+ objective_weights: Optional[np.ndarray] = None,
708
+ u_prev: Optional[ControlVector] = None
709
+ ) -> Tuple[ControlVector, Dict[str, Any]]:
710
+ """
711
+ Solve MPC optimization problem.
712
+
713
+ Args:
714
+ x_t: Current state
715
+ scenarios: List of disturbance scenarios
716
+ objective_weights: Weights for objectives (default: equal)
717
+ u_prev: Previous control (for rate limits)
718
+
719
+ Returns:
720
+ Tuple[ControlVector, Dict[str, Any]]: (optimal_policy, solver_info)
721
+ """
722
+ if objective_weights is None:
723
+ objective_weights = np.ones(6) / 6.0 # Equal weights
724
+
725
+ # Sample candidate policies
726
+ budget_categories = [
727
+ "energy", "food", "infrastructure", "education",
728
+ "healthcare", "R&D", "welfare"
729
+ ]
730
+
731
+ n_candidates = 20
732
+ candidate_policies = [
733
+ ControlVector.sample_budget_simplex(budget_categories)
734
+ for _ in range(n_candidates)
735
+ ]
736
+
737
+ # Evaluate candidates under scenarios
738
+ best_policy = None
739
+ best_score = float('inf')
740
+ candidate_scores = []
741
+
742
+ for policy in candidate_policies:
743
+ # Apply rate limits if enabled
744
+ if self.stability_enforcer and u_prev is not None:
745
+ policy = self.stability_enforcer.apply_rate_limits(policy, u_prev)
746
+
747
+ # Evaluate under all scenarios
748
+ scenario_objectives = []
749
+ for scenario in scenarios:
750
+ traj, _, _ = self._simulate_policy(x_t, policy, scenario)
751
+ obj = self.objective_computer.compute(traj, [policy] * len(traj[1:]))
752
+ scenario_objectives.append(obj)
753
+
754
+ # Aggregate objectives (expected value)
755
+ expected_obj = np.mean(scenario_objectives, axis=0)
756
+
757
+ # Weighted sum
758
+ score = np.dot(objective_weights, expected_obj)
759
+ candidate_scores.append(score)
760
+
761
+ # Check feasibility
762
+ traj, _, first_violation = self._simulate_policy(x_t, policy, scenarios[0])
763
+ if first_violation is None: # Feasible
764
+ if score < best_score:
765
+ best_score = score
766
+ best_policy = policy
767
+
768
+ if best_policy is None:
769
+ # Fallback: use first feasible or first candidate
770
+ logger.warning("No feasible policy found, using first candidate")
771
+ best_policy = candidate_policies[0]
772
+
773
+ solver_info = {
774
+ "best_score": best_score,
775
+ "n_candidates": n_candidates,
776
+ "n_scenarios": len(scenarios),
777
+ "feasible_found": best_policy is not None,
778
+ }
779
+
780
+ return best_policy, solver_info
781
+
782
+ def _simulate_policy(
783
+ self,
784
+ x_0: StateVector,
785
+ policy: ControlVector,
786
+ disturbances: List[Dict[str, float]]
787
+ ) -> Tuple[List[StateVector], List[bool], Optional[int]]:
788
+ """Simulate policy forward."""
789
+ from crca_sd.crca_sd_core import ForwardSimulator
790
+
791
+ simulator = ForwardSimulator(self.dynamics, self.constraint_checker)
792
+ return simulator.simulate_scenario(x_0, policy, disturbances, self.horizon)
793
+
794
+ def execute_policy_realtime(
795
+ self,
796
+ policy: ControlVector,
797
+ government_api_config: Optional[Dict[str, Any]] = None,
798
+ execution_id: Optional[str] = None
799
+ ) -> Tuple[bool, str, Dict[str, Any]]:
800
+ """
801
+ Execute policy in real-time via government API integration.
802
+
803
+ Args:
804
+ policy: Policy to execute
805
+ government_api_config: Government API configuration
806
+ execution_id: Optional execution ID
807
+
808
+ Returns:
809
+ Tuple[bool, str, Dict[str, Any]]: (success, message, execution_info)
810
+ """
811
+ # Import PolicyExecutor from realtime module
812
+ try:
813
+ from crca_sd.crca_sd_realtime import PolicyExecutor
814
+
815
+ executor = PolicyExecutor(government_api_config)
816
+ success, message, info = executor.execute_policy(policy, execution_id)
817
+
818
+ logger.info(f"Policy execution {'succeeded' if success else 'failed'}: {message}")
819
+
820
+ return success, message, info
821
+
822
+ except ImportError:
823
+ logger.warning("PolicyExecutor not available, using mock execution")
824
+ return True, "Mock execution successful", {"execution_id": execution_id or "mock"}
825
+
826
+ def solve_and_execute(
827
+ self,
828
+ x_t: StateVector,
829
+ scenarios: List[List[Dict[str, float]]],
830
+ previous_policy: Optional[ControlVector] = None,
831
+ government_api_config: Optional[Dict[str, Any]] = None,
832
+ require_approval_check: Optional[Callable[[ControlVector, Optional[ControlVector]], Tuple[bool, str]]] = None
833
+ ) -> Tuple[Optional[ControlVector], Dict[str, Any]]:
834
+ """
835
+ Solve MPC and execute policy (with approval check if needed).
836
+
837
+ Args:
838
+ x_t: Current state
839
+ scenarios: Disturbance scenarios
840
+ previous_policy: Previous policy (for rate limits and approval check)
841
+ government_api_config: Government API configuration
842
+ require_approval_check: Function to check if approval is needed
843
+
844
+ Returns:
845
+ Tuple[Optional[ControlVector], Dict[str, Any]]: (executed_policy, execution_info)
846
+ """
847
+ # Solve MPC
848
+ policy, solver_info = self.solve(x_t, scenarios, u_prev=previous_policy)
849
+
850
+ # Check if approval is needed
851
+ requires_approval = False
852
+ if require_approval_check:
853
+ requires_approval, reason = require_approval_check(policy, previous_policy)
854
+ if requires_approval:
855
+ logger.info(f"Policy requires approval: {reason}")
856
+ return None, {"status": "pending_approval", "reason": reason, "policy": policy}
857
+
858
+ # Execute policy
859
+ success, message, exec_info = self.execute_policy_realtime(
860
+ policy,
861
+ government_api_config
862
+ )
863
+
864
+ execution_info = {
865
+ **solver_info,
866
+ **exec_info,
867
+ "requires_approval": requires_approval,
868
+ }
869
+
870
+ return policy if success else None, execution_info
871
+
872
+
873
+ class StateEstimator:
874
+ """
875
+ State estimator for partial observability (EKF/UKF).
876
+
877
+ Implements Extended Kalman Filter for state estimation with noisy observations.
878
+ Observation model: y_t = h(x_t) + ν_t
879
+
880
+ Args:
881
+ dynamics: Dynamics model
882
+ observation_noise_cov: Observation noise covariance matrix
883
+ process_noise_cov: Process noise covariance matrix
884
+ """
885
+
886
+ def __init__(
887
+ self,
888
+ dynamics: DynamicsModel,
889
+ observation_noise_cov: Optional[np.ndarray] = None,
890
+ process_noise_cov: Optional[np.ndarray] = None,
891
+ update_frequency: float = 86400.0, # Daily updates
892
+ ) -> None:
893
+ """Initialize state estimator."""
894
+ self.dynamics = dynamics
895
+
896
+ # Default noise covariances (diagonal)
897
+ n_state = 16 # Number of state variables
898
+ if observation_noise_cov is None:
899
+ observation_noise_cov = np.eye(n_state) * 0.01
900
+ if process_noise_cov is None:
901
+ process_noise_cov = np.eye(n_state) * 0.001
902
+
903
+ self.R = observation_noise_cov # Observation noise
904
+ self.Q = process_noise_cov # Process noise
905
+
906
+ # State covariance
907
+ self.P = np.eye(n_state) * 0.1
908
+
909
+ # Real-time capabilities
910
+ self.update_frequency = update_frequency
911
+ self.last_update_time = 0.0
912
+ self.current_state_estimate: Optional[StateVector] = None
913
+ self.state_confidence: float = 0.0
914
+
915
+ def update(
916
+ self,
917
+ y_t: Dict[str, float],
918
+ u_t: ControlVector
919
+ ) -> StateVector:
920
+ """
921
+ Update state estimate with observation (EKF update step).
922
+
923
+ Args:
924
+ y_t: Noisy observation
925
+ u_t: Control applied
926
+
927
+ Returns:
928
+ StateVector: Updated state estimate
929
+ """
930
+ # Simplified EKF: assume linear observation model h(x) = x
931
+ # In practice, h(x) would map state to observable variables
932
+
933
+ # Predict step (already done in predict)
934
+ x_pred = self.predict(u_t)
935
+
936
+ # Convert state to vector for EKF
937
+ x_vec = self._state_to_vector(x_pred)
938
+ y_vec = self._observation_to_vector(y_t, x_pred)
939
+
940
+ # Innovation
941
+ innovation = y_vec - x_vec # Simplified: h(x) = x
942
+
943
+ # Kalman gain
944
+ S = self.P + self.R # Innovation covariance
945
+ K = self.P @ np.linalg.pinv(S) # Kalman gain
946
+
947
+ # Update
948
+ x_updated_vec = x_vec + K @ innovation
949
+ self.P = (np.eye(len(x_vec)) - K) @ self.P
950
+
951
+ # Convert back to StateVector
952
+ x_updated = self._vector_to_state(x_updated_vec)
953
+
954
+ # Store updated estimate
955
+ self.current_state_estimate = x_updated
956
+ self.last_update_time = time.time()
957
+
958
+ # Compute confidence from covariance trace
959
+ trace_P = np.trace(self.P)
960
+ self.state_confidence = max(0.0, 1.0 - trace_P / 10.0) # Normalized confidence
961
+
962
+ return x_updated
963
+
964
+ def predict(
965
+ self,
966
+ u_t: ControlVector
967
+ ) -> StateVector:
968
+ """
969
+ One-step prediction: x_pred_{t+1} = f(x_hat_t, u_t).
970
+
971
+ Args:
972
+ u_t: Control vector
973
+
974
+ Returns:
975
+ StateVector: Predicted next state
976
+ """
977
+ # Use stored state estimate
978
+ if self.current_state_estimate is None:
979
+ self.current_state_estimate = StateVector()
980
+
981
+ # Predict next state using dynamics
982
+ x_pred = self.dynamics.step(self.current_state_estimate, u_t)
983
+
984
+ return x_pred
985
+
986
+ def should_update(self) -> bool:
987
+ """
988
+ Check if state should be updated based on frequency (daily).
989
+
990
+ Returns:
991
+ bool: True if should update
992
+ """
993
+ if self.last_update_time == 0.0:
994
+ return True
995
+
996
+ elapsed = time.time() - self.last_update_time
997
+ return elapsed >= self.update_frequency
998
+
999
+ def get_current_estimate(self) -> Optional[StateVector]:
1000
+ """Get current state estimate."""
1001
+ return self.current_state_estimate
1002
+
1003
+ def get_confidence(self) -> float:
1004
+ """Get state estimate confidence."""
1005
+ return self.state_confidence
1006
+
1007
+ def update_with_multi_sensor(
1008
+ self,
1009
+ observations: List[Dict[str, float]],
1010
+ u_t: ControlVector,
1011
+ sensor_weights: Optional[Dict[str, float]] = None
1012
+ ) -> StateVector:
1013
+ """
1014
+ Update state estimate with multiple sensor observations (multi-sensor fusion).
1015
+
1016
+ Args:
1017
+ observations: List of observation dictionaries from different sensors
1018
+ u_t: Control vector
1019
+ sensor_weights: Optional weights for each sensor (by source name)
1020
+
1021
+ Returns:
1022
+ StateVector: Updated state estimate
1023
+ """
1024
+ if not observations:
1025
+ return self.predict(u_t)
1026
+
1027
+ # Fuse observations (weighted average)
1028
+ fused_observation = {}
1029
+ total_weight = 0.0
1030
+
1031
+ for obs in observations:
1032
+ source = obs.get("source", "unknown")
1033
+ weight = sensor_weights.get(source, 1.0) if sensor_weights else 1.0
1034
+
1035
+ for key, value in obs.items():
1036
+ if key == "source":
1037
+ continue
1038
+
1039
+ if key not in fused_observation:
1040
+ fused_observation[key] = 0.0
1041
+
1042
+ fused_observation[key] += value * weight
1043
+ total_weight += weight
1044
+
1045
+ # Normalize
1046
+ if total_weight > 0:
1047
+ fused_observation = {k: v / total_weight for k, v in fused_observation.items()}
1048
+
1049
+ # Update with fused observation
1050
+ updated = self.update(fused_observation, u_t)
1051
+
1052
+ # Update stored estimate and confidence
1053
+ self.current_state_estimate = updated
1054
+ self.state_confidence = min(1.0, len(observations) * 0.1) # More sensors = higher confidence
1055
+ self.last_update_time = time.time()
1056
+
1057
+ return updated
1058
+
1059
+ def _state_to_vector(self, x: StateVector) -> np.ndarray:
1060
+ """Convert StateVector to numpy array."""
1061
+ return np.array([
1062
+ x.P, x.L, x.U, x.W, x.S,
1063
+ x.literacy, x.Ecap, x.Hcap,
1064
+ x.K, x.I, x.Tcap,
1065
+ x.E_stock, x.F_stock, x.M_stock, x.C, x.Y
1066
+ ])
1067
+
1068
+ def _vector_to_state(self, vec: np.ndarray) -> StateVector:
1069
+ """Convert numpy array to StateVector."""
1070
+ return StateVector(
1071
+ P=vec[0], L=vec[1], U=vec[2], W=vec[3], S=vec[4],
1072
+ literacy=vec[5], Ecap=vec[6], Hcap=vec[7],
1073
+ K=vec[8], I=vec[9], Tcap=vec[10],
1074
+ E_stock=vec[11], F_stock=vec[12], M_stock=vec[13], C=vec[14], Y=vec[15]
1075
+ )
1076
+
1077
+ def _observation_to_vector(
1078
+ self,
1079
+ y_t: Dict[str, float],
1080
+ x_t: StateVector
1081
+ ) -> np.ndarray:
1082
+ """Convert observation to vector (fill missing with state values)."""
1083
+ vec = self._state_to_vector(x_t)
1084
+
1085
+ # Override with observed values if available
1086
+ # Handle both direct values and percentage values (U is a rate)
1087
+ if "P" in y_t:
1088
+ vec[0] = float(y_t["P"])
1089
+ if "L" in y_t:
1090
+ vec[1] = float(y_t["L"])
1091
+ if "U" in y_t:
1092
+ # U can be percentage (like 1.434) or rate (like 0.01434)
1093
+ u_val = float(y_t["U"])
1094
+ if u_val > 1.0:
1095
+ vec[2] = u_val / 100.0 # Convert percentage to rate
1096
+ else:
1097
+ vec[2] = u_val
1098
+ if "Y" in y_t:
1099
+ vec[15] = float(y_t["Y"])
1100
+ if "W" in y_t:
1101
+ vec[3] = float(y_t["W"])
1102
+ if "S" in y_t:
1103
+ vec[4] = float(y_t["S"])
1104
+ if "literacy" in y_t:
1105
+ lit_val = float(y_t["literacy"])
1106
+ if lit_val > 1.0:
1107
+ vec[5] = lit_val / 100.0 # Convert percentage to rate
1108
+ else:
1109
+ vec[5] = lit_val
1110
+ if "Ecap" in y_t:
1111
+ vec[6] = float(y_t["Ecap"])
1112
+ if "Hcap" in y_t:
1113
+ vec[7] = float(y_t["Hcap"])
1114
+ if "K" in y_t:
1115
+ vec[8] = float(y_t["K"])
1116
+ if "I" in y_t:
1117
+ vec[9] = float(y_t["I"])
1118
+ if "Tcap" in y_t:
1119
+ vec[10] = float(y_t["Tcap"])
1120
+ if "E_stock" in y_t:
1121
+ vec[11] = float(y_t["E_stock"])
1122
+ if "F_stock" in y_t:
1123
+ vec[12] = float(y_t["F_stock"])
1124
+ if "M_stock" in y_t:
1125
+ vec[13] = float(y_t["M_stock"])
1126
+ if "C" in y_t:
1127
+ vec[14] = float(y_t["C"])
1128
+
1129
+ return vec
1130
+