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,1028 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Optional
5
+
6
+ from pydantic import BaseModel, Field, model_validator
7
+
8
+ from services.products.models.product_common import ComponentType, UnitOfMeasure, utc_now, ensure_utc
9
+
10
+
11
+ from pydantic import BaseModel, Field, field_validator, model_validator
12
+ from typing import Optional, List
13
+ from enum import Enum
14
+ from datetime import date, datetime, timezone
15
+ from decimal import Decimal
16
+
17
+
18
+ class UnitOfMeasure(str, Enum):
19
+ PIECE = "piece"
20
+ KILOGRAM = "kg"
21
+ GRAM = "g"
22
+ LITER = "liter"
23
+ METER = "meter"
24
+ SQUARE_METER = "sq_meter"
25
+ CUBIC_METER = "cu_meter"
26
+ INCH = "inch"
27
+ FOOT = "foot"
28
+ POUND = "pound"
29
+ OUNCE = "ounce"
30
+
31
+
32
+ class Currency(str, Enum):
33
+ USD = "USD"
34
+ EUR = "EUR"
35
+ GBP = "GBP"
36
+ INR = "INR"
37
+ JPY = "JPY"
38
+ CNY = "CNY"
39
+
40
+
41
+ class PartCategory(str, Enum):
42
+ RAW_MATERIAL = "raw_material"
43
+ COMPONENT = "component"
44
+ SUBASSEMBLY = "subassembly"
45
+ CONSUMABLE = "consumable"
46
+ PACKAGING = "packaging"
47
+ FASTENER = "fastener"
48
+ ELECTRONIC = "electronic"
49
+ OTHER = "other"
50
+
51
+
52
+ class ECNStatus(str, Enum):
53
+ DRAFT = "draft"
54
+ UNDER_REVIEW = "under_review"
55
+ APPROVED = "approved"
56
+ RELEASED = "released"
57
+ REJECTED = "rejected"
58
+ OBSOLETE = "obsolete"
59
+
60
+
61
+ class HazardClass(str, Enum):
62
+ FLAMMABLE = "flammable"
63
+ CORROSIVE = "corrosive"
64
+ TOXIC = "toxic"
65
+ OXIDIZER = "oxidizer"
66
+ EXPLOSIVE = "explosive"
67
+ RADIOACTIVE = "radioactive"
68
+ IRRITANT = "irritant"
69
+ CARCINOGEN = "carcinogen"
70
+
71
+
72
+ class BOMRelationshipType(str, Enum):
73
+ ASSEMBLY = "assembly"
74
+ SUBASSEMBLY = "subassembly"
75
+ PHANTOM = "phantom" # Transient assembly — not stocked
76
+ REFERENCE = "reference" # Informational only, not consumed
77
+ CO_PRODUCT = "co_product" # By-product produced alongside main output
78
+
79
+
80
+ class BOMComponentType(str, Enum):
81
+ RAW_MATERIAL = "Raw Material"
82
+ COMPONENT = "Component"
83
+ SUBASSEMBLY = "Subassembly"
84
+ FINISHED_GOOD = "Finished Good"
85
+ PACKAGING = "Packaging"
86
+ CONSUMABLE = "Consumable"
87
+ OTHER = "Other"
88
+
89
+
90
+ class UsageType(str, Enum):
91
+ STANDARD = "Standard"
92
+ OPTIONAL = "Optional"
93
+ ALTERNATE = "Alternate"
94
+ SUBSTITUTE = "Substitute"
95
+ PHANTOM = "Phantom"
96
+ REFERENCE = "Reference"
97
+
98
+
99
+ class BOMType(str, Enum):
100
+ ENGINEERING = "Engineering"
101
+ MANUFACTURING = "Manufacturing"
102
+ SERVICE = "Service"
103
+ SALES = "Sales"
104
+ CONFIGURABLE = "Configurable"
105
+
106
+
107
+ class BOMStatus(str, Enum):
108
+ DRAFT = "Draft"
109
+ ACTIVE = "Active"
110
+ INACTIVE = "Inactive"
111
+ OBSOLETE = "Obsolete"
112
+ ARCHIVED = "Archived"
113
+
114
+
115
+ class LeadTimeStatus(str, Enum):
116
+ IN_STOCK = "In Stock"
117
+ SHORT = "Short"
118
+ MEDIUM = "Medium"
119
+ LONG = "Long"
120
+ CRITICAL = "Critical"
121
+ UNKNOWN = "Unknown"
122
+
123
+
124
+ class Supplier(BaseModel):
125
+ supplier_id: Optional[str] = None
126
+ supplier_name: Optional[str] = None
127
+ lead_time_days: Optional[int] = Field(None, ge=0)
128
+ preferred: bool = False
129
+ remarks: Optional[str] = None
130
+
131
+
132
+ class ScrapAndYield(BaseModel):
133
+ """Scrap factors and yield rates for manufacturing consumption calculations."""
134
+
135
+ scrap_factor_percent: Decimal = Field(
136
+ default=Decimal("0"),
137
+ ge=0,
138
+ le=100,
139
+ description=(
140
+ "Extra material ordered/consumed to cover expected scrap. "
141
+ "e.g. 5 means order 5 % more than BOM quantity."
142
+ ),
143
+ )
144
+ process_yield_percent: Decimal = Field(
145
+ default=Decimal("100"),
146
+ gt=0,
147
+ le=100,
148
+ description=(
149
+ "Expected good-unit yield from this process step. "
150
+ "e.g. 95 means 5 % of units are rejected."
151
+ ),
152
+ )
153
+ shrinkage_factor_percent: Decimal = Field(
154
+ default=Decimal("0"),
155
+ ge=0,
156
+ le=100,
157
+ description="Material lost due to shrinkage, evaporation, or trimming.",
158
+ )
159
+ operation_scrap_code: Optional[str] = Field(
160
+ None,
161
+ max_length=20,
162
+ description="ERP/MES scrap reason code (e.g. 'TRIM', 'DEFECT', 'EVAP').",
163
+ )
164
+
165
+ @property
166
+ def gross_quantity_multiplier(self) -> Decimal:
167
+ """
168
+ Combined multiplier to apply to the BOM net quantity so the work order
169
+ issues enough material to yield the required good output.
170
+
171
+ gross_qty = net_qty × gross_quantity_multiplier
172
+ """
173
+ scrap = Decimal("1") + (self.scrap_factor_percent / Decimal("100"))
174
+ shrink = Decimal("1") + (self.shrinkage_factor_percent / Decimal("100"))
175
+ yield_ = self.process_yield_percent / Decimal("100")
176
+ return (scrap * shrink) / yield_
177
+
178
+ def gross_quantity_for(self, net_quantity: Decimal) -> Decimal:
179
+ """Returns the gross quantity needed to deliver `net_quantity` good units."""
180
+ return (net_quantity * self.gross_quantity_multiplier).quantize(Decimal("0.0001"))
181
+
182
+ class ReferenceDesignator(BaseModel):
183
+ """
184
+ PCB/electronics reference designator entry.
185
+ Each instance maps one refdes (e.g. 'C12') to this part on a specific board.
186
+ """
187
+
188
+ designator: str = Field(
189
+ ...,
190
+ max_length=20,
191
+ pattern=r"^[A-Z]{1,5}\d+[A-Z]?$",
192
+ description="IPC-7711 reference designator, e.g. R1, C12, U3A, TP101.",
193
+ )
194
+ board_id: str = Field(..., max_length=50, description="PCB assembly part number or ID.")
195
+ schematic_page: Optional[int] = Field(None, ge=1, description="Schematic sheet number.")
196
+ x_mm: Optional[Decimal] = Field(None, description="X centroid on board (mm).")
197
+ y_mm: Optional[Decimal] = Field(None, description="Y centroid on board (mm).")
198
+ rotation_deg: Optional[Decimal] = Field(None, ge=0, lt=360, description="Placement rotation (°).")
199
+ side: Optional[str] = Field(None, pattern=r"^(top|bottom)$", description="'top' or 'bottom'.")
200
+ dnp: bool = Field(
201
+ default=False,
202
+ description="Do Not Place — part is listed but intentionally unpopulated.",
203
+ )
204
+
205
+ @field_validator("designator", mode="before")
206
+ @classmethod
207
+ def uppercase_designator(cls, v: str) -> str:
208
+ return v.upper() if isinstance(v, str) else v
209
+
210
+ class ShelfLifeInfo(BaseModel):
211
+ """Shelf-life tracking for moisture-sensitive, chemical, or perishable parts."""
212
+
213
+ shelf_life_days: Optional[int] = Field(
214
+ None, gt=0, description="Total shelf life from manufacture date (days)."
215
+ )
216
+ floor_life_hours: Optional[int] = Field(
217
+ None, gt=0,
218
+ description=(
219
+ "IPC/JEDEC MSL floor life once the original packaging is opened (hours). "
220
+ "Relevant for moisture-sensitive devices (MSDs)."
221
+ ),
222
+ )
223
+ msl_level: Optional[int] = Field(
224
+ None, ge=1, le=6,
225
+ description="IPC/JEDEC J-STD-020 Moisture Sensitivity Level (1–6).",
226
+ )
227
+ storage_temperature_min_c: Optional[Decimal] = Field(
228
+ None, description="Minimum storage temperature (°C)."
229
+ )
230
+ storage_temperature_max_c: Optional[Decimal] = Field(
231
+ None, description="Maximum storage temperature (°C)."
232
+ )
233
+ storage_humidity_max_pct: Optional[Decimal] = Field(
234
+ None, ge=0, le=100, description="Maximum relative humidity for storage (%)."
235
+ )
236
+ requires_dry_pack: bool = Field(
237
+ default=False, description="Must be stored/shipped in a sealed dry-pack bag."
238
+ )
239
+ requires_cold_chain: bool = Field(
240
+ default=False, description="Must remain in a refrigerated/frozen supply chain."
241
+ )
242
+ date_code_format: Optional[str] = Field(
243
+ None, max_length=20,
244
+ description="Expected date-code format on the part label, e.g. 'YYWW' or 'YYYYMMDD'.",
245
+ )
246
+
247
+ @model_validator(mode="after")
248
+ def validate_temp_range(self) -> "ShelfLifeInfo":
249
+ lo = self.storage_temperature_min_c
250
+ hi = self.storage_temperature_max_c
251
+ if lo is not None and hi is not None and lo >= hi:
252
+ raise ValueError("storage_temperature_min_c must be less than storage_temperature_max_c")
253
+ return self
254
+
255
+ def is_expired(self, manufacture_date: date) -> bool:
256
+ """True if shelf life has elapsed since manufacture_date."""
257
+ if self.shelf_life_days is None:
258
+ return False
259
+ age = (date.today() - manufacture_date).days
260
+ return age > self.shelf_life_days
261
+
262
+ def remaining_shelf_life_days(self, manufacture_date: date) -> Optional[int]:
263
+ if self.shelf_life_days is None:
264
+ return None
265
+ return max(0, self.shelf_life_days - (date.today() - manufacture_date).days)
266
+
267
+
268
+ class HazmatInfo(BaseModel):
269
+ batch_tracking_required: bool = False
270
+ serial_tracking_required: bool = False
271
+ expiry_tracking_required: bool = False
272
+ hazmat: bool = False
273
+ temp_sensitive: bool = False
274
+ controlled_substance: bool = False
275
+ regulatory_certifications: Optional[list[str]] = None
276
+ msds_url: Optional[str] = None
277
+
278
+ model_config = {"str_strip_whitespace": True}
279
+
280
+
281
+ class ECNRecord(BaseModel):
282
+ """A single Engineering Change Notice affecting this part."""
283
+
284
+ ecn_number: str = Field(
285
+ ...,
286
+ max_length=30,
287
+ pattern=r"^ECN-\d{4,10}$",
288
+ description="ECN identifier, e.g. 'ECN-20240312'.",
289
+ )
290
+ title: str = Field(..., max_length=200)
291
+ description: str = Field(..., max_length=2000)
292
+ status: ECNStatus = Field(default=ECNStatus.DRAFT)
293
+ initiated_by: str = Field(..., max_length=100, description="Engineer or team who raised the ECN.")
294
+ approved_by: Optional[str] = Field(None, max_length=100)
295
+ date_initiated: date = Field(default_factory=date.today)
296
+ date_approved: Optional[date] = None
297
+ date_effective: Optional[date] = None
298
+ affected_revision_from: Optional[str] = Field(
299
+ None, max_length=10, description="Previous part revision this ECN supersedes."
300
+ )
301
+ affected_revision_to: Optional[str] = Field(
302
+ None, max_length=10, description="New part revision introduced by this ECN."
303
+ )
304
+ reason_code: Optional[str] = Field(
305
+ None, max_length=50,
306
+ description="Reason category, e.g. 'COST_REDUCTION', 'SAFETY', 'SUPPLIER_CHANGE'.",
307
+ )
308
+ linked_documents: List[str] = Field(
309
+ default_factory=list,
310
+ description="Document IDs (drawings, test reports) related to this ECN.",
311
+ )
312
+
313
+ @model_validator(mode="after")
314
+ def validate_approval_flow(self) -> "ECNRecord":
315
+ if self.status in (ECNStatus.APPROVED, ECNStatus.RELEASED) and not self.approved_by:
316
+ raise ValueError("approved_by is required when status is APPROVED or RELEASED")
317
+ if self.date_approved and self.date_approved < self.date_initiated:
318
+ raise ValueError("date_approved cannot precede date_initiated")
319
+ if self.date_effective and self.date_approved and self.date_effective < self.date_approved:
320
+ raise ValueError("date_effective cannot precede date_approved")
321
+ return self
322
+
323
+ @property
324
+ def is_released(self) -> bool:
325
+ return self.status == ECNStatus.RELEASED
326
+
327
+ @property
328
+ def days_open(self) -> int:
329
+ end = self.date_approved or date.today()
330
+ return (end - self.date_initiated).days
331
+
332
+ class BOMLink(BaseModel):
333
+ """
334
+ Defines this part's position inside a multi-level Bill of Materials.
335
+ One part may appear in multiple BOMs (shared components), so a list of
336
+ BOMLink objects is attached to the part rather than a single parent ID.
337
+ """
338
+
339
+ parent_bom_id: str = Field(
340
+ ..., max_length=50,
341
+ description="Part number of the immediate parent assembly in the BOM.",
342
+ )
343
+ bom_level: int = Field(
344
+ ..., ge=1,
345
+ description="BOM depth level (1 = top-level assembly, 2 = subassembly, …).",
346
+ )
347
+ quantity_per_assembly: Decimal = Field(
348
+ ..., gt=0,
349
+ description="Net quantity of this part required per one parent assembly.",
350
+ )
351
+ find_number: Optional[int] = Field(
352
+ None, ge=1,
353
+ description="Find number / item sequence on the engineering drawing BOM table.",
354
+ )
355
+ relationship_type: BOMRelationshipType = Field(default=BOMRelationshipType.ASSEMBLY)
356
+ is_configurable: bool = Field(
357
+ default=False,
358
+ description="Part may be substituted based on product configuration options.",
359
+ )
360
+ substitute_part_numbers: List[str] = Field(
361
+ default_factory=list,
362
+ description="Approved alternate part numbers that may be used in place of this part.",
363
+ )
364
+ effective_date_start: Optional[date] = Field(
365
+ None, description="Date from which this BOM relationship is valid."
366
+ )
367
+ effective_date_end: Optional[date] = Field(
368
+ None, description="Date after which this BOM relationship is superseded."
369
+ )
370
+ notes: Optional[str] = Field(None, max_length=500)
371
+
372
+ @model_validator(mode="after")
373
+ def validate_effectivity(self) -> "BOMLink":
374
+ s, e = self.effective_date_start, self.effective_date_end
375
+ if s and e and s >= e:
376
+ raise ValueError("effective_date_start must be earlier than effective_date_end")
377
+ return self
378
+
379
+ @property
380
+ def is_currently_effective(self) -> bool:
381
+ today = date.today()
382
+ after_start = (self.effective_date_start is None) or (today >= self.effective_date_start)
383
+ before_end = (self.effective_date_end is None) or (today < self.effective_date_end)
384
+ return after_start and before_end
385
+
386
+
387
+ class CostInfo(BaseModel):
388
+ unit_cost: Decimal = Field(..., gt=0, decimal_places=4)
389
+ currency: Currency = Field(default=Currency.USD)
390
+ minimum_order_quantity: Decimal = Field(..., gt=0)
391
+ bulk_discount_threshold: Optional[Decimal] = Field(None, gt=0)
392
+ bulk_discount_percent: Optional[Decimal] = Field(None, ge=0, le=100)
393
+ last_price_update: Optional[date] = None
394
+
395
+ @model_validator(mode="after")
396
+ def validate_bulk_discount(self) -> "CostInfo":
397
+ has_threshold = self.bulk_discount_threshold is not None
398
+ has_discount = self.bulk_discount_percent is not None
399
+ if has_threshold != has_discount:
400
+ raise ValueError(
401
+ "Both bulk_discount_threshold and bulk_discount_percent must be provided together"
402
+ )
403
+ return self
404
+
405
+ def effective_unit_cost(self, quantity: Decimal) -> Decimal:
406
+ """Returns unit cost after applying bulk discount if applicable."""
407
+ if (
408
+ self.bulk_discount_threshold
409
+ and self.bulk_discount_percent
410
+ and quantity >= self.bulk_discount_threshold
411
+ ):
412
+ discount = self.unit_cost * (self.bulk_discount_percent / Decimal("100"))
413
+ return self.unit_cost - discount
414
+ return self.unit_cost
415
+
416
+ class QuantityInfo(BaseModel):
417
+ base_unit_of_measure: UnitOfMeasure = "Each"
418
+ packaging_type: Optional[str] = None
419
+ units_per_case: Optional[float] = Field(default=None, gt=0)
420
+ units_per_pallet: Optional[float] = Field(default=None, gt=0)
421
+ alternate_uoms: Optional[list[Any]] = None # AlternateUom handled via dict in DB
422
+
423
+ model_config = {"str_strip_whitespace": True}
424
+
425
+ class MaterialSpec(BaseModel):
426
+ weight_kg: Optional[float] = Field(default=None, ge=0)
427
+ volume_liters: Optional[float] = Field(default=None, ge=0)
428
+ length_cm: Optional[float] = Field(default=None, ge=0)
429
+ width_cm: Optional[float] = Field(default=None, ge=0)
430
+ height_cm: Optional[float] = Field(default=None, ge=0)
431
+
432
+ model_config = {"str_strip_whitespace": True}
433
+
434
+
435
+ class ProductComponent(BaseModel):
436
+ component_id: Optional[str] = Field(
437
+ None,
438
+ min_length=1,
439
+ description="System-generated product component identifier.",
440
+ )
441
+ product_id: Optional[str] = Field(
442
+ None,
443
+ min_length=1,
444
+ description="Parent product identifier for this component entry.",
445
+ )
446
+ part_number: str = Field(
447
+ ...,
448
+ min_length=3,
449
+ max_length=50,
450
+ pattern=r"^[A-Z0-9\-_]+$",
451
+ description="Unique alphanumeric part identifier (uppercase, hyphens, underscores allowed)",
452
+ )
453
+ part_name: str = Field(..., min_length=2, max_length=200)
454
+ description: Optional[str] = Field(None, max_length=1000)
455
+ category: PartCategory
456
+ is_active: bool = Field(default=True)
457
+ created_date: date = Field(default_factory=date.today)
458
+ notes: Optional[str] = Field(None, max_length=2000)
459
+
460
+ # asset tag
461
+ asset_tag: Optional[str] = None
462
+ sku: Optional[str] = None
463
+ gtin: Optional[str] = None
464
+ upc: Optional[str] = None
465
+ ean: Optional[str] = None
466
+ cas_number: Optional[str] = None
467
+
468
+ # drawing association
469
+ revision: str = Field(default="A", max_length=10, description="Part revision/version")
470
+ drawing_number: Optional[str] = Field(None, max_length=50)
471
+
472
+ quantity: QuantityInfo
473
+ cost: CostInfo
474
+ supplier_name: str
475
+ material_spec: MaterialSpec
476
+
477
+ # ── Manufacturing-specific fields ─────────────────────────────────────────
478
+ scrap_and_yield: ScrapAndYield = Field(
479
+ default_factory=ScrapAndYield,
480
+ description="Scrap factors and process yield rates for consumption calculations.",
481
+ )
482
+ reference_designators: List[ReferenceDesignator] = Field(
483
+ default_factory=list,
484
+ description="PCB/electronics reference designators (e.g. R1, C12, U3A).",
485
+ )
486
+ shelf_life: Optional[ShelfLifeInfo] = Field(
487
+ None,
488
+ description="Shelf-life, floor-life, MSL, and storage condition requirements.",
489
+ )
490
+ hazmat: HazmatInfo = Field(
491
+ default_factory=HazmatInfo,
492
+ description="Hazardous material classification and RoHS/REACH compliance flags.",
493
+ )
494
+ ecn_history: List[ECNRecord] = Field(
495
+ default_factory=list,
496
+ description="Engineering Change Notices that have affected this part.",
497
+ )
498
+ parent_bom_id: Optional[str] = Field(
499
+ None,
500
+ max_length=50,
501
+ description="Part number of the immediate parent assembly in the BOM.",
502
+ )
503
+ bom_level: Optional[int] = Field(
504
+ None,
505
+ ge=1,
506
+ description="BOM depth level (1 = top-level assembly, 2 = subassembly, ...).",
507
+ )
508
+ quantity_per_assembly: Optional[Decimal] = Field(
509
+ None,
510
+ gt=0,
511
+ description="Net quantity of this part required per one parent assembly.",
512
+ )
513
+ find_number: Optional[int] = Field(
514
+ None,
515
+ ge=1,
516
+ description="Find number / item sequence on the engineering drawing BOM table.",
517
+ )
518
+ relationship_type: BOMRelationshipType = Field(default=BOMRelationshipType.ASSEMBLY)
519
+ is_configurable: bool = Field(
520
+ default=False,
521
+ description="Part may be substituted based on product configuration options.",
522
+ )
523
+ substitute_part_numbers: List[str] = Field(
524
+ default_factory=list,
525
+ description="Approved alternate part numbers that may be used in place of this part.",
526
+ )
527
+ effective_date_start: Optional[date] = Field(
528
+ None,
529
+ description="Date from which this BOM relationship is valid.",
530
+ )
531
+ effective_date_end: Optional[date] = Field(
532
+ None,
533
+ description="Date after which this BOM relationship is superseded.",
534
+ )
535
+ bom_notes: Optional[str] = Field(None, max_length=500)
536
+
537
+ model_config = {"str_strip_whitespace": True}
538
+
539
+ @field_validator("part_number", mode="before")
540
+ @classmethod
541
+ def uppercase_part_number(cls, v: str) -> str:
542
+ return v.upper() if isinstance(v, str) else v
543
+
544
+ @model_validator(mode="after")
545
+ def validate_bom_effectivity(self) -> "ProductComponent":
546
+ s, e = self.effective_date_start, self.effective_date_end
547
+ if s and e and s >= e:
548
+ raise ValueError("effective_date_start must be earlier than effective_date_end")
549
+ return self
550
+
551
+ # ── Helpers ───────────────────────────────────────────────────────────────
552
+
553
+ def total_stock_value(self) -> Decimal:
554
+ """Returns total value of on-hand inventory."""
555
+ return self.quantity.quantity_on_hand * self.cost.unit_cost
556
+
557
+ def reorder_cost_estimate(self) -> Decimal:
558
+ """Returns estimated cost of a reorder."""
559
+ qty = self.quantity.reorder_quantity
560
+ unit = self.cost.effective_unit_cost(qty)
561
+ return qty * unit
562
+
563
+ def gross_quantity_needed(self, net_quantity: Decimal) -> Decimal:
564
+ """
565
+ Returns how much of this part must be issued to a work order
566
+ to obtain `net_quantity` good units, after scrap and yield losses.
567
+ """
568
+ return self.scrap_and_yield.gross_quantity_for(net_quantity)
569
+
570
+ @property
571
+ def quantity_per_parent(self) -> float:
572
+ """Compatibility alias for BOM node quantity resolution."""
573
+ if self.quantity_per_assembly is None:
574
+ return 1.0
575
+ return float(self.quantity_per_assembly)
576
+
577
+ @property
578
+ def latest_ecn(self) -> Optional[ECNRecord]:
579
+ """Most recently initiated ECN, or None."""
580
+ if not self.ecn_history:
581
+ return None
582
+ return max(self.ecn_history, key=lambda e: e.date_initiated)
583
+
584
+ @property
585
+ def active_bom_links(self) -> List[BOMLink]:
586
+ """Compatibility view over the flattened BOM input fields."""
587
+ if not (
588
+ self.parent_bom_id
589
+ and self.bom_level is not None
590
+ and self.quantity_per_assembly is not None
591
+ ):
592
+ return []
593
+ link = BOMLink(
594
+ parent_bom_id=self.parent_bom_id,
595
+ bom_level=self.bom_level,
596
+ quantity_per_assembly=self.quantity_per_assembly,
597
+ find_number=self.find_number,
598
+ relationship_type=self.relationship_type,
599
+ is_configurable=self.is_configurable,
600
+ substitute_part_numbers=self.substitute_part_numbers,
601
+ effective_date_start=self.effective_date_start,
602
+ effective_date_end=self.effective_date_end,
603
+ notes=self.bom_notes,
604
+ )
605
+ return [link] if link.is_currently_effective else []
606
+
607
+ @property
608
+ def is_hazardous(self) -> bool:
609
+ return self.hazmat.is_hazardous
610
+
611
+ @property
612
+ def has_reference_designators(self) -> bool:
613
+ return len(self.reference_designators) > 0
614
+
615
+
616
+ class BOMComponent(BaseModel):
617
+ """
618
+ A single line-item inside a BOM.
619
+
620
+ Fields merged from the production schema, multilevel tree support,
621
+ and MBOM enrichment fields.
622
+ """
623
+
624
+ line_id: str = Field(..., min_length=1, description="Unique line identifier within this BOM")
625
+ component_product_id: str = Field(..., min_length=1, description="FK to the product catalogue")
626
+ component_sku: str = Field(default="")
627
+ component_name: str = Field(default="")
628
+ component_type: BOMComponentType
629
+ quantity_per_parent: float = Field(..., gt=0)
630
+ uom: str = Field(..., min_length=1, description="Unit of measure, e.g. 'Each', 'kg'")
631
+ usage_type: UsageType = UsageType.STANDARD
632
+ scrap_rate_pct: Optional[float] = Field(default=None, ge=0)
633
+ sequence_number: int = Field(..., ge=0, description="Assembly sequence order")
634
+ operation_step: Optional[int] = Field(default=None, ge=0, description="Work-centre operation ref")
635
+ effective_from: Optional[date] = None
636
+ effective_to: Optional[date] = None
637
+ material: Optional[str] = Field(
638
+ default=None,
639
+ description="Raw material spec, e.g. 'Western Red Cedar (1/4\" thick)'",
640
+ )
641
+ lead_time: Optional[LeadTimeStatus] = None
642
+ supplier: Optional[Supplier] = None
643
+ notes: Optional[str] = None
644
+ children: list["BOMNode"] = Field(
645
+ default_factory=list,
646
+ description="Sub-components; non-empty means this component is itself an assembly.",
647
+ )
648
+
649
+ @model_validator(mode="after")
650
+ def effective_to_after_from(self) -> "BOMComponent":
651
+ if self.effective_from and self.effective_to and self.effective_to < self.effective_from:
652
+ raise ValueError("effective_to must be on or after effective_from")
653
+ return self
654
+
655
+ @property
656
+ def is_assembly(self) -> bool:
657
+ """True when this component contains sub-components (non-leaf node)."""
658
+ return len(self.children) > 0
659
+
660
+ @property
661
+ def net_quantity(self) -> float:
662
+ """
663
+ Quantity adjusted for scrap rate:
664
+ net = quantity_per_parent / (1 - scrap_rate_pct / 100)
665
+ Falls back to quantity_per_parent when scrap_rate_pct is unset.
666
+ """
667
+ if self.scrap_rate_pct:
668
+ return self.quantity_per_parent / (1 - self.scrap_rate_pct / 100)
669
+ return self.quantity_per_parent
670
+
671
+
672
+ class BOMNode(BaseModel):
673
+ """
674
+ Positional wrapper around a BOMComponent.
675
+
676
+ Allows the same component to appear at multiple positions in the tree
677
+ with independent position labels and quantity overrides without
678
+ duplicating the component definition.
679
+ """
680
+
681
+ component: BOMComponent
682
+ level: int = Field(default=0, ge=0, description="Depth in the BOM tree (0 = root)")
683
+ position: Optional[str] = Field(
684
+ default=None,
685
+ description="Dotted find-number within the parent assembly, e.g. '1.2.3'",
686
+ )
687
+ quantity_override: Optional[float] = Field(
688
+ default=None,
689
+ gt=0,
690
+ description=(
691
+ "Overrides component.quantity_per_parent for this specific "
692
+ "parent-context without modifying the master component record."
693
+ ),
694
+ )
695
+
696
+ @property
697
+ def effective_quantity(self) -> float:
698
+ """Resolved quantity at this node position."""
699
+ return (
700
+ self.quantity_override
701
+ if self.quantity_override is not None
702
+ else self.component.quantity_per_parent
703
+ )
704
+
705
+
706
+ class BOMBase(BaseModel):
707
+ """Core BOM fields shared by Create, Update, and Record variants."""
708
+
709
+ bom_id: str = Field(..., min_length=1)
710
+ bom_code: str = Field(..., min_length=1)
711
+ version: str = Field(default="1.0", min_length=1)
712
+ parent_product_id: str = Field(..., min_length=1)
713
+ parent_product_name: str = ""
714
+ parent_sku: str = ""
715
+ bom_type: BOMType = BOMType.MANUFACTURING
716
+ status: BOMStatus = BOMStatus.DRAFT
717
+ effective_date: Optional[date] = None
718
+ expiry_date: Optional[date] = None
719
+ base_quantity: float = Field(default=1, gt=0)
720
+ base_uom: str = Field(default="Each", min_length=1)
721
+ routing_id: Optional[str] = None
722
+ approved_process_definition_id: Optional[str] = Field(
723
+ default=None,
724
+ description="Approved manufacturing/packaging process definition this BOM is authorized for.",
725
+ )
726
+ approved_process_definition_revision: Optional[str] = Field(
727
+ default=None,
728
+ description="Revision of the approved process definition captured when this BOM was released.",
729
+ )
730
+ approved_process_route_id: Optional[str] = Field(
731
+ default=None,
732
+ description="Approved process route/version used with this BOM.",
733
+ )
734
+ mbmr_template_id: Optional[str] = Field(
735
+ default=None,
736
+ description="Master Batch Manufacturing Record template bound to this BOM.",
737
+ )
738
+ bmr_template_id: Optional[str] = Field(
739
+ default=None,
740
+ description="Batch Manufacturing Record template generated/executed from this BOM.",
741
+ )
742
+ bpr_template_id: Optional[str] = Field(
743
+ default=None,
744
+ description="Batch Packaging Record template used when this BOM covers packaging.",
745
+ )
746
+ batch_execution_record_ids: list[str] = Field(
747
+ default_factory=list,
748
+ description="Batch execution records that have executed or instantiated this BOM.",
749
+ )
750
+ standard_work_hours: Optional[float] = Field(default=None, ge=0)
751
+ overall_scrap_rate_pct: Optional[float] = Field(default=None, ge=0)
752
+ expected_yield_pct: Optional[float] = Field(default=None, ge=0, le=100)
753
+ components: list[BOMComponent] = Field(default_factory=list)
754
+ revision_notes: Optional[str] = None
755
+ engineering_change_no: Optional[str] = None
756
+ remarks: Optional[str] = None
757
+ attachments: list[str] = Field(default_factory=list)
758
+
759
+ @model_validator(mode="after")
760
+ def expiry_after_effective(self) -> "BOMBase":
761
+ if self.effective_date and self.expiry_date and self.expiry_date < self.effective_date:
762
+ raise ValueError("expiry_date must be on or after effective_date")
763
+ if self.status == BOMStatus.ACTIVE:
764
+ if not (self.approved_process_definition_id or self.approved_process_route_id or self.routing_id):
765
+ raise ValueError("active BOMs must be bound to an approved process definition or route")
766
+ if not (self.mbmr_template_id or self.bmr_template_id or self.bpr_template_id):
767
+ raise ValueError("active BOMs must be bound to an MBMR, BMR, or BPR template")
768
+ return self
769
+
770
+
771
+ class BOMRecord(BOMBase):
772
+ """
773
+ Persisted BOM document with audit data and computed tree metrics.
774
+ """
775
+
776
+ is_current_version: bool = False
777
+ approval_date: Optional[datetime] = None
778
+ approved_by: Optional[str] = None
779
+ created_by: Optional[str] = None
780
+ updated_by: Optional[str] = None
781
+ created_at: datetime = Field(default_factory=utc_now)
782
+ updated_at: datetime = Field(default_factory=utc_now)
783
+ estimated_material_cost: Optional[float] = None
784
+ total_component_lines: int = 0
785
+ is_multi_level: bool = False
786
+ has_alternates: bool = False
787
+ is_valid: bool = False
788
+ is_production_bound: bool = False
789
+ raw_materials: list[BOMComponent] = Field(
790
+ default_factory=list,
791
+ description="All raw material component lines in this BOM, collected across every BOM level.",
792
+ )
793
+
794
+ @model_validator(mode="after")
795
+ def normalize_and_compute(self) -> "BOMRecord":
796
+ self.created_at = ensure_utc(self.created_at)
797
+ self.updated_at = ensure_utc(self.updated_at)
798
+ self.approval_date = ensure_utc(self.approval_date)
799
+
800
+ all_components = self._walk_all_components()
801
+ self.total_component_lines = len(all_components)
802
+ self.is_multi_level = any(c.is_assembly for c in self.components)
803
+ self.has_alternates = any(
804
+ c.usage_type in {UsageType.ALTERNATE, UsageType.SUBSTITUTE}
805
+ for c in all_components
806
+ )
807
+ self.raw_materials = [
808
+ component
809
+ for component in all_components
810
+ if component.component_type == BOMComponentType.RAW_MATERIAL
811
+ ]
812
+
813
+ today = datetime.now(timezone.utc).date()
814
+ effective = self.effective_date is None or self.effective_date <= today
815
+ unexpired = self.expiry_date is None or self.expiry_date >= today
816
+ self.is_valid = self.status == BOMStatus.ACTIVE and effective and unexpired
817
+ self.is_production_bound = bool(
818
+ self.approved_process_definition_id
819
+ or self.approved_process_route_id
820
+ or self.routing_id
821
+ ) and bool(
822
+ self.mbmr_template_id
823
+ or self.bmr_template_id
824
+ or self.bpr_template_id
825
+ )
826
+
827
+ return self
828
+
829
+ def _walk_all_components(self) -> list[BOMComponent]:
830
+ """DFS walk returning every component in the BOM tree."""
831
+ result: list[BOMComponent] = []
832
+
833
+ def _recurse(component: BOMComponent) -> None:
834
+ result.append(component)
835
+ for node in component.children:
836
+ _recurse(node.component)
837
+
838
+ for component in self.components:
839
+ _recurse(component)
840
+ return result
841
+
842
+ def flat_list(self) -> list[tuple[int, str, BOMComponent]]:
843
+ """DFS walk as (level, position, component) tuples."""
844
+ result: list[tuple[int, str, BOMComponent]] = []
845
+
846
+ def _recurse(component: BOMComponent, level: int, position: str) -> None:
847
+ result.append((level, position, component))
848
+ for idx, node in enumerate(component.children, start=1):
849
+ child_position = node.position or f"{position}.{idx}"
850
+ _recurse(node.component, level + 1, child_position)
851
+
852
+ for idx, component in enumerate(self.components, start=1):
853
+ _recurse(component, 0, str(idx))
854
+ return result
855
+
856
+ def find_component(self, *, product_id: str) -> list[BOMComponent]:
857
+ """Find every component with the given product id, regardless of depth."""
858
+ return [
859
+ component
860
+ for component in self._walk_all_components()
861
+ if component.component_product_id == product_id
862
+ ]
863
+
864
+ def max_depth(self) -> int:
865
+ """Maximum nesting depth, where 0 is a flat BOM."""
866
+ depths: list[int] = []
867
+
868
+ def _recurse(component: BOMComponent, depth: int) -> None:
869
+ depths.append(depth)
870
+ for node in component.children:
871
+ _recurse(node.component, depth + 1)
872
+
873
+ for component in self.components:
874
+ _recurse(component, 0)
875
+ return max(depths, default=0)
876
+
877
+
878
+ class BOMCreate(BOMBase):
879
+ """Payload for creating a new BOM."""
880
+
881
+ created_by: Optional[str] = None
882
+
883
+
884
+ class BOMUpdate(BaseModel):
885
+ """Partial-update payload for stored BOM records."""
886
+
887
+ bom_code: Optional[str] = None
888
+ version: Optional[str] = None
889
+ parent_product_id: Optional[str] = None
890
+ parent_product_name: Optional[str] = None
891
+ parent_sku: Optional[str] = None
892
+ bom_type: Optional[BOMType] = None
893
+ status: Optional[BOMStatus] = None
894
+ is_current_version: Optional[bool] = None
895
+ effective_date: Optional[date] = None
896
+ expiry_date: Optional[date] = None
897
+ approval_date: Optional[datetime] = None
898
+ approved_by: Optional[str] = None
899
+ base_quantity: Optional[float] = Field(default=None, gt=0)
900
+ base_uom: Optional[str] = None
901
+ routing_id: Optional[str] = None
902
+ approved_process_definition_id: Optional[str] = None
903
+ approved_process_definition_revision: Optional[str] = None
904
+ approved_process_route_id: Optional[str] = None
905
+ mbmr_template_id: Optional[str] = None
906
+ bmr_template_id: Optional[str] = None
907
+ bpr_template_id: Optional[str] = None
908
+ batch_execution_record_ids: Optional[list[str]] = None
909
+ standard_work_hours: Optional[float] = Field(default=None, ge=0)
910
+ overall_scrap_rate_pct: Optional[float] = Field(default=None, ge=0)
911
+ expected_yield_pct: Optional[float] = Field(default=None, ge=0, le=100)
912
+ components: Optional[list[BOMComponent]] = None
913
+ revision_notes: Optional[str] = None
914
+ engineering_change_no: Optional[str] = None
915
+ remarks: Optional[str] = None
916
+ attachments: Optional[list[str]] = None
917
+ estimated_material_cost: Optional[float] = None
918
+ updated_by: Optional[str] = None
919
+
920
+
921
+ class PaginatedBOMResponse(BaseModel):
922
+ total: int
923
+ page: int
924
+ page_size: int
925
+ results: list[BOMRecord]
926
+
927
+
928
+ class ProductComponentCreate(ProductComponent):
929
+ pass
930
+
931
+
932
+ class ProductComponentUpdate(BaseModel):
933
+ product_id: Optional[str] = Field(None, min_length=1)
934
+ part_number: Optional[str] = Field(
935
+ None,
936
+ min_length=3,
937
+ max_length=50,
938
+ pattern=r"^[A-Z0-9\-_]+$",
939
+ description="Unique alphanumeric part identifier (uppercase, hyphens, underscores allowed)",
940
+ )
941
+ part_name: Optional[str] = Field(None, min_length=2, max_length=200)
942
+ description: Optional[str] = Field(None, max_length=1000)
943
+ category: Optional[PartCategory] = None
944
+ is_active: Optional[bool] = None
945
+ created_date: Optional[date] = None
946
+ notes: Optional[str] = None
947
+ asset_tag: Optional[str] = None
948
+ sku: Optional[str] = None
949
+ gtin: Optional[str] = None
950
+ upc: Optional[str] = None
951
+ ean: Optional[str] = None
952
+ cas_number: Optional[str] = None
953
+ revision: Optional[str] = Field(None, max_length=10)
954
+ drawing_number: Optional[str] = Field(None, max_length=50)
955
+ quantity: Optional[QuantityInfo] = None
956
+ cost: Optional[CostInfo] = None
957
+ supplier_name: Optional[str] = None
958
+ material_spec: Optional[MaterialSpec] = None
959
+ scrap_and_yield: Optional[ScrapAndYield] = None
960
+ reference_designators: Optional[List[ReferenceDesignator]] = Field(
961
+ None,
962
+ description="PCB/electronics reference designators (e.g. R1, C12, U3A).",
963
+ )
964
+ shelf_life: Optional[ShelfLifeInfo] = None
965
+ hazmat: Optional[HazmatInfo] = None
966
+ ecn_history: Optional[List[ECNRecord]] = Field(
967
+ None,
968
+ description="Engineering Change Notices that have affected this part.",
969
+ )
970
+ parent_bom_id: Optional[str] = Field(
971
+ None,
972
+ max_length=50,
973
+ description="Part number of the immediate parent assembly in the BOM.",
974
+ )
975
+ bom_level: Optional[int] = Field(
976
+ None,
977
+ ge=1,
978
+ description="BOM depth level (1 = top-level assembly, 2 = subassembly, ...).",
979
+ )
980
+ quantity_per_assembly: Optional[Decimal] = Field(
981
+ None,
982
+ gt=0,
983
+ description="Net quantity of this part required per one parent assembly.",
984
+ )
985
+ find_number: Optional[int] = Field(
986
+ None,
987
+ ge=1,
988
+ description="Find number / item sequence on the engineering drawing BOM table.",
989
+ )
990
+ relationship_type: Optional[BOMRelationshipType] = None
991
+ is_configurable: Optional[bool] = Field(
992
+ None,
993
+ description="Part may be substituted based on product configuration options.",
994
+ )
995
+ substitute_part_numbers: Optional[List[str]] = Field(
996
+ None,
997
+ description="Approved alternate part numbers that may be used in place of this part.",
998
+ )
999
+ effective_date_start: Optional[date] = Field(
1000
+ None,
1001
+ description="Date from which this BOM relationship is valid.",
1002
+ )
1003
+ effective_date_end: Optional[date] = Field(
1004
+ None,
1005
+ description="Date after which this BOM relationship is superseded.",
1006
+ )
1007
+ bom_notes: Optional[str] = Field(None, max_length=500)
1008
+
1009
+ @field_validator("part_number", mode="before")
1010
+ @classmethod
1011
+ def uppercase_part_number(cls, v: Optional[str]) -> Optional[str]:
1012
+ return v.upper() if isinstance(v, str) else v
1013
+
1014
+ @model_validator(mode="after")
1015
+ def validate_bom_effectivity(self) -> "ProductComponentUpdate":
1016
+ s, e = self.effective_date_start, self.effective_date_end
1017
+ if s and e and s >= e:
1018
+ raise ValueError("effective_date_start must be earlier than effective_date_end")
1019
+ return self
1020
+
1021
+
1022
+ class PaginatedProductComponentResponse(BaseModel):
1023
+ total: int
1024
+ page: int
1025
+ page_size: int
1026
+ results: list[ProductComponent]
1027
+
1028
+ ProductComponentRecord = ProductComponent