raptor-studio 0.8.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 (433) hide show
  1. raptor/__init__.py +4 -0
  2. raptor/api/__init__.py +5 -0
  3. raptor/api/analyse.py +624 -0
  4. raptor/api/annotations.py +554 -0
  5. raptor/api/batch.py +104 -0
  6. raptor/api/build.py +3386 -0
  7. raptor/api/distributions.py +94 -0
  8. raptor/api/estimate.py +660 -0
  9. raptor/api/importance.py +452 -0
  10. raptor/api/inspect.py +273 -0
  11. raptor/api/isolate.py +572 -0
  12. raptor/api/limits.py +74 -0
  13. raptor/api/markdown.py +833 -0
  14. raptor/api/metric_definitions.py +122 -0
  15. raptor/api/model_diff.py +1115 -0
  16. raptor/api/paths.py +294 -0
  17. raptor/api/presentation.py +433 -0
  18. raptor/api/rates.py +345 -0
  19. raptor/api/render.py +195 -0
  20. raptor/api/report_html.py +478 -0
  21. raptor/api/results.py +153 -0
  22. raptor/api/run.py +1178 -0
  23. raptor/api/run_tables.py +134 -0
  24. raptor/api/series.py +417 -0
  25. raptor/api/stepping.py +107 -0
  26. raptor/api/table_export.py +315 -0
  27. raptor/api/tables.py +794 -0
  28. raptor/api/validate.py +869 -0
  29. raptor/api/workspace.py +1308 -0
  30. raptor/bundled.py +40 -0
  31. raptor/cli/__init__.py +6 -0
  32. raptor/cli/__main__.py +3 -0
  33. raptor/cli/cmd_gui.py +52 -0
  34. raptor/cli/cmd_info.py +242 -0
  35. raptor/cli/cmd_mcp.py +23 -0
  36. raptor/cli/cmd_misc.py +225 -0
  37. raptor/cli/cmd_run.py +370 -0
  38. raptor/cli/cmd_tools.py +649 -0
  39. raptor/cli/common.py +227 -0
  40. raptor/cli/exitcodes.py +30 -0
  41. raptor/cli/main.py +196 -0
  42. raptor/cli/manpages.py +321 -0
  43. raptor/cli/runopts.py +118 -0
  44. raptor/core/__init__.py +0 -0
  45. raptor/core/annotations.py +69 -0
  46. raptor/core/dev_options.py +220 -0
  47. raptor/core/diagnostics.py +50 -0
  48. raptor/core/distro_rules.py +365 -0
  49. raptor/core/engine/__init__.py +0 -0
  50. raptor/core/engine/accounting.py +174 -0
  51. raptor/core/engine/block_env.py +168 -0
  52. raptor/core/engine/block_lifecycle.py +4039 -0
  53. raptor/core/engine/blocks.py +369 -0
  54. raptor/core/engine/capacity.py +785 -0
  55. raptor/core/engine/cascade.py +198 -0
  56. raptor/core/engine/cascade_cold.py +278 -0
  57. raptor/core/engine/cascade_dependency.py +206 -0
  58. raptor/core/engine/cascade_phase.py +361 -0
  59. raptor/core/engine/cascade_status.py +269 -0
  60. raptor/core/engine/cold_standby.py +931 -0
  61. raptor/core/engine/costs.py +330 -0
  62. raptor/core/engine/cumulative.py +259 -0
  63. raptor/core/engine/data_files.py +92 -0
  64. raptor/core/engine/dependency.py +1090 -0
  65. raptor/core/engine/diststats.py +158 -0
  66. raptor/core/engine/domain_tree.py +99 -0
  67. raptor/core/engine/driver.py +890 -0
  68. raptor/core/engine/errors.py +75 -0
  69. raptor/core/engine/eventlog.py +186 -0
  70. raptor/core/engine/events.py +201 -0
  71. raptor/core/engine/fev.py +555 -0
  72. raptor/core/engine/fmeca.py +185 -0
  73. raptor/core/engine/graph.py +645 -0
  74. raptor/core/engine/interrupts.py +63 -0
  75. raptor/core/engine/invariants.py +80 -0
  76. raptor/core/engine/kickoff.py +1131 -0
  77. raptor/core/engine/load.py +245 -0
  78. raptor/core/engine/model_checks.py +134 -0
  79. raptor/core/engine/negative_draws.py +35 -0
  80. raptor/core/engine/nodes.py +719 -0
  81. raptor/core/engine/orchestrator.py +1104 -0
  82. raptor/core/engine/phasing.py +244 -0
  83. raptor/core/engine/pool.py +570 -0
  84. raptor/core/engine/report_format.py +376 -0
  85. raptor/core/engine/reporting.py +540 -0
  86. raptor/core/engine/reports.py +1852 -0
  87. raptor/core/engine/run_control.py +97 -0
  88. raptor/core/engine/runner.py +678 -0
  89. raptor/core/engine/sim_details.py +130 -0
  90. raptor/core/engine/spf.py +90 -0
  91. raptor/core/engine/spf_fev.py +192 -0
  92. raptor/core/engine/stats.py +130 -0
  93. raptor/core/engine/step_session.py +160 -0
  94. raptor/core/engine/system_status.py +533 -0
  95. raptor/core/engine/triggers.py +148 -0
  96. raptor/core/engine/wait_sites.py +201 -0
  97. raptor/core/engine/weak_metric.py +283 -0
  98. raptor/core/fileio/__init__.py +0 -0
  99. raptor/core/fileio/builder.py +553 -0
  100. raptor/core/fileio/legacy_layout.py +868 -0
  101. raptor/core/fileio/phase_edit.py +221 -0
  102. raptor/core/fileio/rbd.py +900 -0
  103. raptor/core/fileio/rbdlib.py +410 -0
  104. raptor/core/fileio/rbds.py +877 -0
  105. raptor/core/fileio/rbl.py +322 -0
  106. raptor/core/fileio/typed.py +548 -0
  107. raptor/core/fileio/view_transform.py +307 -0
  108. raptor/core/fileio/xlsx_writer.py +284 -0
  109. raptor/core/input_rules.py +1050 -0
  110. raptor/core/model.py +635 -0
  111. raptor/core/rng.py +476 -0
  112. raptor/core/run_experiment.py +441 -0
  113. raptor/core/run_manifest.py +148 -0
  114. raptor/core/scheduler.py +1121 -0
  115. raptor/gui/__init__.py +1 -0
  116. raptor/gui/app.py +201 -0
  117. raptor/gui/clipboard_plan.py +287 -0
  118. raptor/gui/command_table.py +1263 -0
  119. raptor/gui/commands.py +553 -0
  120. raptor/gui/cores.py +40 -0
  121. raptor/gui/dev_inspector.py +157 -0
  122. raptor/gui/dev_settings.py +53 -0
  123. raptor/gui/examples.py +91 -0
  124. raptor/gui/hierarchy_plan.py +448 -0
  125. raptor/gui/model_edit.py +645 -0
  126. raptor/gui/new_model.py +144 -0
  127. raptor/gui/open_models.py +100 -0
  128. raptor/gui/resources/__init__.py +1 -0
  129. raptor/gui/resources/design_tokens.py +945 -0
  130. raptor/gui/resources/examples/AirlinerMission_dark.png +0 -0
  131. raptor/gui/resources/examples/AirlinerMission_light.png +0 -0
  132. raptor/gui/resources/examples/Bakery1_dark.png +0 -0
  133. raptor/gui/resources/examples/Bakery1_light.png +0 -0
  134. raptor/gui/resources/examples/CarCare0_dark.png +0 -0
  135. raptor/gui/resources/examples/CarCare0_light.png +0 -0
  136. raptor/gui/resources/examples/CarCare1_dark.png +0 -0
  137. raptor/gui/resources/examples/CarCare1_light.png +0 -0
  138. raptor/gui/resources/examples/CarWash1_dark.png +0 -0
  139. raptor/gui/resources/examples/CarWash1_light.png +0 -0
  140. raptor/gui/resources/examples/DC1_dark.png +0 -0
  141. raptor/gui/resources/examples/DC1_light.png +0 -0
  142. raptor/gui/resources/examples/DC2_dark.png +0 -0
  143. raptor/gui/resources/examples/DC2_light.png +0 -0
  144. raptor/gui/resources/examples/Drone1_dark.png +0 -0
  145. raptor/gui/resources/examples/Drone1_light.png +0 -0
  146. raptor/gui/resources/examples/Espresso1_dark.png +0 -0
  147. raptor/gui/resources/examples/Espresso1_light.png +0 -0
  148. raptor/gui/resources/examples/EspressoDep1_dark.png +0 -0
  149. raptor/gui/resources/examples/EspressoDep1_light.png +0 -0
  150. raptor/gui/resources/examples/FinaleShow1_dark.png +0 -0
  151. raptor/gui/resources/examples/FinaleShow1_light.png +0 -0
  152. raptor/gui/resources/examples/Flight1_dark.png +0 -0
  153. raptor/gui/resources/examples/Flight1_light.png +0 -0
  154. raptor/gui/resources/examples/HospitalPower_dark.png +0 -0
  155. raptor/gui/resources/examples/HospitalPower_light.png +0 -0
  156. raptor/gui/resources/examples/Lantern1_dark.png +0 -0
  157. raptor/gui/resources/examples/Lantern1_light.png +0 -0
  158. raptor/gui/resources/examples/Laundromat1_dark.png +0 -0
  159. raptor/gui/resources/examples/Laundromat1_light.png +0 -0
  160. raptor/gui/resources/examples/ProdLine0_dark.png +0 -0
  161. raptor/gui/resources/examples/ProdLine0_light.png +0 -0
  162. raptor/gui/resources/examples/ProdLine1_dark.png +0 -0
  163. raptor/gui/resources/examples/ProdLine1_light.png +0 -0
  164. raptor/gui/resources/examples/TacoTruck1_dark.png +0 -0
  165. raptor/gui/resources/examples/TacoTruck1_light.png +0 -0
  166. raptor/gui/resources/examples/Toaster_Expo_dark.png +0 -0
  167. raptor/gui/resources/examples/Toaster_Expo_light.png +0 -0
  168. raptor/gui/resources/examples/Toaster_Fixed_dark.png +0 -0
  169. raptor/gui/resources/examples/Toaster_Fixed_light.png +0 -0
  170. raptor/gui/resources/examples/Toaster_Weibull_dark.png +0 -0
  171. raptor/gui/resources/examples/Toaster_Weibull_light.png +0 -0
  172. raptor/gui/resources/examples/Van1_dark.png +0 -0
  173. raptor/gui/resources/examples/Van1_light.png +0 -0
  174. raptor/gui/resources/examples/VanFlat_dark.png +0 -0
  175. raptor/gui/resources/examples/VanFlat_light.png +0 -0
  176. raptor/gui/resources/examples/Vendo1_dark.png +0 -0
  177. raptor/gui/resources/examples/Vendo1_light.png +0 -0
  178. raptor/gui/resources/examples/Water1_dark.png +0 -0
  179. raptor/gui/resources/examples/Water1_light.png +0 -0
  180. raptor/gui/resources/examples/Water2_dark.png +0 -0
  181. raptor/gui/resources/examples/Water2_light.png +0 -0
  182. raptor/gui/resources/examples/WaterTreatment_dark.png +0 -0
  183. raptor/gui/resources/examples/WaterTreatment_light.png +0 -0
  184. raptor/gui/resources/icons.py +2147 -0
  185. raptor/gui/resources/indicator_images.py +96 -0
  186. raptor/gui/resources/raptor_icon.png +0 -0
  187. raptor/gui/resources/raptor_logo.png +0 -0
  188. raptor/gui/row_ops.py +152 -0
  189. raptor/gui/ui/__init__.py +1 -0
  190. raptor/gui/ui/backstage.py +743 -0
  191. raptor/gui/ui/batch_dialogs.py +1114 -0
  192. raptor/gui/ui/block_labels.py +202 -0
  193. raptor/gui/ui/block_library.py +68 -0
  194. raptor/gui/ui/contrast.py +44 -0
  195. raptor/gui/ui/data_tables.py +1848 -0
  196. raptor/gui/ui/diagram.py +6043 -0
  197. raptor/gui/ui/diagram_annotations.py +908 -0
  198. raptor/gui/ui/diagram_commands.py +1125 -0
  199. raptor/gui/ui/diagram_common.py +65 -0
  200. raptor/gui/ui/diagram_items.py +1886 -0
  201. raptor/gui/ui/diagram_live.py +889 -0
  202. raptor/gui/ui/diagram_menu.py +324 -0
  203. raptor/gui/ui/dialogs.py +1834 -0
  204. raptor/gui/ui/diff_dialog.py +389 -0
  205. raptor/gui/ui/distro_combo.py +98 -0
  206. raptor/gui/ui/distro_editor.py +340 -0
  207. raptor/gui/ui/empirical_dialog.py +180 -0
  208. raptor/gui/ui/event_log_model.py +155 -0
  209. raptor/gui/ui/explorer.py +239 -0
  210. raptor/gui/ui/expr_eval.py +199 -0
  211. raptor/gui/ui/fev_mixin.py +383 -0
  212. raptor/gui/ui/filedialogs.py +150 -0
  213. raptor/gui/ui/help_viewer.py +415 -0
  214. raptor/gui/ui/inspector.py +3790 -0
  215. raptor/gui/ui/isolate_mixin.py +168 -0
  216. raptor/gui/ui/main_window.py +9290 -0
  217. raptor/gui/ui/modality.py +17 -0
  218. raptor/gui/ui/msgbox.py +27 -0
  219. raptor/gui/ui/param_entry.py +148 -0
  220. raptor/gui/ui/phasing_dialog.py +1027 -0
  221. raptor/gui/ui/plots.py +695 -0
  222. raptor/gui/ui/pool_dialogs.py +684 -0
  223. raptor/gui/ui/preflight.py +282 -0
  224. raptor/gui/ui/printing.py +215 -0
  225. raptor/gui/ui/qtcompat.py +8 -0
  226. raptor/gui/ui/rates_dialog.py +428 -0
  227. raptor/gui/ui/report_pdf.py +263 -0
  228. raptor/gui/ui/result_streams.py +120 -0
  229. raptor/gui/ui/results.py +3626 -0
  230. raptor/gui/ui/ribbon_builders.py +723 -0
  231. raptor/gui/ui/rule_gate.py +127 -0
  232. raptor/gui/ui/run_eta.py +131 -0
  233. raptor/gui/ui/run_experiment_mixin.py +742 -0
  234. raptor/gui/ui/run_panel.py +1050 -0
  235. raptor/gui/ui/settings.py +293 -0
  236. raptor/gui/ui/shortcuts_window.py +87 -0
  237. raptor/gui/ui/splash.py +165 -0
  238. raptor/gui/ui/table_export.py +177 -0
  239. raptor/gui/ui/tables_dialog.py +345 -0
  240. raptor/gui/ui/units_conv.py +78 -0
  241. raptor/gui/widgets/__init__.py +1 -0
  242. raptor/gui/widgets/ribbon.py +725 -0
  243. raptor/mcp/__init__.py +1 -0
  244. raptor/mcp/__main__.py +4 -0
  245. raptor/mcp/async_runs.py +160 -0
  246. raptor/mcp/context.py +151 -0
  247. raptor/mcp/docs.py +195 -0
  248. raptor/mcp/examples.py +62 -0
  249. raptor/mcp/http_transport.py +302 -0
  250. raptor/mcp/jobs.py +103 -0
  251. raptor/mcp/output_schemas.json +1311 -0
  252. raptor/mcp/prompts.py +66 -0
  253. raptor/mcp/resources.py +220 -0
  254. raptor/mcp/result.py +337 -0
  255. raptor/mcp/schemas.py +378 -0
  256. raptor/mcp/server.py +500 -0
  257. raptor/mcp/tools_data.py +192 -0
  258. raptor/mcp/tools_metrics.py +150 -0
  259. raptor/mcp/tools_model.py +1930 -0
  260. raptor/mcp/tools_run.py +866 -0
  261. raptor/mcp/tools_tables.py +204 -0
  262. raptor/mcp/tools_view.py +123 -0
  263. raptor/render/__init__.py +10 -0
  264. raptor/render/scene_pixmap.py +114 -0
  265. raptor/render/worker.py +540 -0
  266. raptor_studio-0.8.0.data/data/docs/IEC61078.md +76 -0
  267. raptor_studio-0.8.0.data/data/docs/KEYBOARD_SHORTCUTS.md +91 -0
  268. raptor_studio-0.8.0.data/data/docs/MCP.md +222 -0
  269. raptor_studio-0.8.0.data/data/docs/RBDS_FORMAT.md +561 -0
  270. raptor_studio-0.8.0.data/data/docs/formulas.html +690 -0
  271. raptor_studio-0.8.0.data/data/docs/js/mathjax/LICENSE +202 -0
  272. raptor_studio-0.8.0.data/data/docs/js/mathjax/core.js +1 -0
  273. raptor_studio-0.8.0.data/data/docs/js/mathjax/input/asciimath.js +1 -0
  274. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/tex.js +1 -0
  275. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff +0 -0
  276. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff +0 -0
  277. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff +0 -0
  278. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff +0 -0
  279. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff +0 -0
  280. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff +0 -0
  281. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff +0 -0
  282. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff +0 -0
  283. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff +0 -0
  284. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff +0 -0
  285. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff +0 -0
  286. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff +0 -0
  287. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff +0 -0
  288. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff +0 -0
  289. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff +0 -0
  290. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff +0 -0
  291. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff +0 -0
  292. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff +0 -0
  293. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff +0 -0
  294. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff +0 -0
  295. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff +0 -0
  296. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff +0 -0
  297. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml/fonts/woff-v2/MathJax_Zero.woff +0 -0
  298. raptor_studio-0.8.0.data/data/docs/js/mathjax/output/chtml.js +1 -0
  299. raptor_studio-0.8.0.data/data/docs/js/mathjax/startup.js +1 -0
  300. raptor_studio-0.8.0.data/data/docs/js/mathjax-config.js +7 -0
  301. raptor_studio-0.8.0.data/data/docs/manual.html +2639 -0
  302. raptor_studio-0.8.0.data/data/docs/ops_asciidoctor.css +556 -0
  303. raptor_studio-0.8.0.data/data/new_docs/book.adoc +70 -0
  304. raptor_studio-0.8.0.data/data/new_docs/book.html +11742 -0
  305. raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-developer-options.adoc +72 -0
  306. raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-glossary.adoc +209 -0
  307. raptor_studio-0.8.0.data/data/new_docs/chapters/appendix-iec61078.adoc +71 -0
  308. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-capstone-flight.adoc +341 -0
  309. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-capstone-line.adoc +256 -0
  310. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-dependency.adoc +213 -0
  311. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-events.adoc +186 -0
  312. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-phasing.adoc +243 -0
  313. raptor_studio-0.8.0.data/data/new_docs/chapters/ch-transients.adoc +207 -0
  314. raptor_studio-0.8.0.data/data/new_docs/chapters/ch00-welcome.adoc +185 -0
  315. raptor_studio-0.8.0.data/data/new_docs/chapters/ch01-first-model.adoc +311 -0
  316. raptor_studio-0.8.0.data/data/new_docs/chapters/ch02-redundancy.adoc +181 -0
  317. raptor_studio-0.8.0.data/data/new_docs/chapters/ch03-voting.adoc +163 -0
  318. raptor_studio-0.8.0.data/data/new_docs/chapters/ch04-distributions.adoc +191 -0
  319. raptor_studio-0.8.0.data/data/new_docs/chapters/ch05-repair.adoc +195 -0
  320. raptor_studio-0.8.0.data/data/new_docs/chapters/ch06-many-trials.adoc +202 -0
  321. raptor_studio-0.8.0.data/data/new_docs/chapters/ch07-sparing.adoc +167 -0
  322. raptor_studio-0.8.0.data/data/new_docs/chapters/ch08-standby.adoc +158 -0
  323. raptor_studio-0.8.0.data/data/new_docs/chapters/ch09-preventive.adoc +188 -0
  324. raptor_studio-0.8.0.data/data/new_docs/chapters/ch10-hierarchy.adoc +286 -0
  325. raptor_studio-0.8.0.data/data/new_docs/chapters/ch11-capacity.adoc +192 -0
  326. raptor_studio-0.8.0.data/data/new_docs/chapters/ch12-cost.adoc +168 -0
  327. raptor_studio-0.8.0.data/data/new_docs/chapters/ch13-weaklink.adoc +241 -0
  328. raptor_studio-0.8.0.data/data/new_docs/chapters/ch14-capstone.adoc +296 -0
  329. raptor_studio-0.8.0.data/data/new_docs/chapters/ch15-debugger.adoc +211 -0
  330. raptor_studio-0.8.0.data/data/new_docs/chapters/ch16-mcp.adoc +1522 -0
  331. raptor_studio-0.8.0.data/data/new_docs/images/app-first-look.png +0 -0
  332. raptor_studio-0.8.0.data/data/new_docs/images/bakery-loaded.png +0 -0
  333. raptor_studio-0.8.0.data/data/new_docs/images/carcare-results.png +0 -0
  334. raptor_studio-0.8.0.data/data/new_docs/images/carwash-loaded.png +0 -0
  335. raptor_studio-0.8.0.data/data/new_docs/images/dc-loaded.png +0 -0
  336. raptor_studio-0.8.0.data/data/new_docs/images/dc-power-inside.png +0 -0
  337. raptor_studio-0.8.0.data/data/new_docs/images/dist-shapes.svg +1 -0
  338. raptor_studio-0.8.0.data/data/new_docs/images/drone-loaded.png +0 -0
  339. raptor_studio-0.8.0.data/data/new_docs/images/espresso-loaded.png +0 -0
  340. raptor_studio-0.8.0.data/data/new_docs/images/espressodep-badge.png +0 -0
  341. raptor_studio-0.8.0.data/data/new_docs/images/finale-loaded.png +0 -0
  342. raptor_studio-0.8.0.data/data/new_docs/images/flight1-loaded.png +0 -0
  343. raptor_studio-0.8.0.data/data/new_docs/images/flight1-results.png +0 -0
  344. raptor_studio-0.8.0.data/data/new_docs/images/kofn-drone.svg +53 -0
  345. raptor_studio-0.8.0.data/data/new_docs/images/lantern-loaded.png +0 -0
  346. raptor_studio-0.8.0.data/data/new_docs/images/lantern-results.png +0 -0
  347. raptor_studio-0.8.0.data/data/new_docs/images/laundromat-naive.png +0 -0
  348. raptor_studio-0.8.0.data/data/new_docs/images/parallel-pumps.svg +36 -0
  349. raptor_studio-0.8.0.data/data/new_docs/images/prodline-loaded.png +0 -0
  350. raptor_studio-0.8.0.data/data/new_docs/images/prodline1-results.png +0 -0
  351. raptor_studio-0.8.0.data/data/new_docs/images/runs_converge.svg +1 -0
  352. raptor_studio-0.8.0.data/data/new_docs/images/series-lantern.svg +25 -0
  353. raptor_studio-0.8.0.data/data/new_docs/images/simulate-cores.png +0 -0
  354. raptor_studio-0.8.0.data/data/new_docs/images/start-new.png +0 -0
  355. raptor_studio-0.8.0.data/data/new_docs/images/start-page.png +0 -0
  356. raptor_studio-0.8.0.data/data/new_docs/images/step-fail.png +0 -0
  357. raptor_studio-0.8.0.data/data/new_docs/images/step-ribbon.png +0 -0
  358. raptor_studio-0.8.0.data/data/new_docs/images/step-start.png +0 -0
  359. raptor_studio-0.8.0.data/data/new_docs/images/sweep-dialog.png +0 -0
  360. raptor_studio-0.8.0.data/data/new_docs/images/tacotruck-loaded.png +0 -0
  361. raptor_studio-0.8.0.data/data/new_docs/images/van-engine-inside.png +0 -0
  362. raptor_studio-0.8.0.data/data/new_docs/images/van-loaded.png +0 -0
  363. raptor_studio-0.8.0.data/data/new_docs/images/van-tires-inside.png +0 -0
  364. raptor_studio-0.8.0.data/data/new_docs/images/vendo-data-tables.png +0 -0
  365. raptor_studio-0.8.0.data/data/new_docs/images/vendo-results.png +0 -0
  366. raptor_studio-0.8.0.data/data/new_docs/images/water-loaded.png +0 -0
  367. raptor_studio-0.8.0.data/data/new_docs/images/water2-sysconfig.png +0 -0
  368. raptor_studio-0.8.0.data/data/new_docs/models/AirlinerMission.rbds +2121 -0
  369. raptor_studio-0.8.0.data/data/new_docs/models/Bakery1.rbds +658 -0
  370. raptor_studio-0.8.0.data/data/new_docs/models/CarCare0.rbds +342 -0
  371. raptor_studio-0.8.0.data/data/new_docs/models/CarCare1.rbds +342 -0
  372. raptor_studio-0.8.0.data/data/new_docs/models/CarWash1.rbds +494 -0
  373. raptor_studio-0.8.0.data/data/new_docs/models/DC1.rbds +2456 -0
  374. raptor_studio-0.8.0.data/data/new_docs/models/DC2.rbds +2663 -0
  375. raptor_studio-0.8.0.data/data/new_docs/models/Drone1.rbds +1275 -0
  376. raptor_studio-0.8.0.data/data/new_docs/models/Espresso1.rbds +658 -0
  377. raptor_studio-0.8.0.data/data/new_docs/models/EspressoDep1.rbds +658 -0
  378. raptor_studio-0.8.0.data/data/new_docs/models/FinaleShow1.rbds +554 -0
  379. raptor_studio-0.8.0.data/data/new_docs/models/Flight1.rbds +1182 -0
  380. raptor_studio-0.8.0.data/data/new_docs/models/HospitalPower.rbds +3087 -0
  381. raptor_studio-0.8.0.data/data/new_docs/models/Lantern1.rbds +560 -0
  382. raptor_studio-0.8.0.data/data/new_docs/models/Laundromat1.rbds +342 -0
  383. raptor_studio-0.8.0.data/data/new_docs/models/ProdLine0.rbds +1761 -0
  384. raptor_studio-0.8.0.data/data/new_docs/models/ProdLine1.rbds +1761 -0
  385. raptor_studio-0.8.0.data/data/new_docs/models/TacoTruck1.rbds +451 -0
  386. raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Expo.rbds +342 -0
  387. raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Fixed.rbds +342 -0
  388. raptor_studio-0.8.0.data/data/new_docs/models/Toaster_Weibull.rbds +342 -0
  389. raptor_studio-0.8.0.data/data/new_docs/models/Van1.rbds +1272 -0
  390. raptor_studio-0.8.0.data/data/new_docs/models/VanFlat.rbds +1021 -0
  391. raptor_studio-0.8.0.data/data/new_docs/models/Vendo1.rbds +451 -0
  392. raptor_studio-0.8.0.data/data/new_docs/models/Water1.rbds +678 -0
  393. raptor_studio-0.8.0.data/data/new_docs/models/Water2.rbds +679 -0
  394. raptor_studio-0.8.0.data/data/new_docs/models/WaterTreatment.rbds +2854 -0
  395. raptor_studio-0.8.0.data/data/new_docs/ops_asciidoctor.css +556 -0
  396. raptor_studio-0.8.0.data/data/share/bash-completion/completions/raptor +38 -0
  397. raptor_studio-0.8.0.data/data/share/fish/vendor_completions.d/raptor.fish +213 -0
  398. raptor_studio-0.8.0.data/data/share/man/man1/raptor-compare.1 +58 -0
  399. raptor_studio-0.8.0.data/data/share/man/man1/raptor-completion.1 +9 -0
  400. raptor_studio-0.8.0.data/data/share/man/man1/raptor-config.1 +9 -0
  401. raptor_studio-0.8.0.data/data/share/man/man1/raptor-convert.1 +17 -0
  402. raptor_studio-0.8.0.data/data/share/man/man1/raptor-diff.1 +61 -0
  403. raptor_studio-0.8.0.data/data/share/man/man1/raptor-doctor.1 +14 -0
  404. raptor_studio-0.8.0.data/data/share/man/man1/raptor-estimate.1 +20 -0
  405. raptor_studio-0.8.0.data/data/share/man/man1/raptor-extract.1 +51 -0
  406. raptor_studio-0.8.0.data/data/share/man/man1/raptor-fev.1 +87 -0
  407. raptor_studio-0.8.0.data/data/share/man/man1/raptor-get.1 +22 -0
  408. raptor_studio-0.8.0.data/data/share/man/man1/raptor-gui.1 +17 -0
  409. raptor_studio-0.8.0.data/data/share/man/man1/raptor-help.1 +9 -0
  410. raptor_studio-0.8.0.data/data/share/man/man1/raptor-info.1 +17 -0
  411. raptor_studio-0.8.0.data/data/share/man/man1/raptor-mcp.1 +85 -0
  412. raptor_studio-0.8.0.data/data/share/man/man1/raptor-render.1 +32 -0
  413. raptor_studio-0.8.0.data/data/share/man/man1/raptor-report.1 +20 -0
  414. raptor_studio-0.8.0.data/data/share/man/man1/raptor-run.1 +192 -0
  415. raptor_studio-0.8.0.data/data/share/man/man1/raptor-set.1 +38 -0
  416. raptor_studio-0.8.0.data/data/share/man/man1/raptor-spf.1 +20 -0
  417. raptor_studio-0.8.0.data/data/share/man/man1/raptor-step.1 +51 -0
  418. raptor_studio-0.8.0.data/data/share/man/man1/raptor-sweep.1 +130 -0
  419. raptor_studio-0.8.0.data/data/share/man/man1/raptor-tables.1 +67 -0
  420. raptor_studio-0.8.0.data/data/share/man/man1/raptor-validate.1 +86 -0
  421. raptor_studio-0.8.0.data/data/share/man/man1/raptor.1 +117 -0
  422. raptor_studio-0.8.0.data/data/share/man/man5/raptor-config.5 +16 -0
  423. raptor_studio-0.8.0.data/data/share/man/man7/raptor-set-paths.7 +39 -0
  424. raptor_studio-0.8.0.data/data/share/zsh/site-functions/_raptor +11 -0
  425. raptor_studio-0.8.0.dist-info/METADATA +275 -0
  426. raptor_studio-0.8.0.dist-info/RECORD +433 -0
  427. raptor_studio-0.8.0.dist-info/WHEEL +5 -0
  428. raptor_studio-0.8.0.dist-info/entry_points.txt +6 -0
  429. raptor_studio-0.8.0.dist-info/licenses/LICENSE +49 -0
  430. raptor_studio-0.8.0.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
  431. raptor_studio-0.8.0.dist-info/licenses/NOTICE.md +38 -0
  432. raptor_studio-0.8.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +36 -0
  433. raptor_studio-0.8.0.dist-info/top_level.txt +1 -0
raptor/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """RAPTOR Studio — a from-scratch Python port of the RAPTOR
2
+ reliability simulation engine."""
3
+
4
+ __version__ = "0.8.0" # x-release-please-version
raptor/api/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """RAPTOR Studio API — headless, Qt-free program interface.
2
+
3
+ Business-logic layer between the MCP server and the core engine. It is
4
+ importable and testable without a display or PySide6.
5
+ """
raptor/api/analyse.py ADDED
@@ -0,0 +1,624 @@
1
+ """Analyses API — single point failures, failure effects, weak link,
2
+ comparison and parameter sweep.
3
+
4
+ Wraps engine functions into MCP-friendly calls. No Qt imports.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import copy
9
+ import functools
10
+ import itertools
11
+ import threading
12
+ from typing import Any
13
+
14
+ from raptor.api.run import (run, run_dir, RunResult, RunNotFoundError,
15
+ RunCancelledError)
16
+ from raptor.api.results import Metric
17
+ from raptor.api.workspace import Workspace, UnknownHandle
18
+
19
+
20
+ class AnalysisError(Exception):
21
+ """Base error for analysis operations."""
22
+
23
+
24
+ #: Serialises `sweep`/`compare`, which each create/close several scratch
25
+ #: models in the shared workspace. Two issued concurrently (an assistant
26
+ #: naturally reaches for Promise.all) used to race on model-id allocation
27
+ #: and eviction, surfacing as an expired handle or a missing model
28
+ #: directory. A single process-wide lock makes them safe at the cost of
29
+ #: running one at a time -- the right trade for what-if studies.
30
+ _ANALYSIS_LOCK = threading.RLock()
31
+
32
+
33
+ def _serialised(fn):
34
+ @functools.wraps(fn)
35
+ def wrapper(*args, **kwargs):
36
+ with _ANALYSIS_LOCK:
37
+ return fn(*args, **kwargs)
38
+ return wrapper
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Single Point Failures
43
+ # ---------------------------------------------------------------------------
44
+
45
+ def single_point_failures(workspace: Workspace, model_id: str, *,
46
+ workers: int = 1,
47
+ use_cache: bool = True) -> list[dict]:
48
+ """The Single Point Failure rows, found as Raptor 7 does: each Running or
49
+ Standby block is failed in Failure Effects View and the system tested
50
+ (`raptor.core.engine.spf_fev`), so dependencies and cold standby count.
51
+
52
+ Each row: ``{"kind": "Block"|"Event", "id": int, "name": str, "crit": float}``;
53
+ a phased model's rows also carry ``"phase"`` (one set per phase).
54
+
55
+ The result is content-addressed by the model hash, so asking again after
56
+ no edit returns without re-testing 3,000+ candidates. `workers > 1`
57
+ parallelises the independent candidate tests on an unphased model.
58
+ """
59
+ import json
60
+
61
+ from raptor.api.run import model_hash as _hash
62
+ handle = workspace.get(model_id)
63
+ digest = _hash(handle.rbd)
64
+ cache_dir = workspace.root / ".spf_cache"
65
+ cache_path = cache_dir / f"{digest}.json"
66
+ if use_cache:
67
+ try:
68
+ cached = json.loads(cache_path.read_text())
69
+ if isinstance(cached, list):
70
+ return cached
71
+ except (OSError, ValueError):
72
+ pass
73
+ from raptor.core.engine.spf_fev import single_point_failures as _spf
74
+ rows = _spf(handle.rbd, workers=workers)
75
+ if use_cache:
76
+ try:
77
+ cache_dir.mkdir(parents=True, exist_ok=True)
78
+ cache_path.write_text(json.dumps(rows))
79
+ except OSError:
80
+ pass
81
+ return rows
82
+
83
+
84
+ def _criticality_on(explicit: bool | None) -> bool:
85
+ if explicit is not None:
86
+ return explicit
87
+ from raptor.core.dev_options import from_environment
88
+ return from_environment().show_criticality
89
+
90
+
91
+ def spf_listing(workspace: Workspace, model_id: str,
92
+ file_name: str | None = None, *, criticality: bool | None = None) -> str:
93
+ """The Raptor 7-format Single Point Failure file text (CRLF), as the
94
+ original's `Create Single Point Failure File` writes it. Phased models
95
+ get the per-phase layout. `criticality=None` follows the `show_criticality` developer
96
+ option (`RAPTOR_DEV_FEATURES`)."""
97
+ handle = workspace.get(model_id)
98
+ criticality = _criticality_on(criticality)
99
+ from raptor.core.engine.spf import spf_date_string
100
+ from raptor.core.engine.spf_fev import build_spf_listing
101
+ return build_spf_listing(handle.rbd, file_name or f"{handle.name}.txt",
102
+ spf_date_string(), password=criticality)
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Failure Effects
107
+ # ---------------------------------------------------------------------------
108
+
109
+ def _resolve_fev_element(workspace: Workspace, model_id: str, selector: str | int) -> tuple[str, int]:
110
+ """A block or event selector -> ("block"|"event", id)."""
111
+ if isinstance(selector, int):
112
+ return "block", selector
113
+ if isinstance(selector, str) and selector.strip().lstrip("-").isdigit():
114
+ return "block", int(selector)
115
+ from raptor.api.build import BuildError, resolve_element
116
+ try:
117
+ section, row = resolve_element(workspace.get(model_id).rbd, str(selector))
118
+ except BuildError as exc:
119
+ raise AnalysisError(str(exc)) from exc
120
+ if section == "BLOCK_DATA":
121
+ return "block", int(row["ID"])
122
+ if section == "EVENT_DATA":
123
+ return "event", int(row["ID"])
124
+ raise AnalysisError(
125
+ f"{selector!r} is not a block or an event; Failure Effects View "
126
+ "changes blocks and events only")
127
+
128
+
129
+ def failure_effects(
130
+ workspace: Workspace,
131
+ model_id: str,
132
+ *,
133
+ fail: list[str | int] | None = None,
134
+ repair: list[str | int] | None = None,
135
+ phase: int | None = None,
136
+ ) -> dict:
137
+ """Failure Effects View as a script: start from the all-operating state
138
+ (phase 1 of a phased model), optionally go to `phase`, fail the `fail`
139
+ elements in order, then repair the `repair` elements.
140
+
141
+ Runs `raptor.core.engine.fev.FevSession`, the port of Raptor 7's Failure
142
+ Effects View: nothing is simulated and no random number is drawn. Returns
143
+ ``{"system_status", "affected", "log", "effects", "criticality", "phase"}``;
144
+ `log` is the Failure Effects text (what Raptor 7's Write To Failure
145
+ Effects Log appends), `effects` the same facts as lists.
146
+ """
147
+ from raptor.core.engine.errors import UnrunnableModel
148
+ from raptor.core.engine.fev import FevSession
149
+ from raptor.core.engine.fmeca import build_fmeca, fmeca_lists
150
+ from raptor.core.model import BlockStatus, EventStatus
151
+
152
+ handle = workspace.get(model_id)
153
+ try:
154
+ session = FevSession(handle.rbd)
155
+ except UnrunnableModel as exc:
156
+ raise AnalysisError(str(exc)) from exc
157
+
158
+ if phase is not None:
159
+ if not session.phased:
160
+ raise AnalysisError("this model has no phases")
161
+ n = len(session.model.phases)
162
+ if not 1 <= phase <= n:
163
+ raise AnalysisError(f"phase {phase} is not in 1..{n}")
164
+ session.goto_phase(phase)
165
+
166
+ affected: list[str] = []
167
+ for selector in fail or []:
168
+ kind, eid = _resolve_fev_element(workspace, model_id, selector)
169
+ if kind == "event":
170
+ event = session.model.events_by_id.get(eid)
171
+ if event is None or event.op_status != EventStatus.SUCCESS:
172
+ raise AnalysisError(
173
+ f"Could not fail event {selector!r} (already failed or not found)")
174
+ session.click_event(eid)
175
+ else:
176
+ b = session.model.blocks_by_id.get(eid)
177
+ if b is None or not (b.op_status == BlockStatus.RUNNING or (
178
+ b.op_status == BlockStatus.STANDBY and b.sb_stress > 0.0)):
179
+ raise AnalysisError(
180
+ f"Could not fail block {selector!r} "
181
+ "(already failed, not operating, or not found)")
182
+ session.click_block(eid)
183
+ affected.append(str(selector))
184
+ for selector in repair or []:
185
+ kind, eid = _resolve_fev_element(workspace, model_id, selector)
186
+ if kind == "event":
187
+ event = session.model.events_by_id.get(eid)
188
+ if event is None or event.op_status != EventStatus.FAILURE:
189
+ raise AnalysisError(
190
+ f"Could not repair event {selector!r} (not failed or not found)")
191
+ session.click_event(eid)
192
+ else:
193
+ b = session.model.blocks_by_id.get(eid)
194
+ if b is None or b.op_status not in (BlockStatus.REPAIRING, BlockStatus.PM):
195
+ raise AnalysisError(
196
+ f"Could not repair block {selector!r} "
197
+ "(already running or not found)")
198
+ session.click_block(eid)
199
+ affected.append(str(selector))
200
+
201
+ return {
202
+ "system_status": session.system_color.value,
203
+ "affected": affected,
204
+ "log": build_fmeca(session, show_criticality=_criticality_on(None)),
205
+ "effects": fmeca_lists(session),
206
+ "criticality": session.criticality,
207
+ "phase": session.phase_number if session.phased else None,
208
+ }
209
+
210
+
211
+ FEV_SCRIPT_ACTIONS = {
212
+ "click": "left-click a block or event (cycles a hatched block)",
213
+ "fail": "click an operating block, or an event that has not failed",
214
+ "repair": "click a failed or PM block, or a failed event",
215
+ "phase": "go to phase N",
216
+ "next": "the forward phase arrow (wraps)",
217
+ "prev": "the back phase arrow (wraps)",
218
+ "erase": "the large eraser: everything operating, phase 1",
219
+ "erase-phase": "the small eraser: clear failures, keep the phase",
220
+ "write": "Write To Failure Effects Log: record the text now",
221
+ }
222
+
223
+
224
+ def parse_fev_script(text: str) -> list[tuple[str, str]]:
225
+ """One action per line: ``ACTION [ARGUMENT]``; blank lines and ``#`` comments
226
+ are ignored. Raises `AnalysisError` naming the line."""
227
+ actions: list[tuple[str, str]] = []
228
+ for n, raw in enumerate(text.splitlines(), 1):
229
+ line = raw.split("#", 1)[0].strip()
230
+ if not line:
231
+ continue
232
+ verb, _, arg = line.partition(" ")
233
+ verb, arg = verb.lower(), arg.strip()
234
+ if verb not in FEV_SCRIPT_ACTIONS:
235
+ raise AnalysisError(
236
+ f"line {n}: unknown action {verb!r}; use one of "
237
+ f"{', '.join(FEV_SCRIPT_ACTIONS)}")
238
+ needs_arg = verb in ("click", "fail", "repair", "phase")
239
+ if needs_arg and not arg:
240
+ raise AnalysisError(f"line {n}: {verb} needs an argument")
241
+ if not needs_arg and arg:
242
+ raise AnalysisError(f"line {n}: {verb} takes no argument")
243
+ actions.append((verb, arg))
244
+ return actions
245
+
246
+
247
+ def fev_script(workspace: Workspace, model_id: str,
248
+ actions: list[tuple[str, str]], *,
249
+ criticality: bool | None = None) -> list[dict]:
250
+ """Drive Failure Effects View through `actions` and return one record per
251
+ ``write`` (or a single record of the final state when there is none)."""
252
+ from raptor.core.engine.errors import UnrunnableModel
253
+ from raptor.core.engine.fev import FevSession
254
+ from raptor.core.engine.fmeca import build_fmeca, fmeca_lists
255
+ from raptor.core.model import BlockStatus, EventStatus
256
+
257
+ criticality = _criticality_on(criticality)
258
+ try:
259
+ session = FevSession(workspace.get(model_id).rbd)
260
+ except UnrunnableModel as exc:
261
+ raise AnalysisError(str(exc)) from exc
262
+
263
+ def record(label: str) -> dict:
264
+ return {
265
+ "after": label,
266
+ "system_status": session.system_color.value,
267
+ "phase": session.phase_number if session.phased else None,
268
+ "criticality": session.criticality,
269
+ "lines": build_fmeca(session, show_criticality=criticality),
270
+ "effects": fmeca_lists(session),
271
+ }
272
+
273
+ def element(arg: str) -> tuple[str, int]:
274
+ return _resolve_fev_element(workspace, model_id, arg)
275
+
276
+ def need_phased() -> None:
277
+ if not session.phased:
278
+ raise AnalysisError("this model has no phases")
279
+
280
+ writes: list[dict] = []
281
+ last = "start"
282
+ for verb, arg in actions:
283
+ label = f"{verb} {arg}".strip()
284
+ if verb in ("click", "fail", "repair"):
285
+ kind, eid = element(arg)
286
+ if kind == "event":
287
+ event = session.model.events_by_id.get(eid)
288
+ if event is None:
289
+ raise AnalysisError(f"no event {arg!r}")
290
+ wanted = {"fail": EventStatus.SUCCESS, "repair": EventStatus.FAILURE}.get(verb)
291
+ if wanted is not None and event.op_status != wanted:
292
+ raise AnalysisError(f"cannot {verb} event {arg!r} (it is {event.op_status.value})")
293
+ session.click_event(eid)
294
+ else:
295
+ b = session.model.blocks_by_id.get(eid)
296
+ if b is None:
297
+ raise AnalysisError(f"no block {arg!r}")
298
+ if verb == "fail" and not (b.op_status == BlockStatus.RUNNING or (
299
+ b.op_status == BlockStatus.STANDBY and b.sb_stress > 0.0)):
300
+ raise AnalysisError(f"cannot fail block {arg!r} (it is {b.op_status.value})")
301
+ if verb == "repair" and b.op_status not in (BlockStatus.REPAIRING, BlockStatus.PM):
302
+ raise AnalysisError(f"cannot repair block {arg!r} (it is {b.op_status.value})")
303
+ session.click_block(eid)
304
+ elif verb == "phase":
305
+ need_phased()
306
+ try:
307
+ n = int(arg)
308
+ except ValueError:
309
+ raise AnalysisError(f"phase needs a number, got {arg!r}") from None
310
+ if not 1 <= n <= len(session.model.phases):
311
+ raise AnalysisError(f"phase {n} is not in 1..{len(session.model.phases)}")
312
+ session.goto_phase(n)
313
+ elif verb == "next":
314
+ need_phased()
315
+ session.next_phase()
316
+ elif verb == "prev":
317
+ need_phased()
318
+ session.previous_phase()
319
+ elif verb == "erase":
320
+ session.refresh_all()
321
+ elif verb == "erase-phase":
322
+ need_phased()
323
+ session.refresh_phase()
324
+ elif verb == "write":
325
+ writes.append(record(last))
326
+ continue
327
+ last = label
328
+ if not writes:
329
+ writes.append(record(last))
330
+ return writes
331
+
332
+
333
+ # ---------------------------------------------------------------------------
334
+ # Weak Link
335
+ # ---------------------------------------------------------------------------
336
+
337
+ def weak_link(workspace: Workspace, run_id: str) -> list[dict]:
338
+ """Extract weak-link rows from a completed run's final results."""
339
+ from raptor.api.run import get_report
340
+
341
+ report = get_report(workspace, run_id, section="weak_link", limit=2000)
342
+ rows: list[dict] = []
343
+ for line in report["lines"]:
344
+ stripped = line.strip()
345
+ if not stripped or stripped.startswith("***"):
346
+ continue
347
+ parts = stripped.split()
348
+ if len(parts) >= 2:
349
+ rows.append({
350
+ "block_name": parts[0],
351
+ "impact": " ".join(parts[1:]),
352
+ })
353
+ return rows
354
+
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # Compare
358
+ # ---------------------------------------------------------------------------
359
+
360
+ _HIGHER_IS_BETTER = {
361
+ "Availability": True,
362
+ "Reliability": True,
363
+ "Conditional_Reliability": True,
364
+ "Green_Percent": True,
365
+ "MDT": False,
366
+ "MMT": False,
367
+ "MTBM": False,
368
+ "MTBDE": True,
369
+ "System_Failures": False,
370
+ "Component_Failures": False,
371
+ "Red_Percent": False,
372
+ "Yellow_Percent": False,
373
+ }
374
+
375
+
376
+ def _result_from_run(workspace: Workspace, run_id: str) -> RunResult:
377
+ """Build a RunResult from a cached run's summary.json."""
378
+ import json
379
+ path = run_dir(workspace, run_id) / "summary.json"
380
+ if not path.is_file():
381
+ raise RunNotFoundError(f"Run {run_id!r} not found")
382
+ data = json.loads(path.read_text())
383
+ metrics = [Metric(**v) for v in data.get("metrics", {}).values()]
384
+ return RunResult(
385
+ run_id=run_id,
386
+ model_id=data.get("model_id", run_id),
387
+ num_runs=data.get("num_runs", 0),
388
+ model_name=data.get("model_name", ""),
389
+ metrics=metrics,
390
+ model_hash=data.get("model_hash", ""),
391
+ )
392
+
393
+
394
+ @_serialised
395
+ def compare(
396
+ workspace: Workspace,
397
+ models_or_runs: list[str],
398
+ *,
399
+ num_runs: int = 5,
400
+ metrics: list[str] | None = None,
401
+ abort: Any = None,
402
+ progress: Any = None,
403
+ ) -> dict:
404
+ """Compare two or more models (run afresh) or completed runs (cached).
405
+
406
+ Returns per-metric values, deltas against the first entry, and the
407
+ best entry per metric.
408
+ """
409
+ if len(models_or_runs) < 2:
410
+ # Caller-input error (not a model-state problem): ValueError maps to
411
+ # the MCP "invalid_argument" code, unlike AnalysisError.
412
+ raise ValueError("need at least 2 models or runs to compare")
413
+ if num_runs < 1:
414
+ raise ValueError("num_runs must be at least 1")
415
+
416
+ results: list[RunResult] = []
417
+ for step, ref in enumerate(models_or_runs):
418
+ if abort is not None and abort.is_set():
419
+ raise RunCancelledError("comparison was cancelled")
420
+ if progress is not None:
421
+ progress(step, len(models_or_runs))
422
+ try:
423
+ workspace.get(ref)
424
+ is_model = True
425
+ except UnknownHandle:
426
+ is_model = False
427
+
428
+ if is_model:
429
+ results.append(run(workspace, ref, num_runs=num_runs,
430
+ timeout_s=120, confirm_long_run=True,
431
+ abort=abort))
432
+ else:
433
+ results.append(_result_from_run(workspace, ref))
434
+
435
+ # Human-readable labels for the raw model_id/run_id refs in
436
+ # `models_or_runs` -- both branches above populate `model_name` (a
437
+ # fresh run always does; a cached run's summary.json carries it from
438
+ # when it was made). Falls back to the raw ref only when a name is
439
+ # genuinely unavailable, and disambiguates duplicate names (two
440
+ # models can share a name) by appending a short ref suffix.
441
+ labels = [r.model_name or ref
442
+ for r, ref in zip(results, models_or_runs)]
443
+ seen: dict[str, int] = {}
444
+ for label in labels:
445
+ seen[label] = seen.get(label, 0) + 1
446
+ for i, label in enumerate(labels):
447
+ if seen[label] > 1:
448
+ # A repeated model/run ref makes the old "(ref)" suffix
449
+ # itself duplicate; include the position to stay unique.
450
+ labels[i] = f"{label} ({models_or_runs[i]}) #{i + 1}"
451
+
452
+ names = sorted({m.name for r in results for m in r.metrics})
453
+ if metrics:
454
+ wanted = {m.lower() for m in metrics}
455
+ names = [n for n in names if n.lower() in wanted]
456
+
457
+ values: dict[str, list[float | None]] = {}
458
+ for name in names:
459
+ row: list[float | None] = []
460
+ for r in results:
461
+ match = next((m for m in r.metrics if m.name == name), None)
462
+ row.append(match.mean if match else None)
463
+ values[name] = row
464
+
465
+ deltas: dict[str, float | None] = {}
466
+ for name in names:
467
+ base = values[name][0]
468
+ for i, label in enumerate(labels[1:], start=1):
469
+ other = values[name][i]
470
+ if base is not None and other is not None:
471
+ deltas[f"{name}_delta_{label}"] = other - base
472
+
473
+ best: dict[str, str] = {}
474
+ for name in names:
475
+ row = values[name]
476
+ candidates = [(v, i) for i, v in enumerate(row) if v is not None]
477
+ if not candidates:
478
+ continue
479
+ higher = _HIGHER_IS_BETTER.get(name, True)
480
+ chosen = max(candidates) if higher else min(candidates)
481
+ best[name] = labels[chosen[1]]
482
+
483
+ return {
484
+ "models_or_runs": models_or_runs,
485
+ "labels": labels,
486
+ "metrics": values,
487
+ "deltas": deltas,
488
+ "best": best,
489
+ }
490
+
491
+
492
+ # ---------------------------------------------------------------------------
493
+ # Sweep
494
+ # ---------------------------------------------------------------------------
495
+
496
+ @_serialised
497
+ def sweep(
498
+ workspace: Workspace,
499
+ model_id: str,
500
+ selector: str,
501
+ field: str,
502
+ values: list[Any],
503
+ *,
504
+ num_runs: int = 5,
505
+ metrics: list[str] | None = None,
506
+ abort: Any = None,
507
+ progress: Any = None,
508
+ ) -> dict:
509
+ """Sweep a parameter across values, on copies of the model."""
510
+ from raptor.api.build import update_element
511
+
512
+ if not values:
513
+ raise ValueError("sweep needs at least one value")
514
+ if num_runs < 1:
515
+ raise ValueError("num_runs must be at least 1")
516
+ source = workspace.get(model_id)
517
+ rows: list[dict] = []
518
+ created: list[str] = []
519
+ try:
520
+ for step, val in enumerate(values):
521
+ if abort is not None and abort.is_set():
522
+ raise RunCancelledError("sweep was cancelled")
523
+ if progress is not None:
524
+ progress(step, len(values))
525
+ copy_result = workspace.create_model(
526
+ f"sweep_{selector}_{val}", template="blank"
527
+ )
528
+ copy_id = copy_result["model_id"]
529
+ created.append(copy_id)
530
+ copy_handle = workspace.get(copy_id)
531
+ copy_handle.rbd = copy.deepcopy(source.rbd)
532
+ copy_handle.rbd.file_name = f"sweep_{val}"
533
+ # A copy, not an edit: the source's own state is not re-judged.
534
+ # The swept value itself goes through `update_element` below,
535
+ # so a value Raptor 7 would refuse is refused for the sweep too.
536
+ workspace.save(copy_id, check_rules=False)
537
+
538
+ update_element(workspace, copy_id, selector, {field: val})
539
+ result = run(workspace, copy_id, num_runs=num_runs,
540
+ timeout_s=120, confirm_long_run=True,
541
+ abort=abort)
542
+
543
+ # Provenance: the run that produced this row, and how many
544
+ # replications it actually completed (a sweep row whose runs
545
+ # < num_runs was silently truncated by the timeout).
546
+ row: dict[str, Any] = {"value": val, "run_id": result.run_id,
547
+ "runs": result.num_runs}
548
+ wanted = {m.lower() for m in metrics} if metrics else None
549
+ for m in result.metrics:
550
+ if wanted is None or m.name.lower() in wanted:
551
+ row[m.name] = m.mean
552
+ rows.append(row)
553
+ workspace.close(copy_id, delete=True)
554
+ created.remove(copy_id)
555
+ finally:
556
+ # Never leave THIS call's sweep copies behind on error. A name
557
+ # prefix is not ownership: a user model called sweep_Casing_1
558
+ # must not be deleted by an unrelated sweep.
559
+ for cid in created:
560
+ try:
561
+ workspace.close(cid, delete=True)
562
+ except Exception:
563
+ pass
564
+
565
+ monotonic = _is_monotonic(rows)
566
+ warnings: list[str] = []
567
+ identical = _all_rows_identical(rows)
568
+ if identical:
569
+ warnings.append(
570
+ "Every sweep row has identical metric values -- either the "
571
+ "parameter genuinely has no effect, or it was not applied. "
572
+ "Check the per-row run_id and the field name."
573
+ )
574
+ short = [r for r in rows if r.get("runs") is not None and r["runs"] < num_runs]
575
+ if short:
576
+ warnings.append(
577
+ f"{len(short)} of {len(rows)} rows completed fewer than "
578
+ f"{num_runs} runs (timeout); their estimates are noisier than "
579
+ f"requested."
580
+ )
581
+ return {
582
+ "selector": selector,
583
+ "field": field,
584
+ "rows": rows,
585
+ "monotonic": monotonic,
586
+ "all_rows_identical": identical,
587
+ "warnings": warnings,
588
+ }
589
+
590
+
591
+ def _all_rows_identical(rows: list[dict]) -> bool:
592
+ """True when every row's metric values are bit-identical (so a reader
593
+ cannot tell "no effect" from "value never applied")."""
594
+ if len(rows) < 2:
595
+ return False
596
+ metric_keys = [k for k in rows[0] if k not in _NON_METRIC_KEYS]
597
+ if not metric_keys:
598
+ return False
599
+ first = rows[0]
600
+ return all(
601
+ all(r.get(k) == first.get(k) for k in metric_keys)
602
+ for r in rows[1:]
603
+ )
604
+
605
+
606
+ _NON_METRIC_KEYS = frozenset({"value", "run_id", "runs", "warnings"})
607
+
608
+
609
+ def _is_monotonic(rows: list[dict]) -> bool:
610
+ if len(rows) < 2:
611
+ return True
612
+ metric = next((k for k in rows[0] if k not in _NON_METRIC_KEYS), None)
613
+ if metric is None:
614
+ return True
615
+ vals = [r.get(metric) for r in rows]
616
+ increasing = all(
617
+ b is None or a is None or b >= a
618
+ for a, b in itertools.pairwise(vals)
619
+ )
620
+ decreasing = all(
621
+ b is None or a is None or b <= a
622
+ for a, b in itertools.pairwise(vals)
623
+ )
624
+ return increasing or decreasing