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.
- .github/ISSUE_TEMPLATE/bug_report.md +65 -0
- .github/ISSUE_TEMPLATE/feature_request.md +41 -0
- .github/PULL_REQUEST_TEMPLATE.md +20 -0
- .github/workflows/publish-manual.yml +61 -0
- .github/workflows/publish.yml +64 -0
- .gitignore +214 -0
- CRCA.py +4156 -0
- LICENSE +201 -0
- MANIFEST.in +43 -0
- PKG-INFO +5035 -0
- README.md +4959 -0
- __init__.py +17 -0
- branches/CRCA-Q.py +2728 -0
- branches/crca_cg/corposwarm.py +9065 -0
- branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
- branches/crca_cg/package.json +5 -0
- branches/crca_cg/test_bolt_integration.py +446 -0
- branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
- branches/crca_cg/test_new_features.py +163 -0
- branches/crca_sd/__init__.py +149 -0
- branches/crca_sd/crca_sd_core.py +770 -0
- branches/crca_sd/crca_sd_governance.py +1325 -0
- branches/crca_sd/crca_sd_mpc.py +1130 -0
- branches/crca_sd/crca_sd_realtime.py +1844 -0
- branches/crca_sd/crca_sd_tui.py +1133 -0
- crca-1.4.0.dist-info/METADATA +5035 -0
- crca-1.4.0.dist-info/RECORD +501 -0
- crca-1.4.0.dist-info/WHEEL +4 -0
- crca-1.4.0.dist-info/licenses/LICENSE +201 -0
- docs/CRCA-Q.md +2333 -0
- examples/config.yaml.example +25 -0
- examples/crca_sd_example.py +513 -0
- examples/data_broker_example.py +294 -0
- examples/logistics_corporation.py +861 -0
- examples/palantir_example.py +299 -0
- examples/policy_bench.py +934 -0
- examples/pridnestrovia-sd.py +705 -0
- examples/pridnestrovia_realtime.py +1902 -0
- prompts/__init__.py +10 -0
- prompts/default_crca.py +101 -0
- pyproject.toml +151 -0
- requirements.txt +76 -0
- schemas/__init__.py +43 -0
- schemas/mcpSchemas.py +51 -0
- schemas/policy.py +458 -0
- templates/__init__.py +38 -0
- templates/base_specialized_agent.py +195 -0
- templates/drift_detection.py +325 -0
- templates/examples/causal_agent_template.py +309 -0
- templates/examples/drag_drop_example.py +213 -0
- templates/examples/logistics_agent_template.py +207 -0
- templates/examples/trading_agent_template.py +206 -0
- templates/feature_mixins.py +253 -0
- templates/graph_management.py +442 -0
- templates/llm_integration.py +194 -0
- templates/module_registry.py +276 -0
- templates/mpc_planner.py +280 -0
- templates/policy_loop.py +1168 -0
- templates/prediction_framework.py +448 -0
- templates/statistical_methods.py +778 -0
- tests/sanity.yml +31 -0
- tests/sanity_check +406 -0
- tests/test_core.py +47 -0
- tests/test_crca_excel.py +166 -0
- tests/test_crca_sd.py +780 -0
- tests/test_data_broker.py +424 -0
- tests/test_palantir.py +349 -0
- tools/__init__.py +38 -0
- tools/actuators.py +437 -0
- tools/bolt.diy/Dockerfile +103 -0
- tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
- tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
- tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
- tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
- tools/bolt.diy/app/components/@settings/index.ts +12 -0
- tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
- tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
- tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
- tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
- tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
- tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
- tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
- tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
- tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
- tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
- tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
- tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
- tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
- tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
- tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
- tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
- tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
- tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
- tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
- tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
- tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
- tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
- tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
- tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
- tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
- tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
- tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
- tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
- tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
- tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
- tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
- tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
- tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
- tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
- tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
- tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
- tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
- tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
- tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
- tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
- tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
- tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
- tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
- tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
- tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
- tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
- tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
- tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
- tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
- tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
- tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
- tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
- tools/bolt.diy/app/components/header/Header.tsx +42 -0
- tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
- tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
- tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
- tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
- tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
- tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
- tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
- tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
- tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
- tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
- tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
- tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
- tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
- tools/bolt.diy/app/components/txt +1 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
- tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
- tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
- tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
- tools/bolt.diy/app/components/ui/Button.tsx +46 -0
- tools/bolt.diy/app/components/ui/Card.tsx +55 -0
- tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
- tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
- tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
- tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
- tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
- tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
- tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
- tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
- tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
- tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
- tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
- tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
- tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
- tools/bolt.diy/app/components/ui/Input.tsx +22 -0
- tools/bolt.diy/app/components/ui/Label.tsx +20 -0
- tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
- tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
- tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
- tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
- tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
- tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
- tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
- tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
- tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
- tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
- tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
- tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
- tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
- tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
- tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
- tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
- tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
- tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
- tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
- tools/bolt.diy/app/components/ui/index.ts +38 -0
- tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
- tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
- tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
- tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
- tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
- tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
- tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
- tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
- tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
- tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
- tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
- tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
- tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
- tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
- tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
- tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
- tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
- tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
- tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
- tools/bolt.diy/app/entry.client.tsx +7 -0
- tools/bolt.diy/app/entry.server.tsx +80 -0
- tools/bolt.diy/app/root.tsx +156 -0
- tools/bolt.diy/app/routes/_index.tsx +175 -0
- tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
- tools/bolt.diy/app/routes/api.chat.ts +463 -0
- tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
- tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
- tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
- tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
- tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
- tools/bolt.diy/app/routes/api.git-info.ts +69 -0
- tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
- tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
- tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
- tools/bolt.diy/app/routes/api.github-template.ts +242 -0
- tools/bolt.diy/app/routes/api.github-user.ts +287 -0
- tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
- tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
- tools/bolt.diy/app/routes/api.health.ts +8 -0
- tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
- tools/bolt.diy/app/routes/api.mandate.ts +351 -0
- tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
- tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
- tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
- tools/bolt.diy/app/routes/api.models.ts +90 -0
- tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
- tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
- tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
- tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
- tools/bolt.diy/app/routes/api.supabase.ts +56 -0
- tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
- tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
- tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
- tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
- tools/bolt.diy/app/routes/api.update.ts +21 -0
- tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
- tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
- tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
- tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
- tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
- tools/bolt.diy/app/routes/git.tsx +25 -0
- tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
- tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
- tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
- tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
- tools/bolt.diy/app/styles/animations.scss +49 -0
- tools/bolt.diy/app/styles/components/code.scss +9 -0
- tools/bolt.diy/app/styles/components/editor.scss +135 -0
- tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
- tools/bolt.diy/app/styles/components/terminal.scss +3 -0
- tools/bolt.diy/app/styles/components/toast.scss +23 -0
- tools/bolt.diy/app/styles/diff-view.css +72 -0
- tools/bolt.diy/app/styles/index.scss +73 -0
- tools/bolt.diy/app/styles/variables.scss +255 -0
- tools/bolt.diy/app/styles/z-index.scss +37 -0
- tools/bolt.diy/app/types/GitHub.ts +182 -0
- tools/bolt.diy/app/types/GitLab.ts +103 -0
- tools/bolt.diy/app/types/actions.ts +85 -0
- tools/bolt.diy/app/types/artifact.ts +5 -0
- tools/bolt.diy/app/types/context.ts +26 -0
- tools/bolt.diy/app/types/design-scheme.ts +93 -0
- tools/bolt.diy/app/types/global.d.ts +13 -0
- tools/bolt.diy/app/types/mandate.ts +333 -0
- tools/bolt.diy/app/types/model.ts +25 -0
- tools/bolt.diy/app/types/netlify.ts +94 -0
- tools/bolt.diy/app/types/supabase.ts +54 -0
- tools/bolt.diy/app/types/template.ts +8 -0
- tools/bolt.diy/app/types/terminal.ts +9 -0
- tools/bolt.diy/app/types/theme.ts +1 -0
- tools/bolt.diy/app/types/vercel.ts +67 -0
- tools/bolt.diy/app/utils/buffer.ts +29 -0
- tools/bolt.diy/app/utils/classNames.ts +65 -0
- tools/bolt.diy/app/utils/constants.ts +147 -0
- tools/bolt.diy/app/utils/debounce.ts +13 -0
- tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
- tools/bolt.diy/app/utils/diff.spec.ts +11 -0
- tools/bolt.diy/app/utils/diff.ts +117 -0
- tools/bolt.diy/app/utils/easings.ts +3 -0
- tools/bolt.diy/app/utils/fileLocks.ts +96 -0
- tools/bolt.diy/app/utils/fileUtils.ts +121 -0
- tools/bolt.diy/app/utils/folderImport.ts +73 -0
- tools/bolt.diy/app/utils/formatSize.ts +12 -0
- tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
- tools/bolt.diy/app/utils/githubStats.ts +9 -0
- tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
- tools/bolt.diy/app/utils/logger.ts +162 -0
- tools/bolt.diy/app/utils/markdown.ts +155 -0
- tools/bolt.diy/app/utils/mobile.ts +4 -0
- tools/bolt.diy/app/utils/os.ts +4 -0
- tools/bolt.diy/app/utils/path.ts +19 -0
- tools/bolt.diy/app/utils/projectCommands.ts +197 -0
- tools/bolt.diy/app/utils/promises.ts +19 -0
- tools/bolt.diy/app/utils/react.ts +6 -0
- tools/bolt.diy/app/utils/sampler.ts +49 -0
- tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
- tools/bolt.diy/app/utils/shell.ts +384 -0
- tools/bolt.diy/app/utils/stacktrace.ts +27 -0
- tools/bolt.diy/app/utils/stripIndent.ts +23 -0
- tools/bolt.diy/app/utils/terminal.ts +11 -0
- tools/bolt.diy/app/utils/unreachable.ts +3 -0
- tools/bolt.diy/app/vite-env.d.ts +2 -0
- tools/bolt.diy/assets/entitlements.mac.plist +25 -0
- tools/bolt.diy/assets/icons/icon.icns +0 -0
- tools/bolt.diy/assets/icons/icon.ico +0 -0
- tools/bolt.diy/assets/icons/icon.png +0 -0
- tools/bolt.diy/bindings.js +78 -0
- tools/bolt.diy/bindings.sh +33 -0
- tools/bolt.diy/docker-compose.yaml +145 -0
- tools/bolt.diy/electron/main/index.ts +201 -0
- tools/bolt.diy/electron/main/tsconfig.json +30 -0
- tools/bolt.diy/electron/main/ui/menu.ts +29 -0
- tools/bolt.diy/electron/main/ui/window.ts +54 -0
- tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
- tools/bolt.diy/electron/main/utils/constants.ts +4 -0
- tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
- tools/bolt.diy/electron/main/utils/reload.ts +35 -0
- tools/bolt.diy/electron/main/utils/serve.ts +71 -0
- tools/bolt.diy/electron/main/utils/store.ts +3 -0
- tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
- tools/bolt.diy/electron/main/vite.config.ts +44 -0
- tools/bolt.diy/electron/preload/index.ts +22 -0
- tools/bolt.diy/electron/preload/tsconfig.json +7 -0
- tools/bolt.diy/electron/preload/vite.config.ts +31 -0
- tools/bolt.diy/electron-builder.yml +64 -0
- tools/bolt.diy/electron-update.yml +4 -0
- tools/bolt.diy/eslint.config.mjs +57 -0
- tools/bolt.diy/functions/[[path]].ts +12 -0
- tools/bolt.diy/icons/angular.svg +1 -0
- tools/bolt.diy/icons/astro.svg +8 -0
- tools/bolt.diy/icons/chat.svg +1 -0
- tools/bolt.diy/icons/expo-brand.svg +1 -0
- tools/bolt.diy/icons/expo.svg +4 -0
- tools/bolt.diy/icons/logo-text.svg +1 -0
- tools/bolt.diy/icons/logo.svg +4 -0
- tools/bolt.diy/icons/mcp.svg +1 -0
- tools/bolt.diy/icons/nativescript.svg +1 -0
- tools/bolt.diy/icons/netlify.svg +10 -0
- tools/bolt.diy/icons/nextjs.svg +1 -0
- tools/bolt.diy/icons/nuxt.svg +1 -0
- tools/bolt.diy/icons/qwik.svg +1 -0
- tools/bolt.diy/icons/react.svg +1 -0
- tools/bolt.diy/icons/remix.svg +24 -0
- tools/bolt.diy/icons/remotion.svg +1 -0
- tools/bolt.diy/icons/shadcn.svg +21 -0
- tools/bolt.diy/icons/slidev.svg +60 -0
- tools/bolt.diy/icons/solidjs.svg +1 -0
- tools/bolt.diy/icons/stars.svg +1 -0
- tools/bolt.diy/icons/svelte.svg +1 -0
- tools/bolt.diy/icons/typescript.svg +1 -0
- tools/bolt.diy/icons/vite.svg +1 -0
- tools/bolt.diy/icons/vue.svg +1 -0
- tools/bolt.diy/load-context.ts +9 -0
- tools/bolt.diy/notarize.cjs +31 -0
- tools/bolt.diy/package.json +218 -0
- tools/bolt.diy/playwright.config.preview.ts +35 -0
- tools/bolt.diy/pre-start.cjs +26 -0
- tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
- tools/bolt.diy/public/apple-touch-icon.png +0 -0
- tools/bolt.diy/public/favicon.ico +0 -0
- tools/bolt.diy/public/favicon.svg +4 -0
- tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
- tools/bolt.diy/public/icons/Anthropic.svg +4 -0
- tools/bolt.diy/public/icons/Cohere.svg +4 -0
- tools/bolt.diy/public/icons/Deepseek.svg +5 -0
- tools/bolt.diy/public/icons/Default.svg +4 -0
- tools/bolt.diy/public/icons/Google.svg +4 -0
- tools/bolt.diy/public/icons/Groq.svg +4 -0
- tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
- tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
- tools/bolt.diy/public/icons/LMStudio.svg +5 -0
- tools/bolt.diy/public/icons/Mistral.svg +4 -0
- tools/bolt.diy/public/icons/Ollama.svg +4 -0
- tools/bolt.diy/public/icons/OpenAI.svg +4 -0
- tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
- tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
- tools/bolt.diy/public/icons/Perplexity.svg +4 -0
- tools/bolt.diy/public/icons/Together.svg +4 -0
- tools/bolt.diy/public/icons/xAI.svg +5 -0
- tools/bolt.diy/public/inspector-script.js +292 -0
- tools/bolt.diy/public/logo-dark-styled.png +0 -0
- tools/bolt.diy/public/logo-dark.png +0 -0
- tools/bolt.diy/public/logo-light-styled.png +0 -0
- tools/bolt.diy/public/logo-light.png +0 -0
- tools/bolt.diy/public/logo.svg +15 -0
- tools/bolt.diy/public/social_preview_index.jpg +0 -0
- tools/bolt.diy/scripts/clean.js +45 -0
- tools/bolt.diy/scripts/electron-dev.mjs +181 -0
- tools/bolt.diy/scripts/setup-env.sh +41 -0
- tools/bolt.diy/scripts/update-imports.sh +7 -0
- tools/bolt.diy/scripts/update.sh +52 -0
- tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
- tools/bolt.diy/services/execution-governor/config.ts +42 -0
- tools/bolt.diy/services/execution-governor/index.ts +683 -0
- tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
- tools/bolt.diy/services/execution-governor/package.json +31 -0
- tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
- tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
- tools/bolt.diy/services/execution-governor/types.ts +145 -0
- tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
- tools/bolt.diy/services/headless-executor/executor.ts +210 -0
- tools/bolt.diy/services/headless-executor/index.ts +323 -0
- tools/bolt.diy/services/headless-executor/package.json +27 -0
- tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
- tools/bolt.diy/services/headless-executor/types.ts +38 -0
- tools/bolt.diy/test-workflows.sh +240 -0
- tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
- tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
- tools/bolt.diy/tests/mandates/complex.json +53 -0
- tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
- tools/bolt.diy/tests/mandates/simple.json +35 -0
- tools/bolt.diy/tsconfig.json +37 -0
- tools/bolt.diy/types/istextorbinary.d.ts +15 -0
- tools/bolt.diy/uno.config.ts +279 -0
- tools/bolt.diy/vite-electron.config.ts +76 -0
- tools/bolt.diy/vite.config.ts +112 -0
- tools/bolt.diy/worker-configuration.d.ts +22 -0
- tools/bolt.diy/wrangler.toml +6 -0
- tools/code_generator.py +461 -0
- tools/file_operations.py +465 -0
- tools/mandate_generator.py +337 -0
- tools/mcpClientUtils.py +1216 -0
- tools/sensors.py +285 -0
- utils/Agent_types.py +15 -0
- utils/AnyToStr.py +0 -0
- utils/HHCS.py +277 -0
- utils/__init__.py +30 -0
- utils/agent.py +3627 -0
- utils/aop.py +2948 -0
- utils/canonical.py +143 -0
- utils/conversation.py +1195 -0
- utils/doctrine_versioning +230 -0
- utils/formatter.py +474 -0
- utils/ledger.py +311 -0
- utils/out_types.py +16 -0
- utils/rollback.py +339 -0
- utils/router.py +929 -0
- utils/tui.py +1908 -0
|
@@ -0,0 +1,1844 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CRCA-SD Real-Time Control System
|
|
3
|
+
|
|
4
|
+
This module implements all real-time control capabilities for transforming CRCA-SD
|
|
5
|
+
from simulation/decision support into a real-world economic control system.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Real-time data acquisition (government systems priority)
|
|
9
|
+
- State estimation with daily updates
|
|
10
|
+
- Automated policy execution (minor changes < 10%)
|
|
11
|
+
- Human approval workflows (major changes > 10%)
|
|
12
|
+
- Safety interlocks and compliance
|
|
13
|
+
- 7-day rollback window
|
|
14
|
+
- Full regulatory compliance
|
|
15
|
+
|
|
16
|
+
Organized into sections:
|
|
17
|
+
1. Data Integration (~500 lines)
|
|
18
|
+
2. Control Execution (~600 lines)
|
|
19
|
+
3. Monitoring & Compliance (~500 lines)
|
|
20
|
+
4. Resilience & Learning (~400 lines)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Dict, List, Optional, Tuple, Any, Callable
|
|
24
|
+
import numpy as np
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from datetime import datetime, timedelta
|
|
27
|
+
import time
|
|
28
|
+
import json
|
|
29
|
+
import uuid
|
|
30
|
+
from enum import Enum
|
|
31
|
+
from loguru import logger
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import requests
|
|
35
|
+
REQUESTS_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
REQUESTS_AVAILABLE = False
|
|
38
|
+
logger.warning("requests not available, API integration limited")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
import sqlalchemy
|
|
42
|
+
SQLALCHEMY_AVAILABLE = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
SQLALCHEMY_AVAILABLE = False
|
|
45
|
+
logger.warning("sqlalchemy not available, database integration limited")
|
|
46
|
+
|
|
47
|
+
from crca_sd.crca_sd_core import StateVector, ControlVector, DynamicsModel, ConstraintChecker
|
|
48
|
+
from crca_sd.crca_sd_mpc import StateEstimator, ObjectiveVector
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ============================================================================
|
|
52
|
+
# SECTION 1: DATA INTEGRATION (~500 lines)
|
|
53
|
+
# ============================================================================
|
|
54
|
+
|
|
55
|
+
class DataSourceType(str, Enum):
|
|
56
|
+
"""Types of data sources."""
|
|
57
|
+
GOVERNMENT_API = "government_api" # Priority
|
|
58
|
+
PUBLIC_API = "public_api"
|
|
59
|
+
DATABASE = "database"
|
|
60
|
+
IOT_SENSOR = "iot_sensor"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class DataPoint:
|
|
65
|
+
"""Single data point with metadata."""
|
|
66
|
+
timestamp: float
|
|
67
|
+
source: str
|
|
68
|
+
source_type: DataSourceType
|
|
69
|
+
variable: str
|
|
70
|
+
value: float
|
|
71
|
+
confidence: float = 1.0
|
|
72
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DataAcquisition:
|
|
76
|
+
"""
|
|
77
|
+
Real-time data acquisition system.
|
|
78
|
+
|
|
79
|
+
Priority: Government system APIs (treasury, ministries, central bank)
|
|
80
|
+
Secondary: Public APIs, databases, IoT sensors
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
government_api_config: Configuration for government APIs
|
|
84
|
+
update_frequency: Update frequency in seconds (default: 86400 = daily)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
government_api_config: Optional[Dict[str, Any]] = None,
|
|
90
|
+
update_frequency: float = 86400.0, # Daily (24 hours)
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Initialize data acquisition system."""
|
|
93
|
+
self.government_api_config = government_api_config or {}
|
|
94
|
+
self.update_frequency = update_frequency
|
|
95
|
+
self.last_update: Dict[str, float] = {}
|
|
96
|
+
self.data_cache: Dict[str, List[DataPoint]] = {}
|
|
97
|
+
self.api_clients: Dict[str, Any] = {}
|
|
98
|
+
|
|
99
|
+
logger.info(f"DataAcquisition initialized with {update_frequency/3600:.1f}h update frequency")
|
|
100
|
+
|
|
101
|
+
def connect_government_api(
|
|
102
|
+
self,
|
|
103
|
+
api_name: str,
|
|
104
|
+
base_url: str,
|
|
105
|
+
auth_config: Dict[str, Any]
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Connect to government API (treasury, ministries, central bank).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
api_name: Name identifier for the API
|
|
112
|
+
base_url: Base URL for the API
|
|
113
|
+
auth_config: Authentication configuration
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
bool: True if connection successful
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
# Store API configuration
|
|
120
|
+
self.government_api_config[api_name] = {
|
|
121
|
+
"base_url": base_url,
|
|
122
|
+
"auth": auth_config,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Initialize API client (placeholder - would use actual client library)
|
|
126
|
+
self.api_clients[api_name] = {
|
|
127
|
+
"base_url": base_url,
|
|
128
|
+
"auth": auth_config,
|
|
129
|
+
"connected": True,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
logger.info(f"Connected to government API: {api_name}")
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"Failed to connect to government API {api_name}: {e}")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def fetch_government_data(
|
|
140
|
+
self,
|
|
141
|
+
api_name: str,
|
|
142
|
+
endpoint: str,
|
|
143
|
+
variables: List[str]
|
|
144
|
+
) -> List[DataPoint]:
|
|
145
|
+
"""
|
|
146
|
+
Fetch data from government API.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
api_name: API identifier
|
|
150
|
+
endpoint: API endpoint path
|
|
151
|
+
variables: List of variable names to fetch
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List[DataPoint]: List of data points
|
|
155
|
+
"""
|
|
156
|
+
if api_name not in self.api_clients:
|
|
157
|
+
logger.warning(f"Government API {api_name} not connected")
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
if not REQUESTS_AVAILABLE:
|
|
161
|
+
logger.warning("requests not available, returning mock data")
|
|
162
|
+
return self._generate_mock_data(variables, DataSourceType.GOVERNMENT_API)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
client = self.api_clients[api_name]
|
|
166
|
+
url = f"{client['base_url']}/{endpoint}"
|
|
167
|
+
|
|
168
|
+
# Make API request (placeholder - would use actual authentication)
|
|
169
|
+
# response = requests.get(url, headers=client['auth'], timeout=10)
|
|
170
|
+
# data = response.json()
|
|
171
|
+
|
|
172
|
+
# For now, return mock data
|
|
173
|
+
logger.debug(f"Fetching from {api_name}/{endpoint} for variables: {variables}")
|
|
174
|
+
data_points = self._generate_mock_data(variables, DataSourceType.GOVERNMENT_API, api_name)
|
|
175
|
+
|
|
176
|
+
# Cache data
|
|
177
|
+
for dp in data_points:
|
|
178
|
+
if dp.variable not in self.data_cache:
|
|
179
|
+
self.data_cache[dp.variable] = []
|
|
180
|
+
self.data_cache[dp.variable].append(dp)
|
|
181
|
+
|
|
182
|
+
return data_points
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"Error fetching government data from {api_name}: {e}")
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
def fetch_public_api_data(
|
|
189
|
+
self,
|
|
190
|
+
api_url: str,
|
|
191
|
+
variables: List[str]
|
|
192
|
+
) -> List[DataPoint]:
|
|
193
|
+
"""
|
|
194
|
+
Fetch data from public API (keyless).
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
api_url: Public API URL
|
|
198
|
+
variables: List of variable names to fetch
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List[DataPoint]: List of data points
|
|
202
|
+
"""
|
|
203
|
+
if not REQUESTS_AVAILABLE:
|
|
204
|
+
return self._generate_mock_data(variables, DataSourceType.PUBLIC_API)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
logger.debug(f"Fetching from public API: {api_url}")
|
|
208
|
+
response = requests.get(api_url, timeout=10)
|
|
209
|
+
response.raise_for_status()
|
|
210
|
+
data = response.json()
|
|
211
|
+
|
|
212
|
+
# Parse response and create data points
|
|
213
|
+
data_points = []
|
|
214
|
+
timestamp = time.time()
|
|
215
|
+
|
|
216
|
+
for var in variables:
|
|
217
|
+
value = self._extract_value_from_response(data, var)
|
|
218
|
+
if value is not None:
|
|
219
|
+
dp = DataPoint(
|
|
220
|
+
timestamp=timestamp,
|
|
221
|
+
source=api_url,
|
|
222
|
+
source_type=DataSourceType.PUBLIC_API,
|
|
223
|
+
variable=var,
|
|
224
|
+
value=float(value),
|
|
225
|
+
confidence=0.85, # Public APIs typically less reliable than government
|
|
226
|
+
)
|
|
227
|
+
data_points.append(dp)
|
|
228
|
+
|
|
229
|
+
return data_points
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Error fetching public API data: {e}")
|
|
233
|
+
return []
|
|
234
|
+
|
|
235
|
+
def fetch_world_bank_data(
|
|
236
|
+
self,
|
|
237
|
+
country_code: str,
|
|
238
|
+
indicators: Dict[str, str]
|
|
239
|
+
) -> List[DataPoint]:
|
|
240
|
+
"""
|
|
241
|
+
Fetch data from World Bank API (keyless, free).
|
|
242
|
+
|
|
243
|
+
World Bank API: https://datahelpdesk.worldbank.org/knowledgebase/articles/889392
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
country_code: ISO country code (e.g., "MD" for Moldova, "XK" for Kosovo - closest to Pridnestrovia)
|
|
247
|
+
indicators: Dict mapping variable names to World Bank indicator codes
|
|
248
|
+
Example: {"Y": "NY.GDP.MKTP.CD", "P": "SP.POP.TOTL"}
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
List[DataPoint]: List of data points
|
|
252
|
+
"""
|
|
253
|
+
if not REQUESTS_AVAILABLE:
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
data_points = []
|
|
257
|
+
timestamp = time.time()
|
|
258
|
+
|
|
259
|
+
# World Bank API base URL (keyless, no authentication required)
|
|
260
|
+
base_url = "https://api.worldbank.org/v2/country"
|
|
261
|
+
|
|
262
|
+
for var_name, indicator_code in indicators.items():
|
|
263
|
+
try:
|
|
264
|
+
# World Bank API format: /country/{country}/indicator/{indicator}?format=json
|
|
265
|
+
url = f"{base_url}/{country_code}/indicator/{indicator_code}"
|
|
266
|
+
params = {
|
|
267
|
+
"format": "json",
|
|
268
|
+
"date": "2020:2025", # Get recent data
|
|
269
|
+
"per_page": 1, # Just get latest value
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
response = requests.get(url, params=params, timeout=30) # World Bank can be slow
|
|
273
|
+
response.raise_for_status()
|
|
274
|
+
data = response.json()
|
|
275
|
+
|
|
276
|
+
# World Bank API returns nested structure
|
|
277
|
+
if len(data) >= 2 and len(data[1]) > 0:
|
|
278
|
+
latest_value = data[1][0].get("value")
|
|
279
|
+
if latest_value is not None:
|
|
280
|
+
dp = DataPoint(
|
|
281
|
+
timestamp=timestamp,
|
|
282
|
+
source="world_bank",
|
|
283
|
+
source_type=DataSourceType.PUBLIC_API,
|
|
284
|
+
variable=var_name,
|
|
285
|
+
value=float(latest_value),
|
|
286
|
+
confidence=0.90, # World Bank is reliable
|
|
287
|
+
metadata={"indicator": indicator_code, "country": country_code}
|
|
288
|
+
)
|
|
289
|
+
data_points.append(dp)
|
|
290
|
+
logger.debug(f"World Bank: {var_name} = {latest_value}")
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.warning(f"Could not fetch World Bank indicator {indicator_code}: {e}")
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
return data_points
|
|
297
|
+
|
|
298
|
+
def fetch_restcountries_data(
|
|
299
|
+
self,
|
|
300
|
+
country_name: str,
|
|
301
|
+
variables: List[str]
|
|
302
|
+
) -> List[DataPoint]:
|
|
303
|
+
"""
|
|
304
|
+
Fetch data from REST Countries API (keyless, free).
|
|
305
|
+
|
|
306
|
+
REST Countries API: https://restcountries.com/
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
country_name: Country name (e.g., "Moldova" - closest to Pridnestrovia)
|
|
310
|
+
variables: List of variable names to extract
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
List[DataPoint]: List of data points
|
|
314
|
+
"""
|
|
315
|
+
if not REQUESTS_AVAILABLE:
|
|
316
|
+
return []
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
# REST Countries API (keyless)
|
|
320
|
+
url = f"https://restcountries.com/v3.1/name/{country_name}"
|
|
321
|
+
response = requests.get(url, timeout=10)
|
|
322
|
+
response.raise_for_status()
|
|
323
|
+
data = response.json()
|
|
324
|
+
|
|
325
|
+
if not data or len(data) == 0:
|
|
326
|
+
return []
|
|
327
|
+
|
|
328
|
+
country_data = data[0] # Get first match
|
|
329
|
+
data_points = []
|
|
330
|
+
timestamp = time.time()
|
|
331
|
+
|
|
332
|
+
# Map variables to country data fields
|
|
333
|
+
var_mapping = {
|
|
334
|
+
"P": country_data.get("population"),
|
|
335
|
+
"literacy": None, # Not directly available
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for var in variables:
|
|
339
|
+
if var == "P" and var_mapping["P"]:
|
|
340
|
+
dp = DataPoint(
|
|
341
|
+
timestamp=timestamp,
|
|
342
|
+
source="restcountries",
|
|
343
|
+
source_type=DataSourceType.PUBLIC_API,
|
|
344
|
+
variable=var,
|
|
345
|
+
value=float(var_mapping["P"]),
|
|
346
|
+
confidence=0.85,
|
|
347
|
+
)
|
|
348
|
+
data_points.append(dp)
|
|
349
|
+
|
|
350
|
+
return data_points
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.warning(f"Could not fetch REST Countries data: {e}")
|
|
354
|
+
return []
|
|
355
|
+
|
|
356
|
+
def _extract_value_from_response(self, data: Any, variable: str) -> Optional[float]:
|
|
357
|
+
"""
|
|
358
|
+
Extract value for variable from API response.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
data: API response data (dict/list)
|
|
362
|
+
variable: Variable name to extract
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Optional[float]: Extracted value or None
|
|
366
|
+
"""
|
|
367
|
+
# Try common response formats
|
|
368
|
+
if isinstance(data, dict):
|
|
369
|
+
# Try direct key
|
|
370
|
+
if variable in data:
|
|
371
|
+
return data[variable]
|
|
372
|
+
|
|
373
|
+
# Try nested keys
|
|
374
|
+
for key in ["value", "data", "result", "indicator"]:
|
|
375
|
+
if key in data and isinstance(data[key], dict):
|
|
376
|
+
if variable in data[key]:
|
|
377
|
+
return data[key][variable]
|
|
378
|
+
|
|
379
|
+
# Try lowercase/uppercase variants
|
|
380
|
+
for key in data.keys():
|
|
381
|
+
if key.lower() == variable.lower():
|
|
382
|
+
return data[key]
|
|
383
|
+
|
|
384
|
+
elif isinstance(data, list) and len(data) > 0:
|
|
385
|
+
# Try first element
|
|
386
|
+
return self._extract_value_from_response(data[0], variable)
|
|
387
|
+
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
def fetch_database_data(
|
|
391
|
+
self,
|
|
392
|
+
connection_string: str,
|
|
393
|
+
query: str,
|
|
394
|
+
variables: List[str]
|
|
395
|
+
) -> List[DataPoint]:
|
|
396
|
+
"""
|
|
397
|
+
Fetch data from database.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
connection_string: Database connection string
|
|
401
|
+
query: SQL query
|
|
402
|
+
variables: List of variable names
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
List[DataPoint]: List of data points
|
|
406
|
+
"""
|
|
407
|
+
if not SQLALCHEMY_AVAILABLE:
|
|
408
|
+
logger.warning("sqlalchemy not available, returning mock data")
|
|
409
|
+
return self._generate_mock_data(variables, DataSourceType.DATABASE)
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
# Placeholder for actual database query
|
|
413
|
+
logger.debug(f"Fetching from database: {query}")
|
|
414
|
+
return self._generate_mock_data(variables, DataSourceType.DATABASE)
|
|
415
|
+
|
|
416
|
+
except Exception as e:
|
|
417
|
+
logger.error(f"Error fetching database data: {e}")
|
|
418
|
+
return []
|
|
419
|
+
|
|
420
|
+
def should_update(self, source: str) -> bool:
|
|
421
|
+
"""
|
|
422
|
+
Check if data source should be updated based on frequency.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
source: Data source identifier
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
bool: True if should update
|
|
429
|
+
"""
|
|
430
|
+
if source not in self.last_update:
|
|
431
|
+
return True
|
|
432
|
+
|
|
433
|
+
elapsed = time.time() - self.last_update[source]
|
|
434
|
+
return elapsed >= self.update_frequency
|
|
435
|
+
|
|
436
|
+
def _generate_mock_data(
|
|
437
|
+
self,
|
|
438
|
+
variables: List[str],
|
|
439
|
+
source_type: DataSourceType,
|
|
440
|
+
source_name: str = "mock"
|
|
441
|
+
) -> List[DataPoint]:
|
|
442
|
+
"""Generate mock data for testing."""
|
|
443
|
+
data_points = []
|
|
444
|
+
timestamp = time.time()
|
|
445
|
+
|
|
446
|
+
for var in variables:
|
|
447
|
+
# Mock values based on variable type
|
|
448
|
+
if var == "P":
|
|
449
|
+
value = 360000.0 # Population
|
|
450
|
+
elif var == "U":
|
|
451
|
+
value = 0.07 # Unemployment
|
|
452
|
+
elif var == "Y":
|
|
453
|
+
value = 1300000000.0 # GDP
|
|
454
|
+
else:
|
|
455
|
+
value = 1000.0 # Default
|
|
456
|
+
|
|
457
|
+
dp = DataPoint(
|
|
458
|
+
timestamp=timestamp,
|
|
459
|
+
source=source_name,
|
|
460
|
+
source_type=source_type,
|
|
461
|
+
variable=var,
|
|
462
|
+
value=value,
|
|
463
|
+
confidence=0.95,
|
|
464
|
+
)
|
|
465
|
+
data_points.append(dp)
|
|
466
|
+
|
|
467
|
+
return data_points
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class DataPipeline:
|
|
471
|
+
"""
|
|
472
|
+
ETL pipeline for data processing.
|
|
473
|
+
|
|
474
|
+
Handles:
|
|
475
|
+
- Data normalization and standardization
|
|
476
|
+
- Time-series alignment
|
|
477
|
+
- Missing data imputation
|
|
478
|
+
- Data versioning and lineage tracking
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
def __init__(self) -> None:
|
|
482
|
+
"""Initialize data pipeline."""
|
|
483
|
+
self.data_history: Dict[str, List[DataPoint]] = {}
|
|
484
|
+
self.normalization_params: Dict[str, Dict[str, float]] = {}
|
|
485
|
+
|
|
486
|
+
def process_data_points(
|
|
487
|
+
self,
|
|
488
|
+
data_points: List[DataPoint],
|
|
489
|
+
normalize: bool = False
|
|
490
|
+
) -> List[DataPoint]:
|
|
491
|
+
"""
|
|
492
|
+
Process data points (with optional normalization).
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
data_points: Raw data points
|
|
496
|
+
normalize: Whether to normalize values (default: False, use raw values)
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
List[DataPoint]: Processed data points
|
|
500
|
+
"""
|
|
501
|
+
processed = []
|
|
502
|
+
|
|
503
|
+
for dp in data_points:
|
|
504
|
+
# Use raw value by default (normalization was causing issues)
|
|
505
|
+
# Only normalize if explicitly requested
|
|
506
|
+
if normalize:
|
|
507
|
+
processed_value = self._normalize_value(dp.variable, dp.value)
|
|
508
|
+
else:
|
|
509
|
+
processed_value = dp.value # Use raw value
|
|
510
|
+
|
|
511
|
+
# Create processed data point
|
|
512
|
+
processed_dp = DataPoint(
|
|
513
|
+
timestamp=dp.timestamp,
|
|
514
|
+
source=dp.source,
|
|
515
|
+
source_type=dp.source_type,
|
|
516
|
+
variable=dp.variable,
|
|
517
|
+
value=processed_value,
|
|
518
|
+
confidence=dp.confidence,
|
|
519
|
+
metadata={**dp.metadata, "normalized": normalize},
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
processed.append(processed_dp)
|
|
523
|
+
|
|
524
|
+
# Store in history
|
|
525
|
+
if dp.variable not in self.data_history:
|
|
526
|
+
self.data_history[dp.variable] = []
|
|
527
|
+
self.data_history[dp.variable].append(processed_dp)
|
|
528
|
+
|
|
529
|
+
return processed
|
|
530
|
+
|
|
531
|
+
def align_time_series(
|
|
532
|
+
self,
|
|
533
|
+
variables: List[str],
|
|
534
|
+
target_timestamp: float
|
|
535
|
+
) -> Dict[str, float]:
|
|
536
|
+
"""
|
|
537
|
+
Align time series to target timestamp.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
variables: List of variable names
|
|
541
|
+
target_timestamp: Target timestamp
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Dict[str, float]: Aligned values
|
|
545
|
+
"""
|
|
546
|
+
aligned = {}
|
|
547
|
+
|
|
548
|
+
for var in variables:
|
|
549
|
+
if var not in self.data_history:
|
|
550
|
+
continue
|
|
551
|
+
|
|
552
|
+
# Find closest data point to target timestamp
|
|
553
|
+
closest = None
|
|
554
|
+
min_diff = float('inf')
|
|
555
|
+
|
|
556
|
+
for dp in self.data_history[var]:
|
|
557
|
+
diff = abs(dp.timestamp - target_timestamp)
|
|
558
|
+
if diff < min_diff:
|
|
559
|
+
min_diff = diff
|
|
560
|
+
closest = dp
|
|
561
|
+
|
|
562
|
+
if closest and min_diff < 3600: # Within 1 hour
|
|
563
|
+
aligned[var] = closest.value
|
|
564
|
+
else:
|
|
565
|
+
# Impute missing value
|
|
566
|
+
aligned[var] = self._impute_missing(var)
|
|
567
|
+
|
|
568
|
+
return aligned
|
|
569
|
+
|
|
570
|
+
def _normalize_value(self, variable: str, value: float) -> float:
|
|
571
|
+
"""Normalize value based on variable type."""
|
|
572
|
+
if variable not in self.normalization_params:
|
|
573
|
+
# Initialize normalization params
|
|
574
|
+
self.normalization_params[variable] = {
|
|
575
|
+
"mean": value,
|
|
576
|
+
"std": abs(value) * 0.1 if value != 0 else 1.0,
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
# Simple normalization (can be enhanced)
|
|
580
|
+
params = self.normalization_params[variable]
|
|
581
|
+
normalized = (value - params["mean"]) / params["std"] if params["std"] > 0 else value
|
|
582
|
+
|
|
583
|
+
return normalized
|
|
584
|
+
|
|
585
|
+
def _impute_missing(self, variable: str) -> float:
|
|
586
|
+
"""Impute missing value using historical data."""
|
|
587
|
+
if variable not in self.data_history or not self.data_history[variable]:
|
|
588
|
+
return 0.0
|
|
589
|
+
|
|
590
|
+
# Use most recent value
|
|
591
|
+
recent = self.data_history[variable][-1]
|
|
592
|
+
return recent.value
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class RealTimeStateEstimator:
|
|
596
|
+
"""
|
|
597
|
+
Real-time state estimator with EKF/UKF and multi-sensor fusion.
|
|
598
|
+
|
|
599
|
+
Extends the base StateEstimator with real-time capabilities:
|
|
600
|
+
- Daily update scheduling
|
|
601
|
+
- Multi-sensor fusion
|
|
602
|
+
- Anomaly detection
|
|
603
|
+
- Confidence quantification
|
|
604
|
+
"""
|
|
605
|
+
|
|
606
|
+
def __init__(
|
|
607
|
+
self,
|
|
608
|
+
dynamics: DynamicsModel,
|
|
609
|
+
observation_noise_cov: Optional[np.ndarray] = None,
|
|
610
|
+
process_noise_cov: Optional[np.ndarray] = None,
|
|
611
|
+
update_frequency: float = 86400.0, # Daily
|
|
612
|
+
) -> None:
|
|
613
|
+
"""Initialize real-time state estimator."""
|
|
614
|
+
# Use base StateEstimator
|
|
615
|
+
self.base_estimator = StateEstimator(dynamics, observation_noise_cov, process_noise_cov)
|
|
616
|
+
self.update_frequency = update_frequency
|
|
617
|
+
self.last_update_time = 0.0
|
|
618
|
+
self.current_state: Optional[StateVector] = None
|
|
619
|
+
self.state_confidence: float = 0.0
|
|
620
|
+
self.anomaly_threshold = 3.0 # Standard deviations
|
|
621
|
+
|
|
622
|
+
def update_with_data_points(
|
|
623
|
+
self,
|
|
624
|
+
data_points: List[DataPoint],
|
|
625
|
+
u_t: ControlVector
|
|
626
|
+
) -> StateVector:
|
|
627
|
+
"""
|
|
628
|
+
Update state estimate with data points from multiple sources.
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
data_points: List of data points from various sources
|
|
632
|
+
u_t: Current control vector
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
StateVector: Updated state estimate
|
|
636
|
+
"""
|
|
637
|
+
# Convert data points to observation dictionary
|
|
638
|
+
observation = {}
|
|
639
|
+
for dp in data_points:
|
|
640
|
+
observation[dp.variable] = dp.value
|
|
641
|
+
|
|
642
|
+
# Update base estimator
|
|
643
|
+
if self.current_state is None:
|
|
644
|
+
self.current_state = StateVector() # Initialize
|
|
645
|
+
|
|
646
|
+
# Use base estimator update
|
|
647
|
+
updated_state = self.base_estimator.update(observation, u_t)
|
|
648
|
+
|
|
649
|
+
# Compute confidence based on data quality
|
|
650
|
+
self.state_confidence = self._compute_confidence(data_points)
|
|
651
|
+
|
|
652
|
+
# Check for anomalies
|
|
653
|
+
if self._detect_anomaly(updated_state, data_points):
|
|
654
|
+
logger.warning("Anomaly detected in state estimate")
|
|
655
|
+
|
|
656
|
+
self.current_state = updated_state
|
|
657
|
+
self.last_update_time = time.time()
|
|
658
|
+
|
|
659
|
+
return updated_state
|
|
660
|
+
|
|
661
|
+
def should_update(self) -> bool:
|
|
662
|
+
"""Check if state should be updated based on frequency."""
|
|
663
|
+
if self.last_update_time == 0.0:
|
|
664
|
+
return True
|
|
665
|
+
|
|
666
|
+
elapsed = time.time() - self.last_update_time
|
|
667
|
+
return elapsed >= self.update_frequency
|
|
668
|
+
|
|
669
|
+
def get_current_state(self) -> Optional[StateVector]:
|
|
670
|
+
"""Get current state estimate."""
|
|
671
|
+
return self.current_state
|
|
672
|
+
|
|
673
|
+
def get_confidence(self) -> float:
|
|
674
|
+
"""Get state estimate confidence."""
|
|
675
|
+
return self.state_confidence
|
|
676
|
+
|
|
677
|
+
def _compute_confidence(self, data_points: List[DataPoint]) -> float:
|
|
678
|
+
"""Compute confidence based on data quality."""
|
|
679
|
+
if not data_points:
|
|
680
|
+
return 0.0
|
|
681
|
+
|
|
682
|
+
# Average confidence across data points
|
|
683
|
+
avg_confidence = np.mean([dp.confidence for dp in data_points])
|
|
684
|
+
|
|
685
|
+
# Penalize if data is stale
|
|
686
|
+
now = time.time()
|
|
687
|
+
max_age = max([now - dp.timestamp for dp in data_points], default=0)
|
|
688
|
+
age_penalty = max(0, 1.0 - max_age / (2 * self.update_frequency))
|
|
689
|
+
|
|
690
|
+
return avg_confidence * age_penalty
|
|
691
|
+
|
|
692
|
+
def _detect_anomaly(
|
|
693
|
+
self,
|
|
694
|
+
state: StateVector,
|
|
695
|
+
data_points: List[DataPoint]
|
|
696
|
+
) -> bool:
|
|
697
|
+
"""Detect anomalies in state estimate."""
|
|
698
|
+
if self.current_state is None:
|
|
699
|
+
return False
|
|
700
|
+
|
|
701
|
+
# Check for large deviations
|
|
702
|
+
state_dict = state.to_dict()
|
|
703
|
+
prev_dict = self.current_state.to_dict()
|
|
704
|
+
|
|
705
|
+
for key in state_dict:
|
|
706
|
+
if key not in prev_dict:
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
diff = abs(state_dict[key] - prev_dict[key])
|
|
710
|
+
prev_val = abs(prev_dict[key])
|
|
711
|
+
|
|
712
|
+
if prev_val > 0:
|
|
713
|
+
relative_change = diff / prev_val
|
|
714
|
+
if relative_change > 0.5: # 50% change
|
|
715
|
+
return True
|
|
716
|
+
|
|
717
|
+
return False
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class RealTimeMonitor:
|
|
721
|
+
"""
|
|
722
|
+
Real-time monitoring system.
|
|
723
|
+
|
|
724
|
+
Monitors:
|
|
725
|
+
- Continuous state monitoring (daily frequency)
|
|
726
|
+
- Constraint violation detection
|
|
727
|
+
- Performance metrics tracking
|
|
728
|
+
- System health checks
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
def __init__(
|
|
732
|
+
self,
|
|
733
|
+
constraint_checker: ConstraintChecker,
|
|
734
|
+
update_frequency: float = 86400.0, # Daily
|
|
735
|
+
) -> None:
|
|
736
|
+
"""Initialize real-time monitor."""
|
|
737
|
+
self.constraint_checker = constraint_checker
|
|
738
|
+
self.update_frequency = update_frequency
|
|
739
|
+
self.last_check_time = 0.0
|
|
740
|
+
self.violation_history: List[Tuple[float, List[str]]] = []
|
|
741
|
+
self.metrics_history: List[Dict[str, Any]] = []
|
|
742
|
+
|
|
743
|
+
def check_state(
|
|
744
|
+
self,
|
|
745
|
+
x_t: StateVector,
|
|
746
|
+
u_t: ControlVector
|
|
747
|
+
) -> Tuple[bool, List[str], Dict[str, Any]]:
|
|
748
|
+
"""
|
|
749
|
+
Check state and constraints.
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
x_t: Current state
|
|
753
|
+
u_t: Current control
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
Tuple[bool, List[str], Dict[str, Any]]: (is_feasible, violations, metrics)
|
|
757
|
+
"""
|
|
758
|
+
is_feasible, violations = self.constraint_checker.check_feasible(x_t, u_t)
|
|
759
|
+
|
|
760
|
+
# Record violation
|
|
761
|
+
if violations:
|
|
762
|
+
self.violation_history.append((time.time(), violations))
|
|
763
|
+
|
|
764
|
+
# Compute metrics
|
|
765
|
+
metrics = {
|
|
766
|
+
"timestamp": time.time(),
|
|
767
|
+
"feasible": is_feasible,
|
|
768
|
+
"n_violations": len(violations),
|
|
769
|
+
"state": x_t.to_dict(),
|
|
770
|
+
}
|
|
771
|
+
self.metrics_history.append(metrics)
|
|
772
|
+
|
|
773
|
+
self.last_check_time = time.time()
|
|
774
|
+
|
|
775
|
+
return is_feasible, violations, metrics
|
|
776
|
+
|
|
777
|
+
def should_check(self) -> bool:
|
|
778
|
+
"""Check if monitoring should run."""
|
|
779
|
+
if self.last_check_time == 0.0:
|
|
780
|
+
return True
|
|
781
|
+
|
|
782
|
+
elapsed = time.time() - self.last_check_time
|
|
783
|
+
return elapsed >= self.update_frequency
|
|
784
|
+
|
|
785
|
+
def get_recent_violations(self, hours: float = 24.0) -> List[Tuple[float, List[str]]]:
|
|
786
|
+
"""Get violations from last N hours."""
|
|
787
|
+
cutoff = time.time() - hours * 3600
|
|
788
|
+
return [(t, v) for t, v in self.violation_history if t >= cutoff]
|
|
789
|
+
|
|
790
|
+
def get_health_status(self) -> Dict[str, Any]:
|
|
791
|
+
"""Get system health status."""
|
|
792
|
+
recent_violations = self.get_recent_violations(24.0)
|
|
793
|
+
|
|
794
|
+
return {
|
|
795
|
+
"status": "healthy" if len(recent_violations) == 0 else "degraded",
|
|
796
|
+
"n_violations_24h": len(recent_violations),
|
|
797
|
+
"last_check": self.last_check_time,
|
|
798
|
+
"next_check": self.last_check_time + self.update_frequency,
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
# ============================================================================
|
|
803
|
+
# SECTION 2: CONTROL EXECUTION (~600 lines)
|
|
804
|
+
# ============================================================================
|
|
805
|
+
|
|
806
|
+
class PolicyExecutor:
|
|
807
|
+
"""
|
|
808
|
+
Automated policy execution engine.
|
|
809
|
+
|
|
810
|
+
Executes policies via government API integration:
|
|
811
|
+
- Automated budget allocation execution
|
|
812
|
+
- API calls to government systems (treasury, ministries)
|
|
813
|
+
- Transaction processing and verification
|
|
814
|
+
- Execution confirmation and status tracking
|
|
815
|
+
- Rollback capabilities
|
|
816
|
+
- Atomic operations (all-or-nothing)
|
|
817
|
+
"""
|
|
818
|
+
|
|
819
|
+
def __init__(
|
|
820
|
+
self,
|
|
821
|
+
government_api_config: Optional[Dict[str, Any]] = None
|
|
822
|
+
) -> None:
|
|
823
|
+
"""Initialize policy executor."""
|
|
824
|
+
self.government_api_config = government_api_config or {}
|
|
825
|
+
self.execution_history: List[Dict[str, Any]] = []
|
|
826
|
+
self.pending_executions: List[Dict[str, Any]] = []
|
|
827
|
+
|
|
828
|
+
def execute_policy(
|
|
829
|
+
self,
|
|
830
|
+
policy: ControlVector,
|
|
831
|
+
execution_id: Optional[str] = None,
|
|
832
|
+
require_approval: bool = False
|
|
833
|
+
) -> Tuple[bool, str, Dict[str, Any]]:
|
|
834
|
+
"""
|
|
835
|
+
Execute a policy.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
policy: Control vector to execute
|
|
839
|
+
execution_id: Optional execution ID
|
|
840
|
+
require_approval: Whether approval is required (for major changes)
|
|
841
|
+
|
|
842
|
+
Returns:
|
|
843
|
+
Tuple[bool, str, Dict[str, Any]]: (success, message, execution_info)
|
|
844
|
+
"""
|
|
845
|
+
if execution_id is None:
|
|
846
|
+
execution_id = str(uuid.uuid4())
|
|
847
|
+
|
|
848
|
+
if require_approval:
|
|
849
|
+
# Queue for approval
|
|
850
|
+
self.pending_executions.append({
|
|
851
|
+
"execution_id": execution_id,
|
|
852
|
+
"policy": policy,
|
|
853
|
+
"timestamp": time.time(),
|
|
854
|
+
"status": "pending_approval",
|
|
855
|
+
})
|
|
856
|
+
return False, "Pending approval", {"execution_id": execution_id, "status": "pending_approval"}
|
|
857
|
+
|
|
858
|
+
# Execute policy
|
|
859
|
+
try:
|
|
860
|
+
# Validate policy
|
|
861
|
+
is_valid, error = policy.validate_simplex()
|
|
862
|
+
if not is_valid:
|
|
863
|
+
return False, f"Invalid policy: {error}", {}
|
|
864
|
+
|
|
865
|
+
# Execute via government API (placeholder)
|
|
866
|
+
success = self._execute_via_api(policy, execution_id)
|
|
867
|
+
|
|
868
|
+
execution_info = {
|
|
869
|
+
"execution_id": execution_id,
|
|
870
|
+
"policy": policy.to_dict(),
|
|
871
|
+
"timestamp": time.time(),
|
|
872
|
+
"status": "executed" if success else "failed",
|
|
873
|
+
"success": success,
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
self.execution_history.append(execution_info)
|
|
877
|
+
|
|
878
|
+
if success:
|
|
879
|
+
logger.info(f"Policy executed successfully: {execution_id}")
|
|
880
|
+
return True, "Policy executed", execution_info
|
|
881
|
+
else:
|
|
882
|
+
logger.error(f"Policy execution failed: {execution_id}")
|
|
883
|
+
return False, "Execution failed", execution_info
|
|
884
|
+
|
|
885
|
+
except Exception as e:
|
|
886
|
+
logger.error(f"Error executing policy: {e}")
|
|
887
|
+
return False, str(e), {}
|
|
888
|
+
|
|
889
|
+
def _execute_via_api(
|
|
890
|
+
self,
|
|
891
|
+
policy: ControlVector,
|
|
892
|
+
execution_id: str
|
|
893
|
+
) -> bool:
|
|
894
|
+
"""
|
|
895
|
+
Execute policy via government API.
|
|
896
|
+
|
|
897
|
+
Args:
|
|
898
|
+
policy: Policy to execute
|
|
899
|
+
execution_id: Execution ID
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
bool: True if successful
|
|
903
|
+
"""
|
|
904
|
+
# Placeholder for actual API integration
|
|
905
|
+
# Would make API calls to treasury/ministries to allocate budget
|
|
906
|
+
|
|
907
|
+
logger.debug(f"Executing policy {execution_id} via government API")
|
|
908
|
+
|
|
909
|
+
# Simulate API call
|
|
910
|
+
time.sleep(0.1) # Simulate network delay
|
|
911
|
+
|
|
912
|
+
return True
|
|
913
|
+
|
|
914
|
+
def get_execution_status(self, execution_id: str) -> Optional[Dict[str, Any]]:
|
|
915
|
+
"""Get execution status by ID."""
|
|
916
|
+
for exec_info in self.execution_history:
|
|
917
|
+
if exec_info["execution_id"] == execution_id:
|
|
918
|
+
return exec_info
|
|
919
|
+
return None
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
class SafetyInterlocks:
|
|
923
|
+
"""
|
|
924
|
+
Safety interlock system.
|
|
925
|
+
|
|
926
|
+
Implements:
|
|
927
|
+
- Hard limits (never exceed absolute bounds)
|
|
928
|
+
- Rate limiters (max change per period)
|
|
929
|
+
- Automated execution for minor changes (< 10%)
|
|
930
|
+
- Human approval gates for major changes (> 10%)
|
|
931
|
+
- Circuit breakers (auto-stop on anomalies)
|
|
932
|
+
- Emergency stop mechanisms
|
|
933
|
+
"""
|
|
934
|
+
|
|
935
|
+
def __init__(
|
|
936
|
+
self,
|
|
937
|
+
max_budget_change: float = 0.20, # 20% max change per period
|
|
938
|
+
major_change_threshold: float = 0.10, # 10% = major change
|
|
939
|
+
confidence_threshold: float = 0.95, # 95% confidence required
|
|
940
|
+
) -> None:
|
|
941
|
+
"""Initialize safety interlocks."""
|
|
942
|
+
self.max_budget_change = max_budget_change
|
|
943
|
+
self.major_change_threshold = major_change_threshold
|
|
944
|
+
self.confidence_threshold = confidence_threshold
|
|
945
|
+
self.circuit_breaker_active = False
|
|
946
|
+
self.emergency_stop_active = False
|
|
947
|
+
|
|
948
|
+
def check_policy_safety(
|
|
949
|
+
self,
|
|
950
|
+
policy: ControlVector,
|
|
951
|
+
previous_policy: Optional[ControlVector],
|
|
952
|
+
state_confidence: float
|
|
953
|
+
) -> Tuple[bool, str, bool]:
|
|
954
|
+
"""
|
|
955
|
+
Check if policy is safe to execute.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
policy: Proposed policy
|
|
959
|
+
previous_policy: Previous policy (for rate limiting)
|
|
960
|
+
state_confidence: State estimate confidence
|
|
961
|
+
|
|
962
|
+
Returns:
|
|
963
|
+
Tuple[bool, str, bool]: (is_safe, reason, requires_approval)
|
|
964
|
+
"""
|
|
965
|
+
# Check emergency stop
|
|
966
|
+
if self.emergency_stop_active:
|
|
967
|
+
return False, "Emergency stop active", True
|
|
968
|
+
|
|
969
|
+
# Check circuit breaker
|
|
970
|
+
if self.circuit_breaker_active:
|
|
971
|
+
return False, "Circuit breaker active", True
|
|
972
|
+
|
|
973
|
+
# Check confidence threshold
|
|
974
|
+
if state_confidence < self.confidence_threshold:
|
|
975
|
+
return False, f"State confidence {state_confidence:.2f} below threshold {self.confidence_threshold}", True
|
|
976
|
+
|
|
977
|
+
# Check rate limits
|
|
978
|
+
if previous_policy is not None:
|
|
979
|
+
is_valid, reason, requires_approval = self._check_rate_limits(policy, previous_policy)
|
|
980
|
+
if not is_valid:
|
|
981
|
+
return False, reason, requires_approval
|
|
982
|
+
|
|
983
|
+
# Check if major change (requires approval)
|
|
984
|
+
if previous_policy is not None:
|
|
985
|
+
is_major = self._is_major_change(policy, previous_policy)
|
|
986
|
+
if is_major:
|
|
987
|
+
return True, "Major change detected", True # Safe but requires approval
|
|
988
|
+
|
|
989
|
+
# Minor change - can execute automatically
|
|
990
|
+
return True, "Safe to execute", False
|
|
991
|
+
|
|
992
|
+
def _check_rate_limits(
|
|
993
|
+
self,
|
|
994
|
+
policy: ControlVector,
|
|
995
|
+
previous_policy: ControlVector
|
|
996
|
+
) -> Tuple[bool, str, bool]:
|
|
997
|
+
"""Check rate limits."""
|
|
998
|
+
# Compute L1 norm of budget change
|
|
999
|
+
budget_change = {}
|
|
1000
|
+
for cat in set(list(policy.budget_shares.keys()) + list(previous_policy.budget_shares.keys())):
|
|
1001
|
+
prev_val = previous_policy.budget_shares.get(cat, 0.0)
|
|
1002
|
+
curr_val = policy.budget_shares.get(cat, 0.0)
|
|
1003
|
+
budget_change[cat] = abs(curr_val - prev_val)
|
|
1004
|
+
|
|
1005
|
+
total_change = sum(budget_change.values())
|
|
1006
|
+
|
|
1007
|
+
if total_change > self.max_budget_change:
|
|
1008
|
+
return False, f"Budget change {total_change:.2%} exceeds limit {self.max_budget_change:.2%}", True
|
|
1009
|
+
|
|
1010
|
+
return True, "Within rate limits", False
|
|
1011
|
+
|
|
1012
|
+
def _is_major_change(
|
|
1013
|
+
self,
|
|
1014
|
+
policy: ControlVector,
|
|
1015
|
+
previous_policy: ControlVector
|
|
1016
|
+
) -> bool:
|
|
1017
|
+
"""Check if change is major (> 10% in any category)."""
|
|
1018
|
+
for cat in set(list(policy.budget_shares.keys()) + list(previous_policy.budget_shares.keys())):
|
|
1019
|
+
prev_val = previous_policy.budget_shares.get(cat, 0.0)
|
|
1020
|
+
curr_val = policy.budget_shares.get(cat, 0.0)
|
|
1021
|
+
change = abs(curr_val - prev_val)
|
|
1022
|
+
|
|
1023
|
+
if change > self.major_change_threshold:
|
|
1024
|
+
return True
|
|
1025
|
+
|
|
1026
|
+
return False
|
|
1027
|
+
|
|
1028
|
+
def activate_circuit_breaker(self, reason: str) -> None:
|
|
1029
|
+
"""Activate circuit breaker."""
|
|
1030
|
+
self.circuit_breaker_active = True
|
|
1031
|
+
logger.warning(f"Circuit breaker activated: {reason}")
|
|
1032
|
+
|
|
1033
|
+
def deactivate_circuit_breaker(self) -> None:
|
|
1034
|
+
"""Deactivate circuit breaker."""
|
|
1035
|
+
self.circuit_breaker_active = False
|
|
1036
|
+
logger.info("Circuit breaker deactivated")
|
|
1037
|
+
|
|
1038
|
+
def emergency_stop(self) -> None:
|
|
1039
|
+
"""Activate emergency stop."""
|
|
1040
|
+
self.emergency_stop_active = True
|
|
1041
|
+
logger.critical("EMERGENCY STOP ACTIVATED")
|
|
1042
|
+
|
|
1043
|
+
def resume(self) -> None:
|
|
1044
|
+
"""Resume after emergency stop."""
|
|
1045
|
+
self.emergency_stop_active = False
|
|
1046
|
+
logger.info("System resumed after emergency stop")
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
class ControlInterface:
|
|
1050
|
+
"""
|
|
1051
|
+
Control interface for policy commands.
|
|
1052
|
+
|
|
1053
|
+
Provides:
|
|
1054
|
+
- REST API for policy commands
|
|
1055
|
+
- WebSocket for real-time updates
|
|
1056
|
+
- Command queue and priority system
|
|
1057
|
+
- Command validation and authorization
|
|
1058
|
+
- Audit logging of all commands
|
|
1059
|
+
"""
|
|
1060
|
+
|
|
1061
|
+
def __init__(self) -> None:
|
|
1062
|
+
"""Initialize control interface."""
|
|
1063
|
+
self.command_queue: List[Dict[str, Any]] = []
|
|
1064
|
+
self.command_history: List[Dict[str, Any]] = []
|
|
1065
|
+
self.authorized_users: Dict[str, Dict[str, Any]] = {}
|
|
1066
|
+
|
|
1067
|
+
def queue_command(
|
|
1068
|
+
self,
|
|
1069
|
+
command: Dict[str, Any],
|
|
1070
|
+
user_id: str,
|
|
1071
|
+
priority: int = 0
|
|
1072
|
+
) -> str:
|
|
1073
|
+
"""
|
|
1074
|
+
Queue a command for execution.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
command: Command dictionary
|
|
1078
|
+
user_id: User ID
|
|
1079
|
+
priority: Priority (higher = more urgent)
|
|
1080
|
+
|
|
1081
|
+
Returns:
|
|
1082
|
+
str: Command ID
|
|
1083
|
+
"""
|
|
1084
|
+
command_id = str(uuid.uuid4())
|
|
1085
|
+
|
|
1086
|
+
queued_command = {
|
|
1087
|
+
"command_id": command_id,
|
|
1088
|
+
"command": command,
|
|
1089
|
+
"user_id": user_id,
|
|
1090
|
+
"priority": priority,
|
|
1091
|
+
"timestamp": time.time(),
|
|
1092
|
+
"status": "queued",
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
# Insert by priority
|
|
1096
|
+
inserted = False
|
|
1097
|
+
for i, cmd in enumerate(self.command_queue):
|
|
1098
|
+
if priority > cmd.get("priority", 0):
|
|
1099
|
+
self.command_queue.insert(i, queued_command)
|
|
1100
|
+
inserted = True
|
|
1101
|
+
break
|
|
1102
|
+
|
|
1103
|
+
if not inserted:
|
|
1104
|
+
self.command_queue.append(queued_command)
|
|
1105
|
+
|
|
1106
|
+
logger.info(f"Command queued: {command_id} by {user_id}")
|
|
1107
|
+
return command_id
|
|
1108
|
+
|
|
1109
|
+
def authorize_user(
|
|
1110
|
+
self,
|
|
1111
|
+
user_id: str,
|
|
1112
|
+
permissions: List[str]
|
|
1113
|
+
) -> None:
|
|
1114
|
+
"""Authorize a user with permissions."""
|
|
1115
|
+
self.authorized_users[user_id] = {
|
|
1116
|
+
"permissions": permissions,
|
|
1117
|
+
"authorized_at": time.time(),
|
|
1118
|
+
}
|
|
1119
|
+
logger.info(f"User authorized: {user_id} with permissions: {permissions}")
|
|
1120
|
+
|
|
1121
|
+
def validate_command(
|
|
1122
|
+
self,
|
|
1123
|
+
command: Dict[str, Any],
|
|
1124
|
+
user_id: str
|
|
1125
|
+
) -> Tuple[bool, str]:
|
|
1126
|
+
"""Validate command and user authorization."""
|
|
1127
|
+
if user_id not in self.authorized_users:
|
|
1128
|
+
return False, "User not authorized"
|
|
1129
|
+
|
|
1130
|
+
# Check required fields
|
|
1131
|
+
if "action" not in command:
|
|
1132
|
+
return False, "Command missing 'action' field"
|
|
1133
|
+
|
|
1134
|
+
return True, "Valid"
|
|
1135
|
+
|
|
1136
|
+
def get_next_command(self) -> Optional[Dict[str, Any]]:
|
|
1137
|
+
"""Get next command from queue."""
|
|
1138
|
+
if not self.command_queue:
|
|
1139
|
+
return None
|
|
1140
|
+
|
|
1141
|
+
return self.command_queue.pop(0)
|
|
1142
|
+
|
|
1143
|
+
def log_command(
|
|
1144
|
+
self,
|
|
1145
|
+
command_id: str,
|
|
1146
|
+
status: str,
|
|
1147
|
+
result: Optional[Dict[str, Any]] = None
|
|
1148
|
+
) -> None:
|
|
1149
|
+
"""Log command execution."""
|
|
1150
|
+
log_entry = {
|
|
1151
|
+
"command_id": command_id,
|
|
1152
|
+
"status": status,
|
|
1153
|
+
"result": result,
|
|
1154
|
+
"timestamp": time.time(),
|
|
1155
|
+
}
|
|
1156
|
+
self.command_history.append(log_entry)
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
# ============================================================================
|
|
1160
|
+
# SECTION 3: MONITORING & COMPLIANCE (~500 lines)
|
|
1161
|
+
# ============================================================================
|
|
1162
|
+
|
|
1163
|
+
class AlertLevel(str, Enum):
|
|
1164
|
+
"""Alert levels."""
|
|
1165
|
+
INFO = "info"
|
|
1166
|
+
WARNING = "warning"
|
|
1167
|
+
CRITICAL = "critical"
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
class AlertingSystem:
|
|
1171
|
+
"""
|
|
1172
|
+
Alerting system for real-time monitoring.
|
|
1173
|
+
|
|
1174
|
+
Features:
|
|
1175
|
+
- Multi-level alerts (info, warning, critical)
|
|
1176
|
+
- Alert routing (email, SMS, dashboard, API)
|
|
1177
|
+
- Alert escalation (unacknowledged alerts escalate)
|
|
1178
|
+
- Alert suppression (avoid alert fatigue)
|
|
1179
|
+
- Alert correlation (group related alerts)
|
|
1180
|
+
"""
|
|
1181
|
+
|
|
1182
|
+
def __init__(self) -> None:
|
|
1183
|
+
"""Initialize alerting system."""
|
|
1184
|
+
self.alerts: List[Dict[str, Any]] = []
|
|
1185
|
+
self.alert_routes: Dict[AlertLevel, List[str]] = {
|
|
1186
|
+
AlertLevel.INFO: ["dashboard"],
|
|
1187
|
+
AlertLevel.WARNING: ["dashboard", "email"],
|
|
1188
|
+
AlertLevel.CRITICAL: ["dashboard", "email", "sms"],
|
|
1189
|
+
}
|
|
1190
|
+
self.suppressed_alerts: Dict[str, float] = {} # alert_key -> suppress_until
|
|
1191
|
+
|
|
1192
|
+
def create_alert(
|
|
1193
|
+
self,
|
|
1194
|
+
level: AlertLevel,
|
|
1195
|
+
message: str,
|
|
1196
|
+
source: str,
|
|
1197
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
1198
|
+
) -> str:
|
|
1199
|
+
"""
|
|
1200
|
+
Create an alert.
|
|
1201
|
+
|
|
1202
|
+
Args:
|
|
1203
|
+
level: Alert level
|
|
1204
|
+
message: Alert message
|
|
1205
|
+
source: Alert source
|
|
1206
|
+
metadata: Additional metadata
|
|
1207
|
+
|
|
1208
|
+
Returns:
|
|
1209
|
+
str: Alert ID
|
|
1210
|
+
"""
|
|
1211
|
+
alert_id = str(uuid.uuid4())
|
|
1212
|
+
alert_key = f"{source}:{message[:50]}"
|
|
1213
|
+
|
|
1214
|
+
# Check if suppressed
|
|
1215
|
+
if alert_key in self.suppressed_alerts:
|
|
1216
|
+
if time.time() < self.suppressed_alerts[alert_key]:
|
|
1217
|
+
logger.debug(f"Alert suppressed: {alert_key}")
|
|
1218
|
+
return alert_id
|
|
1219
|
+
|
|
1220
|
+
alert = {
|
|
1221
|
+
"alert_id": alert_id,
|
|
1222
|
+
"level": level.value,
|
|
1223
|
+
"message": message,
|
|
1224
|
+
"source": source,
|
|
1225
|
+
"timestamp": time.time(),
|
|
1226
|
+
"acknowledged": False,
|
|
1227
|
+
"metadata": metadata or {},
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
self.alerts.append(alert)
|
|
1231
|
+
|
|
1232
|
+
# Route alert
|
|
1233
|
+
self._route_alert(alert)
|
|
1234
|
+
|
|
1235
|
+
logger.log(
|
|
1236
|
+
"INFO" if level == AlertLevel.INFO else "WARNING" if level == AlertLevel.WARNING else "ERROR",
|
|
1237
|
+
f"Alert [{level.value}]: {message}"
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
return alert_id
|
|
1241
|
+
|
|
1242
|
+
def _route_alert(self, alert: Dict[str, Any]) -> None:
|
|
1243
|
+
"""Route alert to appropriate channels."""
|
|
1244
|
+
level = AlertLevel(alert["level"])
|
|
1245
|
+
routes = self.alert_routes.get(level, [])
|
|
1246
|
+
|
|
1247
|
+
for route in routes:
|
|
1248
|
+
if route == "dashboard":
|
|
1249
|
+
# Would send to dashboard
|
|
1250
|
+
pass
|
|
1251
|
+
elif route == "email":
|
|
1252
|
+
# Would send email
|
|
1253
|
+
logger.debug(f"Would send email alert: {alert['message']}")
|
|
1254
|
+
elif route == "sms":
|
|
1255
|
+
# Would send SMS
|
|
1256
|
+
logger.debug(f"Would send SMS alert: {alert['message']}")
|
|
1257
|
+
|
|
1258
|
+
def acknowledge_alert(self, alert_id: str) -> bool:
|
|
1259
|
+
"""Acknowledge an alert."""
|
|
1260
|
+
for alert in self.alerts:
|
|
1261
|
+
if alert["alert_id"] == alert_id:
|
|
1262
|
+
alert["acknowledged"] = True
|
|
1263
|
+
return True
|
|
1264
|
+
return False
|
|
1265
|
+
|
|
1266
|
+
def get_unacknowledged_alerts(self) -> List[Dict[str, Any]]:
|
|
1267
|
+
"""Get unacknowledged alerts."""
|
|
1268
|
+
return [a for a in self.alerts if not a["acknowledged"]]
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
class ComplianceSystem:
|
|
1272
|
+
"""
|
|
1273
|
+
Regulatory compliance system.
|
|
1274
|
+
|
|
1275
|
+
Implements:
|
|
1276
|
+
- Audit trail logging (who, what, when, why)
|
|
1277
|
+
- Regulatory reporting (GDPR, financial regulations)
|
|
1278
|
+
- Data retention policies
|
|
1279
|
+
- Access control and authorization
|
|
1280
|
+
- Change management (version control for policies)
|
|
1281
|
+
"""
|
|
1282
|
+
|
|
1283
|
+
def __init__(self, retention_days: int = 2555) -> None: # 7 years default
|
|
1284
|
+
"""Initialize compliance system."""
|
|
1285
|
+
self.audit_log: List[Dict[str, Any]] = []
|
|
1286
|
+
self.retention_days = retention_days
|
|
1287
|
+
self.access_log: List[Dict[str, Any]] = []
|
|
1288
|
+
|
|
1289
|
+
def log_decision(
|
|
1290
|
+
self,
|
|
1291
|
+
decision_type: str,
|
|
1292
|
+
decision: Dict[str, Any],
|
|
1293
|
+
user_id: str,
|
|
1294
|
+
reason: str,
|
|
1295
|
+
approved_by: Optional[List[str]] = None
|
|
1296
|
+
) -> str:
|
|
1297
|
+
"""
|
|
1298
|
+
Log a decision for audit trail.
|
|
1299
|
+
|
|
1300
|
+
Args:
|
|
1301
|
+
decision_type: Type of decision (policy_execution, approval, etc.)
|
|
1302
|
+
decision: Decision details
|
|
1303
|
+
user_id: User who made/initiated decision
|
|
1304
|
+
reason: Reason for decision
|
|
1305
|
+
approved_by: List of user IDs who approved (if applicable)
|
|
1306
|
+
|
|
1307
|
+
Returns:
|
|
1308
|
+
str: Audit log entry ID
|
|
1309
|
+
"""
|
|
1310
|
+
log_id = str(uuid.uuid4())
|
|
1311
|
+
|
|
1312
|
+
log_entry = {
|
|
1313
|
+
"log_id": log_id,
|
|
1314
|
+
"timestamp": time.time(),
|
|
1315
|
+
"decision_type": decision_type,
|
|
1316
|
+
"decision": decision,
|
|
1317
|
+
"user_id": user_id,
|
|
1318
|
+
"reason": reason,
|
|
1319
|
+
"approved_by": approved_by or [],
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
self.audit_log.append(log_entry)
|
|
1323
|
+
|
|
1324
|
+
logger.info(f"Audit log: {decision_type} by {user_id}: {reason}")
|
|
1325
|
+
|
|
1326
|
+
return log_id
|
|
1327
|
+
|
|
1328
|
+
def log_access(
|
|
1329
|
+
self,
|
|
1330
|
+
user_id: str,
|
|
1331
|
+
action: str,
|
|
1332
|
+
resource: str,
|
|
1333
|
+
success: bool
|
|
1334
|
+
) -> None:
|
|
1335
|
+
"""Log access attempt."""
|
|
1336
|
+
access_entry = {
|
|
1337
|
+
"timestamp": time.time(),
|
|
1338
|
+
"user_id": user_id,
|
|
1339
|
+
"action": action,
|
|
1340
|
+
"resource": resource,
|
|
1341
|
+
"success": success,
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
self.access_log.append(access_entry)
|
|
1345
|
+
|
|
1346
|
+
def get_audit_trail(
|
|
1347
|
+
self,
|
|
1348
|
+
start_time: Optional[float] = None,
|
|
1349
|
+
end_time: Optional[float] = None,
|
|
1350
|
+
user_id: Optional[str] = None
|
|
1351
|
+
) -> List[Dict[str, Any]]:
|
|
1352
|
+
"""Get audit trail with filters."""
|
|
1353
|
+
filtered = self.audit_log
|
|
1354
|
+
|
|
1355
|
+
if start_time:
|
|
1356
|
+
filtered = [e for e in filtered if e["timestamp"] >= start_time]
|
|
1357
|
+
|
|
1358
|
+
if end_time:
|
|
1359
|
+
filtered = [e for e in filtered if e["timestamp"] <= end_time]
|
|
1360
|
+
|
|
1361
|
+
if user_id:
|
|
1362
|
+
filtered = [e for e in filtered if e["user_id"] == user_id]
|
|
1363
|
+
|
|
1364
|
+
return filtered
|
|
1365
|
+
|
|
1366
|
+
def cleanup_old_logs(self) -> int:
|
|
1367
|
+
"""Clean up logs older than retention period."""
|
|
1368
|
+
cutoff = time.time() - self.retention_days * 86400
|
|
1369
|
+
|
|
1370
|
+
initial_count = len(self.audit_log)
|
|
1371
|
+
self.audit_log = [e for e in self.audit_log if e["timestamp"] >= cutoff]
|
|
1372
|
+
|
|
1373
|
+
removed = initial_count - len(self.audit_log)
|
|
1374
|
+
logger.info(f"Cleaned up {removed} old audit log entries")
|
|
1375
|
+
|
|
1376
|
+
return removed
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
class AccountabilitySystem:
|
|
1380
|
+
"""
|
|
1381
|
+
Accountability system for decision attribution.
|
|
1382
|
+
|
|
1383
|
+
Tracks:
|
|
1384
|
+
- Decision attribution (which board/person approved)
|
|
1385
|
+
- Performance attribution (outcomes linked to decisions)
|
|
1386
|
+
- Blame assignment (who is responsible for failures)
|
|
1387
|
+
- Transparency reports
|
|
1388
|
+
"""
|
|
1389
|
+
|
|
1390
|
+
def __init__(self) -> None:
|
|
1391
|
+
"""Initialize accountability system."""
|
|
1392
|
+
self.decision_attribution: Dict[str, Dict[str, Any]] = {}
|
|
1393
|
+
self.performance_tracking: List[Dict[str, Any]] = []
|
|
1394
|
+
|
|
1395
|
+
def attribute_decision(
|
|
1396
|
+
self,
|
|
1397
|
+
decision_id: str,
|
|
1398
|
+
policy: ControlVector,
|
|
1399
|
+
approved_by: List[str],
|
|
1400
|
+
board_votes: Optional[Dict[str, str]] = None
|
|
1401
|
+
) -> None:
|
|
1402
|
+
"""
|
|
1403
|
+
Attribute a decision to approvers.
|
|
1404
|
+
|
|
1405
|
+
Args:
|
|
1406
|
+
decision_id: Decision ID
|
|
1407
|
+
policy: Policy that was approved
|
|
1408
|
+
approved_by: List of user IDs who approved
|
|
1409
|
+
board_votes: Board voting results (optional)
|
|
1410
|
+
"""
|
|
1411
|
+
self.decision_attribution[decision_id] = {
|
|
1412
|
+
"decision_id": decision_id,
|
|
1413
|
+
"policy": policy.to_dict(),
|
|
1414
|
+
"approved_by": approved_by,
|
|
1415
|
+
"board_votes": board_votes or {},
|
|
1416
|
+
"timestamp": time.time(),
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
def track_performance(
|
|
1420
|
+
self,
|
|
1421
|
+
decision_id: str,
|
|
1422
|
+
expected_outcomes: Dict[str, float],
|
|
1423
|
+
actual_outcomes: Dict[str, float],
|
|
1424
|
+
timestamp: float
|
|
1425
|
+
) -> None:
|
|
1426
|
+
"""Track performance of a decision."""
|
|
1427
|
+
performance_entry = {
|
|
1428
|
+
"decision_id": decision_id,
|
|
1429
|
+
"expected_outcomes": expected_outcomes,
|
|
1430
|
+
"actual_outcomes": actual_outcomes,
|
|
1431
|
+
"timestamp": timestamp,
|
|
1432
|
+
"performance_score": self._compute_performance_score(expected_outcomes, actual_outcomes),
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
self.performance_tracking.append(performance_entry)
|
|
1436
|
+
|
|
1437
|
+
def _compute_performance_score(
|
|
1438
|
+
self,
|
|
1439
|
+
expected: Dict[str, float],
|
|
1440
|
+
actual: Dict[str, float]
|
|
1441
|
+
) -> float:
|
|
1442
|
+
"""Compute performance score (0-1, higher is better)."""
|
|
1443
|
+
if not expected:
|
|
1444
|
+
return 0.5
|
|
1445
|
+
|
|
1446
|
+
errors = []
|
|
1447
|
+
for key in expected:
|
|
1448
|
+
if key in actual:
|
|
1449
|
+
error = abs(expected[key] - actual[key]) / (abs(expected[key]) + 1e-6)
|
|
1450
|
+
errors.append(error)
|
|
1451
|
+
|
|
1452
|
+
if not errors:
|
|
1453
|
+
return 0.5
|
|
1454
|
+
|
|
1455
|
+
avg_error = np.mean(errors)
|
|
1456
|
+
score = max(0.0, 1.0 - avg_error) # Lower error = higher score
|
|
1457
|
+
|
|
1458
|
+
return score
|
|
1459
|
+
|
|
1460
|
+
def generate_transparency_report(
|
|
1461
|
+
self,
|
|
1462
|
+
start_time: Optional[float] = None,
|
|
1463
|
+
end_time: Optional[float] = None
|
|
1464
|
+
) -> Dict[str, Any]:
|
|
1465
|
+
"""Generate transparency report."""
|
|
1466
|
+
if start_time is None:
|
|
1467
|
+
start_time = time.time() - 30 * 86400 # Last 30 days
|
|
1468
|
+
|
|
1469
|
+
if end_time is None:
|
|
1470
|
+
end_time = time.time()
|
|
1471
|
+
|
|
1472
|
+
# Filter decisions in time range
|
|
1473
|
+
decisions = [
|
|
1474
|
+
d for d in self.decision_attribution.values()
|
|
1475
|
+
if start_time <= d["timestamp"] <= end_time
|
|
1476
|
+
]
|
|
1477
|
+
|
|
1478
|
+
# Filter performance in time range
|
|
1479
|
+
performance = [
|
|
1480
|
+
p for p in self.performance_tracking
|
|
1481
|
+
if start_time <= p["timestamp"] <= end_time
|
|
1482
|
+
]
|
|
1483
|
+
|
|
1484
|
+
report = {
|
|
1485
|
+
"period": {
|
|
1486
|
+
"start": start_time,
|
|
1487
|
+
"end": end_time,
|
|
1488
|
+
},
|
|
1489
|
+
"n_decisions": len(decisions),
|
|
1490
|
+
"n_performance_tracked": len(performance),
|
|
1491
|
+
"avg_performance_score": np.mean([p["performance_score"] for p in performance]) if performance else 0.0,
|
|
1492
|
+
"decisions": decisions,
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return report
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
class RollbackSystem:
|
|
1499
|
+
"""
|
|
1500
|
+
Rollback system for state and policy recovery.
|
|
1501
|
+
|
|
1502
|
+
Features:
|
|
1503
|
+
- State snapshots (periodic checkpoints)
|
|
1504
|
+
- Policy rollback (undo last N policies)
|
|
1505
|
+
- State restoration (revert to previous state)
|
|
1506
|
+
- Transaction logs (for audit and recovery)
|
|
1507
|
+
- 7-day rollback window
|
|
1508
|
+
"""
|
|
1509
|
+
|
|
1510
|
+
def __init__(self, rollback_window_days: int = 7) -> None:
|
|
1511
|
+
"""Initialize rollback system."""
|
|
1512
|
+
self.rollback_window_days = rollback_window_days
|
|
1513
|
+
self.state_snapshots: List[Tuple[float, StateVector]] = [] # (timestamp, state)
|
|
1514
|
+
self.policy_history: List[Tuple[float, ControlVector, str]] = [] # (timestamp, policy, execution_id)
|
|
1515
|
+
self.transaction_log: List[Dict[str, Any]] = []
|
|
1516
|
+
|
|
1517
|
+
def create_snapshot(
|
|
1518
|
+
self,
|
|
1519
|
+
state: StateVector,
|
|
1520
|
+
snapshot_id: Optional[str] = None
|
|
1521
|
+
) -> str:
|
|
1522
|
+
"""
|
|
1523
|
+
Create a state snapshot.
|
|
1524
|
+
|
|
1525
|
+
Args:
|
|
1526
|
+
state: State to snapshot
|
|
1527
|
+
snapshot_id: Optional snapshot ID
|
|
1528
|
+
|
|
1529
|
+
Returns:
|
|
1530
|
+
str: Snapshot ID
|
|
1531
|
+
"""
|
|
1532
|
+
if snapshot_id is None:
|
|
1533
|
+
snapshot_id = str(uuid.uuid4())
|
|
1534
|
+
|
|
1535
|
+
timestamp = time.time()
|
|
1536
|
+
self.state_snapshots.append((timestamp, state.copy()))
|
|
1537
|
+
|
|
1538
|
+
# Clean up old snapshots outside rollback window
|
|
1539
|
+
cutoff = timestamp - self.rollback_window_days * 86400
|
|
1540
|
+
self.state_snapshots = [(t, s) for t, s in self.state_snapshots if t >= cutoff]
|
|
1541
|
+
|
|
1542
|
+
logger.info(f"State snapshot created: {snapshot_id}")
|
|
1543
|
+
|
|
1544
|
+
return snapshot_id
|
|
1545
|
+
|
|
1546
|
+
def record_policy_execution(
|
|
1547
|
+
self,
|
|
1548
|
+
policy: ControlVector,
|
|
1549
|
+
execution_id: str
|
|
1550
|
+
) -> None:
|
|
1551
|
+
"""Record policy execution for rollback."""
|
|
1552
|
+
timestamp = time.time()
|
|
1553
|
+
self.policy_history.append((timestamp, policy, execution_id))
|
|
1554
|
+
|
|
1555
|
+
# Clean up old history
|
|
1556
|
+
cutoff = timestamp - self.rollback_window_days * 86400
|
|
1557
|
+
self.policy_history = [(t, p, eid) for t, p, eid in self.policy_history if t >= cutoff]
|
|
1558
|
+
|
|
1559
|
+
def rollback_policies(
|
|
1560
|
+
self,
|
|
1561
|
+
n_policies: int
|
|
1562
|
+
) -> List[str]:
|
|
1563
|
+
"""
|
|
1564
|
+
Rollback last N policies.
|
|
1565
|
+
|
|
1566
|
+
Args:
|
|
1567
|
+
n_policies: Number of policies to rollback
|
|
1568
|
+
|
|
1569
|
+
Returns:
|
|
1570
|
+
List[str]: List of execution IDs rolled back
|
|
1571
|
+
"""
|
|
1572
|
+
if n_policies > len(self.policy_history):
|
|
1573
|
+
n_policies = len(self.policy_history)
|
|
1574
|
+
|
|
1575
|
+
rolled_back = []
|
|
1576
|
+
for _ in range(n_policies):
|
|
1577
|
+
if self.policy_history:
|
|
1578
|
+
timestamp, policy, execution_id = self.policy_history.pop()
|
|
1579
|
+
rolled_back.append(execution_id)
|
|
1580
|
+
logger.info(f"Rolled back policy: {execution_id}")
|
|
1581
|
+
|
|
1582
|
+
return rolled_back
|
|
1583
|
+
|
|
1584
|
+
def restore_state(
|
|
1585
|
+
self,
|
|
1586
|
+
snapshot_id: Optional[str] = None,
|
|
1587
|
+
timestamp: Optional[float] = None
|
|
1588
|
+
) -> Optional[StateVector]:
|
|
1589
|
+
"""
|
|
1590
|
+
Restore state from snapshot.
|
|
1591
|
+
|
|
1592
|
+
Args:
|
|
1593
|
+
snapshot_id: Snapshot ID (if provided, use this)
|
|
1594
|
+
timestamp: Timestamp to restore to (if provided, find closest)
|
|
1595
|
+
|
|
1596
|
+
Returns:
|
|
1597
|
+
Optional[StateVector]: Restored state or None
|
|
1598
|
+
"""
|
|
1599
|
+
if timestamp is not None:
|
|
1600
|
+
# Find closest snapshot to timestamp
|
|
1601
|
+
closest = None
|
|
1602
|
+
min_diff = float('inf')
|
|
1603
|
+
|
|
1604
|
+
for snap_timestamp, state in self.state_snapshots:
|
|
1605
|
+
diff = abs(snap_timestamp - timestamp)
|
|
1606
|
+
if diff < min_diff:
|
|
1607
|
+
min_diff = diff
|
|
1608
|
+
closest = state
|
|
1609
|
+
|
|
1610
|
+
if closest and min_diff < 3600: # Within 1 hour
|
|
1611
|
+
logger.info(f"Restored state from timestamp: {timestamp}")
|
|
1612
|
+
return closest.copy()
|
|
1613
|
+
|
|
1614
|
+
# Use most recent snapshot
|
|
1615
|
+
if self.state_snapshots:
|
|
1616
|
+
_, state = self.state_snapshots[-1]
|
|
1617
|
+
logger.info("Restored state from most recent snapshot")
|
|
1618
|
+
return state.copy()
|
|
1619
|
+
|
|
1620
|
+
return None
|
|
1621
|
+
|
|
1622
|
+
def get_rollback_window(self) -> float:
|
|
1623
|
+
"""Get rollback window in seconds."""
|
|
1624
|
+
return self.rollback_window_days * 86400
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
# ============================================================================
|
|
1628
|
+
# SECTION 4: RESILIENCE & LEARNING (~400 lines)
|
|
1629
|
+
# ============================================================================
|
|
1630
|
+
|
|
1631
|
+
class ModelAdaptation:
|
|
1632
|
+
"""
|
|
1633
|
+
Model adaptation for online learning.
|
|
1634
|
+
|
|
1635
|
+
Features:
|
|
1636
|
+
- Online learning (update dynamics model from observations)
|
|
1637
|
+
- Parameter estimation (Bayesian updates)
|
|
1638
|
+
- Model validation (compare predictions to outcomes)
|
|
1639
|
+
- Model versioning (A/B testing of models)
|
|
1640
|
+
- Drift detection (detect when model becomes stale)
|
|
1641
|
+
"""
|
|
1642
|
+
|
|
1643
|
+
def __init__(self, dynamics: DynamicsModel) -> None:
|
|
1644
|
+
"""Initialize model adaptation."""
|
|
1645
|
+
self.dynamics = dynamics
|
|
1646
|
+
self.parameter_history: List[Dict[str, float]] = []
|
|
1647
|
+
self.prediction_errors: List[float] = []
|
|
1648
|
+
self.drift_threshold = 0.1 # 10% error increase
|
|
1649
|
+
|
|
1650
|
+
def update_parameters(
|
|
1651
|
+
self,
|
|
1652
|
+
observed_state: StateVector,
|
|
1653
|
+
predicted_state: StateVector,
|
|
1654
|
+
control: ControlVector
|
|
1655
|
+
) -> Dict[str, float]:
|
|
1656
|
+
"""
|
|
1657
|
+
Update model parameters based on observation.
|
|
1658
|
+
|
|
1659
|
+
Args:
|
|
1660
|
+
observed_state: Actual observed state
|
|
1661
|
+
predicted_state: Model-predicted state
|
|
1662
|
+
control: Control that was applied
|
|
1663
|
+
|
|
1664
|
+
Returns:
|
|
1665
|
+
Dict[str, float]: Updated parameter values
|
|
1666
|
+
"""
|
|
1667
|
+
# Compute prediction error
|
|
1668
|
+
error = self._compute_prediction_error(observed_state, predicted_state)
|
|
1669
|
+
self.prediction_errors.append(error)
|
|
1670
|
+
|
|
1671
|
+
# Simple parameter update (placeholder for Bayesian update)
|
|
1672
|
+
# In practice, would use more sophisticated methods
|
|
1673
|
+
|
|
1674
|
+
updated_params = {
|
|
1675
|
+
"delta_K": self.dynamics.delta_K,
|
|
1676
|
+
"delta_I": self.dynamics.delta_I,
|
|
1677
|
+
"alpha": self.dynamics.alpha,
|
|
1678
|
+
# ... other parameters
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
self.parameter_history.append(updated_params)
|
|
1682
|
+
|
|
1683
|
+
logger.debug(f"Model parameters updated, prediction error: {error:.4f}")
|
|
1684
|
+
|
|
1685
|
+
return updated_params
|
|
1686
|
+
|
|
1687
|
+
def _compute_prediction_error(
|
|
1688
|
+
self,
|
|
1689
|
+
observed: StateVector,
|
|
1690
|
+
predicted: StateVector
|
|
1691
|
+
) -> float:
|
|
1692
|
+
"""Compute prediction error."""
|
|
1693
|
+
obs_dict = observed.to_dict()
|
|
1694
|
+
pred_dict = predicted.to_dict()
|
|
1695
|
+
|
|
1696
|
+
errors = []
|
|
1697
|
+
for key in obs_dict:
|
|
1698
|
+
if key in pred_dict:
|
|
1699
|
+
error = abs(obs_dict[key] - pred_dict[key]) / (abs(obs_dict[key]) + 1e-6)
|
|
1700
|
+
errors.append(error)
|
|
1701
|
+
|
|
1702
|
+
return np.mean(errors) if errors else 0.0
|
|
1703
|
+
|
|
1704
|
+
def detect_drift(self) -> bool:
|
|
1705
|
+
"""Detect if model has drifted."""
|
|
1706
|
+
if len(self.prediction_errors) < 10:
|
|
1707
|
+
return False
|
|
1708
|
+
|
|
1709
|
+
# Compare recent errors to historical average
|
|
1710
|
+
recent_errors = self.prediction_errors[-10:]
|
|
1711
|
+
historical_errors = self.prediction_errors[:-10] if len(self.prediction_errors) > 10 else recent_errors
|
|
1712
|
+
|
|
1713
|
+
recent_avg = np.mean(recent_errors)
|
|
1714
|
+
historical_avg = np.mean(historical_errors)
|
|
1715
|
+
|
|
1716
|
+
if historical_avg > 0:
|
|
1717
|
+
increase = (recent_avg - historical_avg) / historical_avg
|
|
1718
|
+
if increase > self.drift_threshold:
|
|
1719
|
+
logger.warning(f"Model drift detected: {increase:.2%} error increase")
|
|
1720
|
+
return True
|
|
1721
|
+
|
|
1722
|
+
return False
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
class PerformanceFeedback:
|
|
1726
|
+
"""
|
|
1727
|
+
Performance feedback system.
|
|
1728
|
+
|
|
1729
|
+
Tracks:
|
|
1730
|
+
- Outcome measurement (actual vs. predicted)
|
|
1731
|
+
- Policy effectiveness evaluation
|
|
1732
|
+
- Board performance tracking
|
|
1733
|
+
- Scenario accuracy assessment
|
|
1734
|
+
- Continuous improvement loop
|
|
1735
|
+
"""
|
|
1736
|
+
|
|
1737
|
+
def __init__(self) -> None:
|
|
1738
|
+
"""Initialize performance feedback."""
|
|
1739
|
+
self.outcome_comparisons: List[Dict[str, Any]] = []
|
|
1740
|
+
self.policy_effectiveness: Dict[str, float] = {}
|
|
1741
|
+
self.board_performance: Dict[str, List[float]] = {}
|
|
1742
|
+
|
|
1743
|
+
def compare_outcomes(
|
|
1744
|
+
self,
|
|
1745
|
+
policy_id: str,
|
|
1746
|
+
predicted: Dict[str, float],
|
|
1747
|
+
actual: Dict[str, float],
|
|
1748
|
+
timestamp: float
|
|
1749
|
+
) -> Dict[str, Any]:
|
|
1750
|
+
"""
|
|
1751
|
+
Compare predicted vs actual outcomes.
|
|
1752
|
+
|
|
1753
|
+
Args:
|
|
1754
|
+
policy_id: Policy ID
|
|
1755
|
+
predicted: Predicted outcomes
|
|
1756
|
+
actual: Actual outcomes
|
|
1757
|
+
timestamp: Timestamp
|
|
1758
|
+
|
|
1759
|
+
Returns:
|
|
1760
|
+
Dict[str, Any]: Comparison results
|
|
1761
|
+
"""
|
|
1762
|
+
comparison = {
|
|
1763
|
+
"policy_id": policy_id,
|
|
1764
|
+
"predicted": predicted,
|
|
1765
|
+
"actual": actual,
|
|
1766
|
+
"timestamp": timestamp,
|
|
1767
|
+
"errors": {k: abs(predicted.get(k, 0) - actual.get(k, 0)) for k in set(list(predicted.keys()) + list(actual.keys()))},
|
|
1768
|
+
"relative_errors": {k: abs(predicted.get(k, 0) - actual.get(k, 0)) / (abs(actual.get(k, 0)) + 1e-6) for k in actual},
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
self.outcome_comparisons.append(comparison)
|
|
1772
|
+
|
|
1773
|
+
# Update policy effectiveness
|
|
1774
|
+
avg_error = np.mean(list(comparison["relative_errors"].values()))
|
|
1775
|
+
effectiveness = max(0.0, 1.0 - avg_error)
|
|
1776
|
+
self.policy_effectiveness[policy_id] = effectiveness
|
|
1777
|
+
|
|
1778
|
+
return comparison
|
|
1779
|
+
|
|
1780
|
+
def track_board_performance(
|
|
1781
|
+
self,
|
|
1782
|
+
board_id: str,
|
|
1783
|
+
decision_quality: float
|
|
1784
|
+
) -> None:
|
|
1785
|
+
"""Track board decision quality."""
|
|
1786
|
+
if board_id not in self.board_performance:
|
|
1787
|
+
self.board_performance[board_id] = []
|
|
1788
|
+
|
|
1789
|
+
self.board_performance[board_id].append(decision_quality)
|
|
1790
|
+
|
|
1791
|
+
def get_policy_effectiveness(self, policy_id: str) -> Optional[float]:
|
|
1792
|
+
"""Get policy effectiveness score."""
|
|
1793
|
+
return self.policy_effectiveness.get(policy_id)
|
|
1794
|
+
|
|
1795
|
+
|
|
1796
|
+
class FaultTolerance:
|
|
1797
|
+
"""
|
|
1798
|
+
Fault tolerance system.
|
|
1799
|
+
|
|
1800
|
+
Features:
|
|
1801
|
+
- Redundancy (multiple data sources)
|
|
1802
|
+
- Failover mechanisms (backup systems)
|
|
1803
|
+
- Graceful degradation (fallback to simpler models)
|
|
1804
|
+
- Data backup and recovery
|
|
1805
|
+
- System health monitoring
|
|
1806
|
+
"""
|
|
1807
|
+
|
|
1808
|
+
def __init__(self) -> None:
|
|
1809
|
+
"""Initialize fault tolerance."""
|
|
1810
|
+
self.primary_systems: Dict[str, bool] = {}
|
|
1811
|
+
self.backup_systems: Dict[str, bool] = {}
|
|
1812
|
+
self.system_health: Dict[str, str] = {} # "healthy", "degraded", "failed"
|
|
1813
|
+
|
|
1814
|
+
def register_system(
|
|
1815
|
+
self,
|
|
1816
|
+
system_name: str,
|
|
1817
|
+
is_primary: bool = True
|
|
1818
|
+
) -> None:
|
|
1819
|
+
"""Register a system (primary or backup)."""
|
|
1820
|
+
if is_primary:
|
|
1821
|
+
self.primary_systems[system_name] = True
|
|
1822
|
+
self.system_health[system_name] = "healthy"
|
|
1823
|
+
else:
|
|
1824
|
+
self.backup_systems[system_name] = True
|
|
1825
|
+
|
|
1826
|
+
def check_system_health(self, system_name: str) -> str:
|
|
1827
|
+
"""Check system health."""
|
|
1828
|
+
return self.system_health.get(system_name, "unknown")
|
|
1829
|
+
|
|
1830
|
+
def mark_system_failed(self, system_name: str) -> None:
|
|
1831
|
+
"""Mark system as failed and attempt failover."""
|
|
1832
|
+
self.system_health[system_name] = "failed"
|
|
1833
|
+
logger.warning(f"System failed: {system_name}")
|
|
1834
|
+
|
|
1835
|
+
# Attempt failover to backup
|
|
1836
|
+
backup_name = f"{system_name}_backup"
|
|
1837
|
+
if backup_name in self.backup_systems:
|
|
1838
|
+
self.system_health[backup_name] = "healthy"
|
|
1839
|
+
logger.info(f"Failed over to backup: {backup_name}")
|
|
1840
|
+
|
|
1841
|
+
def get_system_status(self) -> Dict[str, str]:
|
|
1842
|
+
"""Get status of all systems."""
|
|
1843
|
+
return self.system_health.copy()
|
|
1844
|
+
|