monkeybrain-runtime 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (838) hide show
  1. monkeybrain_runtime-1.0.0.dist-info/METADATA +76 -0
  2. monkeybrain_runtime-1.0.0.dist-info/RECORD +838 -0
  3. monkeybrain_runtime-1.0.0.dist-info/WHEEL +5 -0
  4. monkeybrain_runtime-1.0.0.dist-info/entry_points.txt +3 -0
  5. monkeybrain_runtime-1.0.0.dist-info/top_level.txt +2 -0
  6. services/__init__.py +8 -0
  7. services/agentos/__init__.py +0 -0
  8. services/agentos/main.py +1 -0
  9. services/assets/helpers/__init__.py +12 -0
  10. services/assets/helpers/device.py +59 -0
  11. services/assets/helpers/equipment.py +179 -0
  12. services/assets/helpers/instruments.py +72 -0
  13. services/assets/helpers/machines.py +183 -0
  14. services/assets/helpers/materials.py +76 -0
  15. services/assets/helpers/parts.py +116 -0
  16. services/assets/helpers/plc.py +134 -0
  17. services/assets/helpers/tags.py +108 -0
  18. services/assets/helpers/tools.py +101 -0
  19. services/assets/main.py +75 -0
  20. services/assets/models/__init__.py +12 -0
  21. services/assets/models/device.py +79 -0
  22. services/assets/models/equipment.py +222 -0
  23. services/assets/models/instruments.py +85 -0
  24. services/assets/models/machines.py +230 -0
  25. services/assets/models/material.py +266 -0
  26. services/assets/models/parts.py +96 -0
  27. services/assets/models/plc.py +264 -0
  28. services/assets/models/tags.py +76 -0
  29. services/assets/models/tools.py +179 -0
  30. services/assets/routers/__init__.py +12 -0
  31. services/assets/routers/classes.py +65 -0
  32. services/assets/routers/device.py +86 -0
  33. services/assets/routers/equipment.py +145 -0
  34. services/assets/routers/families.py +61 -0
  35. services/assets/routers/instruments.py +70 -0
  36. services/assets/routers/machines.py +136 -0
  37. services/assets/routers/materials.py +105 -0
  38. services/assets/routers/parts.py +130 -0
  39. services/assets/routers/plc.py +94 -0
  40. services/assets/routers/subclasses.py +68 -0
  41. services/assets/routers/tags.py +138 -0
  42. services/assets/routers/tools.py +113 -0
  43. services/auth/helpers/__init__.py +13 -0
  44. services/auth/helpers/approval_decisions.py +261 -0
  45. services/auth/helpers/audit_elasticsearch_sync.py +350 -0
  46. services/auth/helpers/departments.py +53 -0
  47. services/auth/helpers/graph_store.py +848 -0
  48. services/auth/helpers/influx_store.py +280 -0
  49. services/auth/helpers/me.py +33 -0
  50. services/auth/helpers/nats_consumer.py +618 -0
  51. services/auth/helpers/nats_store.py +242 -0
  52. services/auth/helpers/permissions.py +62 -0
  53. services/auth/helpers/roles.py +87 -0
  54. services/auth/helpers/store.py +54 -0
  55. services/auth/helpers/team_members.py +155 -0
  56. services/auth/helpers/teams.py +87 -0
  57. services/auth/helpers/tokens.py +71 -0
  58. services/auth/helpers/users.py +119 -0
  59. services/auth/helpers/websocket_broadcast.py +41 -0
  60. services/auth/main.py +88 -0
  61. services/auth/models/__init__.py +12 -0
  62. services/auth/models/departments.py +55 -0
  63. services/auth/models/login.py +20 -0
  64. services/auth/models/permissions.py +61 -0
  65. services/auth/models/roles.py +53 -0
  66. services/auth/models/session.py +26 -0
  67. services/auth/models/teamMembers.py +59 -0
  68. services/auth/models/teams.py +56 -0
  69. services/auth/models/users.py +77 -0
  70. services/auth/routers/__init__.py +14 -0
  71. services/auth/routers/auth.py +3839 -0
  72. services/auth/routers/departments.py +84 -0
  73. services/auth/routers/integration_config.py +703 -0
  74. services/auth/routers/me.py +28 -0
  75. services/auth/routers/permissions.py +96 -0
  76. services/auth/routers/roles.py +139 -0
  77. services/auth/routers/session.py +224 -0
  78. services/auth/routers/team_members.py +152 -0
  79. services/auth/routers/teams.py +112 -0
  80. services/auth/routers/users.py +131 -0
  81. services/auth/routers/websocket_events.py +247 -0
  82. services/batch_execution/__init__.py +19 -0
  83. services/batch_execution/pipeline_triggers/__init__.py +53 -0
  84. services/batch_execution/pipeline_triggers/pipeline_parameter_adjuster.py +440 -0
  85. services/batch_execution/pipeline_triggers/pipeline_trigger_engine.py +445 -0
  86. services/batch_execution/pipeline_triggers/re_evaluation_queue.py +341 -0
  87. services/batch_execution/pipeline_triggers/test_phase3_implementation.py +553 -0
  88. services/batch_execution/pipeline_triggers/workflow_reevaluator.py +367 -0
  89. services/batch_execution/smoke_test_e2e_feedback_loop.py +704 -0
  90. services/batch_execution/workflow_executor.py +478 -0
  91. services/changeover/helpers/__init__.py +11 -0
  92. services/changeover/helpers/changeover.py +87 -0
  93. services/changeover/helpers/changeover_common.py +55 -0
  94. services/changeover/helpers/changeover_events.py +66 -0
  95. services/changeover/helpers/changeover_kpis.py +33 -0
  96. services/changeover/helpers/changeover_matrix.py +60 -0
  97. services/changeover/helpers/changeover_procedures.py +164 -0
  98. services/changeover/helpers/changeover_windows.py +96 -0
  99. services/changeover/main.py +52 -0
  100. services/changeover/models/__init__.py +11 -0
  101. services/changeover/models/changeover.py +73 -0
  102. services/changeover/models/changeover_events.py +142 -0
  103. services/changeover/models/changeover_kpis.py +75 -0
  104. services/changeover/models/changeover_matrix.py +63 -0
  105. services/changeover/models/changeover_procedures.py +108 -0
  106. services/changeover/models/changeover_tasks.py +87 -0
  107. services/changeover/models/changeover_windows.py +72 -0
  108. services/changeover/routers/__init__.py +9 -0
  109. services/changeover/routers/changeover_events.py +127 -0
  110. services/changeover/routers/changeover_kpis.py +80 -0
  111. services/changeover/routers/changeover_matrix.py +80 -0
  112. services/changeover/routers/changeover_procedures.py +118 -0
  113. services/changeover/routers/changeover_windows.py +98 -0
  114. services/common/__init__.py +2 -0
  115. services/common/approval_chains.py +648 -0
  116. services/common/auth.py +56 -0
  117. services/common/cdc.py +52 -0
  118. services/common/compat.py +217 -0
  119. services/common/compliance.py +562 -0
  120. services/common/config.py +134 -0
  121. services/common/cors.py +17 -0
  122. services/common/data_transformation.py +195 -0
  123. services/common/db.py +577 -0
  124. services/common/embeddings.py +97 -0
  125. services/common/event_reducers.py +194 -0
  126. services/common/event_types.py +51 -0
  127. services/common/integration_ingestion.py +169 -0
  128. services/common/logging.py +204 -0
  129. services/common/models/__init__.py +2 -0
  130. services/common/models/databricks.py +25 -0
  131. services/common/models/enums.py +64 -0
  132. services/common/module_control.py +422 -0
  133. services/common/mongo_cdc_watcher.py +106 -0
  134. services/common/n8n_auth.py +22 -0
  135. services/common/neo4j_mirror.py +1087 -0
  136. services/common/ontology_registry.py +110 -0
  137. services/common/reasoning_traces.py +52 -0
  138. services/common/supply_chain_cdc.py +555 -0
  139. services/common/tracing.py +159 -0
  140. services/common/utils.py +30 -0
  141. services/customers/helpers/__init__.py +8 -0
  142. services/customers/helpers/customer_details.py +64 -0
  143. services/customers/helpers/customer_metadata.py +64 -0
  144. services/customers/helpers/customer_order_metrics.py +67 -0
  145. services/customers/helpers/customer_payment_data.py +67 -0
  146. services/customers/main.py +50 -0
  147. services/customers/models/__init__.py +8 -0
  148. services/customers/models/customer_details.py +42 -0
  149. services/customers/models/customer_metadata.py +97 -0
  150. services/customers/models/customer_order_metrics.py +86 -0
  151. services/customers/models/customer_payment_data.py +60 -0
  152. services/customers/routers/__init__.py +8 -0
  153. services/customers/routers/customer_details.py +88 -0
  154. services/customers/routers/customer_metadata.py +88 -0
  155. services/customers/routers/customer_order_metrics.py +88 -0
  156. services/customers/routers/customer_payment_data.py +88 -0
  157. services/documents/__init__.py +1 -0
  158. services/documents/helpers/__init__.py +6 -0
  159. services/documents/helpers/document_metadata.py +569 -0
  160. services/documents/helpers/document_workflows.py +215 -0
  161. services/documents/helpers/report_templates.py +113 -0
  162. services/documents/main.py +49 -0
  163. services/documents/models/__init__.py +6 -0
  164. services/documents/models/document_metadata.py +215 -0
  165. services/documents/models/document_workflows.py +136 -0
  166. services/documents/models/report_templates.py +132 -0
  167. services/documents/routers/__init__.py +6 -0
  168. services/documents/routers/document_metadata.py +654 -0
  169. services/documents/routers/document_workflows.py +146 -0
  170. services/documents/routers/report_templates.py +86 -0
  171. services/events/helpers/__init__.py +5 -0
  172. services/events/helpers/events.py +394 -0
  173. services/events/main.py +40 -0
  174. services/events/models/__init__.py +5 -0
  175. services/events/models/events.py +50 -0
  176. services/events/routers/__init__.py +6 -0
  177. services/events/routers/count_events.py +109 -0
  178. services/events/routers/events.py +75 -0
  179. services/events/seed_events.py +196 -0
  180. services/facilities/helpers/__init__.py +8 -0
  181. services/facilities/helpers/lines.py +74 -0
  182. services/facilities/helpers/locations.py +231 -0
  183. services/facilities/helpers/plants.py +59 -0
  184. services/facilities/helpers/stages.py +110 -0
  185. services/facilities/helpers/workstation.py +213 -0
  186. services/facilities/main.py +60 -0
  187. services/facilities/models/__init__.py +10 -0
  188. services/facilities/models/industrialLine.py +72 -0
  189. services/facilities/models/industrialPlant.py +164 -0
  190. services/facilities/models/locations.py +74 -0
  191. services/facilities/models/stages.py +92 -0
  192. services/facilities/models/worker.py +73 -0
  193. services/facilities/models/workstation.py +117 -0
  194. services/facilities/models/workstation_live_state.py +59 -0
  195. services/facilities/routers/__init__.py +8 -0
  196. services/facilities/routers/bays.py +81 -0
  197. services/facilities/routers/buildings.py +92 -0
  198. services/facilities/routers/floors.py +81 -0
  199. services/facilities/routers/lines.py +154 -0
  200. services/facilities/routers/locations.py +208 -0
  201. services/facilities/routers/plant.py +203 -0
  202. services/facilities/routers/rooms.py +81 -0
  203. services/facilities/routers/stages.py +152 -0
  204. services/facilities/routers/workstation.py +173 -0
  205. services/file/backup.py +71 -0
  206. services/file/main.py +45 -0
  207. services/file/recieve.py +54 -0
  208. services/file/send.py +55 -0
  209. services/file/src/core/config.py +90 -0
  210. services/file/src/core/keycloak.py +152 -0
  211. services/file/src/core/logging_config.py +9 -0
  212. services/file/src/core/security.py +33 -0
  213. services/file/src/helpers/cad_conversion.py +331 -0
  214. services/file/src/helpers/helpers.py +825 -0
  215. services/file/src/routes/cad_conversion.py +26 -0
  216. services/file/src/routes/files.py +136 -0
  217. services/file/src/routes/presigned.py +154 -0
  218. services/file/src/services/websocket.py +293 -0
  219. services/floor_layout/helpers/__init__.py +8 -0
  220. services/floor_layout/helpers/bays.py +92 -0
  221. services/floor_layout/helpers/buildings.py +54 -0
  222. services/floor_layout/helpers/floors.py +65 -0
  223. services/floor_layout/helpers/rooms.py +76 -0
  224. services/floor_layout/main.py +52 -0
  225. services/floor_layout/models/__init__.py +8 -0
  226. services/floor_layout/models/bays.py +65 -0
  227. services/floor_layout/models/buildings.py +52 -0
  228. services/floor_layout/models/floors.py +45 -0
  229. services/floor_layout/models/rooms.py +61 -0
  230. services/floor_layout/routers/__init__.py +9 -0
  231. services/floor_layout/routers/bays.py +143 -0
  232. services/floor_layout/routers/buildings.py +116 -0
  233. services/floor_layout/routers/floors.py +89 -0
  234. services/floor_layout/routers/locations.py +80 -0
  235. services/floor_layout/routers/rooms.py +134 -0
  236. services/inventory/helpers/__init__.py +13 -0
  237. services/inventory/helpers/cycle_counts.py +124 -0
  238. services/inventory/helpers/inventory_allocations.py +134 -0
  239. services/inventory/helpers/inventory_item_counts.py +114 -0
  240. services/inventory/helpers/inventory_item_quantities.py +114 -0
  241. services/inventory/helpers/inventory_items.py +103 -0
  242. services/inventory/helpers/inventory_stage_outputs.py +134 -0
  243. services/inventory/helpers/inventory_transactions.py +112 -0
  244. services/inventory/helpers/stock_adjustment_requests.py +101 -0
  245. services/inventory/helpers/warehouse_cycle_counts.py +133 -0
  246. services/inventory/helpers/warehouse_locations.py +213 -0
  247. services/inventory/helpers/warehouse_regulated_records.py +123 -0
  248. services/inventory/main.py +62 -0
  249. services/inventory/models/__init__.py +17 -0
  250. services/inventory/models/cycle_counts.py +99 -0
  251. services/inventory/models/inventory_allocations.py +121 -0
  252. services/inventory/models/inventory_common.py +65 -0
  253. services/inventory/models/inventory_enums.py +21 -0
  254. services/inventory/models/inventory_item_count.py +65 -0
  255. services/inventory/models/inventory_item_quantity.py +82 -0
  256. services/inventory/models/inventory_items.py +168 -0
  257. services/inventory/models/inventory_responses.py +44 -0
  258. services/inventory/models/inventory_stage_outputs.py +96 -0
  259. services/inventory/models/inventory_state.py +15 -0
  260. services/inventory/models/inventory_transactions.py +80 -0
  261. services/inventory/models/stock_adjustment_requests.py +109 -0
  262. services/inventory/models/warehouse_cycle_counts.py +119 -0
  263. services/inventory/models/warehouse_location_models.py +708 -0
  264. services/inventory/models/warehouse_regulated_records.py +358 -0
  265. services/inventory/routers/__init__.py +13 -0
  266. services/inventory/routers/cycle_counts.py +106 -0
  267. services/inventory/routers/inventory_allocations.py +125 -0
  268. services/inventory/routers/inventory_item_counts.py +105 -0
  269. services/inventory/routers/inventory_item_quantities.py +105 -0
  270. services/inventory/routers/inventory_items.py +109 -0
  271. services/inventory/routers/inventory_stage_outputs.py +122 -0
  272. services/inventory/routers/inventory_transactions.py +96 -0
  273. services/inventory/routers/stock_adjustment_requests.py +124 -0
  274. services/inventory/routers/warehouse_cycle_counts.py +124 -0
  275. services/inventory/routers/warehouse_locations.py +426 -0
  276. services/inventory/routers/warehouse_regulated_records.py +273 -0
  277. services/iot/helpers/__init__.py +8 -0
  278. services/iot/helpers/ble_device.py +87 -0
  279. services/iot/helpers/mqtt_bridge.py +115 -0
  280. services/iot/helpers/sensor_readings.py +63 -0
  281. services/iot/helpers/sensors.py +77 -0
  282. services/iot/helpers/servers.py +72 -0
  283. services/iot/helpers/uwb_device.py +95 -0
  284. services/iot/main.py +53 -0
  285. services/iot/models/__init__.py +8 -0
  286. services/iot/models/ble_device.py +118 -0
  287. services/iot/models/sensors.py +256 -0
  288. services/iot/models/servers.py +206 -0
  289. services/iot/models/uwb_device.py +106 -0
  290. services/iot/routers/__init__.py +8 -0
  291. services/iot/routers/ble_device.py +110 -0
  292. services/iot/routers/sensors.py +144 -0
  293. services/iot/routers/servers.py +141 -0
  294. services/iot/routers/uwb_device.py +148 -0
  295. services/module_control/__init__.py +1 -0
  296. services/module_control/helpers/__init__.py +1 -0
  297. services/module_control/helpers/integration_config.py +243 -0
  298. services/module_control/helpers/security.py +104 -0
  299. services/module_control/main.py +44 -0
  300. services/module_control/models/__init__.py +1 -0
  301. services/module_control/models/module_control.py +65 -0
  302. services/module_control/routers/__init__.py +1 -0
  303. services/module_control/routers/module_control.py +219 -0
  304. services/orders/helpers/__init__.py +11 -0
  305. services/orders/helpers/invoices.py +123 -0
  306. services/orders/helpers/order_customer_metrics.py +61 -0
  307. services/orders/helpers/order_details.py +71 -0
  308. services/orders/helpers/order_metadata.py +61 -0
  309. services/orders/helpers/order_payment_metadata.py +74 -0
  310. services/orders/helpers/orders.py +119 -0
  311. services/orders/helpers/sales_orders.py +136 -0
  312. services/orders/main.py +56 -0
  313. services/orders/models/__init__.py +11 -0
  314. services/orders/models/invoices.py +415 -0
  315. services/orders/models/order_customer_metrics.py +78 -0
  316. services/orders/models/order_details.py +46 -0
  317. services/orders/models/order_metadata.py +60 -0
  318. services/orders/models/order_payment_metadata.py +63 -0
  319. services/orders/models/orders.py +64 -0
  320. services/orders/models/sales_orders.py +130 -0
  321. services/orders/routers/__init__.py +11 -0
  322. services/orders/routers/invoices.py +111 -0
  323. services/orders/routers/order_customer_metrics.py +87 -0
  324. services/orders/routers/order_details.py +87 -0
  325. services/orders/routers/order_metadata.py +87 -0
  326. services/orders/routers/order_payment_metadata.py +87 -0
  327. services/orders/routers/orders.py +74 -0
  328. services/orders/routers/sales_orders.py +111 -0
  329. services/pm/helpers/__init__.py +14 -0
  330. services/pm/helpers/calendar_bookings.py +114 -0
  331. services/pm/helpers/calibration_point.py +110 -0
  332. services/pm/helpers/calibrations.py +196 -0
  333. services/pm/helpers/checklists.py +318 -0
  334. services/pm/helpers/cleaning.py +333 -0
  335. services/pm/helpers/downtime.py +376 -0
  336. services/pm/helpers/kanban_boards.py +186 -0
  337. services/pm/helpers/maintainance.py +177 -0
  338. services/pm/helpers/sop.py +1155 -0
  339. services/pm/helpers/sop_cdc.py +324 -0
  340. services/pm/helpers/weekly_schedules.py +79 -0
  341. services/pm/main.py +62 -0
  342. services/pm/models/__init__.py +14 -0
  343. services/pm/models/calendar_booking.py +82 -0
  344. services/pm/models/calibration_point.py +44 -0
  345. services/pm/models/calibrations.py +167 -0
  346. services/pm/models/checklists.py +117 -0
  347. services/pm/models/cleaning.py +203 -0
  348. services/pm/models/downtime.py +109 -0
  349. services/pm/models/kanban_board.py +178 -0
  350. services/pm/models/maintainanceLog.py +148 -0
  351. services/pm/models/sop.py +152 -0
  352. services/pm/models/weekly_schedule.py +91 -0
  353. services/pm/routers/__init__.py +14 -0
  354. services/pm/routers/calendar_bookings.py +143 -0
  355. services/pm/routers/calibration_point.py +94 -0
  356. services/pm/routers/calibrations.py +232 -0
  357. services/pm/routers/checklists.py +188 -0
  358. services/pm/routers/cleaning.py +127 -0
  359. services/pm/routers/downtime.py +143 -0
  360. services/pm/routers/kanban_boards.py +283 -0
  361. services/pm/routers/maintainance.py +241 -0
  362. services/pm/routers/sop.py +437 -0
  363. services/pm/routers/weekly_schedules.py +108 -0
  364. services/process_definitions/helpers/__init__.py +11 -0
  365. services/process_definitions/helpers/cpp_cqa_registry.py +120 -0
  366. services/process_definitions/helpers/mbmr_templates.py +107 -0
  367. services/process_definitions/helpers/packing_instructions.py +113 -0
  368. services/process_definitions/helpers/process_constraints.py +495 -0
  369. services/process_definitions/helpers/process_corrections.py +279 -0
  370. services/process_definitions/helpers/process_definition.py +996 -0
  371. services/process_definitions/helpers/process_node_catalog.py +786 -0
  372. services/process_definitions/helpers/process_post_checks.py +441 -0
  373. services/process_definitions/helpers/process_pre_checks.py +351 -0
  374. services/process_definitions/helpers/process_steps.py +220 -0
  375. services/process_definitions/main.py +71 -0
  376. services/process_definitions/models/__init__.py +13 -0
  377. services/process_definitions/models/cpp_cqa_registry.py +145 -0
  378. services/process_definitions/models/gxp_change_controls.py +38 -0
  379. services/process_definitions/models/gxp_risk_assessments.py +30 -0
  380. services/process_definitions/models/gxp_validation_evidence.py +33 -0
  381. services/process_definitions/models/mbmr_templates.py +173 -0
  382. services/process_definitions/models/packing_instructions.py +176 -0
  383. services/process_definitions/models/process_constraints.py +159 -0
  384. services/process_definitions/models/process_corrections.py +118 -0
  385. services/process_definitions/models/process_definition.py +685 -0
  386. services/process_definitions/models/process_definition_common.py +48 -0
  387. services/process_definitions/models/process_node_catalog.py +25 -0
  388. services/process_definitions/models/process_post_checks.py +171 -0
  389. services/process_definitions/models/process_pre_checks.py +168 -0
  390. services/process_definitions/models/process_steps.py +170 -0
  391. services/process_definitions/node_services/__init__.py +8 -0
  392. services/process_definitions/node_services/common.py +95 -0
  393. services/process_definitions/node_services/executor.py +499 -0
  394. services/process_definitions/node_services/flow_simulator.py +733 -0
  395. services/process_definitions/node_services/functions.py +193 -0
  396. services/process_definitions/node_services/messaging.py +44 -0
  397. services/process_definitions/node_services/models.py +221 -0
  398. services/process_definitions/node_services/network.py +161 -0
  399. services/process_definitions/node_services/parsers.py +87 -0
  400. services/process_definitions/node_services/sequence.py +95 -0
  401. services/process_definitions/node_services/storage.py +50 -0
  402. services/process_definitions/node_services/webhooks.py +52 -0
  403. services/process_definitions/routers/__init__.py +10 -0
  404. services/process_definitions/routers/cpp_cqa_registry.py +86 -0
  405. services/process_definitions/routers/mbmr_templates.py +84 -0
  406. services/process_definitions/routers/packing_instructions.py +84 -0
  407. services/process_definitions/routers/process_constraints.py +564 -0
  408. services/process_definitions/routers/process_corrections.py +343 -0
  409. services/process_definitions/routers/process_definition.py +992 -0
  410. services/process_definitions/routers/process_post_checks.py +529 -0
  411. services/process_definitions/routers/process_pre_checks.py +435 -0
  412. services/process_definitions/routers/process_steps.py +274 -0
  413. services/procurement/helpers/__init__.py +9 -0
  414. services/procurement/helpers/goods_receipts.py +240 -0
  415. services/procurement/helpers/purchase_order_shipping_information.py +85 -0
  416. services/procurement/helpers/purchase_orders.py +68 -0
  417. services/procurement/helpers/quality_control.py +235 -0
  418. services/procurement/helpers/sampling.py +404 -0
  419. services/procurement/main.py +52 -0
  420. services/procurement/models/__init__.py +9 -0
  421. services/procurement/models/goods_receipts.py +165 -0
  422. services/procurement/models/purchase_orders.py +54 -0
  423. services/procurement/models/quality_control.py +464 -0
  424. services/procurement/models/reinspection_records.py +28 -0
  425. services/procurement/models/sampling.py +262 -0
  426. services/procurement/models/shipping_information.py +51 -0
  427. services/procurement/routers/__init__.py +9 -0
  428. services/procurement/routers/goods_receipts.py +201 -0
  429. services/procurement/routers/purchase_orders.py +106 -0
  430. services/procurement/routers/quality_control.py +386 -0
  431. services/procurement/routers/sampling.py +296 -0
  432. services/procurement/routers/shipping_information.py +97 -0
  433. services/production/__init__.py +1 -0
  434. services/production/agents/__init__.py +5 -0
  435. services/production/agents/batch_planning_agent.py +815 -0
  436. services/production/models/__init__.py +25 -0
  437. services/production/models/batch.py +253 -0
  438. services/products/helpers/__init__.py +10 -0
  439. services/products/helpers/boms.py +100 -0
  440. services/products/helpers/drug_research.py +644 -0
  441. services/products/helpers/product_component.py +168 -0
  442. services/products/helpers/product_inventory.py +221 -0
  443. services/products/helpers/product_pricing.py +123 -0
  444. services/products/helpers/product_utils.py +32 -0
  445. services/products/helpers/products.py +81 -0
  446. services/products/main.py +59 -0
  447. services/products/models/__init__.py +9 -0
  448. services/products/models/drug_research.py +138 -0
  449. services/products/models/product_common.py +60 -0
  450. services/products/models/product_component.py +1028 -0
  451. services/products/models/product_inventory.py +118 -0
  452. services/products/models/product_pricing.py +73 -0
  453. services/products/models/products.py +151 -0
  454. services/products/routers/__init__.py +9 -0
  455. services/products/routers/boms.py +116 -0
  456. services/products/routers/drug_research.py +115 -0
  457. services/products/routers/product_components.py +123 -0
  458. services/products/routers/product_inventory.py +185 -0
  459. services/products/routers/product_pricing.py +136 -0
  460. services/products/routers/products.py +165 -0
  461. services/replenishment/__init__.py +1 -0
  462. services/replenishment/main.py +46 -0
  463. services/replenishment/routers/__init__.py +1 -0
  464. services/replenishment/routers/replenishment.py +20 -0
  465. services/shifts/helpers/__init__.py +7 -0
  466. services/shifts/helpers/shift_templates.py +124 -0
  467. services/shifts/helpers/shifts.py +79 -0
  468. services/shifts/helpers/timesheets.py +137 -0
  469. services/shifts/main.py +48 -0
  470. services/shifts/models/__init__.py +8 -0
  471. services/shifts/models/shift.py +62 -0
  472. services/shifts/models/shift_template.py +82 -0
  473. services/shifts/models/time_range.py +31 -0
  474. services/shifts/models/timesheet.py +196 -0
  475. services/shifts/routers/__init__.py +7 -0
  476. services/shifts/routers/shift_templates.py +97 -0
  477. services/shifts/routers/shifts.py +117 -0
  478. services/shifts/routers/timesheets.py +117 -0
  479. services/shipping/helpers/__init__.py +15 -0
  480. services/shipping/helpers/carrier.py +78 -0
  481. services/shipping/helpers/customs_declaration.py +104 -0
  482. services/shipping/helpers/delivery_note.py +99 -0
  483. services/shipping/helpers/package.py +95 -0
  484. services/shipping/helpers/pallet.py +85 -0
  485. services/shipping/helpers/route.py +93 -0
  486. services/shipping/helpers/shipping_information.py +82 -0
  487. services/shipping/helpers/shipping_provider_details.py +59 -0
  488. services/shipping/helpers/shipping_provider_metadata.py +59 -0
  489. services/shipping/helpers/vehicle.py +85 -0
  490. services/shipping/helpers/waybill.py +86 -0
  491. services/shipping/main.py +64 -0
  492. services/shipping/models/__init__.py +15 -0
  493. services/shipping/models/carrier.py +97 -0
  494. services/shipping/models/customs_declaration.py +138 -0
  495. services/shipping/models/delivery_note.py +163 -0
  496. services/shipping/models/package.py +152 -0
  497. services/shipping/models/pallet.py +137 -0
  498. services/shipping/models/route.py +120 -0
  499. services/shipping/models/shipping_information.py +55 -0
  500. services/shipping/models/shipping_provider_details.py +42 -0
  501. services/shipping/models/shipping_provider_metadata.py +54 -0
  502. services/shipping/models/vehicle.py +129 -0
  503. services/shipping/models/waybill.py +189 -0
  504. services/shipping/routers/__init__.py +15 -0
  505. services/shipping/routers/carrier.py +99 -0
  506. services/shipping/routers/customs_declaration.py +132 -0
  507. services/shipping/routers/delivery_note.py +150 -0
  508. services/shipping/routers/package.py +141 -0
  509. services/shipping/routers/pallet.py +108 -0
  510. services/shipping/routers/route.py +128 -0
  511. services/shipping/routers/shipping_information.py +97 -0
  512. services/shipping/routers/shipping_provider_details.py +80 -0
  513. services/shipping/routers/shipping_provider_metadata.py +80 -0
  514. services/shipping/routers/vehicle.py +117 -0
  515. services/shipping/routers/waybill.py +119 -0
  516. services/suppliers/helpers/__init__.py +13 -0
  517. services/suppliers/helpers/supplier_capabilities.py +58 -0
  518. services/suppliers/helpers/supplier_certifications.py +67 -0
  519. services/suppliers/helpers/supplier_details.py +58 -0
  520. services/suppliers/helpers/supplier_financials.py +58 -0
  521. services/suppliers/helpers/supplier_inventory.py +74 -0
  522. services/suppliers/helpers/supplier_locations.py +60 -0
  523. services/suppliers/helpers/supplier_pricing.py +69 -0
  524. services/suppliers/helpers/supplier_quality.py +69 -0
  525. services/suppliers/helpers/supplier_shipping.py +69 -0
  526. services/suppliers/main.py +60 -0
  527. services/suppliers/models/__init__.py +13 -0
  528. services/suppliers/models/supplier_capabilities.py +70 -0
  529. services/suppliers/models/supplier_certifications.py +64 -0
  530. services/suppliers/models/supplier_details.py +75 -0
  531. services/suppliers/models/supplier_financials.py +69 -0
  532. services/suppliers/models/supplier_inventory.py +76 -0
  533. services/suppliers/models/supplier_locations.py +70 -0
  534. services/suppliers/models/supplier_pricing.py +74 -0
  535. services/suppliers/models/supplier_quality.py +74 -0
  536. services/suppliers/models/supplier_shipping.py +76 -0
  537. services/suppliers/routers/__init__.py +13 -0
  538. services/suppliers/routers/supplier_capabilities.py +88 -0
  539. services/suppliers/routers/supplier_certifications.py +87 -0
  540. services/suppliers/routers/supplier_details.py +83 -0
  541. services/suppliers/routers/supplier_financials.py +83 -0
  542. services/suppliers/routers/supplier_inventory.py +105 -0
  543. services/suppliers/routers/supplier_locations.py +89 -0
  544. services/suppliers/routers/supplier_pricing.py +96 -0
  545. services/suppliers/routers/supplier_quality.py +96 -0
  546. services/suppliers/routers/supplier_shipping.py +96 -0
  547. services/supply_allocation/main.py +46 -0
  548. services/supply_allocation/routers/__init__.py +1 -0
  549. services/supply_allocation/routers/allocation.py +20 -0
  550. services/taxonomy/helpers/__init__.py +7 -0
  551. services/taxonomy/helpers/classes.py +48 -0
  552. services/taxonomy/helpers/family.py +53 -0
  553. services/taxonomy/helpers/subclass.py +58 -0
  554. services/taxonomy/main.py +48 -0
  555. services/taxonomy/models/__init__.py +7 -0
  556. services/taxonomy/models/classes.py +52 -0
  557. services/taxonomy/models/family.py +60 -0
  558. services/taxonomy/models/subclass.py +50 -0
  559. services/taxonomy/routers/__init__.py +7 -0
  560. services/taxonomy/routers/classes.py +78 -0
  561. services/taxonomy/routers/family.py +77 -0
  562. services/taxonomy/routers/subclass.py +82 -0
  563. services/warehouse_execution/__init__.py +1 -0
  564. services/warehouse_execution/main.py +46 -0
  565. services/warehouse_execution/routers/__init__.py +1 -0
  566. services/warehouse_execution/routers/execution.py +21 -0
  567. services/work_order_agent/__init__.py +17 -0
  568. services/work_order_agent/agent/__init__.py +17 -0
  569. services/work_order_agent/agent/work_order_agent.py +658 -0
  570. services/work_order_agent/tracking/__init__.py +101 -0
  571. services/work_order_agent/tracking/event_system.py +182 -0
  572. services/work_order_agent/tracking/state_machine.py +163 -0
  573. services/work_order_agent/tracking/state_machine_integrator.py +295 -0
  574. services/work_order_agent/tracking/test_phase2_implementation.py +302 -0
  575. services/work_order_agent/tracking/time_analysis.py +301 -0
  576. services/work_order_agent/tracking/tracked_work_order.py +255 -0
  577. services/work_order_agent/tracking/work_order_adapter.py +367 -0
  578. services/work_order_agent/tracking/work_order_batch_manager.py +406 -0
  579. services/work_order_agent/tracking/work_order_repository.py +431 -0
  580. services/workorders/helpers/__init__.py +5 -0
  581. services/workorders/helpers/area_room_usage_ledger.py +139 -0
  582. services/workorders/helpers/batch_execution_records.py +265 -0
  583. services/workorders/helpers/batch_release_workflows.py +158 -0
  584. services/workorders/helpers/batch_step_executions.py +145 -0
  585. services/workorders/helpers/equipment_usage_ledger.py +209 -0
  586. services/workorders/helpers/executed_bmr_records.py +170 -0
  587. services/workorders/helpers/executed_bpr_records.py +170 -0
  588. services/workorders/helpers/executed_instruction_evidence.py +155 -0
  589. services/workorders/helpers/ipc_result_records.py +134 -0
  590. services/workorders/helpers/production_batches.py +117 -0
  591. services/workorders/helpers/work_orders.py +367 -0
  592. services/workorders/helpers/yield_reconciliation_records.py +158 -0
  593. services/workorders/main.py +110 -0
  594. services/workorders/models/__init__.py +5 -0
  595. services/workorders/models/area_room_usage_ledger.py +154 -0
  596. services/workorders/models/batch_execution_records.py +575 -0
  597. services/workorders/models/batch_release_workflows.py +190 -0
  598. services/workorders/models/batch_step_executions.py +142 -0
  599. services/workorders/models/equipment_usage_ledger.py +144 -0
  600. services/workorders/models/executed_bmr_records.py +220 -0
  601. services/workorders/models/executed_bpr_records.py +220 -0
  602. services/workorders/models/executed_instruction_evidence.py +128 -0
  603. services/workorders/models/ipc_result_records.py +164 -0
  604. services/workorders/models/production_batches.py +181 -0
  605. services/workorders/models/work_orders.py +255 -0
  606. services/workorders/models/yield_reconciliation_records.py +175 -0
  607. services/workorders/routers/__init__.py +5 -0
  608. services/workorders/routers/area_room_usage_ledger.py +117 -0
  609. services/workorders/routers/batch_execution_records.py +103 -0
  610. services/workorders/routers/batch_release_workflows.py +86 -0
  611. services/workorders/routers/batch_step_executions.py +88 -0
  612. services/workorders/routers/equipment_usage_ledger.py +115 -0
  613. services/workorders/routers/executed_bmr_records.py +86 -0
  614. services/workorders/routers/executed_bpr_records.py +86 -0
  615. services/workorders/routers/executed_instruction_evidence.py +86 -0
  616. services/workorders/routers/ipc_result_records.py +86 -0
  617. services/workorders/routers/production_batches.py +86 -0
  618. services/workorders/routers/work_orders.py +257 -0
  619. services/workorders/routers/yield_reconciliation_records.py +86 -0
  620. src/broca/__init__.py +5 -0
  621. src/broca/agent.py +201 -0
  622. src/cerebellum/__init__.py +0 -0
  623. src/cerebellum/adapter.py +84 -0
  624. src/cerebellum/capabilities/__init__.py +0 -0
  625. src/cerebellum/capabilities/agent/__init__.py +0 -0
  626. src/cerebellum/capabilities/agent/agents.py +65 -0
  627. src/cerebellum/capabilities/ai/__init__.py +0 -0
  628. src/cerebellum/capabilities/ai/providers.py +106 -0
  629. src/cerebellum/capabilities/api/__init__.py +0 -0
  630. src/cerebellum/capabilities/api/graphql.py +35 -0
  631. src/cerebellum/capabilities/api/rest_api.py +45 -0
  632. src/cerebellum/capabilities/api/webhook.py +30 -0
  633. src/cerebellum/capabilities/browser/__init__.py +0 -0
  634. src/cerebellum/capabilities/browser/browsers.py +27 -0
  635. src/cerebellum/capabilities/cloud/__init__.py +0 -0
  636. src/cerebellum/capabilities/cloud/cloud.py +46 -0
  637. src/cerebellum/capabilities/communication/__init__.py +0 -0
  638. src/cerebellum/capabilities/communication/communication.py +62 -0
  639. src/cerebellum/capabilities/database/__init__.py +0 -0
  640. src/cerebellum/capabilities/database/elasticsearch.py +40 -0
  641. src/cerebellum/capabilities/database/influxdb.py +37 -0
  642. src/cerebellum/capabilities/database/mongodb.py +50 -0
  643. src/cerebellum/capabilities/database/neo4j.py +32 -0
  644. src/cerebellum/capabilities/database/redis.py +44 -0
  645. src/cerebellum/capabilities/enterprise/__init__.py +0 -0
  646. src/cerebellum/capabilities/enterprise/opensource.py +180 -0
  647. src/cerebellum/capabilities/enterprise/proprietary.py +313 -0
  648. src/cerebellum/capabilities/event_streaming/__init__.py +0 -0
  649. src/cerebellum/capabilities/event_streaming/streaming.py +38 -0
  650. src/cerebellum/capabilities/infrastructure/__init__.py +0 -0
  651. src/cerebellum/capabilities/infrastructure/infrastructure.py +30 -0
  652. src/cerebellum/capabilities/productivity/__init__.py +0 -0
  653. src/cerebellum/capabilities/productivity/productivity.py +158 -0
  654. src/cerebellum/capabilities/robotics/__init__.py +0 -0
  655. src/cerebellum/capabilities/robotics/robotics.py +124 -0
  656. src/cerebellum/capabilities/runtime/__init__.py +0 -0
  657. src/cerebellum/capabilities/runtime/runtimes.py +92 -0
  658. src/cerebellum/capabilities/search/__init__.py +0 -0
  659. src/cerebellum/capabilities/search/search.py +63 -0
  660. src/cerebellum/capabilities/source_control/__init__.py +0 -0
  661. src/cerebellum/capabilities/source_control/source_control.py +113 -0
  662. src/cerebellum/capabilities/storage/__init__.py +0 -0
  663. src/cerebellum/capabilities/storage/storage.py +94 -0
  664. src/cerebellum/capabilities/workflow/__init__.py +0 -0
  665. src/cerebellum/capabilities/workflow/workflows.py +49 -0
  666. src/cerebellum/capability.py +108 -0
  667. src/cerebellum/config.py +157 -0
  668. src/cerebellum/fallback.py +147 -0
  669. src/cerebellum/fallback_engine.py +121 -0
  670. src/cerebellum/key_manager.py +129 -0
  671. src/cerebellum/keystore.py +179 -0
  672. src/cerebellum/lifecycle.py +54 -0
  673. src/cerebellum/metadata.py +61 -0
  674. src/cerebellum/operator/base.py +25 -0
  675. src/cerebellum/peripheral.py +92 -0
  676. src/cerebellum/registry.py +98 -0
  677. src/cerebellum/resolve_entity_capability.py +259 -0
  678. src/cingulate/benchmark/__init__.py +23 -0
  679. src/cingulate/benchmark/reporter.py +102 -0
  680. src/cingulate/benchmark/runner.py +159 -0
  681. src/cingulate/benchmark/scenario_runner.py +150 -0
  682. src/cingulate/benchmark/validator.py +102 -0
  683. src/cingulate/governance/__init__.py +21 -0
  684. src/cingulate/governance/architecture_validator.py +194 -0
  685. src/cingulate/governance/compliance.py +104 -0
  686. src/cingulate/governance/governance.py +77 -0
  687. src/cingulate/governance/policy_registry.py +91 -0
  688. src/cortex/__init__.py +33 -0
  689. src/cortex/cost.py +71 -0
  690. src/cortex/counterfactual.py +162 -0
  691. src/cortex/digital_twin.py +90 -0
  692. src/cortex/experience.py +83 -0
  693. src/cortex/feedback.py +144 -0
  694. src/cortex/loss.py +116 -0
  695. src/cortex/prediction.py +142 -0
  696. src/cortex/replay.py +130 -0
  697. src/cortex/reward.py +113 -0
  698. src/cortex/simulator.py +102 -0
  699. src/cortex/world_model.py +180 -0
  700. src/cortex/world_model_simulation.py +1591 -0
  701. src/cortex/world_state.py +121 -0
  702. src/cortex/xavier.py +250 -0
  703. src/deepdive/__init__.py +29 -0
  704. src/deepdive/aggregation.py +113 -0
  705. src/deepdive/digital_twin_aggregator.py +128 -0
  706. src/deepdive/elasticsearch_adapter.py +110 -0
  707. src/deepdive/fleet_analytics.py +131 -0
  708. src/deepdive/knowledge_aggregator.py +130 -0
  709. src/homeostasis/__init__.py +19 -0
  710. src/homeostasis/control_plane.py +159 -0
  711. src/introspection/__init__.py +38 -0
  712. src/introspection/alerting.py +142 -0
  713. src/introspection/health.py +101 -0
  714. src/introspection/lemon.py +243 -0
  715. src/introspection/logging.py +147 -0
  716. src/introspection/metrics.py +106 -0
  717. src/introspection/tracing.py +162 -0
  718. src/monkey_brain/__init__.py +1 -0
  719. src/monkey_brain/api/main.py +148 -0
  720. src/monkey_brain/api/models.py +81 -0
  721. src/monkey_brain/api/routes/routes/keys.py +106 -0
  722. src/monkey_brain/api/routes/routes/run.py +169 -0
  723. src/monkey_brain/api/routes/routes/simulate.py +485 -0
  724. src/monkey_brain/dlm/__init__.py +44 -0
  725. src/monkey_brain/dlm/dlm.py +139 -0
  726. src/monkey_brain/dlm/gc.py +115 -0
  727. src/monkey_brain/dlm/lifecycle.py +149 -0
  728. src/monkey_brain/dlm/orphans.py +99 -0
  729. src/monkey_brain/dlm/storage.py +149 -0
  730. src/monkey_brain/dlm/ttl.py +140 -0
  731. src/monkey_brain/documents/__init__.py +0 -0
  732. src/monkey_brain/documents/document_ocr.py +6 -0
  733. src/monkey_brain/kernel/__init__.py +53 -0
  734. src/monkey_brain/kernel/capability_interface.py +144 -0
  735. src/monkey_brain/kernel/classifier/__init__.py +1 -0
  736. src/monkey_brain/kernel/classifier/embed_classifier.py +125 -0
  737. src/monkey_brain/kernel/classifier/intent_examples.py +106 -0
  738. src/monkey_brain/kernel/dag.py +23 -0
  739. src/monkey_brain/kernel/execution_state.py +257 -0
  740. src/monkey_brain/kernel/goal_planner.py +85 -0
  741. src/monkey_brain/kernel/goal_router.py +20 -0
  742. src/monkey_brain/kernel/goals/__init__.py +1 -0
  743. src/monkey_brain/kernel/goals/goal.py +130 -0
  744. src/monkey_brain/kernel/goals/goal_bootstrap.py +38 -0
  745. src/monkey_brain/kernel/goals/goal_classifier.py +132 -0
  746. src/monkey_brain/kernel/goals/goal_registry.py +75 -0
  747. src/monkey_brain/kernel/intents/__init__.py +1 -0
  748. src/monkey_brain/kernel/intents/event_adapter.py +246 -0
  749. src/monkey_brain/kernel/intents/helpers.py +13 -0
  750. src/monkey_brain/kernel/intents/intent_registry.py +705 -0
  751. src/monkey_brain/kernel/intents/intent_router.py +102 -0
  752. src/monkey_brain/kernel/intents/predicates/approval_create.py +9 -0
  753. src/monkey_brain/kernel/intents/predicates/approval_decision.py +9 -0
  754. src/monkey_brain/kernel/intents/predicates/approval_hold.py +9 -0
  755. src/monkey_brain/kernel/intents/predicates/approval_query.py +9 -0
  756. src/monkey_brain/kernel/intents/predicates/batch_close.py +9 -0
  757. src/monkey_brain/kernel/intents/predicates/batch_creation.py +9 -0
  758. src/monkey_brain/kernel/intents/predicates/batch_delete.py +9 -0
  759. src/monkey_brain/kernel/intents/predicates/batch_hold.py +9 -0
  760. src/monkey_brain/kernel/intents/predicates/batch_record.py +9 -0
  761. src/monkey_brain/kernel/intents/predicates/batch_update.py +9 -0
  762. src/monkey_brain/kernel/intents/predicates/change_control.py +49 -0
  763. src/monkey_brain/kernel/intents/predicates/compliance_audit.py +14 -0
  764. src/monkey_brain/kernel/intents/predicates/decision_intelligence.py +9 -0
  765. src/monkey_brain/kernel/intents/predicates/drug_research.py +9 -0
  766. src/monkey_brain/kernel/intents/predicates/fuzzy_match.py +19 -0
  767. src/monkey_brain/kernel/intents/predicates/production_kpi.py +9 -0
  768. src/monkey_brain/kernel/intents/predicates/sop_create.py +9 -0
  769. src/monkey_brain/kernel/intents/predicates/sop_query.py +9 -0
  770. src/monkey_brain/kernel/intents/predicates/sop_update.py +9 -0
  771. src/monkey_brain/kernel/intents/predicates/warehouse_shipping.py +9 -0
  772. src/monkey_brain/kernel/intents/predicates/work_order_create.py +9 -0
  773. src/monkey_brain/kernel/intents/predicates/work_order_delete.py +9 -0
  774. src/monkey_brain/kernel/intents/predicates/work_order_hold.py +9 -0
  775. src/monkey_brain/kernel/intents/predicates/work_order_query.py +9 -0
  776. src/monkey_brain/kernel/intents/predicates/work_order_status.py +9 -0
  777. src/monkey_brain/kernel/intents/predicates/work_order_update.py +9 -0
  778. src/monkey_brain/kernel/intents/predicates/worker.py +9 -0
  779. src/monkey_brain/kernel/intents/telemetry_adapter.py +274 -0
  780. src/monkey_brain/kernel/intents/utils.py +68 -0
  781. src/monkey_brain/kernel/learning.py +98 -0
  782. src/monkey_brain/kernel/llm_explorer.py +188 -0
  783. src/monkey_brain/kernel/loss.py +81 -0
  784. src/monkey_brain/kernel/nlp/__init__.py +1 -0
  785. src/monkey_brain/kernel/nlp/compat.py +23 -0
  786. src/monkey_brain/kernel/nlp/models.py +10 -0
  787. src/monkey_brain/kernel/nlp/question_analyzer.py +203 -0
  788. src/monkey_brain/kernel/nlp/spacy_parser.py +53 -0
  789. src/monkey_brain/kernel/observer.py +97 -0
  790. src/monkey_brain/kernel/parser/__init__.py +3 -0
  791. src/monkey_brain/kernel/parser/ast.py +28 -0
  792. src/monkey_brain/kernel/parser/extractors/__init__.py +11 -0
  793. src/monkey_brain/kernel/parser/extractors/entities.py +21 -0
  794. src/monkey_brain/kernel/parser/extractors/filters.py +16 -0
  795. src/monkey_brain/kernel/parser/extractors/projections.py +36 -0
  796. src/monkey_brain/kernel/parser/extractors/verbs.py +31 -0
  797. src/monkey_brain/kernel/parser/parser.py +57 -0
  798. src/monkey_brain/kernel/parser/rules.py +75 -0
  799. src/monkey_brain/kernel/pipeline.py +44 -0
  800. src/monkey_brain/kernel/planner.py +57 -0
  801. src/monkey_brain/kernel/rl/__init__.py +33 -0
  802. src/monkey_brain/kernel/rl/learner.py +98 -0
  803. src/monkey_brain/kernel/rl/policy.py +254 -0
  804. src/monkey_brain/kernel/rl/reward.py +117 -0
  805. src/monkey_brain/kernel/rl/transition.py +112 -0
  806. src/monkey_brain/persistence/__init__.py +47 -0
  807. src/monkey_brain/persistence/adapters.py +49 -0
  808. src/monkey_brain/persistence/events.py +105 -0
  809. src/monkey_brain/persistence/manager.py +124 -0
  810. src/monkey_brain/persistence/mongodb_adapter.py +91 -0
  811. src/monkey_brain/persistence/redis_adapter.py +93 -0
  812. src/monkey_brain/persistence/reducer.py +111 -0
  813. src/monkey_brain/runtime/__init__.py +49 -0
  814. src/monkey_brain/runtime/depedencies.py +8 -0
  815. src/monkey_brain/runtime/engine.py +183 -0
  816. src/monkey_brain/runtime/message_bus.py +82 -0
  817. src/monkey_brain/runtime/process.py +144 -0
  818. src/monkey_brain/runtime/resource_manager.py +100 -0
  819. src/monkey_brain/runtime/routers.py +8 -0
  820. src/monkey_brain/runtime/runtime.py +199 -0
  821. src/monkey_brain/runtime/scheduler.py +165 -0
  822. src/monkey_brain/runtime/supervisor.py +133 -0
  823. src/monkey_brain/runtime/worker_pool.py +111 -0
  824. src/plasticity/seed/__init__.py +30 -0
  825. src/plasticity/seed/benchmark_generator.py +105 -0
  826. src/plasticity/seed/event_generator.py +122 -0
  827. src/plasticity/seed/scenario_builder.py +134 -0
  828. src/plasticity/seed/seed_data.py +206 -0
  829. src/plasticity/seed/seeder.py +122 -0
  830. src/plasticity/testing/__init__.py +28 -0
  831. src/plasticity/testing/performance.py +131 -0
  832. src/plasticity/testing/profiler.py +255 -0
  833. src/plasticity/testing/reporter.py +84 -0
  834. src/plasticity/testing/runner.py +209 -0
  835. src/sync/__init__.py +12 -0
  836. src/sync/cloud_aggregator.py +63 -0
  837. src/sync/edge_node.py +51 -0
  838. src/sync/sync_manager.py +74 -0
@@ -0,0 +1,1591 @@
1
+ """simulator.py — AgentOS Unified Simulator
2
+
3
+ Unifies three previously separate simulation layers into a single entry point:
4
+
5
+ Layer 1 — Sensor / event impact (reactive, real-time, GMP-compliant)
6
+ Layer 2 — World model snapshot (environment state capture + DAG layout)
7
+ Layer 3 — Workflow execution (DAG execution, bandit agents, correction loop)
8
+
9
+ Public API
10
+ ----------
11
+ simulate(prompt, *, context) async Full unified simulation pipeline.
12
+ approve_and_execute(prompt, sim_result) async Post-approval execution vs predicted state.
13
+ execute_with_correction(prompt, predicted) async Retry loop with delta-based correction.
14
+
15
+ Internal helpers
16
+ ----------------
17
+ _classify_prompt(prompt, context)
18
+ _run_sensor_layer(prompt, context, db)
19
+ _run_world_model_layer(prompt, context, db, domains, scope)
20
+ _run_workflow_layer(prompt, context, store)
21
+ _capture_world_state(db)
22
+ _compare_states(before, after)
23
+ _build_execution_dag(steps)
24
+ _execute_dag(nodes, world_state, prompt, store)
25
+ _execute_single_step(capability, inputs, world_state, prompt, db)
26
+ _build_impact_assessment(step_results)
27
+ _build_correction_prompt(prompt, delta, results)
28
+ _sensor_document(db, context)
29
+ _asset_context(db, sensor)
30
+ _reading_classification(sensor, previous_value, new_value)
31
+ _sensor_effects(sensor, classification, asset_context, desired_result)
32
+ _web_search(prompt)
33
+ _document_search(db, prompt)
34
+ _retrieve_from_db(db, inputs, prompt)
35
+ _format_result(res)
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import json
41
+ import logging
42
+ import os
43
+ import re
44
+ import time
45
+ from datetime import datetime, timezone
46
+ from typing import Any
47
+ from uuid import uuid4
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Constants
54
+ # ---------------------------------------------------------------------------
55
+
56
+ DOMAIN_COLLECTIONS: dict[str, list[str]] = {
57
+ "sops": ["sops"],
58
+ "process_definitions": ["process_definitions", "process_definition_canvas_layouts"],
59
+ "material_flows": ["material_flows", "material_flow_nodes"],
60
+ "equipment": ["pharmaceutical_machines", "pharmaceutical_equipment"],
61
+ "inventory": ["inventory_items", "inventory_lots", "inventory_reservations", "stock_movements"],
62
+ "batch": ["batch_records", "batch_production_execution_records", "executed_bmr_records", "executed_bpr_records"],
63
+ "quality": ["cpp_cqa_registry", "ipc_result_records", "deviation_records", "capa_records"],
64
+ "maintenance": ["maintenance", "cleaning", "calibrations", "downtime"],
65
+ "procurement": ["purchase_orders", "procurement_requisitions"],
66
+ "orders": ["orders", "customer_orders", "work_orders"],
67
+ "shipping": ["shipments", "shipment_lines", "shipping_events"],
68
+ "documents": ["document_metadata", "documents", "uploaded_files"],
69
+ "approvals": ["gxp_change_controls", "gxp_proposed_changes", "approvals", "notifications"],
70
+ }
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Entity collections — tracked with name + status for loss calculation
74
+ # ---------------------------------------------------------------------------
75
+
76
+ ENTITY_TRACKED_COLLECTIONS: dict[str, dict[str, str]] = {
77
+ "industrial_plants": {"name_field": "name", "status_field": "status"},
78
+ "industrial_lines": {"name_field": "name", "status_field": "status"},
79
+ "industrial_stages": {"name_field": "name", "status_field": "status"},
80
+ "industrial_workstations": {"name_field": "name", "status_field": "status"},
81
+ "pharmaceutical_machines": {"name_field": "name", "status_field": "status"},
82
+ "pharmaceutical_equipment": {"name_field": "name", "status_field": "status"},
83
+ "production_batches": {"name_field": "batch_number", "status_field": "status"},
84
+ "batch_production_execution_records": {"name_field": "batch_execution_record_id", "status_field": "status"},
85
+ "batch_step_executions": {"name_field": "step_name", "status_field": "status"},
86
+ "work_orders": {"name_field": "title", "status_field": "status"},
87
+ "gxp_change_controls": {"name_field": "title", "status_field": "status"},
88
+ }
89
+
90
+ SIM_RUNS_COLLECTION = "simulation_runs"
91
+ SIM_AUDITS_COLLECTION = "simulation_audits"
92
+ SIM_TRANSITIONS_COLLECTION = "simulation_transitions"
93
+
94
+ # Keywords that trigger the sensor layer
95
+ _SENSOR_KEYWORDS = {
96
+ "sensor", "reading", "threshold", "alarm", "alert", "temperature",
97
+ "pressure", "humidity", "weight", "variation", "drift", "excursion",
98
+ "calibration", "measurement", "value", "spike", "deviation", "trend",
99
+ }
100
+
101
+ # Keywords that suggest a world-model / what-if query
102
+ _WHATIF_KEYWORDS = {
103
+ "what if", "what would", "simulate", "impact", "scenario", "effect of",
104
+ "consequence", "predict", "forecast", "change", "modify", "update",
105
+ "replace", "swap", "upgrade",
106
+ }
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Utilities
111
+ # ---------------------------------------------------------------------------
112
+
113
+ def _now() -> datetime:
114
+ return datetime.now(timezone.utc)
115
+
116
+
117
+ def _num(value: Any) -> float | None:
118
+ try:
119
+ return None if (value is None or value == "") else float(value)
120
+ except (TypeError, ValueError):
121
+ return None
122
+
123
+
124
+ def _text(value: Any) -> str:
125
+ return str(value or "").strip()
126
+
127
+
128
+ def _projection() -> dict[str, int]:
129
+ return {"_id": 0, "embedding": 0}
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Prompt classification
134
+ # ---------------------------------------------------------------------------
135
+
136
+ def _classify_prompt(prompt: str, context: dict[str, Any]) -> dict[str, bool]:
137
+ """Return which simulation layers should run for this prompt + context."""
138
+ lower = prompt.lower()
139
+
140
+ sensor_score = sum(1 for kw in _SENSOR_KEYWORDS if kw in lower)
141
+ has_sensor_context = bool(
142
+ context.get("sensor_id") or context.get("sensor_name")
143
+ or context.get("previous_value") is not None
144
+ or context.get("new_value") is not None
145
+ )
146
+ run_sensor = has_sensor_context or sensor_score >= 2
147
+
148
+ whatif_score = sum(1 for kw in _WHATIF_KEYWORDS if kw in lower)
149
+ run_world_model = whatif_score >= 1 or bool(context.get("scenario"))
150
+
151
+ # Workflow layer always runs — it is the backbone execution engine.
152
+ run_workflow = True
153
+
154
+ return {
155
+ "sensor": run_sensor,
156
+ "world_model": run_world_model,
157
+ "workflow": run_workflow,
158
+ }
159
+
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # Public entry points
163
+ # ---------------------------------------------------------------------------
164
+
165
+ async def simulate(
166
+ prompt: str,
167
+ *,
168
+ context: dict[str, Any] | None = None,
169
+ ) -> dict[str, Any]:
170
+ """Unified simulation entry point.
171
+
172
+ Accepts a free-text prompt and an optional context dict. Automatically
173
+ activates whichever simulation layers are relevant, then merges their
174
+ results into a single structured response.
175
+
176
+ context keys (all optional)
177
+ ----------------------------
178
+ sensor_id, sensor_name — trigger sensor layer
179
+ previous_value, new_value — required by sensor layer
180
+ desired_result — goal statement for sensor layer
181
+ scenario — dict describing a what-if scenario
182
+ domains — list[str] of domain names for world model
183
+ scope — dict of scope filters (plant_id, line_id …)
184
+ store — AgentOS shared state store (Mem0)
185
+ db — AsyncIOMotorDatabase (injected if available)
186
+ """
187
+ ctx = context or {}
188
+ simulation_id = f"sim-{uuid4().hex[:12]}"
189
+ started_at = _now()
190
+
191
+ layers = _classify_prompt(prompt, ctx)
192
+ logger.info("simulate(%s) layers=%s", simulation_id, layers)
193
+
194
+ db = ctx.get("db")
195
+ store = ctx.get("store")
196
+
197
+ results: dict[str, Any] = {
198
+ "simulation_id": simulation_id,
199
+ "prompt": prompt,
200
+ "layers_activated": layers,
201
+ "sensor_result": None,
202
+ "world_model_result": None,
203
+ "workflow_result": None,
204
+ "state_before": None,
205
+ "state_after": None,
206
+ "state_delta": None,
207
+ "impact_assessment": None,
208
+ "grounding_score": 0.0,
209
+ "needs_correction": False,
210
+ "correction_prompt": None,
211
+ "feasibility_verdict": "unknown",
212
+ "created_at": started_at,
213
+ }
214
+
215
+ # --- Layer 1: Sensor / event impact ---
216
+ if layers["sensor"] and db is not None:
217
+ try:
218
+ sensor_result = await _run_sensor_layer(prompt, ctx, db)
219
+ results["sensor_result"] = sensor_result
220
+ except Exception as exc:
221
+ logger.warning("Sensor layer failed: %s", exc)
222
+ results["sensor_result"] = {"error": str(exc)}
223
+
224
+ # --- Layer 2: World model snapshot ---
225
+ if layers["world_model"] and db is not None:
226
+ try:
227
+ domains = ctx.get("domains") or list(DOMAIN_COLLECTIONS.keys())
228
+ scope = ctx.get("scope") or {}
229
+ scenario = ctx.get("scenario") or {"prompt": prompt, "scenario_type": "analysis"}
230
+ world_model_result = await _run_world_model_layer(
231
+ prompt, ctx, db, domains=domains, scope=scope, scenario=scenario
232
+ )
233
+ results["world_model_result"] = world_model_result
234
+ except Exception as exc:
235
+ logger.warning("World model layer failed: %s", exc)
236
+ results["world_model_result"] = {"error": str(exc)}
237
+
238
+ # --- Layer 3: Workflow execution ---
239
+ if layers["workflow"]:
240
+ try:
241
+ state_before = await _capture_world_state(db)
242
+ workflow_result = await _run_workflow_layer(prompt, ctx, store)
243
+ state_after = await _capture_world_state(db)
244
+ state_delta = _compare_states(state_before, state_after)
245
+ impact = _build_impact_assessment(
246
+ workflow_result.get("execution_results", [])
247
+ )
248
+
249
+ results["state_before"] = state_before
250
+ results["state_after"] = state_after
251
+ results["state_delta"] = state_delta
252
+ results["workflow_result"] = workflow_result
253
+ results["impact_assessment"] = impact
254
+
255
+ needs_correction = (
256
+ state_delta.get("has_changes", False)
257
+ and state_delta.get("severity", "low") in ("medium", "high")
258
+ )
259
+ results["needs_correction"] = needs_correction
260
+ if needs_correction:
261
+ results["correction_prompt"] = _build_correction_prompt(
262
+ prompt, state_delta, workflow_result.get("execution_results", [])
263
+ )
264
+ except Exception as exc:
265
+ logger.warning("Workflow layer failed: %s", exc)
266
+ results["workflow_result"] = {"error": str(exc)}
267
+
268
+ # --- Merge grounding score across layers ---
269
+ results["grounding_score"] = _compute_grounding_score(results)
270
+ results["feasibility_verdict"] = _compute_feasibility(results)
271
+ results["finished_at"] = _now()
272
+
273
+ # --- Loss calculation: compare predicted (state_before) vs actual (state_after) ---
274
+ if results["state_before"] is not None and results["state_after"] is not None:
275
+ loss_result = _calculate_loss(
276
+ results["state_before"], results["state_after"], prompt
277
+ )
278
+ results["loss"] = loss_result
279
+
280
+ # --- Bellman transition: store (s, a, r, s') ---
281
+ if db is not None:
282
+ action = ctx.get("action") or prompt[:200]
283
+ await _store_transition(
284
+ db,
285
+ simulation_id=simulation_id,
286
+ prompt=prompt,
287
+ action=action,
288
+ state_before=results["state_before"],
289
+ state_after=results["state_after"],
290
+ loss_result=loss_result,
291
+ grounding_score=results["grounding_score"],
292
+ feasibility_verdict=results["feasibility_verdict"],
293
+ )
294
+ else:
295
+ results["loss"] = {"loss": 1.0, "total_facts": 0, "matching_facts": 0,
296
+ "count_accuracy": 0.0, "name_accuracy": 0.0, "status_accuracy": 0.0,
297
+ "entity_details": []}
298
+
299
+ # --- Dispatch result to shared state if store is available ---
300
+ if store is not None:
301
+ try:
302
+ from src.monkey_brain.events.reducer import Action
303
+ await store.dispatch(Action(
304
+ action_type="simulation.completed",
305
+ payload={
306
+ "simulation_id": simulation_id,
307
+ "grounding_score": results["grounding_score"],
308
+ "feasibility_verdict": results["feasibility_verdict"],
309
+ "layers_activated": layers,
310
+ "needs_correction": results["needs_correction"],
311
+ },
312
+ source_thread_id=f"simulator-{simulation_id}",
313
+ ))
314
+ except Exception as exc:
315
+ logger.warning("Store dispatch failed: %s", exc)
316
+
317
+ # --- Persist to MongoDB if db is available ---
318
+ if db is not None:
319
+ try:
320
+ await db[SIM_RUNS_COLLECTION].insert_one(
321
+ {**_json_safe(results), "_inserted_at": _now()}
322
+ )
323
+ except Exception as exc:
324
+ logger.warning("Simulation persistence failed: %s", exc)
325
+
326
+ return results
327
+
328
+
329
+ async def analyze_sensor_event_impact(
330
+ prompt: str,
331
+ context: dict[str, Any],
332
+ db: Any,
333
+ ) -> dict[str, Any]:
334
+ """Public entry point for sensor/event impact analysis."""
335
+ return await _run_sensor_layer(prompt, context, db)
336
+
337
+
338
+ async def approve_and_execute(
339
+ prompt: str,
340
+ simulation_result: dict[str, Any],
341
+ *,
342
+ context: dict[str, Any] | None = None,
343
+ ) -> dict[str, Any]:
344
+ """Execute after user approval and compare actual vs predicted state.
345
+
346
+ Runs execute_with_correction against the predicted state captured during
347
+ simulate(), then returns a signed audit package with the delta.
348
+ """
349
+ predicted_state = simulation_result.get("state_after") or {}
350
+ result = await execute_with_correction(
351
+ prompt, predicted_state, context=context
352
+ )
353
+ return {
354
+ "prompt": prompt,
355
+ "simulation_id": simulation_result.get("simulation_id"),
356
+ "execution_result": result,
357
+ "predicted_state": predicted_state,
358
+ "actual_state": result.get("state_after", {}),
359
+ "delta": result.get("delta_with_predicted", {}),
360
+ "corrections_applied": result.get("correction_count", 0),
361
+ "final_status": result.get("final_status", "unknown"),
362
+ "approved_at": _now(),
363
+ }
364
+
365
+
366
+ async def execute_with_correction(
367
+ prompt: str,
368
+ predicted_state: dict[str, Any],
369
+ *,
370
+ context: dict[str, Any] | None = None,
371
+ max_retries: int = 3,
372
+ ) -> dict[str, Any]:
373
+ """Execute and correct based on delta against a predicted state.
374
+
375
+ Each retry replaces the prompt with the auto-generated correction prompt
376
+ so the compiler can pick a safer workflow on the next attempt.
377
+ """
378
+ correction_count = 0
379
+ last_result: dict[str, Any] = {}
380
+ current_prompt = prompt
381
+
382
+ for attempt in range(max_retries):
383
+ result = await simulate(current_prompt, context=context)
384
+ if "error" in result:
385
+ return {"error": result["error"], "attempts": attempt + 1}
386
+
387
+ actual_state = result.get("state_after") or {}
388
+ delta = _compare_states(predicted_state, actual_state)
389
+
390
+ last_result = result
391
+ last_result["attempt"] = attempt + 1
392
+ last_result["delta_with_predicted"] = delta
393
+
394
+ if not delta.get("has_changes") or delta.get("severity") == "low":
395
+ last_result["correction_applied"] = False
396
+ last_result["final_status"] = "accepted"
397
+ return last_result
398
+
399
+ correction_count += 1
400
+ correction_prompt = _build_correction_prompt(
401
+ current_prompt, delta, result.get("workflow_result", {}).get("execution_results", [])
402
+ )
403
+ last_result["correction_prompt"] = correction_prompt
404
+ last_result["correction_applied"] = True
405
+ current_prompt = correction_prompt
406
+
407
+ last_result["final_status"] = "max_retries_exceeded"
408
+ last_result["correction_count"] = correction_count
409
+ return last_result
410
+
411
+
412
+ # ---------------------------------------------------------------------------
413
+ # Layer 1 — Sensor / event impact
414
+ # ---------------------------------------------------------------------------
415
+
416
+ async def _run_sensor_layer(
417
+ prompt: str,
418
+ context: dict[str, Any],
419
+ db: Any,
420
+ ) -> dict[str, Any]:
421
+ """Classify a sensor event and determine GMP-required actions."""
422
+ sensor = await _sensor_document(db, context)
423
+ if not sensor:
424
+ # Try to infer sensor from prompt when no explicit sensor context given.
425
+ sensor = await db["sensors"].find_one(
426
+ {"$or": [
427
+ {"name": {"$regex": re.escape(prompt[:60]), "$options": "i"}},
428
+ {"sensor_type": {"$regex": r"weight|temperature|pressure|humidity", "$options": "i"}},
429
+ ]},
430
+ _projection(),
431
+ ) or {}
432
+
433
+ if not sensor:
434
+ return {
435
+ "status": "no_sensor_found",
436
+ "message": "No sensor matched the prompt or context.",
437
+ }
438
+
439
+ previous_value = _num(context.get("previous_value"))
440
+ new_value = _num(context.get("new_value"))
441
+
442
+ if previous_value is None or new_value is None:
443
+ return {
444
+ "status": "sensor_found",
445
+ "sensor": sensor,
446
+ "message": "Sensor located but previous_value / new_value not provided — skipping classification.",
447
+ }
448
+
449
+ desired_result = _text(context.get("desired_result")) or (
450
+ "Keep the process compliant and prevent unsafe downstream release."
451
+ )
452
+ asset_ctx = await _asset_context(db, sensor)
453
+ classification = _reading_classification(sensor, previous_value, new_value)
454
+ effects = _sensor_effects(sensor, classification, asset_ctx, desired_result)
455
+
456
+ return {
457
+ "status": "analyzed",
458
+ "sensor": {
459
+ "sensor_id": sensor.get("sensor_id"),
460
+ "name": sensor.get("name"),
461
+ "sensor_type": sensor.get("sensor_type"),
462
+ "unit": sensor.get("measurement_unit"),
463
+ },
464
+ "reading_change": classification,
465
+ "effects": effects,
466
+ "asset_context": {
467
+ "stage_id": asset_ctx.get("stage_id"),
468
+ "linked_sops_count": len(asset_ctx.get("linked_sops", [])),
469
+ "related_work_orders_count": len(asset_ctx.get("related_work_orders", [])),
470
+ },
471
+ }
472
+
473
+
474
+ async def _sensor_document(db: Any, context: dict[str, Any]) -> dict[str, Any] | None:
475
+ if db is None:
476
+ return None
477
+ sensor_id = _text(context.get("sensor_id"))
478
+ sensor_name = _text(context.get("sensor_name"))
479
+ clauses: list[dict[str, Any]] = []
480
+ if sensor_id:
481
+ clauses += [
482
+ {"sensor_id": {"$regex": f"^{re.escape(sensor_id)}$", "$options": "i"}},
483
+ {"id": {"$regex": f"^{re.escape(sensor_id)}$", "$options": "i"}},
484
+ ]
485
+ if sensor_name:
486
+ clauses.append({"name": {"$regex": re.escape(sensor_name), "$options": "i"}})
487
+ if not clauses:
488
+ return None
489
+ return await db["sensors"].find_one({"$or": clauses}, _projection())
490
+
491
+
492
+ async def _asset_context(db: Any, sensor: dict[str, Any]) -> dict[str, Any]:
493
+ if db is None:
494
+ return {"machine": None, "equipment": None, "workstation": None,
495
+ "stage_id": None, "linked_sops": [], "related_work_orders": []}
496
+
497
+ machine_id = _text(sensor.get("machine_id"))
498
+ equipment_id = _text(sensor.get("equipment_id"))
499
+ workstation_id = _text(sensor.get("workstation_id"))
500
+
501
+ machine = None
502
+ if machine_id:
503
+ machine = await db["pharmaceutical_machines"].find_one(
504
+ {"$or": [{"machine_id": machine_id}, {"id": machine_id}]}, _projection()
505
+ )
506
+ equipment = None
507
+ if equipment_id:
508
+ equipment = await db["pharmaceutical_equipment"].find_one(
509
+ {"$or": [{"equipment_id": equipment_id}, {"id": equipment_id}]}, _projection()
510
+ )
511
+
512
+ if not workstation_id:
513
+ workstation_id = _text(
514
+ (machine or {}).get("workstation_id") or (equipment or {}).get("workstation_id")
515
+ )
516
+ workstation = None
517
+ if workstation_id:
518
+ workstation = await db["workstations"].find_one(
519
+ {"$or": [{"id": workstation_id}, {"workstation_id": workstation_id}]}, _projection()
520
+ )
521
+
522
+ stage_id = _text(
523
+ (machine or {}).get("stage_id") or (equipment or {}).get("stage_id")
524
+ or (workstation or {}).get("stage_id") or sensor.get("stage_id")
525
+ )
526
+
527
+ linked_sops: list[dict] = []
528
+ sop_clauses: list[dict] = []
529
+ for key, val in [("stage_id", stage_id), ("workstation_id", workstation_id),
530
+ ("machine_id", machine_id), ("equipment_id", equipment_id)]:
531
+ if val:
532
+ sop_clauses.extend([{key: val}, {f"process_definition.{key}": val}])
533
+ if sop_clauses:
534
+ linked_sops = await db["sops"].find(
535
+ {"$or": sop_clauses}, _projection()
536
+ ).limit(25).to_list(25)
537
+
538
+ related_work_orders: list[dict] = []
539
+ wo_clauses: list[dict] = []
540
+ if machine_id:
541
+ wo_clauses += [{"machine_id": machine_id}, {"equipment_id": machine_id}]
542
+ if equipment_id:
543
+ wo_clauses.append({"equipment_id": equipment_id})
544
+ if wo_clauses:
545
+ related_work_orders = await db["work_orders"].find(
546
+ {"$or": wo_clauses}, _projection()
547
+ ).limit(10).to_list(10)
548
+
549
+ return {
550
+ "machine": machine,
551
+ "equipment": equipment,
552
+ "workstation": workstation,
553
+ "stage_id": stage_id or None,
554
+ "linked_sops": linked_sops,
555
+ "related_work_orders": related_work_orders,
556
+ }
557
+
558
+
559
+ def _reading_classification(
560
+ sensor: dict[str, Any], previous_value: float, new_value: float
561
+ ) -> dict[str, Any]:
562
+ low = _num(sensor.get("alert_threshold_low"))
563
+ high = _num(sensor.get("alert_threshold_high"))
564
+ range_min = _num(sensor.get("measurement_range_min"))
565
+ range_max = _num(sensor.get("measurement_range_max"))
566
+
567
+ delta = new_value - previous_value
568
+ delta_pct = (delta / previous_value * 100.0) if previous_value else None
569
+ threshold_crossed = bool(
570
+ (high is not None and new_value >= high) or (low is not None and new_value <= low)
571
+ )
572
+ range_exceeded = bool(
573
+ (range_max is not None and new_value > range_max)
574
+ or (range_min is not None and new_value < range_min)
575
+ )
576
+ approaching_high = bool(high is not None and new_value < high and new_value >= high * 0.85)
577
+ approaching_low = bool(low is not None and new_value > low and new_value <= low * 1.15)
578
+ significant_delta = bool(delta_pct is not None and abs(delta_pct) >= 5.0)
579
+
580
+ if range_exceeded:
581
+ severity, event_state = "critical", "measurement_range_exceeded"
582
+ elif threshold_crossed:
583
+ severity, event_state = "high", "alert_threshold_crossed"
584
+ elif approaching_high or approaching_low or significant_delta:
585
+ severity, event_state = "medium", "warning_trend"
586
+ else:
587
+ severity, event_state = "low", "within_limits"
588
+
589
+ return {
590
+ "previous_value": previous_value,
591
+ "new_value": new_value,
592
+ "delta": round(delta, 6),
593
+ "delta_pct": round(delta_pct, 3) if delta_pct is not None else None,
594
+ "alert_threshold_low": low,
595
+ "alert_threshold_high": high,
596
+ "measurement_range_min": range_min,
597
+ "measurement_range_max": range_max,
598
+ "threshold_crossed": threshold_crossed,
599
+ "range_exceeded": range_exceeded,
600
+ "approaching_high_threshold": approaching_high,
601
+ "approaching_low_threshold": approaching_low,
602
+ "significant_delta": significant_delta,
603
+ "event_state": event_state,
604
+ "severity": severity,
605
+ }
606
+
607
+
608
+ def _sensor_effects(
609
+ sensor: dict[str, Any],
610
+ classification: dict[str, Any],
611
+ asset_ctx: dict[str, Any],
612
+ desired_result: str,
613
+ ) -> dict[str, Any]:
614
+ sensor_name = _text(sensor.get("name") or sensor.get("sensor_id") or "sensor")
615
+ unit = _text(sensor.get("measurement_unit")) or "units"
616
+ threshold_crossed = classification["threshold_crossed"]
617
+ warning = classification["event_state"] == "warning_trend"
618
+ new_val = classification["new_value"]
619
+
620
+ if threshold_crossed:
621
+ summary = f"{sensor_name} crossed alert threshold at {new_val} {unit}."
622
+ operational = [
623
+ "Place affected asset/process on controlled hold.",
624
+ "Execute threshold-response checklist.",
625
+ "Notify Facilities, Engineering, and QA.",
626
+ "Investigate sensor validity and product impact before release.",
627
+ ]
628
+ changes_needed = [
629
+ {"area": "Quality workflow", "change": "Open deviation/CAPA record.",
630
+ "approval_required": True, "target_records": ["deviation_records", "capa_records"]},
631
+ {"area": "Release gate", "change": "Hold downstream release until QA disposition.",
632
+ "approval_required": True},
633
+ ]
634
+ elif warning:
635
+ summary = f"{sensor_name} is trending toward threshold at {new_val} {unit}."
636
+ operational = [
637
+ "Continue under heightened monitoring.",
638
+ "Verify sensor calibration status.",
639
+ "Prepare escalation if trend persists.",
640
+ ]
641
+ changes_needed = [
642
+ {"area": "Preventive action", "change": "Schedule follow-up monitoring.",
643
+ "approval_required": False, "target_records": ["calibration_records", "maintenance_logs"]},
644
+ ]
645
+ else:
646
+ summary = f"{sensor_name} changed to {new_val} {unit}, within configured limits."
647
+ operational = ["Record the reading and continue normal monitoring."]
648
+ changes_needed = [
649
+ {"area": "Event record", "change": "Record before/after reading and delta.",
650
+ "approval_required": False},
651
+ ]
652
+
653
+ return {
654
+ "summary": summary,
655
+ "desired_result": desired_result,
656
+ "impacted": {
657
+ "sensor": {"sensor_id": sensor.get("sensor_id"), "name": sensor_name,
658
+ "unit": unit, "status": sensor.get("status")},
659
+ "asset": {"machine_id": sensor.get("machine_id"),
660
+ "equipment_id": sensor.get("equipment_id"),
661
+ "stage_id": asset_ctx.get("stage_id")},
662
+ "linked_sops": [
663
+ {"id": s.get("id") or s.get("sop_id"), "title": s.get("title") or s.get("name")}
664
+ for s in asset_ctx.get("linked_sops", [])
665
+ ],
666
+ },
667
+ "result_effect": {
668
+ "event_state": classification["event_state"],
669
+ "severity": classification["severity"],
670
+ "operational_effects": operational,
671
+ },
672
+ "changes_needed": changes_needed,
673
+ }
674
+
675
+
676
+ # ---------------------------------------------------------------------------
677
+ # Layer 2 — World model snapshot
678
+ # ---------------------------------------------------------------------------
679
+
680
+ async def _run_world_model_layer(
681
+ prompt: str,
682
+ context: dict[str, Any],
683
+ db: Any,
684
+ *,
685
+ domains: list[str],
686
+ scope: dict[str, Any],
687
+ scenario: dict[str, Any],
688
+ sample_limit: int = 25,
689
+ ) -> dict[str, Any]:
690
+ """Build a world state snapshot and compile a simulation DAG layout."""
691
+ if db is None:
692
+ return {"status": "skipped", "reason": "No database connection."}
693
+
694
+ # Build scoped query
695
+ scope_keys = (
696
+ "plant_id", "line_id", "stage_id", "workstation_id",
697
+ "machine_id", "equipment_id", "batch_id", "order_id",
698
+ )
699
+ values = {k: scope[k] for k in scope_keys if scope.get(k)}
700
+ query = {"$or": [{k: v} for k, v in values.items()]} if values else {}
701
+ proj = _projection()
702
+
703
+ # Collect domain collections
704
+ collections: list[str] = []
705
+ for domain in domains:
706
+ collections.extend(DOMAIN_COLLECTIONS.get(domain, [domain]))
707
+ collections = list(dict.fromkeys(collections))
708
+
709
+ sampled: dict[str, list[dict]] = {}
710
+ counts: dict[str, int] = {}
711
+ for col in collections:
712
+ try:
713
+ limit = sample_limit if query else min(sample_limit, 5)
714
+ docs = await db[col].find(query or {}, proj).limit(limit).to_list(limit)
715
+ sampled[col] = [_compact(d) for d in docs]
716
+ counts[col] = await db[col].count_documents(query or {})
717
+ except Exception:
718
+ sampled[col] = []
719
+ counts[col] = 0
720
+
721
+ snapshot_id = f"snapshot-{uuid4().hex[:10]}"
722
+ snapshot = {
723
+ "snapshot_id": snapshot_id,
724
+ "scenario": scenario,
725
+ "scope": scope,
726
+ "domains": domains,
727
+ "record_counts": counts,
728
+ "records_summary": {col: len(recs) for col, recs in sampled.items()},
729
+ "created_at": _now(),
730
+ }
731
+
732
+ # LLM impact assessment
733
+ impact = await _llm_impact_assessment(prompt, scenario, snapshot)
734
+
735
+ return {
736
+ "status": "completed",
737
+ "snapshot_id": snapshot_id,
738
+ "record_counts": counts,
739
+ "impact_assessment": impact,
740
+ "scenario": scenario,
741
+ }
742
+
743
+
744
+ async def _llm_impact_assessment(
745
+ prompt: str,
746
+ scenario: dict[str, Any],
747
+ snapshot: dict[str, Any],
748
+ ) -> dict[str, Any]:
749
+ """Call LLM for world model impact assessment. Tries Ollama → OpenRouter → fallback."""
750
+ system_msg = (
751
+ "You are a world model simulation impact assessor for GMP pharmaceutical manufacturing. "
752
+ "Given a prompt, scenario, and world state snapshot, assess the impact step by step. "
753
+ "Identify affected entities, state changes, risks, and required approvals. "
754
+ "Return only valid JSON."
755
+ )
756
+ user_msg = json.dumps({
757
+ "prompt": prompt,
758
+ "scenario": scenario,
759
+ "world_state_summary": {
760
+ "domains": snapshot.get("domains", []),
761
+ "record_counts": snapshot.get("record_counts", {}),
762
+ },
763
+ "required_response_schema": {
764
+ "step_analysis": [{"step_number": 0, "entities_affected": [],
765
+ "impact_on_state": "", "risks_or_constraints": []}],
766
+ "cumulative_results": {"total_entities_affected": 0,
767
+ "collections_impacted": [],
768
+ "approvals_required": [],
769
+ "compliance_impacts": []},
770
+ "assessment_summary": "",
771
+ "feasibility_verdict": "feasible|risky|not_recommended",
772
+ "recommended_precautions": [],
773
+ },
774
+ }, default=str)
775
+
776
+ result = await _call_ollama(system_msg, user_msg)
777
+ if result:
778
+ result["determination_source"] = "ollama"
779
+ return result
780
+
781
+ api_key = os.getenv("OPENROUTER_API_KEY", "")
782
+ if api_key:
783
+ result = await _call_openrouter(system_msg, user_msg, api_key)
784
+ if result:
785
+ result["determination_source"] = "openrouter"
786
+ return result
787
+
788
+ return {
789
+ "assessment_summary": f"Fallback assessment for: {prompt[:120]}",
790
+ "feasibility_verdict": "feasible",
791
+ "step_analysis": [],
792
+ "cumulative_results": {"total_entities_affected": 0, "collections_impacted": [],
793
+ "approvals_required": [], "compliance_impacts": []},
794
+ "recommended_precautions": [],
795
+ "determination_source": "fallback",
796
+ }
797
+
798
+
799
+ async def _call_ollama(system_msg: str, user_msg: str) -> dict | None:
800
+ try:
801
+ import httpx
802
+ base = os.getenv("OLLAMA_BASE_URL", "http://127.0.0.1:11434").rstrip("/")
803
+ model = os.getenv("OLLAMA_MODEL", "gemma3:latest")
804
+ async with httpx.AsyncClient(timeout=120.0) as client:
805
+ r = await client.post(f"{base}/api/chat", json={
806
+ "model": model, "stream": False,
807
+ "messages": [{"role": "system", "content": system_msg},
808
+ {"role": "user", "content": user_msg}],
809
+ })
810
+ if r.status_code != 200:
811
+ return None
812
+ content = r.json().get("message", {}).get("content", "")
813
+ return _parse_llm_json(content)
814
+ except Exception as exc:
815
+ logger.warning("Ollama call failed: %s", exc)
816
+ return None
817
+
818
+
819
+ async def _call_openrouter(system_msg: str, user_msg: str, api_key: str) -> dict | None:
820
+ try:
821
+ import httpx
822
+ base = os.getenv("OPENROUTER_API_BASE_URL", "https://openrouter.ai/api/v1").rstrip("/")
823
+ model = os.getenv("OPENROUTER_MODEL", "openai/gpt-4o-mini")
824
+ async with httpx.AsyncClient(timeout=30.0) as client:
825
+ r = await client.post(
826
+ f"{base}/chat/completions",
827
+ headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
828
+ json={"model": model, "response_format": {"type": "json_object"},
829
+ "messages": [{"role": "system", "content": system_msg},
830
+ {"role": "user", "content": user_msg}]},
831
+ )
832
+ if r.status_code != 200:
833
+ return None
834
+ content = r.json().get("choices", [{}])[0].get("message", {}).get("content", "")
835
+ return _parse_llm_json(content)
836
+ except Exception as exc:
837
+ logger.warning("OpenRouter call failed: %s", exc)
838
+ return None
839
+
840
+
841
+ def _parse_llm_json(content: str) -> dict | None:
842
+ try:
843
+ return json.loads(content)
844
+ except json.JSONDecodeError:
845
+ pass
846
+ m = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", content, re.DOTALL)
847
+ if m:
848
+ try:
849
+ return json.loads(m.group(1))
850
+ except json.JSONDecodeError:
851
+ pass
852
+ s, e = content.find("{"), content.rfind("}")
853
+ if s != -1 and e > s:
854
+ try:
855
+ return json.loads(content[s:e + 1])
856
+ except json.JSONDecodeError:
857
+ pass
858
+ return None
859
+
860
+
861
+ # ---------------------------------------------------------------------------
862
+ # Layer 3 — Workflow execution
863
+ # ---------------------------------------------------------------------------
864
+
865
+ async def _run_workflow_layer(
866
+ prompt: str,
867
+ context: dict[str, Any],
868
+ store: Any,
869
+ ) -> dict[str, Any]:
870
+ """Classify goal → compile workflow → build DAG → execute with bandit agents."""
871
+ try:
872
+ from src.monkey_brain.goals.goal_classifier import classify_goal
873
+ from compiler import compile as _compile # type: ignore[import]
874
+ except ImportError as exc:
875
+ logger.warning("Workflow layer imports unavailable: %s", exc)
876
+ return {"status": "skipped", "reason": str(exc), "execution_results": []}
877
+
878
+ goal = await classify_goal(prompt)
879
+ if not goal:
880
+ try:
881
+ from src.monkey_brain.goals.goal import Goal, GoalType
882
+ goal = Goal(
883
+ name="simulation",
884
+ goal_type=GoalType.ANALYZE,
885
+ required_inputs=["question"],
886
+ required_outputs=["impact_assessment"],
887
+ pipeline_id="simulation",
888
+ expert="simulation",
889
+ metadata={"question": prompt},
890
+ )
891
+ except ImportError:
892
+ return {"status": "skipped", "reason": "Goal class unavailable.", "execution_results": []}
893
+
894
+ compilation_result = await _compile(goal)
895
+ if "error" in compilation_result:
896
+ return {"status": "error", "error": compilation_result["error"], "execution_results": []}
897
+
898
+ steps = compilation_result.get("steps", [])
899
+ if not steps:
900
+ return {"status": "no_steps", "execution_results": []}
901
+
902
+ dag = _build_execution_dag(steps)
903
+
904
+ db = context.get("db")
905
+ execution_results = await _execute_dag(list(dag.nodes), {"status": "ready"}, prompt, store, db)
906
+
907
+ return {
908
+ "status": "completed",
909
+ "goal": getattr(goal, "name", str(goal)),
910
+ "workflow_id": compilation_result.get("workflow_id"),
911
+ "steps_count": len(steps),
912
+ "execution_results": execution_results,
913
+ }
914
+
915
+
916
+ # ---------------------------------------------------------------------------
917
+ # DAG construction + execution
918
+ # ---------------------------------------------------------------------------
919
+
920
+ def _build_execution_dag(steps: list[dict[str, Any]]) -> Any:
921
+ from kernel.dag import ExecutionDAG
922
+ from kernel.dag import DAGNode
923
+ from kernel.dag import DAGEdge
924
+
925
+ nodes, edges = [], []
926
+ for step in steps:
927
+ nodes.append(DAGNode(
928
+ node_id=step.get("id", "unknown"),
929
+ operator_type=step.get("capability", "unknown"),
930
+ metadata={"inputs": step.get("inputs", []), "outputs": step.get("outputs", [])},
931
+ ))
932
+ for dep in step.get("dependencies", []):
933
+ edges.append(DAGEdge(source_node_id=dep, target_node_id=step.get("id", "unknown")))
934
+
935
+ return ExecutionDAG(dag_id=f"dag-{uuid4().hex[:8]}", nodes=nodes, edges=edges)
936
+
937
+
938
+ async def _execute_dag(
939
+ scheduled_nodes: list,
940
+ world_state: dict[str, Any],
941
+ prompt: str,
942
+ store: Any,
943
+ db: Any,
944
+ ) -> list[dict[str, Any]]:
945
+ try:
946
+ from src.monkey_brain.execution.agents.agent_bootstrap import AgentBootstrap
947
+ from src.monkey_brain.learning.capability_bandit import compute_reward
948
+ from src.monkey_brain.learning import store_example, get_similar_examples
949
+ from src.monkey_brain.learning.agent_bandit import select_agent, record_agent_reward
950
+ except ImportError:
951
+ # Run without bandit if runtime imports are unavailable
952
+ results = []
953
+ for node in scheduled_nodes:
954
+ exec_result = await _execute_single_step(
955
+ node.operator_type,
956
+ node.metadata.get("inputs", []) if node.metadata else [],
957
+ world_state, prompt, db,
958
+ )
959
+ results.append({"node_id": node.node_id, "operator_type": node.operator_type,
960
+ "result": exec_result, "agent_selected": None})
961
+ return results
962
+
963
+ agent_bootstrap = AgentBootstrap()
964
+ all_agents = agent_bootstrap.list_agents()
965
+ cap_to_agents: dict[str, list[str]] = {}
966
+ for agent in all_agents:
967
+ for cap in agent.capabilities:
968
+ cap_to_agents.setdefault(cap, []).append(agent.agent_id)
969
+
970
+ redis = _connect_redis()
971
+ await get_similar_examples(redis, prompt, limit=2)
972
+
973
+ results: list[dict[str, Any]] = []
974
+ for node in scheduled_nodes:
975
+ node_id = node.node_id
976
+ operator_type = node.operator_type
977
+ inputs = node.metadata.get("inputs", []) if node.metadata else []
978
+
979
+ available = list(set(
980
+ aid
981
+ for cap, aids in cap_to_agents.items()
982
+ if cap.lower() == operator_type.lower()
983
+ for aid in aids
984
+ ))
985
+
986
+ step: dict[str, Any] = {
987
+ "node_id": node_id, "operator_type": operator_type,
988
+ "available_agents": available, "agent_selected": None,
989
+ "inputs": inputs, "result": None,
990
+ "selection_method": "none",
991
+ }
992
+
993
+ if available:
994
+ selected_id = (
995
+ await select_agent(redis, operator_type, available)
996
+ if len(available) > 1 else available[0]
997
+ )
998
+ step["agent_selected"] = selected_id
999
+ step["selection_method"] = "bandit" if len(available) > 1 else "single"
1000
+
1001
+ exec_result = await _execute_single_step(
1002
+ operator_type, inputs, world_state, prompt, db
1003
+ )
1004
+ step["result"] = exec_result
1005
+
1006
+ reward = compute_reward(exec_result)
1007
+ await record_agent_reward(redis, selected_id, operator_type, reward)
1008
+ await store_example(redis, prompt, operator_type,
1009
+ _format_result(exec_result), reward,
1010
+ [{"capability": operator_type, "inputs": inputs}])
1011
+ else:
1012
+ step["result"] = await _execute_single_step(
1013
+ operator_type, inputs, world_state, prompt, db
1014
+ )
1015
+ step["selection_method"] = "fallback"
1016
+
1017
+ # Dispatch step result to shared state
1018
+ if store is not None:
1019
+ try:
1020
+ from src.monkey_brain.events.reducer import Action
1021
+ await store.dispatch(Action(
1022
+ action_type="step.completed",
1023
+ payload={
1024
+ "step_id": node_id,
1025
+ "operator_type": operator_type,
1026
+ "result": str(step["result"])[:200],
1027
+ "status": "completed",
1028
+ "agent_selected": step["agent_selected"],
1029
+ "selection_method": step["selection_method"],
1030
+ },
1031
+ source_thread_id=f"simulator-dag-{node_id}",
1032
+ ))
1033
+ except Exception as exc:
1034
+ logger.warning("store.dispatch failed for %s: %s", node_id, exc)
1035
+
1036
+ results.append(step)
1037
+
1038
+ return results
1039
+
1040
+
1041
+ async def _execute_single_step(
1042
+ capability: str,
1043
+ inputs: list[str],
1044
+ world_state: dict[str, Any],
1045
+ prompt: str,
1046
+ db: Any,
1047
+ ) -> dict[str, Any]:
1048
+ cap = capability.lower()
1049
+ if cap == "retrieve":
1050
+ if db is not None:
1051
+ result = await _retrieve_from_db(db, inputs, prompt)
1052
+ if result.get("results_found", 0) > 0:
1053
+ return result
1054
+ return await _web_search(prompt)
1055
+ elif cap == "web_search":
1056
+ return await _web_search(prompt)
1057
+ elif cap == "document_search":
1058
+ return await _document_search(db, prompt) if db else {"status": "skipped"}
1059
+ elif cap == "transform":
1060
+ return {"status": "transformed", "effect": f"Applied transformation to {inputs}"}
1061
+ elif cap == "aggregate":
1062
+ return {"status": "aggregated", "effect": f"Aggregated data from {inputs}"}
1063
+ elif cap == "validate":
1064
+ return {"status": "validated", "effect": f"Validated inputs {inputs}"}
1065
+ else:
1066
+ return {"status": "simulated", "effect": f"Simulated {capability} with inputs {inputs}"}
1067
+
1068
+
1069
+ # ---------------------------------------------------------------------------
1070
+ # World state capture + comparison (entity-aware)
1071
+ # ---------------------------------------------------------------------------
1072
+
1073
+ async def _capture_world_state(db: Any) -> dict[str, Any]:
1074
+ state: dict[str, Any] = {"collections": {}, "entities": {}, "timestamp": time.time()}
1075
+ if db is None:
1076
+ return state
1077
+ try:
1078
+ for col in (await db.list_collection_names())[:30]:
1079
+ try:
1080
+ count = await db[col].count_documents({})
1081
+ state["collections"][col] = {"count": count}
1082
+ except Exception:
1083
+ pass
1084
+
1085
+ for col, fields in ENTITY_TRACKED_COLLECTIONS.items():
1086
+ try:
1087
+ name_f = fields["name_field"]
1088
+ status_f = fields["status_field"]
1089
+ entities: list[dict] = []
1090
+ async for doc in db[col].find(
1091
+ {}, {name_f: 1, status_f: 1, "_id": 0}
1092
+ ).limit(100):
1093
+ entities.append({
1094
+ "name": doc.get(name_f, ""),
1095
+ "status": doc.get(status_f, ""),
1096
+ })
1097
+ status_counts: dict[str, int] = {}
1098
+ for e in entities:
1099
+ s = e["status"] or "unknown"
1100
+ status_counts[s] = status_counts.get(s, 0) + 1
1101
+ state["entities"][col] = {
1102
+ "count": len(entities),
1103
+ "names": sorted(e["name"] for e in entities if e["name"]),
1104
+ "statuses": status_counts,
1105
+ }
1106
+ except Exception:
1107
+ pass
1108
+ except Exception:
1109
+ pass
1110
+ return state
1111
+
1112
+
1113
+ def _compare_states(before: dict, after: dict) -> dict[str, Any]:
1114
+ before_cols = before.get("collections", {})
1115
+ after_cols = after.get("collections", {})
1116
+ changes: list[dict] = []
1117
+ for col in set(list(before_cols) + list(after_cols)):
1118
+ delta = after_cols.get(col, {}).get("count", 0) - before_cols.get(col, {}).get("count", 0)
1119
+ if delta:
1120
+ changes.append({"collection": col,
1121
+ "before": before_cols.get(col, {}).get("count", 0),
1122
+ "after": after_cols.get(col, {}).get("count", 0),
1123
+ "delta": delta})
1124
+ total = sum(abs(c["delta"]) for c in changes)
1125
+ severity = "none" if total == 0 else "low" if total <= 2 else "medium" if total <= 10 else "high"
1126
+
1127
+ before_ent = before.get("entities", {})
1128
+ after_ent = after.get("entities", {})
1129
+ entity_changes: list[dict] = []
1130
+ for col in set(list(before_ent) + list(after_ent)):
1131
+ b = before_ent.get(col, {})
1132
+ a = after_ent.get(col, {})
1133
+ b_names = set(b.get("names", []))
1134
+ a_names = set(a.get("names", []))
1135
+ b_statuses = b.get("statuses", {})
1136
+ a_statuses = a.get("statuses", {})
1137
+ added = sorted(a_names - b_names)
1138
+ removed = sorted(b_names - a_names)
1139
+ status_diff = {}
1140
+ all_statuses = set(list(b_statuses) + list(a_statuses))
1141
+ for s in all_statuses:
1142
+ bd = b_statuses.get(s, 0)
1143
+ ad = a_statuses.get(s, 0)
1144
+ if bd != ad:
1145
+ status_diff[s] = {"before": bd, "after": ad, "delta": ad - bd}
1146
+ if added or removed or status_diff:
1147
+ entity_changes.append({
1148
+ "collection": col,
1149
+ "added_names": added,
1150
+ "removed_names": removed,
1151
+ "status_changes": status_diff,
1152
+ })
1153
+
1154
+ return {
1155
+ "changes": changes, "total_changes": total, "severity": severity,
1156
+ "has_changes": bool(changes),
1157
+ "entity_changes": entity_changes,
1158
+ "has_entity_changes": bool(entity_changes),
1159
+ }
1160
+
1161
+
1162
+ # ---------------------------------------------------------------------------
1163
+ # Loss calculation
1164
+ # ---------------------------------------------------------------------------
1165
+
1166
+ def _calculate_loss(
1167
+ predicted: dict[str, Any],
1168
+ actual: dict[str, Any],
1169
+ prompt: str,
1170
+ ) -> dict[str, Any]:
1171
+ """Calculate loss between predicted and actual world states.
1172
+
1173
+ Loss = 1 - (matching facts / total facts).
1174
+ Facts include: collection counts, entity names, entity statuses.
1175
+ """
1176
+ pred_cols = predicted.get("collections", {})
1177
+ act_cols = actual.get("collections", {})
1178
+ all_cols = set(list(pred_cols) + list(act_cols))
1179
+
1180
+ count_matches = 0
1181
+ count_total = 0
1182
+ for col in all_cols:
1183
+ pc = pred_cols.get(col, {}).get("count", 0)
1184
+ ac = act_cols.get(col, {}).get("count", 0)
1185
+ count_total += 1
1186
+ if pc == ac:
1187
+ count_matches += 1
1188
+
1189
+ pred_ent = predicted.get("entities", {})
1190
+ act_ent = actual.get("entities", {})
1191
+ name_matches = 0
1192
+ name_total = 0
1193
+ status_matches = 0
1194
+ status_total = 0
1195
+ entity_details: list[dict] = []
1196
+
1197
+ for col in set(list(pred_ent) + list(act_ent)):
1198
+ p = pred_ent.get(col, {})
1199
+ a = act_ent.get(col, {})
1200
+ p_names = set(p.get("names", []))
1201
+ a_names = set(a.get("names", []))
1202
+ name_total += max(len(p_names | a_names), 1)
1203
+ name_matches += len(p_names & a_names)
1204
+
1205
+ p_stat = p.get("statuses", {})
1206
+ a_stat = a.get("statuses", {})
1207
+ for s in set(list(p_stat) + list(a_stat)):
1208
+ status_total += 1
1209
+ if p_stat.get(s, 0) == a_stat.get(s, 0):
1210
+ status_matches += 1
1211
+
1212
+ if p_names != a_names or p_stat != a_stat:
1213
+ entity_details.append({
1214
+ "collection": col,
1215
+ "predicted_names": sorted(p_names),
1216
+ "actual_names": sorted(a_names),
1217
+ "name_overlap": sorted(p_names & a_names),
1218
+ "predicted_statuses": p_stat,
1219
+ "actual_statuses": a_stat,
1220
+ })
1221
+
1222
+ total_facts = count_total + name_total + status_total
1223
+ total_matches = count_matches + name_matches + status_matches
1224
+ loss = round(1.0 - (total_matches / total_facts), 4) if total_facts else 1.0
1225
+
1226
+ return {
1227
+ "loss": loss,
1228
+ "total_facts": total_facts,
1229
+ "matching_facts": total_matches,
1230
+ "count_accuracy": round(count_matches / count_total, 4) if count_total else 1.0,
1231
+ "name_accuracy": round(name_matches / name_total, 4) if name_total else 1.0,
1232
+ "status_accuracy": round(status_matches / status_total, 4) if status_total else 1.0,
1233
+ "entity_details": entity_details,
1234
+ }
1235
+
1236
+
1237
+ # ---------------------------------------------------------------------------
1238
+ # Bellman transition values
1239
+ # ---------------------------------------------------------------------------
1240
+
1241
+ async def _store_transition(
1242
+ db: Any,
1243
+ *,
1244
+ simulation_id: str,
1245
+ prompt: str,
1246
+ action: str,
1247
+ pipeline_name: str | None = None,
1248
+ state_before: dict[str, Any],
1249
+ state_after: dict[str, Any],
1250
+ loss_result: dict[str, Any],
1251
+ grounding_score: float,
1252
+ feasibility_verdict: str,
1253
+ ) -> None:
1254
+ """Store a Bellman-style transition (s, a, r, s') for the planner."""
1255
+ if db is None:
1256
+ return
1257
+
1258
+ loss = loss_result.get("loss", 1.0)
1259
+ reward = round(1.0 - loss, 4)
1260
+
1261
+ transition = {
1262
+ "simulation_id": simulation_id,
1263
+ "prompt": prompt,
1264
+ "action": action,
1265
+ "pipeline_name": pipeline_name or action[:200],
1266
+ "state_before": _json_safe(state_before),
1267
+ "state_after": _json_safe(state_after),
1268
+ "loss": loss,
1269
+ "reward": reward,
1270
+ "count_accuracy": loss_result.get("count_accuracy", 0.0),
1271
+ "name_accuracy": loss_result.get("name_accuracy", 0.0),
1272
+ "status_accuracy": loss_result.get("status_accuracy", 0.0),
1273
+ "matching_facts": loss_result.get("matching_facts", 0),
1274
+ "total_facts": loss_result.get("total_facts", 0),
1275
+ "grounding_score": grounding_score,
1276
+ "feasibility_verdict": feasibility_verdict,
1277
+ "created_at": _now(),
1278
+ }
1279
+
1280
+ try:
1281
+ await db[SIM_TRANSITIONS_COLLECTION].insert_one(transition)
1282
+ except Exception as exc:
1283
+ logger.warning("Failed to store transition: %s", exc)
1284
+
1285
+
1286
+ async def get_transition_values(
1287
+ db: Any,
1288
+ action: str | None = None,
1289
+ pipeline_name: str | None = None,
1290
+ limit: int = 100,
1291
+ ) -> dict[str, Any]:
1292
+ """Retrieve Bellman transition values for the planner.
1293
+
1294
+ Supports query by action (exact prompt) or pipeline_name (pipeline identifier).
1295
+ """
1296
+ if db is None:
1297
+ return {"transitions": [], "action_values": {}}
1298
+
1299
+ if pipeline_name:
1300
+ # Match transitions where pipeline_name matches
1301
+ query = {"pipeline_name": pipeline_name}
1302
+ elif action:
1303
+ query = {"action": action}
1304
+ else:
1305
+ query = {}
1306
+
1307
+ transitions: list[dict] = []
1308
+ async for doc in db[SIM_TRANSITIONS_COLLECTION].find(query).sort("created_at", -1).limit(limit):
1309
+ doc.pop("_id", None)
1310
+ transitions.append(doc)
1311
+
1312
+ # Aggregate by pipeline_name (preferred) or action
1313
+ action_values: dict[str, dict] = {}
1314
+ for t in transitions:
1315
+ key = t.get("pipeline_name") or t.get("action", "")
1316
+ if key not in action_values:
1317
+ action_values[key] = {"rewards": [], "losses": [], "count": 0}
1318
+ action_values[key]["rewards"].append(t.get("reward", 0))
1319
+ action_values[key]["losses"].append(t.get("loss", 1.0))
1320
+ action_values[key]["count"] += 1
1321
+
1322
+ for a, v in action_values.items():
1323
+ v["expected_reward"] = round(sum(v["rewards"]) / len(v["rewards"]), 4) if v["rewards"] else 0
1324
+ v["expected_loss"] = round(sum(v["losses"]) / len(v["losses"]), 4) if v["losses"] else 1.0
1325
+ v["min_loss"] = round(min(v["losses"]), 4) if v["losses"] else 1.0
1326
+ v["max_loss"] = round(max(v["losses"]), 4) if v["losses"] else 1.0
1327
+ v["rewards"] = v["rewards"][-10:]
1328
+ v["losses"] = v["losses"][-10:]
1329
+
1330
+ return {"transitions": transitions, "action_values": action_values}
1331
+
1332
+ def _build_impact_assessment(step_results: list[dict[str, Any]]) -> dict[str, Any]:
1333
+ total, collections_hit, records = 0, [], []
1334
+ for step in step_results:
1335
+ r = step.get("result") or {}
1336
+ for detail in r.get("details", []):
1337
+ col = detail.get("collection")
1338
+ found = detail.get("found", 0)
1339
+ if col:
1340
+ collections_hit.append(col)
1341
+ total += found
1342
+ records.append({"collection": col, "records": found})
1343
+ return {
1344
+ "step_analysis": [
1345
+ {"step_number": i, "node_id": s.get("node_id"),
1346
+ "capability": s.get("operator_type"),
1347
+ "effect": (s.get("result") or {}).get("effect") or (s.get("result") or {}).get("status"),
1348
+ "selection_method": s.get("selection_method")}
1349
+ for i, s in enumerate(step_results)
1350
+ ],
1351
+ "cumulative_results": {
1352
+ "total_entities_affected": total,
1353
+ "collections_impacted": list(set(collections_hit)),
1354
+ "records_affected": records,
1355
+ },
1356
+ "assessment_summary": (
1357
+ f"Executed {len(step_results)} steps, "
1358
+ f"affected {total} records across {len(set(collections_hit))} collections."
1359
+ ),
1360
+ "feasibility_verdict": "feasible" if total > 0 else "no_data_found",
1361
+ }
1362
+
1363
+
1364
+ def _compute_grounding_score(results: dict[str, Any]) -> float:
1365
+ """Aggregate a grounding score 0.0–1.0 across all active layers."""
1366
+ scores: list[float] = []
1367
+
1368
+ sensor = results.get("sensor_result") or {}
1369
+ if sensor.get("status") == "analyzed":
1370
+ scores.append(1.0 if sensor.get("reading_change", {}).get("event_state") else 0.5)
1371
+
1372
+ wm = results.get("world_model_result") or {}
1373
+ if wm.get("status") == "completed":
1374
+ counts = wm.get("record_counts", {})
1375
+ populated = sum(1 for v in counts.values() if v > 0)
1376
+ total_domains = max(len(counts), 1)
1377
+ scores.append(populated / total_domains)
1378
+
1379
+ wf = results.get("workflow_result") or {}
1380
+ exec_results = wf.get("execution_results", [])
1381
+ if exec_results:
1382
+ found = sum(
1383
+ 1 for s in exec_results
1384
+ if (s.get("result") or {}).get("results_found", 0) > 0
1385
+ or (s.get("result") or {}).get("status") not in ("error", "simulated", None)
1386
+ )
1387
+ scores.append(found / max(len(exec_results), 1))
1388
+
1389
+ return round(sum(scores) / len(scores), 3) if scores else 0.0
1390
+
1391
+
1392
+ def _compute_feasibility(results: dict[str, Any]) -> str:
1393
+ score = results.get("grounding_score", 0.0)
1394
+ sensor = results.get("sensor_result") or {}
1395
+ severity = sensor.get("reading_change", {}).get("severity", "low")
1396
+
1397
+ if severity == "critical":
1398
+ return "not_recommended"
1399
+ if severity == "high":
1400
+ return "risky"
1401
+ if score >= 0.6:
1402
+ return "feasible"
1403
+ if score >= 0.3:
1404
+ return "risky"
1405
+ return "insufficient_data"
1406
+
1407
+
1408
+ def _build_correction_prompt(
1409
+ prompt: str,
1410
+ delta: dict[str, Any],
1411
+ execution_results: list[dict],
1412
+ ) -> str:
1413
+ summary = ", ".join(
1414
+ f"{c['collection']}: {c['delta']:+d} records"
1415
+ for c in delta.get("changes", [])[:5]
1416
+ )
1417
+ return (
1418
+ f"The previous simulation for '{prompt}' caused unexpected state changes: "
1419
+ f"{summary}. Retrigger with a safer workflow that minimises state changes."
1420
+ )
1421
+
1422
+
1423
+ # ---------------------------------------------------------------------------
1424
+ # Data retrieval helpers
1425
+ # ---------------------------------------------------------------------------
1426
+
1427
+ async def _web_search(prompt: str) -> dict[str, Any]:
1428
+ try:
1429
+ from src.monkey_brain.kernel.intents.helpers import (
1430
+ _ollama_web_search, _tavily_search, _fallback_web_search,
1431
+ )
1432
+ for fn in [_ollama_web_search, _tavily_search, _fallback_web_search]:
1433
+ try:
1434
+ results = await fn(prompt, limit=5)
1435
+ if results:
1436
+ return {"status": "searched", "source": "web",
1437
+ "results_found": len(results),
1438
+ "details": [{"title": r.get("title"), "text": r.get("snippet", "")[:500],
1439
+ "url": r.get("url")} for r in results[:5]]}
1440
+ except Exception:
1441
+ continue
1442
+ except ImportError:
1443
+ pass
1444
+ return {"status": "searched", "source": "web", "results_found": 0, "details": []}
1445
+
1446
+
1447
+ async def _document_search(db: Any, prompt: str) -> dict[str, Any]:
1448
+ if db is None:
1449
+ return {"status": "skipped", "results_found": 0, "details": []}
1450
+ try:
1451
+ terms = re.findall(r"\b\w{3,}\b", prompt.lower())
1452
+ results: list[dict] = []
1453
+ for col in ["sops", "documents", "uploaded_files", "document_metadata"]:
1454
+ try:
1455
+ if not await db[col].count_documents({}):
1456
+ continue
1457
+ conditions = [
1458
+ {field: {"$regex": t, "$options": "i"}}
1459
+ for t in terms[:5]
1460
+ for field in ("title", "name", "content", "description")
1461
+ ]
1462
+ docs = await db[col].find({"$or": conditions}).limit(3).to_list(3)
1463
+ if docs:
1464
+ results.append({"collection": col, "found": len(docs),
1465
+ "documents": [{k: v for k, v in d.items()
1466
+ if k != "_id" and not isinstance(v, bytes)}
1467
+ for d in docs]})
1468
+ except Exception:
1469
+ continue
1470
+ return {"status": "searched", "source": "documents", "results_found": len(results), "details": results}
1471
+ except Exception as exc:
1472
+ return {"status": "error", "error": str(exc)}
1473
+
1474
+
1475
+ async def _retrieve_from_db(db: Any, inputs: list[str], prompt: str) -> dict[str, Any]:
1476
+ if db is None:
1477
+ return {"status": "skipped", "results_found": 0, "details": []}
1478
+ try:
1479
+ all_text = " ".join(inputs) + " " + prompt
1480
+ id_patterns = re.findall(r"\b[A-Z]{2,}[-_]\w+\b", all_text)
1481
+ words = re.findall(r"\b[a-zA-Z]{3,}\b", prompt)
1482
+ terms = list(set(inputs + id_patterns + words)) or [prompt[:50]]
1483
+
1484
+ results: list[dict] = []
1485
+ for col in (await db.list_collection_names())[:30]:
1486
+ try:
1487
+ conditions = [
1488
+ {field: {"$regex": t, "$options": "i"}}
1489
+ for t in terms
1490
+ for field in ("name", "id", "batch_id", "lot_number", "label",
1491
+ "description", "plant_id")
1492
+ ]
1493
+ docs = await db[col].find({"$or": conditions}).limit(3).to_list(3)
1494
+ if docs:
1495
+ results.append({
1496
+ "collection": col, "found": len(docs),
1497
+ "documents": [{k: v for k, v in d.items()
1498
+ if k != "_id" and not isinstance(v, bytes)}
1499
+ for d in docs],
1500
+ })
1501
+ except Exception:
1502
+ continue
1503
+
1504
+ # Relationship traversal fallback
1505
+ if not results:
1506
+ for term in terms:
1507
+ for col, child_col, id_field in [
1508
+ ("industrial_plants", "industrial_lines", "plant_id"),
1509
+ ("industrial_lines", "industrial_stages", "line_id"),
1510
+ ]:
1511
+ try:
1512
+ doc = await db[col].find_one(
1513
+ {"name": {"$regex": re.escape(term), "$options": "i"}}
1514
+ )
1515
+ if doc:
1516
+ doc_id = doc.get("id", "")
1517
+ if doc_id:
1518
+ related = await db[child_col].find(
1519
+ {id_field: doc_id}
1520
+ ).limit(10).to_list(10)
1521
+ if related:
1522
+ results.append({
1523
+ "collection": child_col, "found": len(related),
1524
+ "documents": [{k: v for k, v in r.items()
1525
+ if k != "_id"} for r in related],
1526
+ "via": f"{col}: {doc.get('name')}",
1527
+ })
1528
+ except Exception:
1529
+ continue
1530
+ if results:
1531
+ break
1532
+
1533
+ return {"status": "retrieved", "results_found": len(results), "details": results}
1534
+ except Exception as exc:
1535
+ return {"status": "error", "error": str(exc)}
1536
+
1537
+
1538
+ # ---------------------------------------------------------------------------
1539
+ # Misc helpers
1540
+ # ---------------------------------------------------------------------------
1541
+
1542
+ def _compact(record: dict[str, Any]) -> dict[str, Any]:
1543
+ keep = ("id", "sop_id", "batch_id", "order_id", "machine_id", "equipment_id",
1544
+ "name", "title", "status", "stage_id", "line_id", "plant_id",
1545
+ "workstation_id", "version", "updated_at", "created_at")
1546
+ return {k: record[k] for k in keep if k in record}
1547
+
1548
+
1549
+ def _json_safe(value: Any) -> Any:
1550
+ if isinstance(value, dict):
1551
+ return {k: _json_safe(v) for k, v in value.items()}
1552
+ if isinstance(value, (list, tuple)):
1553
+ return [_json_safe(v) for v in value]
1554
+ if isinstance(value, datetime):
1555
+ return value.isoformat()
1556
+ try:
1557
+ from bson import ObjectId
1558
+ if isinstance(value, ObjectId):
1559
+ return str(value)
1560
+ except ImportError:
1561
+ pass
1562
+ return value
1563
+
1564
+
1565
+ def _connect_redis() -> Any:
1566
+ try:
1567
+ import redis as _redis
1568
+ url = os.getenv("REDIS_URL")
1569
+ if url:
1570
+ r = _redis.from_url(url)
1571
+ else:
1572
+ r = _redis.Redis(
1573
+ host=os.getenv("REDIS_HOST", "localhost"),
1574
+ port=int(os.getenv("REDIS_PORT", 6379)),
1575
+ db=int(os.getenv("REDIS_DB", 0)),
1576
+ socket_connect_timeout=2,
1577
+ )
1578
+ r.ping()
1579
+ return r
1580
+ except Exception:
1581
+ return None
1582
+
1583
+
1584
+ def _format_result(res: dict) -> str:
1585
+ parts = [
1586
+ doc.get("name") or doc.get("title", "")
1587
+ for d in res.get("details", [])[:2]
1588
+ for doc in d.get("documents", [])[:1]
1589
+ if doc.get("name") or doc.get("title")
1590
+ ]
1591
+ return "; ".join(parts) if parts else res.get("status", "no result")