openprocess 0.12.1__tar.gz → 0.13.0__tar.gz

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 (260) hide show
  1. {openprocess-0.12.1/openprocess.egg-info → openprocess-0.13.0}/PKG-INFO +35 -1
  2. {openprocess-0.12.1 → openprocess-0.13.0}/README.md +31 -0
  3. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/cli.py +89 -19
  5. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/__init__.py +2 -2
  6. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/box.py +7 -0
  7. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/check.py +5 -0
  8. openprocess-0.13.0/openprocess/flow/boxes/cpn.py +97 -0
  9. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/discover.py +36 -0
  10. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/input.py +18 -0
  11. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/measure.py +2 -3
  12. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/output.py +10 -0
  13. openprocess-0.13.0/openprocess/flow/changes.py +253 -0
  14. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/convert.py +5 -0
  15. openprocess-0.13.0/openprocess/flow/derivation.py +283 -0
  16. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/library.py +5 -2
  17. openprocess-0.13.0/openprocess/flow/studio.py +168 -0
  18. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/types.py +3 -1
  19. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/workflow.py +29 -3
  20. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/page.py +13 -2
  21. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/picker.py +1 -1
  22. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/viewers.py +121 -16
  23. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/app.py +66 -5
  24. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/definition_view.py +5 -1
  25. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/graph_builders.py +30 -0
  26. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/graph_view.py +18 -1
  27. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/log_page.py +2 -1
  28. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/model_page.py +2 -2
  29. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/petri_page.py +2 -2
  30. openprocess-0.13.0/openprocess/gui/studio/remote.py +169 -0
  31. openprocess-0.13.0/openprocess/gui/studio/review.py +166 -0
  32. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/spaces.py +3 -3
  33. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/widgets.py +5 -0
  34. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/workspace.py +2 -1
  35. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/answers.py +22 -3
  36. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/checks.py +34 -6
  37. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/computed.py +11 -9
  38. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/exercise-packs.md +1 -1
  39. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/notation.py +9 -2
  40. openprocess-0.13.0/openprocess/mcp/__init__.py +29 -0
  41. openprocess-0.13.0/openprocess/mcp/__main__.py +5 -0
  42. openprocess-0.13.0/openprocess/mcp/api.py +1142 -0
  43. openprocess-0.13.0/openprocess/mcp/connecting.md +88 -0
  44. openprocess-0.13.0/openprocess/mcp/install.py +127 -0
  45. openprocess-0.13.0/openprocess/mcp/server.py +489 -0
  46. openprocess-0.13.0/openprocess/mining/bpmn.py +826 -0
  47. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/columns.py +2 -0
  48. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/alignments.py +12 -2
  49. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/quality.py +13 -17
  50. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/token_replay.py +104 -20
  51. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/definitions.py +9 -5
  52. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/alpha.py +60 -35
  53. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/heuristics.py +6 -2
  54. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/inductive.py +33 -3
  55. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/state_regions.py +2 -0
  56. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/dotted.py +30 -4
  57. openprocess-0.13.0/openprocess/mining/drawing.py +471 -0
  58. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/filtering.py +5 -0
  59. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/log.py +6 -1
  60. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/ocel.py +1 -1
  61. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/pnml.py +14 -0
  62. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/regions.py +12 -1
  63. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/xes.py +2 -0
  64. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/references.py +15 -2
  65. openprocess-0.13.0/openprocess/sim/performance.py +408 -0
  66. {openprocess-0.12.1 → openprocess-0.13.0/openprocess.egg-info}/PKG-INFO +35 -1
  67. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/SOURCES.txt +19 -0
  68. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/entry_points.txt +1 -0
  69. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/requires.txt +4 -0
  70. {openprocess-0.12.1 → openprocess-0.13.0}/pyproject.toml +6 -1
  71. openprocess-0.13.0/tests/test_bpmn.py +404 -0
  72. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_flow_gui.py +9 -0
  73. openprocess-0.13.0/tests/test_mcp.py +285 -0
  74. openprocess-0.13.0/tests/test_mcp_gui.py +140 -0
  75. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_measure.py +2 -0
  76. openprocess-0.13.0/tests/test_performance.py +201 -0
  77. openprocess-0.13.0/tests/test_review_fixes.py +222 -0
  78. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_studio_gui.py +8 -0
  79. openprocess-0.12.1/openprocess/flow/boxes/cpn.py +0 -53
  80. {openprocess-0.12.1 → openprocess-0.13.0}/LICENSE +0 -0
  81. {openprocess-0.12.1 → openprocess-0.13.0}/cpnpy/__init__.py +0 -0
  82. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/__init__.py +0 -0
  83. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/state_space.py +0 -0
  84. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/state_space_process.py +0 -0
  85. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/citation.py +0 -0
  86. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  87. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  88. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  89. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  90. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  91. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  92. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  93. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  94. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  95. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  96. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  97. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  98. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  99. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  100. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  101. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  102. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  103. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  104. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  105. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/pack.md +0 -0
  106. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/__init__.py +0 -0
  107. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/compare.py +0 -0
  108. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/filter.py +0 -0
  109. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/predict.py +0 -0
  110. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/science.py +0 -0
  111. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/sweeps.py +0 -0
  112. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/datasets.py +0 -0
  113. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/explain.py +0 -0
  114. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/figures.py +0 -0
  115. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/history.py +0 -0
  116. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/provenance.py +0 -0
  117. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/record.py +0 -0
  118. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/report.py +0 -0
  119. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/reproducibility.py +0 -0
  120. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/runner.py +0 -0
  121. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/script.py +0 -0
  122. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/snapshots.py +0 -0
  123. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/sweep.py +0 -0
  124. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/__init__.py +0 -0
  125. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/app.py +0 -0
  126. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/arc_editing.py +0 -0
  127. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/canvas.py +0 -0
  128. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/__init__.py +0 -0
  129. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/canvas.py +0 -0
  130. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/runs.py +0 -0
  131. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/stepper.py +0 -0
  132. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/summary.py +0 -0
  133. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/templates.py +0 -0
  134. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/items.py +0 -0
  135. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/answer_boxes.py +0 -0
  136. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/concealment.py +0 -0
  137. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/mode.py +0 -0
  138. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/panning.py +0 -0
  139. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  140. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/__init__.py +0 -0
  141. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/__main__.py +0 -0
  142. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/alignment_view.py +0 -0
  143. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/charts.py +0 -0
  144. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/cite.py +0 -0
  145. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/command_palette.py +0 -0
  146. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/compare_page.py +0 -0
  147. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/connections.py +0 -0
  148. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/cpn_page.py +0 -0
  149. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/derivation_view.py +0 -0
  150. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/documents.py +0 -0
  151. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/dotted_chart.py +0 -0
  152. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  153. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/filter_dialog.py +0 -0
  154. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/instances.py +0 -0
  155. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/loading.py +0 -0
  156. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/log_editor.py +0 -0
  157. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/markdown_view.py +0 -0
  158. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/mathtext.py +0 -0
  159. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  160. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/motion.py +0 -0
  161. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/net_comparison.py +0 -0
  162. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  163. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/provenance.py +0 -0
  164. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/regions_view.py +0 -0
  165. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/sidebar.py +0 -0
  166. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/style.py +0 -0
  167. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/tool_icons.py +0 -0
  168. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/updates.py +0 -0
  169. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/workers.py +0 -0
  170. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/theme.py +0 -0
  171. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/tidy.py +0 -0
  172. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/__init__.py +0 -0
  173. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/cpn_reader.py +0 -0
  174. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/cpn_writer.py +0 -0
  175. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/__init__.py +0 -0
  176. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/context.py +0 -0
  177. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/exam.py +0 -0
  178. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/importer.py +0 -0
  179. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/pack.py +0 -0
  180. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/sheet.py +0 -0
  181. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/__init__.py +0 -0
  182. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/analysis.py +0 -0
  183. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/cases.py +0 -0
  184. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/compare_nets.py +0 -0
  185. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/__init__.py +0 -0
  186. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/csv_import.py +0 -0
  187. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/dfg.py +0 -0
  188. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/__init__.py +0 -0
  189. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/footprint.py +0 -0
  190. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/invariants.py +0 -0
  191. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/layout.py +0 -0
  192. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/petrinet.py +0 -0
  193. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/playout.py +0 -0
  194. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/pm4py_bridge.py +0 -0
  195. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/processtree.py +0 -0
  196. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/stats.py +0 -0
  197. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/structure.py +0 -0
  198. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/transition_system.py +0 -0
  199. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/__init__.py +0 -0
  200. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/ast_nodes.py +0 -0
  201. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/builtins.py +0 -0
  202. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/colorsets.py +0 -0
  203. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/errors.py +0 -0
  204. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/evaluator.py +0 -0
  205. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/lexer.py +0 -0
  206. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/multiset.py +0 -0
  207. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/parser.py +0 -0
  208. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/values.py +0 -0
  209. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/__init__.py +0 -0
  210. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/declarations.py +0 -0
  211. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/examples.py +0 -0
  212. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/net.py +0 -0
  213. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/plain.py +0 -0
  214. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/packages.py +0 -0
  215. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/palette.py +0 -0
  216. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/__init__.py +0 -0
  217. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/binding.py +0 -0
  218. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/export.py +0 -0
  219. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/simulator.py +0 -0
  220. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/__init__.py +0 -0
  221. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/answers.py +0 -0
  222. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/checks.py +0 -0
  223. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/pack.py +0 -0
  224. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/sheet.py +0 -0
  225. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/dependency_links.txt +0 -0
  226. {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/top_level.txt +0 -0
  227. {openprocess-0.12.1 → openprocess-0.13.0}/setup.cfg +0 -0
  228. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_arc_editing.py +0 -0
  229. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_citation.py +0 -0
  230. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_colorsets.py +0 -0
  231. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_columns.py +0 -0
  232. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_cpn_course_model.py +0 -0
  233. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_definitions.py +0 -0
  234. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_docs.py +0 -0
  235. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_exercises_gui.py +0 -0
  236. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_flow.py +0 -0
  237. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_framework_contract.py +0 -0
  238. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_hierarchy.py +0 -0
  239. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_history.py +0 -0
  240. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_io.py +0 -0
  241. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_large_logs.py +0 -0
  242. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_learn.py +0 -0
  243. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_learn_gui.py +0 -0
  244. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_log_editing.py +0 -0
  245. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_mining.py +0 -0
  246. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_ml.py +0 -0
  247. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_multiset.py +0 -0
  248. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_ocel.py +0 -0
  249. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_packages.py +0 -0
  250. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_regions.py +0 -0
  251. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_rename.py +0 -0
  252. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_report.py +0 -0
  253. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_simulation.py +0 -0
  254. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_snapshots.py +0 -0
  255. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_state_space.py +0 -0
  256. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_teaching.py +0 -0
  257. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_tidy.py +0 -0
  258. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_updates.py +0 -0
  259. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_wf_structure.py +0 -0
  260. {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openprocess
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Process mining, Petri nets and workflows in one open app: every algorithm visible, every result reproducible, every step teachable
5
5
  Author: Sam Struthers
6
6
  License: MIT
@@ -26,6 +26,7 @@ Requires-Dist: PySide6>=6.6; extra == "gui"
26
26
  Requires-Dist: certifi; extra == "gui"
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=8.0; extra == "dev"
29
+ Requires-Dist: mcp>=2.3; extra == "dev"
29
30
  Provides-Extra: science
30
31
  Requires-Dist: numpy>=1.24; extra == "science"
31
32
  Requires-Dist: pandas>=2.0; extra == "science"
@@ -35,6 +36,8 @@ Provides-Extra: predict
35
36
  Requires-Dist: scikit-learn>=1.3; extra == "predict"
36
37
  Provides-Extra: pm4py
37
38
  Requires-Dist: pm4py>=2.7; extra == "pm4py"
39
+ Provides-Extra: mcp
40
+ Requires-Dist: mcp>=2.3; extra == "mcp"
38
41
  Dynamic: license-file
39
42
 
40
43
  <p align="center">
@@ -91,6 +94,7 @@ tools (and, until version 0.7, went by the name CPNpy):
91
94
  - [For developers](#for-developers)
92
95
  - [Workflows](#workflows)
93
96
  - [Workflows from Python](#workflows-from-python)
97
+ - [AI assistants](#ai-assistants)
94
98
  - [Limitations and roadmap](#limitations-and-roadmap)
95
99
 
96
100
  ---
@@ -1017,6 +1021,36 @@ the clone is older than the latest release. See
1017
1021
 
1018
1022
  ---
1019
1023
 
1024
+ ## AI assistants
1025
+
1026
+ OpenProcess comes with an **MCP server**, so Claude (Desktop or Code), Cursor
1027
+ or any other assistant that speaks the Model Context Protocol can use it the
1028
+ way you do:
1029
+
1030
+ ```bash
1031
+ pip install "openprocess[app,mcp]"
1032
+ openprocess mcp install claude-desktop # or claude-code, cursor; "print" for the JSON
1033
+ ```
1034
+
1035
+ Then ask in your own words, naming your files: *"Summarise
1036
+ `Week 2/plane_boarding.xes` and show me its dotted chart"*, *"Discover a model
1037
+ with IMf at 0.2, check its fit and open it in OpenProcess"*, *"Why is
1038
+ `order_handling.pnml` unsound?"*, *"Write three exercises about token replay
1039
+ for Week 7"*. Its 30 tools cover logs, discovery, conformance, net analysis,
1040
+ diagrams, BPMN, workflows and boxes, exercise packs and coloured nets.
1041
+
1042
+ What the assistant is told, before anything else: **cite** the works every
1043
+ algorithm follows (each result carries them), say **how to reproduce** each
1044
+ number, keep analyses as **workflows** with their record (`openprocess run
1045
+ FILE --check`), write a new algorithm as a **`@box`** in the folder's
1046
+ `boxes/`, work in **your folder**, and **open** what it made in OpenProcess.
1047
+ Every file it writes is journaled with a backup: the status bar says *AI
1048
+ changed N files · Review*, with a diff, *Undo* and *Keep* for each.
1049
+
1050
+ The full guide is [openprocess/mcp/connecting.md](openprocess/mcp/connecting.md)
1051
+ (also *Help ▸ Connect an AI Assistant*). The same operations are plain Python
1052
+ in `openprocess.mcp.api`, without the MCP SDK.
1053
+
1020
1054
  ## Limitations and roadmap
1021
1055
 
1022
1056
  Plainly stated:
@@ -52,6 +52,7 @@ tools (and, until version 0.7, went by the name CPNpy):
52
52
  - [For developers](#for-developers)
53
53
  - [Workflows](#workflows)
54
54
  - [Workflows from Python](#workflows-from-python)
55
+ - [AI assistants](#ai-assistants)
55
56
  - [Limitations and roadmap](#limitations-and-roadmap)
56
57
 
57
58
  ---
@@ -978,6 +979,36 @@ the clone is older than the latest release. See
978
979
 
979
980
  ---
980
981
 
982
+ ## AI assistants
983
+
984
+ OpenProcess comes with an **MCP server**, so Claude (Desktop or Code), Cursor
985
+ or any other assistant that speaks the Model Context Protocol can use it the
986
+ way you do:
987
+
988
+ ```bash
989
+ pip install "openprocess[app,mcp]"
990
+ openprocess mcp install claude-desktop # or claude-code, cursor; "print" for the JSON
991
+ ```
992
+
993
+ Then ask in your own words, naming your files: *"Summarise
994
+ `Week 2/plane_boarding.xes` and show me its dotted chart"*, *"Discover a model
995
+ with IMf at 0.2, check its fit and open it in OpenProcess"*, *"Why is
996
+ `order_handling.pnml` unsound?"*, *"Write three exercises about token replay
997
+ for Week 7"*. Its 30 tools cover logs, discovery, conformance, net analysis,
998
+ diagrams, BPMN, workflows and boxes, exercise packs and coloured nets.
999
+
1000
+ What the assistant is told, before anything else: **cite** the works every
1001
+ algorithm follows (each result carries them), say **how to reproduce** each
1002
+ number, keep analyses as **workflows** with their record (`openprocess run
1003
+ FILE --check`), write a new algorithm as a **`@box`** in the folder's
1004
+ `boxes/`, work in **your folder**, and **open** what it made in OpenProcess.
1005
+ Every file it writes is journaled with a backup: the status bar says *AI
1006
+ changed N files · Review*, with a diff, *Undo* and *Keep* for each.
1007
+
1008
+ The full guide is [openprocess/mcp/connecting.md](openprocess/mcp/connecting.md)
1009
+ (also *Help ▸ Connect an AI Assistant*). The same operations are plain Python
1010
+ in `openprocess.mcp.api`, without the MCP SDK.
1011
+
981
1012
  ## Limitations and roadmap
982
1013
 
983
1014
  Plainly stated:
@@ -48,7 +48,7 @@ from .sim.simulator import Simulator
48
48
  #: The one place the version number is written (pyproject.toml, the update
49
49
  #: check and packaging/build.py read it from here). A release is tagged
50
50
  #: "v" + this, e.g. v0.2.0.
51
- __version__ = "0.12.1"
51
+ __version__ = "0.13.0"
52
52
 
53
53
  __all__ = [
54
54
  "Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
@@ -1,9 +1,10 @@
1
1
  """Command-line interface: ``openprocess <command> [options]``.
2
2
 
3
3
  The CLI exists so that the engine is usable without the GUI -- in a script, in
4
- a notebook, from a Makefile, or over SSH. Every command works on a ``.cpn``
5
- file, except ``example``, which writes one of the built-in demo models so you
6
- have something to try immediately.
4
+ a notebook, from a Makefile, or over SSH. The first five commands work on a
5
+ ``.cpn`` file (``example`` writes one of the built-in demo models so you have
6
+ something to try immediately); the others take event logs, PNML nets,
7
+ workflow files and exercise packs.
7
8
 
8
9
  Commands
9
10
  --------
@@ -24,6 +25,7 @@ Commands
24
25
  ``--export`` and ``--report`` write a zip and an HTML page
25
26
  ``boxes`` list the boxes (OpenProcess's own, a folder's ``boxes/``, packages)
26
27
  ``datasets`` the public logs known by name: ``list``, ``fetch``, ``where``
28
+ ``mcp`` the MCP server for AI assistants; ``install CLIENT``, ``status``
27
29
  """
28
30
 
29
31
  from __future__ import annotations
@@ -338,7 +340,7 @@ def command_mine_invariants(arguments: argparse.Namespace) -> int:
338
340
  # ---------------------------------------------------------------------------
339
341
  def command_exercises_check(arguments: argparse.Namespace) -> int:
340
342
  """List a pack's exercises and answer boxes; report mistakes in them."""
341
- from .learn.checks import Context, TaskError, model_answer_text, validate
343
+ from .learn.checks import Context, TaskError, computed_answer_text, validate
342
344
  from .learn.pack import load_pack
343
345
  pack = load_pack(arguments.folder)
344
346
  if not pack.exercises:
@@ -357,11 +359,15 @@ def command_exercises_check(arguments: argparse.Namespace) -> int:
357
359
  checked = "checked" if task.checkable else "self-checked"
358
360
  line = f" - {task.id:<8} {task.type:<10} {checked}"
359
361
  if arguments.answers and task.type not in ("net", "footprint", "trace", "open"):
362
+ # The answer the app works out (not the author's solution text),
363
+ # so the author can compare the two.
360
364
  try:
361
- answer = model_answer_text(exercise, task, context)
365
+ answer = computed_answer_text(exercise, task, context)
362
366
  except TaskError as error:
363
367
  answer = f"(cannot work it out: {error})"
364
368
  line += " → " + " ".join(answer.split())[:100]
369
+ if task.solution and task.type not in ("open",):
370
+ line += " (solution: " + " ".join(task.solution.split())[:60] + ")"
365
371
  print(line)
366
372
  for problem in found:
367
373
  print(f" ! {problem}")
@@ -452,6 +458,10 @@ def command_run(arguments: argparse.Namespace) -> int:
452
458
  target.write_text(requirements_lock(record), encoding="utf-8")
453
459
  print(f"Wrote {target}")
454
460
  return 0
461
+ if arguments.sweep and arguments.check:
462
+ print("openprocess: --check compares the recorded results, which a sweep changes; "
463
+ "run the two separately", file=sys.stderr)
464
+ return 2
455
465
  for spec in arguments.sweep or []:
456
466
  name, _, values = spec.partition("=")
457
467
  node_id, _, setting = name.rpartition(".")
@@ -517,7 +527,10 @@ def _brief(value) -> str:
517
527
  from .flow.figures import tile_parts
518
528
  from .flow.types import Figure, Scores, Table
519
529
  if dataclasses.is_dataclass(value) and not isinstance(value, type) and tile_parts(value):
520
- return " · ".join(tile_parts(value))
530
+ big, small = tile_parts(value)
531
+ if " · " in big and big.count(" · ") == small.count(" · "): # "8 · 7" over "places · transitions"
532
+ return " · ".join(f"{a} {b}" for a, b in zip(big.split(" · "), small.split(" · ")))
533
+ return f"{big} ({small})" if small and small not in big else big
521
534
  if isinstance(value, Scores):
522
535
  return ", ".join(f"{k} {v:.3f}" if isinstance(v, float) else f"{k} {v}" for k, v in value.metrics.items())
523
536
  if isinstance(value, Table):
@@ -533,26 +546,70 @@ def _brief(value) -> str:
533
546
 
534
547
 
535
548
  def _write_outputs(workflow, run, folder: Path) -> None:
536
- from .flow.types import Figure, Scores, Table
549
+ """Every result that has a file form (tables, scores, figures, nets, logs,
550
+ texts), named after its box; the same files --export puts in the zip."""
551
+ from .flow.record import as_file
552
+ from .flow.types import Figure, Table
537
553
  folder.mkdir(parents=True, exist_ok=True)
554
+ written = 0
538
555
  for node in workflow.order():
539
556
  value = run.value(node)
540
557
  stem = f"{node.id} {workflow.title(node)}".replace("/", "-")
541
- if isinstance(value, Table):
558
+ if isinstance(value, Table): # with their provenance sidecars
542
559
  value.to_csv(folder / f"{stem}.csv")
543
- elif isinstance(value, Scores):
544
- Table.from_scores([value]).to_csv(folder / f"{stem}.csv")
545
- elif isinstance(value, Figure):
560
+ elif isinstance(value, Figure) and (value.svg or value.png):
546
561
  value.save(folder / f"{stem}.svg" if value.svg else folder / f"{stem}.png")
547
- elif value.__class__.__name__ == "PetriNet":
548
- from .mining.pnml import write_pnml
549
- write_pnml(value, folder / f"{stem}.pnml")
550
- print(f"Wrote the results into {folder}")
562
+ else:
563
+ found = as_file(value, stem)
564
+ if found is None:
565
+ continue
566
+ name, content = found
567
+ if isinstance(content, bytes):
568
+ (folder / name).write_bytes(content)
569
+ else:
570
+ (folder / name).write_text(content, encoding="utf-8")
571
+ written += 1
572
+ print(f"Wrote {written} result file(s) into {folder}")
573
+
574
+
575
+ def command_mcp(arguments: argparse.Namespace) -> int:
576
+ """``openprocess mcp``: the MCP server, and connecting assistants to it."""
577
+ if arguments.action == "install":
578
+ from .mcp.install import install
579
+ if not arguments.client:
580
+ print("openprocess: say which assistant: claude-desktop, claude-code, cursor, windsurf, or print",
581
+ file=sys.stderr)
582
+ return 2
583
+ try:
584
+ print(install(arguments.client))
585
+ except ValueError as error:
586
+ print(f"openprocess: {error}", file=sys.stderr)
587
+ return 1
588
+ return 0
589
+ if arguments.action == "status":
590
+ from .mcp.install import server_command, status
591
+ print("Server command: " + " ".join(server_command()))
592
+ for client, state in status().items():
593
+ print(f" {client:16} {'connected' if state['configured'] else 'not connected':14} {state['config'] or ''}")
594
+ return 0
595
+ try:
596
+ from .mcp.server import main as serve
597
+ except ImportError as error:
598
+ print(f"openprocess: {error}", file=sys.stderr)
599
+ return 1
600
+ argv = ["--http", "--host", arguments.host, "--port", str(arguments.port)] if arguments.http else []
601
+ return serve(argv)
551
602
 
552
603
 
553
604
  def command_boxes(arguments: argparse.Namespace) -> int:
554
605
  from .flow import library_for
555
- library = library_for(arguments.folder)
606
+ folder = arguments.folder
607
+ if folder is not None and not (Path(folder) / "boxes").is_dir():
608
+ # The folder given is itself a folder of box files (Week 5/boxes).
609
+ library = library_for(None)
610
+ library.load_folder(folder)
611
+ else:
612
+ library = library_for(folder)
556
613
  for group, specs in library.by_group().items():
557
614
  print(f"{group}")
558
615
  for spec in specs:
@@ -577,8 +634,9 @@ def command_datasets(arguments: argparse.Namespace) -> int:
577
634
  return 0
578
635
  try:
579
636
  path = datasets.fetch(arguments.name)
580
- except datasets.DatasetMissing as error:
581
- print(f"openprocess: {error}", file=sys.stderr)
637
+ except (datasets.DatasetMissing, KeyError) as error:
638
+ message = error.args[0] if isinstance(error, KeyError) and error.args else error
639
+ print(f"openprocess: {message}", file=sys.stderr)
582
640
  return 1
583
641
  print(f"{arguments.name}: {path}\nsha256 {datasets.sha256_file(path)}")
584
642
  return 0
@@ -587,7 +645,8 @@ def command_datasets(arguments: argparse.Namespace) -> int:
587
645
  def build_parser() -> argparse.ArgumentParser:
588
646
  parser = argparse.ArgumentParser(
589
647
  prog="openprocess",
590
- description="Coloured Petri Net tools: edit, simulate and analyse .cpn models.",
648
+ description="OpenProcess from the command line: process mining on event logs, Petri net "
649
+ "analysis, coloured net simulation, workflows, exercise packs and an MCP server.",
591
650
  )
592
651
  subparsers = parser.add_subparsers(dest="command", required=True)
593
652
 
@@ -683,6 +742,17 @@ def build_parser() -> argparse.ArgumentParser:
683
742
  dataset_commands.add_parser("where", help="print the cache folder")
684
743
  datasets.set_defaults(handler=command_datasets)
685
744
 
745
+ mcp = subparsers.add_parser("mcp", help="serve OpenProcess to AI assistants (Model Context Protocol)",
746
+ description="Without a command: serve on stdio, as an assistant starts it. "
747
+ "`install CLIENT` connects Claude Desktop, Claude Code or Cursor; "
748
+ "`status` says which have it.")
749
+ mcp.add_argument("action", nargs="?", choices=["serve", "install", "status"], default="serve")
750
+ mcp.add_argument("client", nargs="?", help="for install: claude-desktop, claude-code, cursor, windsurf or print")
751
+ mcp.add_argument("--http", action="store_true", help="serve Streamable HTTP on localhost instead of stdio")
752
+ mcp.add_argument("--host", default="127.0.0.1")
753
+ mcp.add_argument("--port", type=int, default=8765)
754
+ mcp.set_defaults(handler=command_mcp)
755
+
686
756
  mine = subparsers.add_parser("mine", help="process mining on event logs")
687
757
  mining = mine.add_subparsers(dest="mining_command", required=True)
688
758
  stats = mining.add_parser("stats", help="summarise an event log")
@@ -35,14 +35,14 @@ from .library import Library, library_for, standard_library
35
35
  from .record import check, differences, load, save
36
36
  from .runner import Cache, Result, Run, Runner
37
37
  from .sweep import Sweep
38
- from .types import (Column, AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
38
+ from .types import (BPMN, Column, AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
39
39
  PetriNet, Predictions, Predictor, ProcessTree, Regions, ReplayResult, Scores,
40
40
  SimpleLog, Table, Text, TransitionSystem, register_type)
41
41
  from .workflow import Edge, Group, Node, Workflow, WorkflowError, to_python, workflow
42
42
 
43
43
  __all__ = [
44
44
  "Column",
45
- "AlignmentResult", "Any", "Box", "BoxError", "BoxSpec", "CPNet", "Cache", "DFG", "Dataset", "Edge",
45
+ "AlignmentResult", "Any", "BPMN", "Box", "BoxError", "BoxSpec", "CPNet", "Cache", "DFG", "Dataset", "Edge",
46
46
  "EventLog", "Explanation", "Figure", "Footprint", "Group", "Library", "Marking", "Node", "PetriNet",
47
47
  "Port", "Predictions", "Predictor", "ProcessTree", "Regions", "ReplayResult", "Result", "Run",
48
48
  "Runner", "Scores", "Setting", "SimpleLog", "Sweep", "Table", "Text", "TransitionSystem", "Workflow",
@@ -255,6 +255,13 @@ class Box:
255
255
  def __repr__(self) -> str:
256
256
  return f"<box {self.spec.id}>"
257
257
 
258
+ def publish(self, name: str | None = None) -> dict:
259
+ """Make this box, defined in a notebook or a script, a box of the folder
260
+ open in OpenProcess Studio: its source goes to ``boxes/<name>.py`` there
261
+ (see :func:`openprocess.flow.studio.publish_box`)."""
262
+ from .studio import publish_box
263
+ return publish_box(self, name)
264
+
258
265
 
259
266
  # ---------------------------------------------------------------------------
260
267
  # Reading the signature
@@ -48,6 +48,11 @@ def check_fit(model: PetriNet, log: EventLog,
48
48
  scores = Scores(model.name, {"fitness": fitness, "precision": precision(model, simple),
49
49
  "generalisation": generalisation(replay), "simplicity": simplicity(model)})
50
50
  scores.note = f"Checked against all {replay.trace_count} cases of {log.name} ({method.lower()})."
51
+ if method == "Alignments" and alignments.incomplete:
52
+ cases = sum(a.count for a in alignments.incomplete)
53
+ scores.note += (f" No optimal alignment was found for {len(alignments.incomplete)} variants "
54
+ f"({cases:,} cases), counted as fitness 0, so this fitness is a lower bound; "
55
+ "token replay gives a figure for every case.")
51
56
  return scores
52
57
 
53
58
 
@@ -0,0 +1,97 @@
1
+ """Coloured net boxes: play a CPN out many times, and compute its state space."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ from ... import flow
8
+ from ...analysis.state_space import StateSpace
9
+ from ...mining.log import EventLog
10
+ from ...model.net import CPNet
11
+ from ...sim.export import case_variables, simulation_to_log
12
+ from ...sim.performance import analyse_performance
13
+ from ...sim.simulator import DeadMarkingError, Simulator
14
+ from ..box import box
15
+ from ..types import Scores, Table, Text
16
+
17
+
18
+ @box(name="Simulate CPN", group="Coloured nets")
19
+ def simulate_cpn(net: CPNet, steps: int = 500, case_variable: str = "", seed: int = 0,
20
+ unit: Literal["seconds", "minutes", "hours", "days"] = "minutes") -> EventLog:
21
+ """Runs the simulator for *steps* firings and turns the firing history
22
+ into an event log: one case per value of *case_variable* (the variable
23
+ that identifies a case in the inscriptions, e.g. ``p`` or ``order``),
24
+ one event per firing, the model time as the timestamp.
25
+
26
+ case_variable: the variable whose value is the case id (empty: the first one found)
27
+ """
28
+ simulator = Simulator(net, seed=seed)
29
+ try:
30
+ fired = simulator.run(steps)
31
+ except DeadMarkingError:
32
+ fired = simulator.step_count
33
+ variable = case_variable or next(iter(case_variables(net)), "")
34
+ if not variable:
35
+ raise ValueError("No variable to use as the case id: set case_variable")
36
+ log = simulation_to_log(net, simulator.log, variable, unit)
37
+ flow.note(f"{fired} firings, model time {simulator.clock}, case variable {variable}: "
38
+ f"{len(log)} cases, {log.event_count} events")
39
+ return log
40
+
41
+
42
+ @box(name="Performance", group="Coloured nets")
43
+ def performance(net: CPNet, runs: int = 10, steps: int = 1000, warm_up: float = 0.0, seed: int = 0,
44
+ distribution: Literal["throughput", "waiting time"] = "throughput") -> Table:
45
+ """Runs a timed coloured net *runs* times (seeds *seed*, *seed* + 1, …),
46
+ each for *steps* firings or until it is dead, and reports one row per
47
+ transition (firings, throughput in firings per time unit, utilisation:
48
+ the share of the model time during which one of its firings was still in
49
+ progress) and one per place (the waiting time of its tokens from their
50
+ time stamp to their consumption, the time-averaged number of tokens, and
51
+ occupancy: the share of the time it was not empty), after the first
52
+ *warm_up* time units of every run, averaged over the runs. An untimed
53
+ net is measured in steps instead. The samples behind the table are one
54
+ number per run, for Compare samples and Plot.
55
+
56
+ runs: how many seeded runs to average over
57
+ steps: firings per run (a run stops earlier when the model is dead)
58
+ warm_up: model time left out at the start of every run (firings, for an untimed net)
59
+ seed: the first run's seed; run k uses seed + k
60
+ distribution: what the samples hold per run: the model's throughput, or its mean waiting time
61
+ """
62
+ result = analyse_performance(net, runs=runs, steps=steps, warm_up=warm_up, seed=seed)
63
+ for note in result.notes:
64
+ flow.note(note)
65
+ for run in result.runs:
66
+ flow.note(result.summary(run))
67
+ flow.show(Table("Per run", ["run", "seed", "firings", "end", "stopped", "throughput", "mean waiting"],
68
+ [[k, run.seed, run.firings, run.end, "dead" if run.dead else "steps",
69
+ None if run.throughput is None else round(run.throughput, 4),
70
+ None if run.waiting_mean is None else round(run.waiting_mean, 4)]
71
+ for k, run in enumerate(result.runs)]), "Per run")
72
+ samples = result.samples(distribution)
73
+ dead = sum(run.dead for run in result.runs)
74
+ note = (f"{runs} runs of up to {steps} firings, seeds {seed} to {seed + runs - 1}, the first {warm_up:g} "
75
+ f"{'time units' if result.timed else 'firings'} of each left out"
76
+ f"{f'; {dead} of them ended in a dead marking' if dead else ''}. "
77
+ f"Throughput is firings per {result.unit}; waiting time runs from a token's time stamp to its "
78
+ f"consumption; utilisation is the share of the time a firing of the transition was in progress; "
79
+ f"occupancy the share of the time the place was not empty. "
80
+ f"The samples are the per-run {distribution} of the whole model.")
81
+ return Table(f"Performance of {net.name}", list(result.columns), result.rows(),
82
+ samples=samples or None, note=note)
83
+
84
+
85
+ @box(name="State space", group="Coloured nets", heavy=True)
86
+ def state_space(net: CPNet, max_nodes: int = 10000) -> Scores:
87
+ """The reachability graph of a coloured net and its report: dead and
88
+ home markings, bounds, dead and live transitions (as CPN Tools reports
89
+ them). The full report is in the notes."""
90
+ space = StateSpace(net).generate(max_nodes=max_nodes)
91
+ report = space.report()
92
+ flow.show(Text(report, "State space report"), "Report")
93
+ dead = space.dead_markings()
94
+ return Scores(net.name, {"nodes": space.node_count, "arcs": space.arc_count,
95
+ "dead markings": len(dead), "home markings": len(space.home_markings()),
96
+ "dead transitions": len(space.dead_transitions()),
97
+ "complete": "No (stopped at the limit)" if space.unexplored_count else "Yes"})
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import Literal
6
6
 
7
7
  from ... import flow
8
+ from ...mining.bpmn import BPMN, bpmn_to_petri_net, petri_net_to_bpmn, tree_to_bpmn as _tree_to_bpmn
8
9
  from ...mining.dfg import DFG, discover_dfg
9
10
  from ...mining.discovery import alpha as alpha_module
10
11
  from ...mining.discovery.heuristics import heuristics_net
@@ -62,6 +63,41 @@ def tree_to_net(tree: ProcessTree) -> PetriNet:
62
63
  return to_petri_net(tree)
63
64
 
64
65
 
66
+ @box(name="Tree to BPMN", group="Discover")
67
+ def tree_to_bpmn(tree: ProcessTree) -> BPMN:
68
+ """Translates a process tree into a BPMN model, block by block: a
69
+ sequence is a chain, a choice exclusive gateways, a parallel block
70
+ parallel gateways, a loop exclusive gateways with a flow back, and τ an
71
+ empty flow."""
72
+ model = _tree_to_bpmn(tree)
73
+ flow.note(model.summary())
74
+ return model
75
+
76
+
77
+ @box(name="BPMN to net", group="Discover")
78
+ def bpmn_to_net(model: BPMN) -> PetriNet:
79
+ """The Petri net semantics of a BPMN model (Dijkman, Dumas & Ouyang,
80
+ 2008): tasks become transitions, sequence flows places, parallel
81
+ gateways silent transitions, exclusive gateways places, the start event
82
+ the source place and the end event the sink place."""
83
+ net = bpmn_to_petri_net(model)
84
+ flow.note(net.summary())
85
+ for warning in model.warnings:
86
+ flow.note(f"Warning: {warning}")
87
+ return net
88
+
89
+
90
+ @box(name="Net to BPMN", group="Discover")
91
+ def net_to_bpmn(net: PetriNet) -> BPMN:
92
+ """A BPMN model of a WF-net: transitions become tasks (with parallel
93
+ gateways where they split or join), places with several producers or
94
+ consumers exclusive gateways, and silent steps plain flows. Exact for
95
+ free-choice nets, the kind the Inductive Miner produces."""
96
+ model = petri_net_to_bpmn(net)
97
+ flow.note(model.summary())
98
+ return model
99
+
100
+
65
101
  @box(name="Heuristics Miner", group="Discover")
66
102
  def heuristics_miner(log: EventLog, dependency: float = 0.9) -> PetriNet:
67
103
  """Keeps the directly-follows relations whose dependency measure
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from typing import Literal
8
8
 
9
9
  from ... import flow
10
+ from ...mining.bpmn import BPMN
10
11
  from ...mining.log import KEY_NAME, Event, EventLog, Trace, parse_simple_log
11
12
  from ...mining.petrinet import PetriNet
12
13
  from ...mining.playout import play_out
@@ -95,6 +96,23 @@ def open_net(file: Path) -> PetriNet:
95
96
  return net
96
97
 
97
98
 
99
+ @box(name="Open BPMN", group="Input")
100
+ def open_bpmn(file: Path) -> BPMN:
101
+ """Opens a BPMN 2.0 model (a ``.bpmn`` file from Camunda Modeler, bpmn.io,
102
+ Signavio, ...): its start and end events, tasks, exclusive and parallel
103
+ gateways and sequence flows. Pools, lanes, message flows and data are
104
+ read past; what had to be approximated is noted.
105
+
106
+ file: the ``.bpmn`` file. Choose… picks it; a name alone means a file in the workflow's folder
107
+ """
108
+ from ...mining.bpmn import read_bpmn
109
+ model = read_bpmn(_resolve(file))
110
+ flow.note(f"{Path(file).name}: {model.summary()}")
111
+ for warning in model.warnings:
112
+ flow.note(f"Warning: {warning}")
113
+ return model
114
+
115
+
98
116
  @box(name="Open coloured net", group="Input")
99
117
  def open_cpn(file: Path) -> CPNet:
100
118
  """Opens a coloured Petri net (a CPN Tools ``.cpn`` file).
@@ -20,7 +20,7 @@ from typing import Literal
20
20
 
21
21
  from ... import flow
22
22
  from ...mining.cases import case_value, cases
23
- from ...mining.dotted import SORT_MODES, X_MODES, DotSettings, Dots, svg
23
+ from ...mining.dotted import SORT_MODES, X_MODES, DotSettings, Dots, how_to_read, svg
24
24
  from ...mining.log import EventLog
25
25
  from ...mining.petrinet import PetriNet
26
26
  from ..box import box
@@ -229,8 +229,7 @@ def dotted_chart(log: EventLog,
229
229
  f"{dots.origin + timedelta(seconds=dots.x_max - dots.x_min):%d %b %Y %H:%M}")
230
230
  title = f"{log.name}: {rows.lower()} by {X_MODES[dots.mode].lower()}, colour by {colour.lower()}"
231
231
  return Figure("Dotted chart", svg=svg(dots, title, unit),
232
- caption=f"Dotted chart: rows {rows}, x {X_MODES[dots.mode]}, colour {colour}, "
233
- f"sorted by {sort.lower()}.")
232
+ caption=f"{how_to_read(settings, dots.mode)} Colour: {colour}.")
234
233
 
235
234
 
236
235
  @box(name="Who does what", group="Measure")
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
 
7
7
  from ... import flow
8
+ from ...mining.bpmn import BPMN
8
9
  from ...mining.log import EventLog
9
10
  from ...mining.petrinet import PetriNet
10
11
  from ..box import box
@@ -25,6 +26,15 @@ def save_pnml(model: PetriNet, file: str = "model.pnml") -> None:
25
26
  flow.note(f"Wrote {file}")
26
27
 
27
28
 
29
+ @box(name="Save as BPMN", group="Output")
30
+ def save_bpmn(model: BPMN, file: str = "model.bpmn") -> None:
31
+ """Writes the model as BPMN 2.0 XML with a diagram (Camunda Modeler,
32
+ bpmn.io and Signavio open it)."""
33
+ from ...mining.bpmn import write_bpmn
34
+ write_bpmn(model, _target(file))
35
+ flow.note(f"Wrote {file}")
36
+
37
+
28
38
  @box(name="Save log", group="Output")
29
39
  def save_log(log: EventLog, file: str = "log.xes") -> None:
30
40
  """Writes the log as XES (``.xes`` or ``.xes.gz``) or CSV (``.csv``)."""