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,825 @@
1
+ from mimetypes import guess_type
2
+ import os
3
+ from pathlib import Path
4
+ import shutil
5
+ from typing import Any, Dict, Optional
6
+ from urllib.parse import quote
7
+ from zoneinfo import ZoneInfo
8
+ import boto3
9
+ from fastapi import FastAPI, File, Response, UploadFile, HTTPException, BackgroundTasks
10
+ from dotenv import load_dotenv
11
+ from botocore.exceptions import BotoCoreError, ClientError
12
+ from uuid import uuid4
13
+ from datetime import datetime
14
+ import asyncio
15
+ import httpx
16
+ import logging
17
+
18
+ import json
19
+ import base64
20
+
21
+ import websockets
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Load environment variables
26
+ load_dotenv()
27
+
28
+ # env variables for s3
29
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID") or os.getenv("ACCESS_KEY_ID")
30
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY") or os.getenv("SECRET_ACCESS_KEY")
31
+ AWS_REGION = os.getenv("AWS_REGION", os.getenv("REGION", "ap-south-1"))
32
+ S3_BUCKET = os.getenv("AWS_S3_BUCKET") or os.getenv("S3_BUCKET")
33
+ S3_BACKUP_BUCKET = os.getenv("AWS_S3_BACKUP_BUCKET") or os.getenv("S3_BACKUP_BUCKET")
34
+ LOCAL_UPLOAD_DIR = Path(os.getenv("FILE_UPLOAD_DIR", "uploads/files"))
35
+
36
+ # env variabls for websocket
37
+ FORWARD_URL = os.getenv("WEBSOCKET_URL") or os.getenv("FORWARD_URL") or ""
38
+ FORWARD_TIMEOUT = int(os.getenv("WEBSOCKET_TIMEOUT", "120"))
39
+ FORWARD_ENABLED = bool(FORWARD_URL and FORWARD_URL.strip())
40
+ FORWARD_EXPECT_JSON = os.getenv("WEBSOCKET_EXPECT_JSON", "false").lower() == "true"
41
+
42
+ # Validate required config
43
+ if not S3_BUCKET:
44
+ logger.warning("S3_BUCKET is not set. Set AWS_S3_BUCKET or S3_BUCKET env var.")
45
+
46
+ # Initialize FastAPI app
47
+ app = FastAPI(
48
+ title="Document Upload API with Backup",
49
+ description="FastAPI application for Azure Container Apps",
50
+ version="1.0.0"
51
+ )
52
+
53
+ # Initialize S3 client (credentials optional if IAM role present)
54
+ s3_client_kwargs = {"region_name": AWS_REGION}
55
+ if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
56
+ s3_client_kwargs.update({
57
+ "aws_access_key_id": AWS_ACCESS_KEY_ID,
58
+ "aws_secret_access_key": AWS_SECRET_ACCESS_KEY,
59
+ })
60
+
61
+ s3_client = boto3.client("s3", **s3_client_kwargs)
62
+
63
+ logger.info(f"Forward URL: {FORWARD_URL}")
64
+ logger.info(f"Forward Enabled: {FORWARD_ENABLED}")
65
+ logger.info(f"Forward Timeout: {FORWARD_TIMEOUT}s")
66
+ logger.info(f"Forward Expect JSON: {FORWARD_EXPECT_JSON}")
67
+ logger.info(f"S3 Bucket: {S3_BUCKET}")
68
+ logger.info(f"S3 Backup Bucket: {S3_BACKUP_BUCKET or (S3_BUCKET + '-backup' if S3_BUCKET else 'N/A')}")
69
+
70
+
71
+ def _local_upload_path(date: str, filename: str) -> Path:
72
+ return LOCAL_UPLOAD_DIR / date / filename
73
+
74
+
75
+ def _local_backup_path(date: str, filename: str) -> Path:
76
+ return LOCAL_UPLOAD_DIR / "backups" / date / filename
77
+
78
+
79
+ def _save_local_file(date: str, filename: str, file_content: bytes) -> Path:
80
+ local_path = _local_upload_path(date, filename)
81
+ local_path.parent.mkdir(parents=True, exist_ok=True)
82
+ local_path.write_bytes(file_content)
83
+ return local_path
84
+
85
+
86
+ def _local_file_info(date: str, path: Path, backup_exists: bool = False) -> dict:
87
+ stat = path.stat()
88
+ return {
89
+ "filename": path.name,
90
+ "key": f"uploads/{date}/{path.name}",
91
+ "size": stat.st_size,
92
+ "last_modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
93
+ "url": str(path),
94
+ "storage_backend": "local",
95
+ "backup_exists": backup_exists,
96
+ }
97
+
98
+
99
+ def _list_local_files(date: str, create_backup: bool = True) -> dict:
100
+ folder = LOCAL_UPLOAD_DIR / date
101
+ if not folder.exists():
102
+ return {"date": date, "file_count": 0, "files": [], "message": "No files found for this date"}
103
+
104
+ files = []
105
+ for path in sorted(item for item in folder.iterdir() if item.is_file()):
106
+ backup_path = _local_backup_path(date, path.name)
107
+ backup_exists = backup_path.exists()
108
+ if create_backup and not backup_exists:
109
+ backup_path.parent.mkdir(parents=True, exist_ok=True)
110
+ shutil.copy2(path, backup_path)
111
+ backup_exists = True
112
+ files.append(_local_file_info(date, path, backup_exists=backup_exists))
113
+
114
+ return {"date": date, "file_count": len(files), "files": files, "storage_backend": "local"}
115
+
116
+
117
+ def _download_headers(filename: str) -> dict[str, str]:
118
+ safe_name = Path(filename or "download").name.replace('"', "")
119
+ encoded_name = quote(safe_name)
120
+ return {
121
+ "Content-Disposition": f"attachment; filename=\"{safe_name}\"; filename*=UTF-8''{encoded_name}",
122
+ "X-Content-Type-Options": "nosniff",
123
+ }
124
+
125
+
126
+ def _read_local_file(date: str, filename: str, *, as_attachment: bool = True) -> Response:
127
+ local_path = _local_upload_path(date, filename)
128
+ if not local_path.exists():
129
+ raise HTTPException(status_code=404, detail="File not found locally")
130
+
131
+ content_type, _ = guess_type(filename)
132
+ if not content_type:
133
+ content_type = "application/octet-stream"
134
+
135
+ headers = _download_headers(filename) if as_attachment else {"X-Content-Type-Options": "nosniff"}
136
+ return Response(content=local_path.read_bytes(), media_type=content_type, headers=headers)
137
+
138
+
139
+ def _delete_local_file(date: str, filename: str, delete_backup: bool = True) -> dict:
140
+ local_path = _local_upload_path(date, filename)
141
+ backup_path = _local_backup_path(date, filename)
142
+ if not local_path.exists():
143
+ raise HTTPException(status_code=404, detail="File not found")
144
+
145
+ local_path.unlink()
146
+ backup_deleted = False
147
+ if delete_backup and backup_path.exists():
148
+ backup_path.unlink()
149
+ backup_deleted = True
150
+
151
+ return {
152
+ "message": "File deleted successfully",
153
+ "filename": filename,
154
+ "date": date,
155
+ "storage_backend": "local",
156
+ "backup_deleted": backup_deleted,
157
+ }
158
+
159
+
160
+ def _delete_local_folder(date: str, delete_backup: bool = True) -> dict:
161
+ folder = LOCAL_UPLOAD_DIR / date
162
+ backup_folder = LOCAL_UPLOAD_DIR / "backups" / date
163
+ if not folder.exists():
164
+ raise HTTPException(status_code=404, detail=f"No files found for date: {date}")
165
+
166
+ deleted_count = sum(1 for item in folder.iterdir() if item.is_file())
167
+ shutil.rmtree(folder)
168
+ backup_deleted = False
169
+ if delete_backup and backup_folder.exists():
170
+ shutil.rmtree(backup_folder)
171
+ backup_deleted = True
172
+
173
+ return {
174
+ "message": f"Deleted {deleted_count} files for date {date}",
175
+ "date": date,
176
+ "deleted_count": deleted_count,
177
+ "storage_backend": "local",
178
+ "backup_deleted": backup_deleted,
179
+ }
180
+
181
+ async def delete_backup_folder(date: str, expected_count: int):
182
+ """Background task to delete backup folder for a specific date."""
183
+ try:
184
+ backup_bucket = S3_BACKUP_BUCKET or (S3_BUCKET + "-backup" if S3_BUCKET else None)
185
+ if not backup_bucket:
186
+ logger.warning("No backup bucket configured; skipping backup deletion.")
187
+ return
188
+
189
+ backup_prefix = f"backups/{date}/"
190
+ logger.info(f"Deleting backup folder: {backup_prefix} from {backup_bucket}")
191
+
192
+ response = s3_client.list_objects_v2(Bucket=backup_bucket, Prefix=backup_prefix)
193
+ if "Contents" in response:
194
+ objects_to_delete = [{"Key": obj["Key"]} for obj in response["Contents"]]
195
+ batch_size = 1000
196
+ deleted_total = 0
197
+ for i in range(0, len(objects_to_delete), batch_size):
198
+ batch = objects_to_delete[i:i + batch_size]
199
+ s3_client.delete_objects(Bucket=backup_bucket, Delete={"Objects": batch})
200
+ deleted_total += len(batch)
201
+ logger.info(f"✅ Deleted {deleted_total} backup files for {date}")
202
+ else:
203
+ logger.info(f"No backup files found for {date}")
204
+ except Exception as e:
205
+ logger.error(f"Failed to delete backup folder for {date}: {e}", exc_info=True)
206
+
207
+
208
+ async def upload_file_helper(
209
+ file: UploadFile = File(...),
210
+ notify: bool = True
211
+ ):
212
+ """
213
+ Upload a document to S3 and optionally forward it to another service via WebSocket.
214
+ Folder structure: uploads/YYYY-MM-DD/<uuid>.<ext>
215
+ """
216
+ try:
217
+ logger.info(f"Uploading file: {file.filename}, notify: {notify}")
218
+
219
+ # Create unique filename and S3 key
220
+ ist_now = datetime.now(ZoneInfo("Asia/Kolkata"))
221
+ date_folder = ist_now.strftime("%Y-%m-%d")
222
+ file_extension = os.path.splitext(file.filename)[-1]
223
+ unique_filename = f"{uuid4()}{file_extension}"
224
+ s3_key = f"uploads/{str(date_folder)}/{unique_filename}"
225
+
226
+ # Read file
227
+ file_content = await file.read()
228
+ logger.info(f"Read {len(file_content)} bytes from uploaded file")
229
+
230
+ # Get MIME type
231
+ mime_type = file.content_type
232
+
233
+ storage_backend = "s3"
234
+ storage_error = None
235
+ local_path = None
236
+
237
+ if S3_BUCKET:
238
+ try:
239
+ s3_client.put_object(
240
+ Bucket=S3_BUCKET,
241
+ Key=s3_key,
242
+ Body=file_content,
243
+ ContentType=file.content_type or "application/octet-stream"
244
+ )
245
+ logger.info(f"Successfully uploaded to S3: {s3_key}")
246
+ file_url = f"https://{S3_BUCKET}.s3.{AWS_REGION}.amazonaws.com/{s3_key}"
247
+ except (BotoCoreError, ClientError, Exception) as exc:
248
+ storage_backend = "local"
249
+ storage_error = str(exc)
250
+ local_path = _save_local_file(date_folder, unique_filename, file_content)
251
+ file_url = str(local_path)
252
+ logger.error(
253
+ "S3 upload failed; saved file locally at %s: %s",
254
+ local_path,
255
+ exc,
256
+ exc_info=True,
257
+ )
258
+ else:
259
+ storage_backend = "local"
260
+ storage_error = "S3 bucket is not configured"
261
+ local_path = _save_local_file(date_folder, unique_filename, file_content)
262
+ file_url = str(local_path)
263
+ logger.warning("S3 bucket is not configured; saved file locally at %s", local_path)
264
+
265
+ response_data = {
266
+ "message": "File uploaded successfully",
267
+ "filename": unique_filename,
268
+ "date_folder": date_folder,
269
+ "url": file_url,
270
+ "mime_type": mime_type,
271
+ "s3_key": s3_key if storage_backend == "s3" else None,
272
+ "storage_backend": storage_backend,
273
+ "local_path": str(local_path) if local_path else None,
274
+ "storage_error": storage_error,
275
+ }
276
+
277
+ # 4️⃣ Forward file via WebSocket
278
+ if notify and FORWARD_ENABLED:
279
+ logger.info(f"Forwarding file to WebSocket bridge at {FORWARD_URL}")
280
+
281
+ # Create temp directory if it doesn't exist
282
+ os.makedirs("/tmp", exist_ok=True)
283
+ temp_file_path = f"/tmp/{unique_filename}"
284
+
285
+ try:
286
+ # Save file temporarily for WebSocket sending
287
+ with open(temp_file_path, "wb") as f:
288
+ f.write(file_content)
289
+ logger.info(f"Saved temp file: {temp_file_path}")
290
+
291
+ logger.info(mime_type)
292
+ if mime_type == "application/pdf":
293
+
294
+ # Pass the original filename to preserve it
295
+ ws_result = await send_file_to_bridge(
296
+ file_path=temp_file_path,
297
+ original_filename=file.filename,
298
+ ws_uri=FORWARD_URL,
299
+ timeout=FORWARD_TIMEOUT
300
+ )
301
+
302
+ # Clean up temp file
303
+ try:
304
+ os.remove(temp_file_path)
305
+ logger.info(f"Cleaned up temp file: {temp_file_path}")
306
+ except Exception as cleanup_err:
307
+ logger.warning(f"Failed to cleanup temp file: {cleanup_err}")
308
+
309
+ if ws_result.get("status") == "queued":
310
+ response_data["forward_status"] = "success"
311
+ response_data["forward_response"] = ws_result
312
+ logger.info(f"✅ File forwarded successfully via WebSocket")
313
+ else:
314
+ response_data["forward_status"] = "failed"
315
+ response_data["forward_response"] = ws_result
316
+ logger.error(f"❌ File forwarding failed: {ws_result}")
317
+
318
+ elif mime_type in ("image/jpeg", "image/png", "image/jpg", "image/webp"):
319
+ logger.info("Processing image with Qwen-VL via OpenRouter for OCR")
320
+
321
+ # Extract text from image using Qwen-VL via OpenRouter
322
+ ocr_result = await extract_text_with_openrouter(file_content, mime_type)
323
+
324
+ if ocr_result.get("success"):
325
+ response_data["ocr_text"] = ocr_result.get("text")
326
+ response_data["ocr_status"] = "success"
327
+ response_data["ocr_model"] = ocr_result.get("model")
328
+ logger.info(f"✅ OCR completed: {len(ocr_result.get('text', ''))} characters extracted")
329
+ # send the extracted text to web socket
330
+ response = json.dumps({"content":ocr_result.get("text"),"mime_type":mime_type})
331
+ await send_text(response)
332
+ else:
333
+ response_data["ocr_status"] = "failed"
334
+ response_data["ocr_error"] = ocr_result.get("error")
335
+ logger.error(f"❌ OCR failed: {ocr_result.get('error')}")
336
+
337
+ # Clean up temp file
338
+ try:
339
+ os.remove(temp_file_path)
340
+ logger.info(f"Cleaned up temp file: {temp_file_path}")
341
+ except Exception as cleanup_err:
342
+ logger.warning(f"Failed to cleanup temp file: {cleanup_err}")
343
+
344
+ except Exception as forward_err:
345
+ logger.error(f"Error during file forwarding: {forward_err}")
346
+ response_data["forward_status"] = "error"
347
+ response_data["forward_error"] = str(forward_err)
348
+
349
+ return response_data
350
+
351
+ except Exception as e:
352
+ logger.error(f"Error uploading file: {str(e)}")
353
+ raise HTTPException(status_code=500, detail=f"File upload failed: {str(e)}")
354
+
355
+
356
+ async def extract_text_with_openrouter(
357
+ image_bytes: bytes,
358
+ mime_type: str,
359
+ model: str = "qwen/qwen-2-vl-72b-instruct"
360
+ ) -> dict:
361
+ """
362
+ Extract text from image using Qwen-VL through OpenRouter API.
363
+
364
+ Args:
365
+ image_bytes: Binary image data
366
+ mime_type: MIME type of the image (e.g., 'image/jpeg')
367
+ model: OpenRouter model to use (default: qwen-2-vl-72b-instruct)
368
+
369
+ Available Qwen-VL models on OpenRouter:
370
+ - qwen/qwen-2-vl-72b-instruct (most capable)
371
+ - qwen/qwen-2-vl-7b-instruct (faster, cheaper)
372
+
373
+ Returns:
374
+ dict with keys: success (bool), text (str), model (str), error (str)
375
+ """
376
+ try:
377
+ # Get OpenRouter API key from environment
378
+ openrouter_api_key = os.getenv("OPENROUTER_API_KEY")
379
+
380
+ if not openrouter_api_key:
381
+ logger.error("OpenRouter API key not configured")
382
+ return {"success": False, "error": "OPENROUTER_API_KEY not configured"}
383
+
384
+ # Encode image to base64
385
+ image_base64 = base64.b64encode(image_bytes).decode('utf-8')
386
+
387
+ # Prepare request payload for OpenRouter
388
+ payload = {
389
+ "model": model,
390
+ "messages": [
391
+ {
392
+ "role": "user",
393
+ "content": [
394
+ {
395
+ "type": "image_url",
396
+ "image_url": {
397
+ "url": f"data:{mime_type};base64,{image_base64}"
398
+ }
399
+ },
400
+ {
401
+ "type": "text",
402
+ "text": "Extract all text from this image. Return only the extracted text without any additional commentary or explanation."
403
+ }
404
+ ]
405
+ }
406
+ ]
407
+ }
408
+
409
+ # Make async HTTP request to OpenRouter
410
+ async with httpx.AsyncClient(timeout=60.0) as client:
411
+ response = await client.post(
412
+ "https://openrouter.ai/api/v1/chat/completions",
413
+ headers={
414
+ "Authorization": f"Bearer {openrouter_api_key}",
415
+ "Content-Type": "application/json",
416
+ "HTTP-Referer": os.getenv("APP_URL", "http://localhost:8000"), # Optional
417
+ "X-Title": os.getenv("APP_NAME", "Document Processor") # Optional
418
+ },
419
+ json=payload
420
+ )
421
+
422
+ if response.status_code == 200:
423
+ result = response.json()
424
+ extracted_text = result.get("choices", [{}])[0].get("message", {}).get("content", "")
425
+
426
+ return {
427
+ "success": True,
428
+ "text": extracted_text.strip(),
429
+ "model": result.get("model"),
430
+ "usage": result.get("usage"),
431
+ "provider": "openrouter"
432
+ }
433
+ else:
434
+ error_detail = response.text
435
+ logger.error(f"OpenRouter API error: {response.status_code} - {error_detail}")
436
+ return {
437
+ "success": False,
438
+ "error": f"API request failed: {response.status_code} - {error_detail}"
439
+ }
440
+
441
+ except httpx.TimeoutException:
442
+ logger.error("OpenRouter API request timed out")
443
+ return {
444
+ "success": False,
445
+ "error": "Request timed out after 60 seconds"
446
+ }
447
+ except Exception as e:
448
+ logger.error(f"Error in OCR processing with OpenRouter: {str(e)}")
449
+ return {
450
+ "success": False,
451
+ "error": str(e)
452
+ }
453
+
454
+ async def list_files_by_date_helper(date: str, create_backup: bool = True):
455
+ """
456
+ List all files uploaded on a given date with backup status.
457
+ Example: GET /files/2025-11-11
458
+ """
459
+ if not S3_BUCKET:
460
+ logger.warning("S3 bucket is not configured; listing local files for %s", date)
461
+ return _list_local_files(date, create_backup=create_backup)
462
+
463
+ try:
464
+ logger.info(f"Listing files for date: {date}")
465
+ prefix = f"uploads/{date}/"
466
+ response = s3_client.list_objects_v2(Bucket=S3_BUCKET, Prefix=prefix)
467
+
468
+ if "Contents" not in response:
469
+ logger.info(f"No files found for date: {date}")
470
+ local_result = _list_local_files(date, create_backup=create_backup)
471
+ if local_result.get("file_count", 0) > 0:
472
+ local_result["s3_message"] = "No files found in S3; returned local files"
473
+ return local_result
474
+ return {"date": date, "files": [], "message": "No files found for this date"}
475
+
476
+ files = []
477
+ backup_bucket = S3_BACKUP_BUCKET or f"{S3_BUCKET}-backup"
478
+
479
+ for obj in response["Contents"]:
480
+ key = obj["Key"]
481
+ filename = key.split("/")[-1]
482
+ backup_key = f"backups/{date}/{filename}"
483
+
484
+ backup_exists = False
485
+ try:
486
+ s3_client.head_object(Bucket=backup_bucket, Key=backup_key)
487
+ backup_exists = True
488
+ except ClientError:
489
+ backup_exists = False
490
+
491
+ if not backup_exists and create_backup:
492
+ try:
493
+ file_obj = s3_client.get_object(Bucket=S3_BUCKET, Key=key)
494
+ file_bytes = file_obj["Body"].read()
495
+ if backup_bucket:
496
+ s3_client.put_object(Bucket=backup_bucket, Key=backup_key, Body=file_bytes)
497
+ backup_exists = True
498
+ logger.info(f"Created backup for: {key}")
499
+ except Exception as e:
500
+ logger.error(f"Failed to upload {key} to backup: {e}")
501
+
502
+ file_info = {
503
+ "filename": filename,
504
+ "key": key,
505
+ "size": obj.get("Size"),
506
+ "last_modified": obj.get("LastModified").isoformat() if obj.get("LastModified") else None,
507
+ "url": f"https://{S3_BUCKET}.s3.{AWS_REGION}.amazonaws.com/{key}",
508
+ "backup_exists": backup_exists
509
+ }
510
+ files.append(file_info)
511
+
512
+ logger.info(f"Found {len(files)} files for date: {date}")
513
+ return {"date": date, "file_count": len(files), "files": files}
514
+
515
+ except (BotoCoreError, ClientError) as e:
516
+ logger.error("S3 list error; listing local files for %s: %s", date, e, exc_info=True)
517
+ result = _list_local_files(date, create_backup=create_backup)
518
+ result["s3_error"] = str(e)
519
+ return result
520
+ except Exception as e:
521
+ logger.error("Unexpected S3 list error; listing local files for %s: %s", date, e, exc_info=True)
522
+ result = _list_local_files(date, create_backup=create_backup)
523
+ result["s3_error"] = str(e)
524
+ return result
525
+
526
+ async def delete_file_helper(date: str, filename: str, delete_backup: bool = True):
527
+ """Delete a file from S3 and optionally its backup."""
528
+ if not S3_BUCKET:
529
+ logger.warning("S3 bucket is not configured; deleting local file %s/%s", date, filename)
530
+ return _delete_local_file(date, filename, delete_backup=delete_backup)
531
+
532
+ s3_key = f"uploads/{date}/{filename}"
533
+ logger.info(f"Deleting file: {s3_key}, delete_backup: {delete_backup}")
534
+
535
+ try:
536
+ # Check existence
537
+ try:
538
+ s3_client.head_object(Bucket=S3_BUCKET, Key=s3_key)
539
+ except ClientError as e:
540
+ err_code = e.response.get("Error", {}).get("Code")
541
+ if err_code in ("404", "NoSuchKey", "NotFound"):
542
+ logger.error("File not found in S3; trying local delete: %s", s3_key)
543
+ return _delete_local_file(date, filename, delete_backup=delete_backup)
544
+ else:
545
+ raise
546
+
547
+ s3_client.delete_object(Bucket=S3_BUCKET, Key=s3_key)
548
+ logger.info(f"Deleted from S3: {s3_key}")
549
+
550
+ response_data = {
551
+ "message": "File deleted successfully",
552
+ "filename": filename,
553
+ "date": date,
554
+ "s3_key": s3_key,
555
+ "backup_deleted": False
556
+ }
557
+
558
+ if delete_backup:
559
+ backup_bucket = S3_BACKUP_BUCKET or f"{S3_BUCKET}-backup"
560
+ backup_key = f"backups/{date}/{filename}"
561
+ try:
562
+ s3_client.delete_object(Bucket=backup_bucket, Key=backup_key)
563
+ response_data["backup_deleted"] = True
564
+ logger.info(f"Deleted backup: {backup_key}")
565
+ except ClientError as e:
566
+ err_code = e.response.get("Error", {}).get("Code")
567
+ if err_code in ("404", "NoSuchKey", "NotFound"):
568
+ response_data["backup_deleted"] = False
569
+ logger.info(f"No backup found: {backup_key}")
570
+ else:
571
+ raise
572
+
573
+ return response_data
574
+
575
+ except HTTPException:
576
+ raise
577
+ except (BotoCoreError, ClientError) as e:
578
+ logger.error("S3 delete error; deleting local file %s/%s: %s", date, filename, e, exc_info=True)
579
+ response_data = _delete_local_file(date, filename, delete_backup=delete_backup)
580
+ response_data["s3_error"] = str(e)
581
+ return response_data
582
+ except Exception as e:
583
+ logger.error("Unexpected S3 delete error; deleting local file %s/%s: %s", date, filename, e, exc_info=True)
584
+ response_data = _delete_local_file(date, filename, delete_backup=delete_backup)
585
+ response_data["s3_error"] = str(e)
586
+ return response_data
587
+
588
+ async def read_file_helper(date: str, filename: str, *, as_attachment: bool = True):
589
+ """
590
+ Read or download a file from S3 given its date and filename.
591
+ URL format: /files/YYYY-MM-DD/<filename>
592
+ """
593
+ if not S3_BUCKET:
594
+ logger.warning("S3 bucket is not configured; reading local file %s/%s", date, filename)
595
+ return _read_local_file(date, filename, as_attachment=as_attachment)
596
+
597
+ s3_key = f"uploads/{date}/{filename}"
598
+ logger.info(f"Reading file: {s3_key}")
599
+
600
+ try:
601
+ file_obj = s3_client.get_object(Bucket=S3_BUCKET, Key=s3_key)
602
+ file_content = file_obj["Body"].read()
603
+
604
+ content_type, _ = guess_type(filename)
605
+ if not content_type:
606
+ content_type = "application/octet-stream"
607
+
608
+ logger.info(f"Successfully read file: {s3_key} ({len(file_content)} bytes)")
609
+ headers = _download_headers(filename) if as_attachment else {"X-Content-Type-Options": "nosniff"}
610
+ return Response(content=file_content, media_type=content_type, headers=headers)
611
+
612
+ except ClientError as e:
613
+ err_code = e.response.get("Error", {}).get("Code")
614
+ if err_code in ("404", "NoSuchKey", "NotFound"):
615
+ logger.error("File not found in S3; trying local fallback: %s", s3_key)
616
+ return _read_local_file(date, filename, as_attachment=as_attachment)
617
+ logger.error("S3 read ClientError; trying local fallback: %s", e, exc_info=True)
618
+ return _read_local_file(date, filename, as_attachment=as_attachment)
619
+ except (BotoCoreError, Exception) as e:
620
+ logger.error("S3 read error; trying local fallback: %s", e, exc_info=True)
621
+ return _read_local_file(date, filename, as_attachment=as_attachment)
622
+
623
+ async def delete_folder_helper(
624
+ date: str,
625
+ delete_backup: bool = True,
626
+ background_tasks: BackgroundTasks = None
627
+ ):
628
+ """Delete all files for a specific date (entire folder)."""
629
+ if not S3_BUCKET:
630
+ logger.warning("S3 bucket is not configured; deleting local folder for %s", date)
631
+ return _delete_local_folder(date, delete_backup=delete_backup)
632
+
633
+ try:
634
+ logger.info(f"Deleting folder for date: {date}, delete_backup: {delete_backup}")
635
+ prefix = f"uploads/{date}/"
636
+ response = s3_client.list_objects_v2(Bucket=S3_BUCKET, Prefix=prefix)
637
+
638
+ if "Contents" not in response:
639
+ logger.error(f"No files found for date: {date}")
640
+ local_result = _delete_local_folder(date, delete_backup=delete_backup)
641
+ local_result["s3_message"] = "No files found in S3; deleted local folder"
642
+ return local_result
643
+
644
+ objects_to_delete = [{"Key": obj["Key"]} for obj in response["Contents"]]
645
+ deleted_count = len(objects_to_delete)
646
+
647
+ if objects_to_delete:
648
+ batch_size = 1000
649
+ for i in range(0, len(objects_to_delete), batch_size):
650
+ batch = objects_to_delete[i:i + batch_size]
651
+ s3_client.delete_objects(Bucket=S3_BUCKET, Delete={"Objects": batch})
652
+
653
+ logger.info(f"Deleted {deleted_count} files for date: {date}")
654
+
655
+ response_data = {
656
+ "message": f"Deleted {deleted_count} files for date {date}",
657
+ "date": date,
658
+ "deleted_count": deleted_count,
659
+ "backup_deleted": False
660
+ }
661
+
662
+ if delete_backup and background_tasks:
663
+ background_tasks.add_task(delete_backup_folder, date, deleted_count)
664
+ response_data["backup_deletion"] = "scheduled"
665
+ logger.info(f"Scheduled backup deletion for date: {date}")
666
+
667
+ return response_data
668
+
669
+ except HTTPException:
670
+ raise
671
+ except (BotoCoreError, ClientError) as e:
672
+ logger.error("S3 folder delete error; deleting local folder for %s: %s", date, e, exc_info=True)
673
+ response_data = _delete_local_folder(date, delete_backup=delete_backup)
674
+ response_data["s3_error"] = str(e)
675
+ return response_data
676
+ except Exception as e:
677
+ logger.error("Unexpected S3 folder delete error; deleting local folder for %s: %s", date, e, exc_info=True)
678
+ response_data = _delete_local_folder(date, delete_backup=delete_backup)
679
+ response_data["s3_error"] = str(e)
680
+ return response_data
681
+
682
+ async def send_file_to_bridge(
683
+ file_path: str,
684
+ original_filename: Optional[str] = None,
685
+ ws_uri: Optional[str] = None,
686
+ timeout: float = 30.0,
687
+ send_as_binary: bool = False
688
+ ) -> Dict[str, Any]:
689
+ """
690
+ Send a file to the WebSocket-to-NATS bridge.
691
+
692
+ Args:
693
+ file_path: Path to the file to send
694
+ original_filename: Original filename to preserve (optional)
695
+ ws_uri: WebSocket URI (default: ws://websocket-server:6789)
696
+ Use 'ws://localhost:6789' for local testing
697
+ timeout: Connection and send timeout in seconds
698
+ send_as_binary: If True, send as binary frame; if False, send as base64 JSON
699
+
700
+ Returns:
701
+ Dict with server response, e.g.:
702
+ {
703
+ "status": "queued",
704
+ "len": 12345,
705
+ "saved_file": "invoice.pdf"
706
+ }
707
+
708
+ Raises:
709
+ FileNotFoundError: If file doesn't exist
710
+ websockets.exceptions.WebSocketException: On connection/send errors
711
+ asyncio.TimeoutError: If operation times out
712
+ json.JSONDecodeError: If server response is invalid
713
+ """
714
+ # Default to Docker service name for container environments
715
+ if ws_uri is None:
716
+ ws_uri = os.getenv("WEBSOCKET_BRIDGE_URI", "ws://websocket-server:6789")
717
+
718
+ # Validate file exists
719
+ if not os.path.isfile(file_path):
720
+ raise FileNotFoundError(f"File not found: {file_path}")
721
+
722
+ # FIX 4: Use original filename if provided, otherwise use basename
723
+ file_name = original_filename if original_filename else os.path.basename(file_path)
724
+ file_size = os.path.getsize(file_path)
725
+
726
+ logger.info("Sending file to WebSocket bridge: %s (%d bytes)", file_name, file_size)
727
+ logger.debug("WebSocket URI: %s", ws_uri)
728
+ logger.debug("Send mode: %s", "binary" if send_as_binary else "base64-JSON")
729
+
730
+ try:
731
+ # Connect to WebSocket server with timeout
732
+ async with websockets.connect(ws_uri, open_timeout=timeout) as websocket:
733
+ logger.debug("✓ Connected to WebSocket server")
734
+
735
+ # Read file contents
736
+ with open(file_path, "rb") as f:
737
+ file_data = f.read()
738
+
739
+ logger.info(f"Read {len(file_data)} bytes from file")
740
+
741
+ # Send file based on mode
742
+ if send_as_binary:
743
+ # Send as raw binary frame
744
+ logger.debug("Sending binary frame...")
745
+ await websocket.send(file_data)
746
+
747
+ else:
748
+ # Send as base64-encoded JSON
749
+ logger.debug("Encoding file as base64...")
750
+ encoded_data = base64.b64encode(file_data).decode('utf-8')
751
+
752
+ payload = {
753
+ "fileName": file_name,
754
+ "data": encoded_data
755
+ }
756
+
757
+ logger.debug("Sending JSON payload with fileName: %s", file_name)
758
+ await websocket.send(json.dumps(payload))
759
+
760
+ logger.debug("Waiting for server acknowledgment...")
761
+ response = {
762
+ "status": "queued",
763
+ "note": "File forwarded to bridge (no ACK expected)"
764
+ }
765
+
766
+
767
+ # Parse response
768
+ if isinstance(response, str):
769
+ try:
770
+ ack = json.loads(response)
771
+ logger.debug("Server response JSON: %s", ack)
772
+ except json.JSONDecodeError as e:
773
+ # FIX 6: Treat non-JSON text responses as successful acknowledgment
774
+ # Many WebSocket servers return simple text confirmations like "OK" or server info
775
+ logger.info(f"WebSocket server response (non-JSON): {response[:200]}")
776
+ ack = {
777
+ "status": "queued", # Treat as success if we got a response
778
+ "server_response": str(response)[:200],
779
+ "note": "Server returned text instead of JSON - treating as acknowledgment"
780
+ }
781
+ else:
782
+ # FIX 5: Handle binary responses - also treat as success
783
+ logger.info(f"WebSocket server sent binary response ({len(response)} bytes)")
784
+ ack = {
785
+ "status": "queued", # Treat as success
786
+ "binary_response_size": len(response),
787
+ "note": "Server returned binary response - treating as acknowledgment"
788
+ }
789
+
790
+ # Check response status
791
+ if ack.get("status") == "queued":
792
+ logger.info("✓ File queued successfully: %s", file_name)
793
+ elif ack.get("status") == "nack":
794
+ reason = ack.get("reason", "unknown")
795
+ logger.warning("✗ File rejected by server: %s", reason)
796
+ else:
797
+ logger.warning("⚠ Unexpected server response status: %s", ack.get("status"))
798
+
799
+ return ack
800
+
801
+ except websockets.exceptions.InvalidURI as e:
802
+ logger.error("Invalid WebSocket URI: %s", ws_uri)
803
+ raise
804
+
805
+ except websockets.exceptions.WebSocketException as e:
806
+ logger.error("WebSocket error: %s", e)
807
+ raise
808
+
809
+ except Exception as e:
810
+ logger.error("Unexpected error sending file: %s", e, exc_info=True)
811
+ raise
812
+
813
+ async def send_text(text):
814
+ payload = {
815
+ "type": "text",
816
+ "message": text
817
+ }
818
+
819
+ async with websockets.connect(FORWARD_URL) as ws:
820
+ print("📡 Connected (text mode)")
821
+ await ws.send(json.dumps(payload))
822
+ print("💬 Message sent — closing connection")
823
+
824
+ await asyncio.sleep(0.1)
825
+ await ws.close()