openprocess 0.14.0__tar.gz → 0.16.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 (275) hide show
  1. {openprocess-0.14.0/openprocess.egg-info → openprocess-0.16.0}/PKG-INFO +38 -25
  2. {openprocess-0.14.0 → openprocess-0.16.0}/README.md +37 -24
  3. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/cli.py +29 -12
  5. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/pack.md +1 -1
  6. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/__init__.py +1 -1
  7. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/figures.py +1 -1
  8. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/record.py +121 -23
  9. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/report.py +1 -1
  10. openprocess-0.16.0/openprocess/flow/reproducibility.py +110 -0
  11. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/studio.py +2 -2
  12. openprocess-0.16.0/openprocess/flow/versions.py +102 -0
  13. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/workflow.py +11 -5
  14. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/page.py +170 -70
  15. openprocess-0.16.0/openprocess/gui/flow/report.py +537 -0
  16. openprocess-0.16.0/openprocess/gui/flow/versions.py +188 -0
  17. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/learn/mode.py +8 -3
  18. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/app.py +35 -12
  19. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/documents.py +2 -1
  20. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/dotted_chart.py +9 -2
  21. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/style.py +5 -1
  22. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/workspace.py +7 -2
  23. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/checks.py +1 -1
  24. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/computed.py +1 -1
  25. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/exercise-packs.md +5 -5
  26. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/pack.py +2 -2
  27. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/api.py +9 -8
  28. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/connecting.md +1 -1
  29. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/server.py +5 -5
  30. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/dotted.py +5 -2
  31. {openprocess-0.14.0 → openprocess-0.16.0/openprocess.egg-info}/PKG-INFO +38 -25
  32. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess.egg-info/SOURCES.txt +3 -2
  33. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_citation.py +3 -2
  34. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_flow.py +11 -5
  35. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_flow_gui.py +312 -59
  36. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_framework_contract.py +39 -5
  37. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_learn.py +1 -1
  38. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_learn_gui.py +1 -1
  39. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_mcp.py +3 -3
  40. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_parallel_boxes.py +2 -2
  41. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_researcher_api.py +2 -2
  42. openprocess-0.16.0/tests/test_snapshots.py +98 -0
  43. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_studio_gui.py +6 -4
  44. openprocess-0.14.0/openprocess/flow/reproducibility.py +0 -79
  45. openprocess-0.14.0/openprocess/gui/flow/runs.py +0 -130
  46. openprocess-0.14.0/openprocess/gui/flow/summary.py +0 -199
  47. openprocess-0.14.0/tests/test_snapshots.py +0 -55
  48. {openprocess-0.14.0 → openprocess-0.16.0}/LICENSE +0 -0
  49. {openprocess-0.14.0 → openprocess-0.16.0}/cpnpy/__init__.py +0 -0
  50. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/analysis/__init__.py +0 -0
  51. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/analysis/state_space.py +0 -0
  52. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/analysis/state_space_process.py +0 -0
  53. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/citation.py +0 -0
  54. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  55. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  56. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  57. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  58. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  59. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  60. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  61. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  62. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  63. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  64. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  65. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  66. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  67. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  68. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  69. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  70. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  71. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  72. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  73. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/box.py +0 -0
  74. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/__init__.py +0 -0
  75. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/check.py +0 -0
  76. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/compare.py +0 -0
  77. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/cpn.py +0 -0
  78. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/discover.py +0 -0
  79. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/filter.py +0 -0
  80. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/input.py +0 -0
  81. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/measure.py +0 -0
  82. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/output.py +0 -0
  83. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/predict.py +0 -0
  84. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/science.py +0 -0
  85. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/boxes/sweeps.py +0 -0
  86. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/changes.py +0 -0
  87. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/convert.py +0 -0
  88. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/datasets.py +0 -0
  89. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/derivation.py +0 -0
  90. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/explain.py +0 -0
  91. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/history.py +0 -0
  92. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/library.py +0 -0
  93. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/loading.py +0 -0
  94. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/parallel.py +0 -0
  95. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/provenance.py +0 -0
  96. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/runner.py +0 -0
  97. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/script.py +0 -0
  98. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/snapshots.py +0 -0
  99. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/sweep.py +0 -0
  100. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/flow/types.py +0 -0
  101. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/__init__.py +0 -0
  102. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/app.py +0 -0
  103. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/arc_editing.py +0 -0
  104. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/canvas.py +0 -0
  105. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/__init__.py +0 -0
  106. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/canvas.py +0 -0
  107. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/picker.py +0 -0
  108. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/stepper.py +0 -0
  109. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/templates.py +0 -0
  110. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/flow/viewers.py +0 -0
  111. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/items.py +0 -0
  112. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/learn/answer_boxes.py +0 -0
  113. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/learn/concealment.py +0 -0
  114. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/panning.py +0 -0
  115. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  116. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/__init__.py +0 -0
  117. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/__main__.py +0 -0
  118. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/alignment_view.py +0 -0
  119. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/charts.py +0 -0
  120. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/cite.py +0 -0
  121. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/command_palette.py +0 -0
  122. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/compare_page.py +0 -0
  123. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/connect.py +0 -0
  124. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/connections.py +0 -0
  125. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/cpn_page.py +0 -0
  126. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/definition_view.py +0 -0
  127. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/derivation_view.py +0 -0
  128. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  129. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/filter_dialog.py +0 -0
  130. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/graph_builders.py +0 -0
  131. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/graph_view.py +0 -0
  132. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/instances.py +0 -0
  133. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/loading.py +0 -0
  134. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/log_editor.py +0 -0
  135. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/log_page.py +0 -0
  136. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/markdown_view.py +0 -0
  137. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/mathtext.py +0 -0
  138. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  139. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/model_page.py +0 -0
  140. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/motion.py +0 -0
  141. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/net_comparison.py +0 -0
  142. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  143. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/petri_page.py +0 -0
  144. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/provenance.py +0 -0
  145. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/regions_view.py +0 -0
  146. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/remote.py +0 -0
  147. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/review.py +0 -0
  148. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/sidebar.py +0 -0
  149. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/spaces.py +0 -0
  150. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/tool_icons.py +0 -0
  151. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/updates.py +0 -0
  152. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/widgets.py +0 -0
  153. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/studio/workers.py +0 -0
  154. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/theme.py +0 -0
  155. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/gui/tidy.py +0 -0
  156. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/io/__init__.py +0 -0
  157. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/io/cpn_reader.py +0 -0
  158. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/io/cpn_writer.py +0 -0
  159. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/__init__.py +0 -0
  160. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/answers.py +0 -0
  161. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/context.py +0 -0
  162. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/exam.py +0 -0
  163. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/importer.py +0 -0
  164. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/notation.py +0 -0
  165. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/learn/sheet.py +0 -0
  166. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/__init__.py +0 -0
  167. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/__main__.py +0 -0
  168. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mcp/install.py +0 -0
  169. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/__init__.py +0 -0
  170. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/analysis.py +0 -0
  171. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/bpmn.py +0 -0
  172. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/cases.py +0 -0
  173. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/columns.py +0 -0
  174. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/compare_nets.py +0 -0
  175. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/conformance/__init__.py +0 -0
  176. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/conformance/alignments.py +0 -0
  177. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/conformance/quality.py +0 -0
  178. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/conformance/token_replay.py +0 -0
  179. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/csv_import.py +0 -0
  180. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/definitions.py +0 -0
  181. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/dfg.py +0 -0
  182. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/discovery/__init__.py +0 -0
  183. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/discovery/alpha.py +0 -0
  184. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/discovery/heuristics.py +0 -0
  185. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/discovery/inductive.py +0 -0
  186. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/discovery/state_regions.py +0 -0
  187. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/drawing.py +0 -0
  188. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/filtering.py +0 -0
  189. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/footprint.py +0 -0
  190. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/invariants.py +0 -0
  191. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/layout.py +0 -0
  192. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/log.py +0 -0
  193. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/ocel.py +0 -0
  194. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/parallel.py +0 -0
  195. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/petrinet.py +0 -0
  196. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/playout.py +0 -0
  197. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/pm4py_bridge.py +0 -0
  198. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/pnml.py +0 -0
  199. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/processtree.py +0 -0
  200. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/regions.py +0 -0
  201. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/stats.py +0 -0
  202. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/structure.py +0 -0
  203. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/transition_system.py +0 -0
  204. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/mining/xes.py +0 -0
  205. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/__init__.py +0 -0
  206. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/ast_nodes.py +0 -0
  207. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/builtins.py +0 -0
  208. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/colorsets.py +0 -0
  209. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/errors.py +0 -0
  210. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/evaluator.py +0 -0
  211. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/lexer.py +0 -0
  212. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/multiset.py +0 -0
  213. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/parser.py +0 -0
  214. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/ml/values.py +0 -0
  215. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/model/__init__.py +0 -0
  216. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/model/declarations.py +0 -0
  217. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/model/examples.py +0 -0
  218. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/model/net.py +0 -0
  219. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/model/plain.py +0 -0
  220. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/packages.py +0 -0
  221. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/palette.py +0 -0
  222. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/parallel.py +0 -0
  223. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/references.py +0 -0
  224. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/sim/__init__.py +0 -0
  225. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/sim/binding.py +0 -0
  226. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/sim/export.py +0 -0
  227. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/sim/performance.py +0 -0
  228. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/sim/simulator.py +0 -0
  229. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/teaching/__init__.py +0 -0
  230. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/teaching/answers.py +0 -0
  231. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/teaching/checks.py +0 -0
  232. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/teaching/pack.py +0 -0
  233. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess/teaching/sheet.py +0 -0
  234. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess.egg-info/dependency_links.txt +0 -0
  235. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess.egg-info/entry_points.txt +0 -0
  236. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess.egg-info/requires.txt +0 -0
  237. {openprocess-0.14.0 → openprocess-0.16.0}/openprocess.egg-info/top_level.txt +0 -0
  238. {openprocess-0.14.0 → openprocess-0.16.0}/pyproject.toml +0 -0
  239. {openprocess-0.14.0 → openprocess-0.16.0}/setup.cfg +0 -0
  240. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_arc_editing.py +0 -0
  241. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_bpmn.py +0 -0
  242. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_colorsets.py +0 -0
  243. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_columns.py +0 -0
  244. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_cpn_course_model.py +0 -0
  245. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_definitions.py +0 -0
  246. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_docs.py +0 -0
  247. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_dotted_chart.py +0 -0
  248. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_exercises_gui.py +0 -0
  249. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_hierarchy.py +0 -0
  250. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_history.py +0 -0
  251. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_io.py +0 -0
  252. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_large_log_page.py +0 -0
  253. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_large_logs.py +0 -0
  254. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_log_editing.py +0 -0
  255. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_mcp_gui.py +0 -0
  256. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_measure.py +0 -0
  257. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_mining.py +0 -0
  258. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_ml.py +0 -0
  259. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_multiset.py +0 -0
  260. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_ocel.py +0 -0
  261. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_packages.py +0 -0
  262. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_parallel_and_loading.py +0 -0
  263. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_performance.py +0 -0
  264. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_regions.py +0 -0
  265. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_rename.py +0 -0
  266. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_report.py +0 -0
  267. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_review_fixes.py +0 -0
  268. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_simulation.py +0 -0
  269. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_state_space.py +0 -0
  270. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_studio_lazy.py +0 -0
  271. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_teaching.py +0 -0
  272. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_tidy.py +0 -0
  273. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_updates.py +0 -0
  274. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_wf_structure.py +0 -0
  275. {openprocess-0.14.0 → openprocess-0.16.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openprocess
3
- Version: 0.14.0
3
+ Version: 0.16.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
@@ -73,7 +73,7 @@ tools (and, until version 0.7, went by the name CPNpy):
73
73
  | **Petri nets & WF-nets** | Draw nets the way the lectures do. Get a soundness verdict with a counterexample for every violation, and replay it in the token game. Also: behavioural properties, P- and T-invariants, the footprint matrix, the reachability graph, PNML import and export. | WoPeD, ProM, pen and paper |
74
74
  | **Process mining** | Import XES or CSV logs, OCEL 2.0 object-centric logs, or type textbook logs like `[<a,b,c>^3, <a,c>^2]`. Filter them, explore variants, the dotted chart and the process map. Discover models (α-algorithm, Inductive Miner, Heuristics Miner, state-based regions). Check conformance (token replay, alignments, precision…) and compare logs. | ProM, Disco |
75
75
  | **Coloured Petri nets** | Open, edit and save CPN Tools models (`.cpn`), hierarchical ones included. Step through or simulate them, compute the state space, and export a simulation as an event log to mine. | CPN Tools / CPN IDE |
76
- | **Workflows** | Boxes on a canvas: a log, a miner, a fitness check, a comparison, a sweep over a setting, a prediction pipeline. Click a box to see its result, *how* it got there (the α-algorithm's eight steps, the replay per variant), its code and its settings. Change a setting and only what follows runs again. Saved as a `.cpnflow` file with everything needed to get the same numbers back. Your own algorithm is one Python function in a `boxes/` folder. | RapidProM |
76
+ | **Workflows** | Boxes on a canvas: a log, a miner, a fitness check, a comparison, a sweep over a setting, a prediction pipeline. Click a box to see its result, *how* it got there (the α-algorithm's eight steps, the replay per variant), its code and its settings. Change a setting and only what follows runs again. Saved as a `.analysis` file with everything needed to get the same numbers back. Your own algorithm is one Python function in a `boxes/` folder. | RapidProM |
77
77
  | **Learn** | A mode of its own for worksheets, built on the app: answer in boxes on the sheet (sets, markings, matrices, cuts and trees, alignments, a net in the editor or a workflow on the canvas beside it) and press **Check**. Most answers are checked automatically, often against answers worked out from the given log or net. Packs are plain Markdown, can be exams with a clock and points, and a past exam imports as a skeleton pack. Demo exercises included. | Answer sheets, a notes app and guesswork |
78
78
  | **Folders** | Open a folder such as *Week 2*: every log and net in it is listed in the sidebar, with its subfolders. The folder and the app stay in step both ways: new nets and edits are saved into it as you go, and changes made in Finder show up by themselves. | Finder windows and *File ▸ Open* every time |
79
79
 
@@ -598,7 +598,7 @@ without distractions, and *Exit* brings your folder back exactly as it was.
598
598
  writes the marks as CSV. A pack can also give every student a **variant**
599
599
  of its own (a log played out from a net with a seed made from their name).
600
600
  - **Your work is saved as you go**, in the exercise's folder:
601
- `my answers.json`, `my answer.pnml` for a net, `my workflow.cpnflow` for a
601
+ `my answers.json`, `my answer.pnml` for a net, `my workflow.analysis` for a
602
602
  workflow and `my notes.md`.
603
603
 
604
604
  **Writing a pack** (for a course or an exam) is plain Markdown: put an
@@ -791,14 +791,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
791
791
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
792
792
  extra) and *Predict the next activity*. Or start empty.
793
793
 
794
- | The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
794
+ | The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
795
795
  |---|---|
796
- | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Summary view: a tile per box, the process map under them](docs/screenshots/workflow-summary.png) |
797
-
798
- - **The page is the canvas.** Its header has **Canvas | Summary**, then
799
- **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
800
- and Save. The sidebar beside it lists the folder's analyses and, folded
801
- under them, its logs.
796
+ | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Report view: a section per box with its result, a Steps outline beside them](docs/screenshots/workflow-report.png) |
797
+
798
+ - **An analysis opens on its Report.** Its header has **Report | Canvas |
799
+ Versions**, then **+ Add box**, **Run ▶**, and **⋯** for Run from scratch,
800
+ Keep as version, Record, Export experiment and Save. The sidebar beside it
801
+ lists the folder's analyses and, folded under them, its logs.
802
+ - **Report** is every box's result, in the order the boxes run, on one
803
+ scrolling page: a section per box with the group it is in, its name, the
804
+ line the canvas shows under it, its status, the result as the side panel
805
+ shows it (a table scrolls sideways inside its section when it is wider; a
806
+ figure keeps its proportions; a score gets a key-figure tile), its
807
+ settings as chips, and *Open ›*, *How* and *Code*, which open the box in
808
+ the side panel on the canvas. A slim *Steps* outline on the left lists
809
+ the sections, grouped as the canvas groups the boxes, with each box's
810
+ key figure; a click scrolls to the section, and on a narrow window the
811
+ outline folds into a *Steps* menu. Nothing is computed there: while a
812
+ run goes, each section is redrawn as its box reports; a box that has not
813
+ run says so, a failed one shows its error. The process map is the
814
+ *Directly-follows graph* box's result; an analysis without that box is
815
+ offered one in one click.
802
816
  - **+ Add box** (or a double-click on the canvas) opens every box, one
803
817
  section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
804
818
  then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
@@ -812,12 +826,6 @@ extra) and *Predict the next activity*. Or start empty.
812
826
  notation…* in the sidebar land as boxes on the current analysis (or a new
813
827
  one named after the file). In a box's **Settings**, *Choose…* picks the
814
828
  file and the list beside it offers only the files the box can read.
815
- - **Summary** is the key figure of every box, in one place: a tile per box,
816
- in the order the boxes run (a score's first metric, a table's size, a
817
- log's cases, a net's places and transitions). Nothing is computed there:
818
- a tile is the box's result, as on the canvas, and a tile opens the box.
819
- The process map under the tiles is the *Directly-follows graph* box's
820
- result; an analysis without that box offers to add it in one click.
821
829
  - **Connect** by dragging from the dot on the right of a box: while you drag,
822
830
  only the inputs that take that kind of result light up, so a wrong
823
831
  connection cannot be made. Click a wire and press Delete to remove it.
@@ -838,9 +846,14 @@ extra) and *Predict the next activity*. Or start empty.
838
846
  - **Only what changed runs again.** Every box shows a status dot: waiting,
839
847
  running, done, failed (the error is on the Result tab; the rest keeps
840
848
  working), or waiting for your OK.
841
- - **Record** shows the workflow as Python and the file with its record;
842
- **Re-run** says what changed since it was saved (an input file, a box file,
843
- a seed) before running everything again.
849
+ - **Versions** is where the numbers stand against the record and every
850
+ version kept. The first run with no failure is kept as v1; from then on the
851
+ badge beside the title says *v1 · reproducible*, *v1 · 2 changes* or
852
+ *Changed since v1*, and **Keep as version…** records the run as v2 with a
853
+ note. Any version can be compared with now (the boxes whose result differs,
854
+ first) or restored whole. **Record** shows the workflow as Python and the
855
+ file; **Run from scratch** says what changed since the version was kept
856
+ before computing everything again.
844
857
  - **Your own boxes**: a `.py` file in the folder's `boxes/` subfolder (see
845
858
  below). The app asks once per folder before running them, and reloads a
846
859
  box when its file is saved.
@@ -894,15 +907,15 @@ extra) and *Predict the next activity*. Or start empty.
894
907
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
895
908
  need the package come alive without a restart. PM4Py needs a Python
896
909
  install (`pip install pm4py`), as parts of it are built from source.
897
- - **Motion** is kept short: a space switch, the side panel and Canvas ↔
898
- Summary settle in a fifth of a second, and a box pulses once when it
910
+ - **Motion** is kept short: a space switch, the side panel and Report ↔
911
+ Canvas settle in a fifth of a second, and a box pulses once when it
899
912
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
900
913
 
901
914
  ## Workflows from Python
902
915
 
903
916
  Every algorithm is also a **box**: a Python function with type hints that
904
917
  the app (from 0.7) draws on a canvas. A workflow of boxes runs from Python
905
- or from the command line, and its `.cpnflow` file records what is needed
918
+ or from the command line, and its `.analysis` file records what is needed
906
919
  to get the same numbers again: fingerprints of the input files and of the
907
920
  box code, every seed, and the installed packages.
908
921
 
@@ -920,12 +933,12 @@ wf.connect(log, model); wf.connect(model, fit, "model"); wf.connect(log, fit, "l
920
933
  run = Runner().run(wf)
921
934
  print(run.value(fit).metrics) # fitness, precision, generalisation, simplicity
922
935
  print(run.result(model).explanation.steps) # how the Inductive Miner got there
923
- save(wf, "orders.cpnflow", run)
936
+ save(wf, "orders.analysis", run)
924
937
  ```
925
938
 
926
939
  ```bash
927
- openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
928
- openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
940
+ openprocess run orders.analysis --check # re-run; fails if any result differs from the record
941
+ openprocess run orders.analysis --sweep noise=0..0.5 step 0.1
929
942
  openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
930
943
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
931
944
  ```
@@ -31,7 +31,7 @@ tools (and, until version 0.7, went by the name CPNpy):
31
31
  | **Petri nets & WF-nets** | Draw nets the way the lectures do. Get a soundness verdict with a counterexample for every violation, and replay it in the token game. Also: behavioural properties, P- and T-invariants, the footprint matrix, the reachability graph, PNML import and export. | WoPeD, ProM, pen and paper |
32
32
  | **Process mining** | Import XES or CSV logs, OCEL 2.0 object-centric logs, or type textbook logs like `[<a,b,c>^3, <a,c>^2]`. Filter them, explore variants, the dotted chart and the process map. Discover models (α-algorithm, Inductive Miner, Heuristics Miner, state-based regions). Check conformance (token replay, alignments, precision…) and compare logs. | ProM, Disco |
33
33
  | **Coloured Petri nets** | Open, edit and save CPN Tools models (`.cpn`), hierarchical ones included. Step through or simulate them, compute the state space, and export a simulation as an event log to mine. | CPN Tools / CPN IDE |
34
- | **Workflows** | Boxes on a canvas: a log, a miner, a fitness check, a comparison, a sweep over a setting, a prediction pipeline. Click a box to see its result, *how* it got there (the α-algorithm's eight steps, the replay per variant), its code and its settings. Change a setting and only what follows runs again. Saved as a `.cpnflow` file with everything needed to get the same numbers back. Your own algorithm is one Python function in a `boxes/` folder. | RapidProM |
34
+ | **Workflows** | Boxes on a canvas: a log, a miner, a fitness check, a comparison, a sweep over a setting, a prediction pipeline. Click a box to see its result, *how* it got there (the α-algorithm's eight steps, the replay per variant), its code and its settings. Change a setting and only what follows runs again. Saved as a `.analysis` file with everything needed to get the same numbers back. Your own algorithm is one Python function in a `boxes/` folder. | RapidProM |
35
35
  | **Learn** | A mode of its own for worksheets, built on the app: answer in boxes on the sheet (sets, markings, matrices, cuts and trees, alignments, a net in the editor or a workflow on the canvas beside it) and press **Check**. Most answers are checked automatically, often against answers worked out from the given log or net. Packs are plain Markdown, can be exams with a clock and points, and a past exam imports as a skeleton pack. Demo exercises included. | Answer sheets, a notes app and guesswork |
36
36
  | **Folders** | Open a folder such as *Week 2*: every log and net in it is listed in the sidebar, with its subfolders. The folder and the app stay in step both ways: new nets and edits are saved into it as you go, and changes made in Finder show up by themselves. | Finder windows and *File ▸ Open* every time |
37
37
 
@@ -556,7 +556,7 @@ without distractions, and *Exit* brings your folder back exactly as it was.
556
556
  writes the marks as CSV. A pack can also give every student a **variant**
557
557
  of its own (a log played out from a net with a seed made from their name).
558
558
  - **Your work is saved as you go**, in the exercise's folder:
559
- `my answers.json`, `my answer.pnml` for a net, `my workflow.cpnflow` for a
559
+ `my answers.json`, `my answer.pnml` for a net, `my workflow.analysis` for a
560
560
  workflow and `my notes.md`.
561
561
 
562
562
  **Writing a pack** (for a course or an exam) is plain Markdown: put an
@@ -749,14 +749,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
749
749
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
750
750
  extra) and *Predict the next activity*. Or start empty.
751
751
 
752
- | The canvas: *Compare discovery*, three miners side by side | Summary: every box's key figure, and the process map |
752
+ | The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
753
753
  |---|---|
754
- | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Summary view: a tile per box, the process map under them](docs/screenshots/workflow-summary.png) |
755
-
756
- - **The page is the canvas.** Its header has **Canvas | Summary**, then
757
- **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
758
- and Save. The sidebar beside it lists the folder's analyses and, folded
759
- under them, its logs.
754
+ | ![The canvas of a Compare discovery analysis, the Compare box's table beside it](docs/screenshots/workflow-canvas.png) | ![The Report view: a section per box with its result, a Steps outline beside them](docs/screenshots/workflow-report.png) |
755
+
756
+ - **An analysis opens on its Report.** Its header has **Report | Canvas |
757
+ Versions**, then **+ Add box**, **Run ▶**, and **⋯** for Run from scratch,
758
+ Keep as version, Record, Export experiment and Save. The sidebar beside it
759
+ lists the folder's analyses and, folded under them, its logs.
760
+ - **Report** is every box's result, in the order the boxes run, on one
761
+ scrolling page: a section per box with the group it is in, its name, the
762
+ line the canvas shows under it, its status, the result as the side panel
763
+ shows it (a table scrolls sideways inside its section when it is wider; a
764
+ figure keeps its proportions; a score gets a key-figure tile), its
765
+ settings as chips, and *Open ›*, *How* and *Code*, which open the box in
766
+ the side panel on the canvas. A slim *Steps* outline on the left lists
767
+ the sections, grouped as the canvas groups the boxes, with each box's
768
+ key figure; a click scrolls to the section, and on a narrow window the
769
+ outline folds into a *Steps* menu. Nothing is computed there: while a
770
+ run goes, each section is redrawn as its box reports; a box that has not
771
+ run says so, a failed one shows its error. The process map is the
772
+ *Directly-follows graph* box's result; an analysis without that box is
773
+ offered one in one click.
760
774
  - **+ Add box** (or a double-click on the canvas) opens every box, one
761
775
  section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
762
776
  then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
@@ -770,12 +784,6 @@ extra) and *Predict the next activity*. Or start empty.
770
784
  notation…* in the sidebar land as boxes on the current analysis (or a new
771
785
  one named after the file). In a box's **Settings**, *Choose…* picks the
772
786
  file and the list beside it offers only the files the box can read.
773
- - **Summary** is the key figure of every box, in one place: a tile per box,
774
- in the order the boxes run (a score's first metric, a table's size, a
775
- log's cases, a net's places and transitions). Nothing is computed there:
776
- a tile is the box's result, as on the canvas, and a tile opens the box.
777
- The process map under the tiles is the *Directly-follows graph* box's
778
- result; an analysis without that box offers to add it in one click.
779
787
  - **Connect** by dragging from the dot on the right of a box: while you drag,
780
788
  only the inputs that take that kind of result light up, so a wrong
781
789
  connection cannot be made. Click a wire and press Delete to remove it.
@@ -796,9 +804,14 @@ extra) and *Predict the next activity*. Or start empty.
796
804
  - **Only what changed runs again.** Every box shows a status dot: waiting,
797
805
  running, done, failed (the error is on the Result tab; the rest keeps
798
806
  working), or waiting for your OK.
799
- - **Record** shows the workflow as Python and the file with its record;
800
- **Re-run** says what changed since it was saved (an input file, a box file,
801
- a seed) before running everything again.
807
+ - **Versions** is where the numbers stand against the record and every
808
+ version kept. The first run with no failure is kept as v1; from then on the
809
+ badge beside the title says *v1 · reproducible*, *v1 · 2 changes* or
810
+ *Changed since v1*, and **Keep as version…** records the run as v2 with a
811
+ note. Any version can be compared with now (the boxes whose result differs,
812
+ first) or restored whole. **Record** shows the workflow as Python and the
813
+ file; **Run from scratch** says what changed since the version was kept
814
+ before computing everything again.
802
815
  - **Your own boxes**: a `.py` file in the folder's `boxes/` subfolder (see
803
816
  below). The app asks once per folder before running them, and reloads a
804
817
  box when its file is saved.
@@ -852,15 +865,15 @@ extra) and *Predict the next activity*. Or start empty.
852
865
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
853
866
  need the package come alive without a restart. PM4Py needs a Python
854
867
  install (`pip install pm4py`), as parts of it are built from source.
855
- - **Motion** is kept short: a space switch, the side panel and Canvas ↔
856
- Summary settle in a fifth of a second, and a box pulses once when it
868
+ - **Motion** is kept short: a space switch, the side panel and Report ↔
869
+ Canvas settle in a fifth of a second, and a box pulses once when it
857
870
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
858
871
 
859
872
  ## Workflows from Python
860
873
 
861
874
  Every algorithm is also a **box**: a Python function with type hints that
862
875
  the app (from 0.7) draws on a canvas. A workflow of boxes runs from Python
863
- or from the command line, and its `.cpnflow` file records what is needed
876
+ or from the command line, and its `.analysis` file records what is needed
864
877
  to get the same numbers again: fingerprints of the input files and of the
865
878
  box code, every seed, and the installed packages.
866
879
 
@@ -878,12 +891,12 @@ wf.connect(log, model); wf.connect(model, fit, "model"); wf.connect(log, fit, "l
878
891
  run = Runner().run(wf)
879
892
  print(run.value(fit).metrics) # fitness, precision, generalisation, simplicity
880
893
  print(run.result(model).explanation.steps) # how the Inductive Miner got there
881
- save(wf, "orders.cpnflow", run)
894
+ save(wf, "orders.analysis", run)
882
895
  ```
883
896
 
884
897
  ```bash
885
- openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
886
- openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
898
+ openprocess run orders.analysis --check # re-run; fails if any result differs from the record
899
+ openprocess run orders.analysis --sweep noise=0..0.5 step 0.1
887
900
  openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
888
901
  openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
889
902
  ```
@@ -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.14.0"
51
+ __version__ = "0.16.0"
52
52
 
53
53
  __all__ = [
54
54
  "Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
@@ -19,7 +19,7 @@ Commands
19
19
  ``stats``, ``filter``, ``discover``, ``conform``,
20
20
  ``soundness``, ``invariants``
21
21
  ``exercises`` ``check``, ``marks``, ``import``, ``computes``: exercise packs (OpenProcess Learn)
22
- ``run`` run a ``.cpnflow`` workflow headless; ``--check`` re-runs and
22
+ ``run`` run a ``.analysis`` workflow headless; ``--check`` re-runs and
23
23
  compares with the record, ``--sweep`` varies a setting,
24
24
  ``--lock`` writes the requirements the record names,
25
25
  ``--export`` and ``--report`` write a zip and an HTML page
@@ -447,12 +447,19 @@ def command_exercises_computes(arguments: argparse.Namespace) -> int:
447
447
  # ---------------------------------------------------------------------------
448
448
  def command_run(arguments: argparse.Namespace) -> int:
449
449
  """Run a workflow file without the app, and say what every box gave."""
450
- from .flow import Runner, Sweep, check, differences, library_for, load, save
451
- from .flow.record import requirements_lock
450
+ from .flow import Runner, Sweep, check, differences, library_for, save
451
+ from .flow.record import load_all, requirements_lock
452
452
  path = Path(arguments.workflow)
453
453
  folder = path.parent
454
454
  library = library_for(folder)
455
- workflow, record = load(path, library)
455
+ workflow, record, versions = load_all(path, library)
456
+ if arguments.against is not None:
457
+ version = versions.get(arguments.against)
458
+ if version is None:
459
+ print(f"openprocess: {path.name} has no version {arguments.against} "
460
+ f"(it has {', '.join(f'v{v.number}' for v in versions.items) or 'none'})", file=sys.stderr)
461
+ return 2
462
+ record = version.record
456
463
  if arguments.lock:
457
464
  target = folder / "requirements.lock"
458
465
  target.write_text(requirements_lock(record), encoding="utf-8")
@@ -504,6 +511,8 @@ def command_run(arguments: argparse.Namespace) -> int:
504
511
  if arguments.report:
505
512
  from .flow.report import write_report
506
513
  print(f"Wrote {write_report(arguments.report, workflow=workflow, run=run, folder=folder)}")
514
+ against = f"v{arguments.against}" if arguments.against is not None else (
515
+ f"v{versions.latest.number}" if versions.latest else "the record")
507
516
  if arguments.check:
508
517
  if not record.results:
509
518
  # Nothing to compare is not a pass: a CI job would claim a reproduction that never happened.
@@ -513,12 +522,18 @@ def command_run(arguments: argparse.Namespace) -> int:
513
522
  report = check(workflow, record, run, complete=True)
514
523
  if report:
515
524
  for line in report:
516
- print(f"DIFFERS: {line}")
525
+ print(f"DIFFERS from {against}: {line}")
517
526
  return 1
518
- print(f"Every recorded result was reproduced ({len(record.results)} boxes).")
527
+ print(f"Every result recorded in {against} was reproduced ({len(record.results)} boxes).")
519
528
  elif arguments.save:
520
- save(workflow, path, run, folder)
521
- print(f"Recorded the results in {path.name}")
529
+ if run.failed():
530
+ save(workflow, path, run, folder, previous=record, versions=versions)
531
+ print(f"A box failed, so {path.name} keeps " + (f"{against} as its record" if record.results
532
+ else "no record yet"))
533
+ else:
534
+ save(workflow, path, run, folder, previous=record, versions=versions, keep=arguments.note or "")
535
+ print(f"Kept the results as v{versions.latest.number} in {path.name}"
536
+ + (f": {arguments.note}" if arguments.note else ""))
522
537
  return 0 if run.ok else 1
523
538
 
524
539
 
@@ -710,11 +725,13 @@ def build_parser() -> argparse.ArgumentParser:
710
725
  "computes", help="list every compute: an answer block may use")
711
726
  exercise_computes.set_defaults(handler=command_exercises_computes)
712
727
 
713
- run = subparsers.add_parser("run", help="run a .cpnflow workflow without the app")
714
- run.add_argument("workflow", help="the .cpnflow file")
728
+ run = subparsers.add_parser("run", help="run a .analysis workflow without the app")
729
+ run.add_argument("workflow", help="the .analysis file")
715
730
  run.add_argument("--check", action="store_true",
716
- help="re-run and fail if any result differs from the recorded one")
717
- run.add_argument("--save", action="store_true", help="record the results in the file")
731
+ help="re-run and fail if any result differs from the recorded one (the latest version)")
732
+ run.add_argument("--against", type=int, metavar="N", help="check against version N instead of the latest")
733
+ run.add_argument("--save", action="store_true", help="keep the results in the file as the next version")
734
+ run.add_argument("--note", metavar="TEXT", help="with --save: the version's note (what changed, or why)")
718
735
  run.add_argument("--sweep", action="append", metavar="SETTING=VALUES",
719
736
  help="vary a setting, e.g. noise=0..0.5 step 0.1 or n3.seed=1,2,3")
720
737
  run.add_argument("--lock", action="store_true",
@@ -7,7 +7,7 @@ straight away, and the app never shows you a result you are meant to work out
7
7
  until you ask for it.
8
8
 
9
9
  Your answers are saved in each exercise's folder (*my answers.json*, and
10
- *my answer.pnml* for a net you draw, *my workflow.cpnflow* for a workflow
10
+ *my answer.pnml* for a net you draw, *my workflow.analysis* for a workflow
11
11
  you build), so you can stop and carry on later.
12
12
 
13
13
  To write your own pack, copy this folder and change the `question.md` files:
@@ -24,7 +24,7 @@ Python::
24
24
  wf.connect(log, model); wf.connect(model, fit, "model"); wf.connect(log, fit, "log")
25
25
  run = Runner().run(wf)
26
26
  print(run.value(fit).metrics)
27
- save(wf, "demo.cpnflow", run) # with the record that makes it reproducible
27
+ save(wf, "demo.analysis", run) # with the record that makes it reproducible
28
28
 
29
29
  Work over many independent items (variants, cases, resamples) runs on all
30
30
  cores with :func:`parallel_map`, with exactly the result of a loop.
@@ -1,6 +1,6 @@
1
1
  """The key figure of a result: one number and a line, for a tile or a report.
2
2
 
3
- Shared by the Summary (:mod:`openprocess.gui.flow.summary`) and the HTML
3
+ Shared by the Report (:mod:`openprocess.gui.flow.report`) and the HTML
4
4
  report (:mod:`openprocess.flow.report`), so both say the same thing about a
5
5
  result: a score's first metric, a table's size, a log's cases, a net's
6
6
  places and transitions, a map's activities and paths.
@@ -1,7 +1,8 @@
1
- """The ``.cpnflow`` file: a workflow, and the record that makes it reproducible.
1
+ """The ``.analysis`` file: a workflow, and the record that makes it reproducible.
2
2
 
3
- A workflow file is JSON: the boxes, their settings and positions, the
4
- connections, the groups, and a **record**:
3
+ An analysis file is JSON: the boxes, their settings and positions, the
4
+ connections, the groups, its **versions** (:mod:`.versions`: the record as
5
+ it was each time the user kept it) and the **record** of the latest version:
5
6
 
6
7
  * the versions of OpenProcess, Python and the optional libraries;
7
8
  * the environment: every installed package and its version (the lock);
@@ -34,7 +35,44 @@ from .runner import DONE, Run
34
35
  from .types import Dataset, Figure, Predictions, Scores, Table, Text
35
36
  from .workflow import Workflow
36
37
 
37
- FORMAT = "cpnflow/1"
38
+ FORMAT = "analysis/1"
39
+
40
+ #: The extension of an analysis file, and the ones it had before (``.cpnflow``,
41
+ #: from when the app was CPNpy: the content is the same, only the name changed).
42
+ EXTENSION = ".analysis"
43
+ OLD_EXTENSIONS = (".cpnflow",)
44
+
45
+
46
+ def is_analysis_file(path) -> bool:
47
+ """Whether ``path`` is an analysis file by its name, under either extension."""
48
+ return str(path).lower().endswith((EXTENSION,) + OLD_EXTENSIONS)
49
+
50
+
51
+ def analysis_name(path) -> Path:
52
+ """``path`` under the current extension (``orders.cpnflow`` → ``orders.analysis``)."""
53
+ path = Path(path)
54
+ return path.with_name(path.stem + EXTENSION) if path.suffix.lower() in OLD_EXTENSIONS else path
55
+
56
+
57
+ def carry_over(folder, depth: int = 3) -> list[Path]:
58
+ """Rename every ``.cpnflow`` under ``folder`` to ``.analysis`` (the content is
59
+ read either way); a file whose new name is taken is left. Returns the new paths."""
60
+ renamed = []
61
+ root = Path(folder)
62
+ for old in OLD_EXTENSIONS:
63
+ for source in sorted(root.rglob(f"*{old}")):
64
+ if len(source.relative_to(root).parts) - 1 > depth or any(
65
+ part.startswith(".") for part in source.relative_to(root).parts):
66
+ continue
67
+ target = analysis_name(source)
68
+ if target.exists():
69
+ continue
70
+ try:
71
+ source.replace(target)
72
+ except OSError:
73
+ continue
74
+ renamed.append(target)
75
+ return renamed
38
76
 
39
77
 
40
78
  # ---------------------------------------------------------------------------
@@ -242,8 +280,11 @@ def canonical(value: Any, _active: set[int] | None = None) -> Any:
242
280
  if isinstance(value, (list, tuple)):
243
281
  return [canonical(v, _active) for v in value]
244
282
  if dataclasses.is_dataclass(value) and not isinstance(value, type):
283
+ # A field named with a leading underscore is a cache or a working
284
+ # value (a DFG's per-arc means and medians, say), not content.
245
285
  fields = {f.name: canonical(getattr(value, f.name, None), _active)
246
- for f in dataclasses.fields(value) if f.name not in _NOT_CONTENT}
286
+ for f in dataclasses.fields(value)
287
+ if not f.name.startswith("_") and f.name not in _NOT_CONTENT}
247
288
  return {"type": type(value).__name__, **fields}
248
289
  attributes = getattr(value, "__dict__", None)
249
290
  if isinstance(attributes, dict):
@@ -456,14 +497,46 @@ def differences(record: Record, workflow: Workflow, folder: str | Path | None =
456
497
  # Files
457
498
  # ---------------------------------------------------------------------------
458
499
  def save(workflow: Workflow, path: str | Path, run: Run | None = None,
459
- folder: str | Path | None = None, lock: bool = True, history: list[dict] | None = None) -> Record:
460
- """Write the workflow and its record to ``path``; returns the record.
461
- ``history``: the analysis's snapshots (see :mod:`.snapshots`), kept in the file."""
500
+ folder: str | Path | None = None, lock: bool = True, history: list[dict] | None = None,
501
+ previous: Record | None = None, versions: "Versions | None" = None,
502
+ keep: str | None = None) -> Record:
503
+ """Write the workflow, its versions and its record to ``path``; returns the record.
504
+
505
+ ``history``: the analysis's runs (see :mod:`.snapshots`), kept in the file.
506
+ ``previous``: the file's record so far, and ``versions`` the versions it
507
+ holds; see :func:`carried`. ``keep``: a note, to keep this run as a new
508
+ version (``""`` for a version without one): the record becomes this run's.
509
+ Without ``keep`` the record stays as it was, except that the first run in
510
+ which no box fails is kept as version 1 by itself, so a new analysis is
511
+ recorded without a word.
512
+
513
+ ``versions`` is updated in place when a version is kept."""
514
+ from .versions import Versions
462
515
  path = Path(path)
463
516
  folder = folder if folder is not None else path.parent
464
- record = make_record(workflow, run, folder, lock)
517
+ versions = versions if versions is not None else Versions()
518
+ if keep is not None and (run is None or run.failed()):
519
+ raise ValueError("A version keeps a run in which no box failed")
520
+ if keep is None and previous is not None and previous.results:
521
+ record = carried(previous, workflow)
522
+ elif keep is None and (run is None or run.failed()):
523
+ record = make_record(workflow, None, folder, lock) # nothing to keep yet
524
+ else:
525
+ record = make_record(workflow, run, folder, lock)
526
+ if previous is not None and previous.scheme == record.scheme:
527
+ # A box that could not run here (a library it needs is missing)
528
+ # keeps the fingerprint recorded where it could.
529
+ still = {k: v for k, v in previous.results.items() if k in workflow.nodes}
530
+ record.results = {**still, **record.results}
531
+ for entry in record.inputs:
532
+ if entry["sha256"] == "missing" and previous is not None:
533
+ then = next((i for i in previous.inputs if i["node"] == entry["node"]
534
+ and i["setting"] == entry["setting"] and i["file"] == entry["file"]), None)
535
+ if then is not None:
536
+ entry["sha256"] = then["sha256"]
537
+ versions.keep(workflow, run, record, keep or ("First run" if not versions.items else ""))
465
538
  record.history = list(history or [])
466
- data = {"format": FORMAT, **workflow.to_dict(), "record": record.to_dict()}
539
+ data = {"format": FORMAT, **workflow.to_dict(), "versions": versions.to_list(), "record": record.to_dict()}
467
540
  text = json.dumps(data, indent=2, ensure_ascii=False, default=str)
468
541
  temporary = path.with_name(path.name + ".tmp")
469
542
  temporary.write_text(text, encoding="utf-8")
@@ -471,13 +544,34 @@ def save(workflow: Workflow, path: str | Path, run: Run | None = None,
471
544
  return record
472
545
 
473
546
 
547
+ def carried(previous: Record, workflow: Workflow) -> Record:
548
+ """The record to write when no version is kept: the previous one as it
549
+ was, results of boxes since removed aside. Saving as you work never
550
+ replaces the record; keeping a version does (:func:`save` with ``keep``)."""
551
+ still = {k: v for k, v in previous.results.items() if k in workflow.nodes}
552
+ return Record(**{**previous.__dict__, "results": still})
553
+
554
+
474
555
  def load(path: str | Path, library=None) -> tuple[Workflow, Record]:
475
- """Read a ``.cpnflow`` file."""
556
+ """Read an analysis file (``.analysis``, or ``.cpnflow`` as it was called)."""
557
+ workflow, record, _versions = load_all(path, library)
558
+ return workflow, record
559
+
560
+
561
+ def load_all(path: str | Path, library=None) -> "tuple[Workflow, Record, Versions]":
562
+ """Read an analysis file: its workflow, the record of its latest version, and
563
+ every version kept. A file from before versions (a record, no list) gives
564
+ its record as version 1."""
565
+ from .versions import Versions
476
566
  data = json.loads(Path(path).read_text(encoding="utf-8"))
477
- if data.get("format", FORMAT).split("/")[0] != "cpnflow":
478
- raise ValueError(f"{path} is not a workflow file")
567
+ if data.get("format", FORMAT).split("/")[0] not in ("analysis", "cpnflow"):
568
+ raise ValueError(f"{path} is not an analysis file")
479
569
  workflow = Workflow.from_dict(data, library)
480
- return workflow, Record.from_dict(data.get("record"))
570
+ record = Record.from_dict(data.get("record"))
571
+ versions = Versions.from_list(data.get("versions"))
572
+ if not versions.items and record.results:
573
+ versions = Versions.first_from(workflow, record)
574
+ return workflow, record, versions
481
575
 
482
576
 
483
577
  def check(workflow: Workflow, record: Record, run: Run, complete: bool = False) -> list[str]:
@@ -547,7 +641,7 @@ def export_experiment(workflow: Workflow, run: Run | None, target: str | Path,
547
641
  name = workflow.name or "workflow"
548
642
  lines = [f"# {name}", "", f"Exported {record.saved} with OpenProcess {record.versions.get('openprocess', '')}, "
549
643
  f"Python {record.versions.get('python', '')}.", "",
550
- "Run it again with `openprocess run " + f"{name}.cpnflow --check`: it fails if any result differs "
644
+ "Run it again with `openprocess run " + f"{name}{EXTENSION} --check`: it fails if any result differs "
551
645
  "from the recorded one. `requirements.lock` is the environment it ran in. `run.py` is the same "
552
646
  "analysis as a plain Python script (`python run.py`). Or drop this zip on OpenProcess: it unpacks "
553
647
  "it into a folder and runs it.", "", "## Boxes", ""]
@@ -575,7 +669,7 @@ def export_experiment(workflow: Workflow, run: Run | None, target: str | Path,
575
669
  lines += ["", "## How to cite", "", citation.app_text(), "",
576
670
  "`CITATIONS.bib` has this entry and the papers the algorithms follow, as BibTeX."]
577
671
  with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zipped:
578
- zipped.writestr(f"{name}.cpnflow", json.dumps(data, indent=2, ensure_ascii=False, default=str))
672
+ zipped.writestr(f"{name}{EXTENSION}", json.dumps(data, indent=2, ensure_ascii=False, default=str))
579
673
  zipped.writestr("requirements.lock", requirements_lock(record))
580
674
  from .script import script_text
581
675
  zipped.writestr("run.py", script_text(packed_workflow, "run.py"))
@@ -611,11 +705,11 @@ def _describe(value) -> str:
611
705
 
612
706
 
613
707
  def is_experiment(path) -> bool:
614
- """Whether ``path`` is a zip :func:`export_experiment` wrote: one ``.cpnflow`` at the top."""
708
+ """Whether ``path`` is a zip :func:`export_experiment` wrote: one analysis file at the top."""
615
709
  import zipfile
616
710
  try:
617
711
  with zipfile.ZipFile(path) as zipped:
618
- return sum(1 for n in zipped.namelist() if n.endswith(".cpnflow") and "/" not in n) == 1
712
+ return sum(1 for n in zipped.namelist() if is_analysis_file(n) and "/" not in n) == 1
619
713
  except (OSError, zipfile.BadZipFile):
620
714
  return False
621
715
 
@@ -630,9 +724,9 @@ def import_experiment(archive, into) -> Path:
630
724
  archive = Path(archive)
631
725
  with zipfile.ZipFile(archive) as zipped:
632
726
  names = zipped.namelist()
633
- flows = [n for n in names if n.endswith(".cpnflow") and "/" not in n]
727
+ flows = [n for n in names if is_analysis_file(n) and "/" not in n]
634
728
  if len(flows) != 1:
635
- raise ValueError(f"{archive.name} is not an exported experiment (one .cpnflow at the top is needed)")
729
+ raise ValueError(f"{archive.name} is not an exported experiment (one {EXTENSION} file at the top is needed)")
636
730
  folder = Path(into) / archive.stem
637
731
  counter = 2
638
732
  while folder.exists():
@@ -648,6 +742,9 @@ def import_experiment(archive, into) -> Path:
648
742
  target.write_bytes(zipped.read(name))
649
743
  flow_file = folder / flows[0]
650
744
  data = json.loads(flow_file.read_text(encoding="utf-8"))
745
+ if flow_file.suffix.lower() in OLD_EXTENSIONS: # an experiment exported before 0.16
746
+ flow_file.unlink()
747
+ flow_file = analysis_name(flow_file)
651
748
  moved: dict[tuple[str, str], str] = {}
652
749
  for item in data.get("record", {}).get("inputs", []):
653
750
  new = f"inputs/{Path(item['file']).name}"
@@ -697,6 +794,7 @@ def _as_file(value, stem: str):
697
794
  #: (name, bytes or text) for a result worth a file of its own; the script an experiment carries uses it.
698
795
  as_file = _as_file
699
796
 
700
- __all__ = ["FORMAT", "Record", "as_file", "check", "differences", "environment", "export_experiment",
701
- "fingerprint", "import_experiment", "is_experiment", "load", "make_record", "requirements_lock",
702
- "save", "sha256_file", "sha256_text", "versions"]
797
+ __all__ = ["EXTENSION", "FORMAT", "OLD_EXTENSIONS", "Record", "analysis_name", "as_file", "carry_over", "check",
798
+ "differences", "environment", "export_experiment", "fingerprint", "import_experiment",
799
+ "is_analysis_file", "is_experiment", "load", "load_all", "make_record", "requirements_lock", "save",
800
+ "sha256_file", "sha256_text", "versions"]
@@ -9,7 +9,7 @@ external assets, readable in any browser and printable.
9
9
 
10
10
  The page is built from the workflow and its run alone; drawings (SVG) are
11
11
  passed in by the app, which has the canvas to render them. So the same
12
- report can be made without the app (``openprocess run analysis.cpnflow
12
+ report can be made without the app (``openprocess run orders.analysis
13
13
  --report report.html``), minus the pictures.
14
14
  """
15
15