openprocess 0.13.2__tar.gz → 0.15.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 (271) hide show
  1. {openprocess-0.13.2/openprocess.egg-info → openprocess-0.15.0}/PKG-INFO +31 -20
  2. {openprocess-0.13.2 → openprocess-0.15.0}/README.md +29 -18
  3. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/cli.py +5 -2
  5. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/figures.py +1 -1
  6. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/record.py +34 -3
  7. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/workflow.py +9 -3
  8. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/page.py +66 -39
  9. openprocess-0.15.0/openprocess/gui/flow/report.py +537 -0
  10. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/app.py +21 -5
  11. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/compare_page.py +10 -2
  12. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/connections.py +1 -1
  13. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/documents.py +21 -14
  14. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/dotted_chart.py +229 -98
  15. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/filter_dialog.py +80 -26
  16. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/log_editor.py +203 -45
  17. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/log_page.py +160 -29
  18. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/style.py +5 -1
  19. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/workers.py +18 -4
  20. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/columns.py +231 -17
  21. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/csv_import.py +65 -8
  22. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/dfg.py +57 -25
  23. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/dotted.py +200 -155
  24. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/filtering.py +36 -1
  25. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/log.py +17 -14
  26. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/stats.py +15 -11
  27. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/xes.py +115 -23
  28. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/packages.py +1 -1
  29. {openprocess-0.13.2 → openprocess-0.15.0/openprocess.egg-info}/PKG-INFO +31 -20
  30. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/SOURCES.txt +3 -1
  31. {openprocess-0.13.2 → openprocess-0.15.0}/pyproject.toml +8 -6
  32. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_citation.py +3 -2
  33. openprocess-0.15.0/tests/test_dotted_chart.py +109 -0
  34. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_flow_gui.py +223 -38
  35. openprocess-0.15.0/tests/test_large_log_page.py +353 -0
  36. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_researcher_api.py +3 -2
  37. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_studio_gui.py +2 -0
  38. openprocess-0.13.2/openprocess/gui/flow/summary.py +0 -199
  39. {openprocess-0.13.2 → openprocess-0.15.0}/LICENSE +0 -0
  40. {openprocess-0.13.2 → openprocess-0.15.0}/cpnpy/__init__.py +0 -0
  41. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/__init__.py +0 -0
  42. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/state_space.py +0 -0
  43. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/state_space_process.py +0 -0
  44. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/citation.py +0 -0
  45. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  46. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  47. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  48. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  49. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  50. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  51. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  52. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  53. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  54. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  55. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  56. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  57. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  58. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  59. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  60. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  61. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  62. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  63. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  64. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/pack.md +0 -0
  65. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/__init__.py +0 -0
  66. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/box.py +0 -0
  67. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/__init__.py +0 -0
  68. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/check.py +0 -0
  69. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/compare.py +0 -0
  70. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/cpn.py +0 -0
  71. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/discover.py +0 -0
  72. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/filter.py +0 -0
  73. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/input.py +0 -0
  74. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/measure.py +0 -0
  75. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/output.py +0 -0
  76. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/predict.py +0 -0
  77. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/science.py +0 -0
  78. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/sweeps.py +0 -0
  79. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/changes.py +0 -0
  80. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/convert.py +0 -0
  81. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/datasets.py +0 -0
  82. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/derivation.py +0 -0
  83. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/explain.py +0 -0
  84. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/history.py +0 -0
  85. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/library.py +0 -0
  86. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/loading.py +0 -0
  87. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/parallel.py +0 -0
  88. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/provenance.py +0 -0
  89. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/report.py +0 -0
  90. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/reproducibility.py +0 -0
  91. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/runner.py +0 -0
  92. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/script.py +0 -0
  93. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/snapshots.py +0 -0
  94. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/studio.py +0 -0
  95. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/sweep.py +0 -0
  96. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/types.py +0 -0
  97. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/__init__.py +0 -0
  98. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/app.py +0 -0
  99. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/arc_editing.py +0 -0
  100. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/canvas.py +0 -0
  101. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/__init__.py +0 -0
  102. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/canvas.py +0 -0
  103. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/picker.py +0 -0
  104. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/runs.py +0 -0
  105. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/stepper.py +0 -0
  106. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/templates.py +0 -0
  107. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/viewers.py +0 -0
  108. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/items.py +0 -0
  109. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/answer_boxes.py +0 -0
  110. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/concealment.py +0 -0
  111. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/mode.py +0 -0
  112. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/panning.py +0 -0
  113. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  114. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/__init__.py +0 -0
  115. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/__main__.py +0 -0
  116. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/alignment_view.py +0 -0
  117. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/charts.py +0 -0
  118. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/cite.py +0 -0
  119. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/command_palette.py +0 -0
  120. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/connect.py +0 -0
  121. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/cpn_page.py +0 -0
  122. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/definition_view.py +0 -0
  123. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/derivation_view.py +0 -0
  124. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  125. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/graph_builders.py +0 -0
  126. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/graph_view.py +0 -0
  127. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/instances.py +0 -0
  128. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/loading.py +0 -0
  129. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/markdown_view.py +0 -0
  130. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/mathtext.py +0 -0
  131. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  132. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/model_page.py +0 -0
  133. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/motion.py +0 -0
  134. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/net_comparison.py +0 -0
  135. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  136. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/petri_page.py +0 -0
  137. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/provenance.py +0 -0
  138. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/regions_view.py +0 -0
  139. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/remote.py +0 -0
  140. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/review.py +0 -0
  141. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/sidebar.py +0 -0
  142. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/spaces.py +0 -0
  143. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/tool_icons.py +0 -0
  144. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/updates.py +0 -0
  145. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/widgets.py +0 -0
  146. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/workspace.py +0 -0
  147. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/theme.py +0 -0
  148. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/tidy.py +0 -0
  149. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/__init__.py +0 -0
  150. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/cpn_reader.py +0 -0
  151. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/cpn_writer.py +0 -0
  152. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/__init__.py +0 -0
  153. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/answers.py +0 -0
  154. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/checks.py +0 -0
  155. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/computed.py +0 -0
  156. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/context.py +0 -0
  157. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/exam.py +0 -0
  158. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/exercise-packs.md +0 -0
  159. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/importer.py +0 -0
  160. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/notation.py +0 -0
  161. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/pack.py +0 -0
  162. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/sheet.py +0 -0
  163. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/__init__.py +0 -0
  164. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/__main__.py +0 -0
  165. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/api.py +0 -0
  166. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/connecting.md +0 -0
  167. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/install.py +0 -0
  168. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/server.py +0 -0
  169. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/__init__.py +0 -0
  170. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/analysis.py +0 -0
  171. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/bpmn.py +0 -0
  172. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/cases.py +0 -0
  173. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/compare_nets.py +0 -0
  174. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/__init__.py +0 -0
  175. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/alignments.py +0 -0
  176. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/quality.py +0 -0
  177. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/token_replay.py +0 -0
  178. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/definitions.py +0 -0
  179. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/__init__.py +0 -0
  180. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/alpha.py +0 -0
  181. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/heuristics.py +0 -0
  182. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/inductive.py +0 -0
  183. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/state_regions.py +0 -0
  184. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/drawing.py +0 -0
  185. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/footprint.py +0 -0
  186. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/invariants.py +0 -0
  187. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/layout.py +0 -0
  188. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/ocel.py +0 -0
  189. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/parallel.py +0 -0
  190. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/petrinet.py +0 -0
  191. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/playout.py +0 -0
  192. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/pm4py_bridge.py +0 -0
  193. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/pnml.py +0 -0
  194. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/processtree.py +0 -0
  195. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/regions.py +0 -0
  196. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/structure.py +0 -0
  197. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/transition_system.py +0 -0
  198. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/__init__.py +0 -0
  199. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/ast_nodes.py +0 -0
  200. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/builtins.py +0 -0
  201. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/colorsets.py +0 -0
  202. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/errors.py +0 -0
  203. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/evaluator.py +0 -0
  204. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/lexer.py +0 -0
  205. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/multiset.py +0 -0
  206. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/parser.py +0 -0
  207. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/values.py +0 -0
  208. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/__init__.py +0 -0
  209. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/declarations.py +0 -0
  210. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/examples.py +0 -0
  211. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/net.py +0 -0
  212. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/plain.py +0 -0
  213. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/palette.py +0 -0
  214. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/parallel.py +0 -0
  215. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/references.py +0 -0
  216. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/__init__.py +0 -0
  217. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/binding.py +0 -0
  218. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/export.py +0 -0
  219. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/performance.py +0 -0
  220. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/simulator.py +0 -0
  221. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/__init__.py +0 -0
  222. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/answers.py +0 -0
  223. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/checks.py +0 -0
  224. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/pack.py +0 -0
  225. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/sheet.py +0 -0
  226. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/dependency_links.txt +0 -0
  227. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/entry_points.txt +0 -0
  228. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/requires.txt +1 -1
  229. {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/top_level.txt +0 -0
  230. {openprocess-0.13.2 → openprocess-0.15.0}/setup.cfg +0 -0
  231. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_arc_editing.py +0 -0
  232. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_bpmn.py +0 -0
  233. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_colorsets.py +0 -0
  234. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_columns.py +0 -0
  235. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_cpn_course_model.py +0 -0
  236. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_definitions.py +0 -0
  237. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_docs.py +0 -0
  238. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_exercises_gui.py +0 -0
  239. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_flow.py +0 -0
  240. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_framework_contract.py +0 -0
  241. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_hierarchy.py +0 -0
  242. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_history.py +0 -0
  243. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_io.py +0 -0
  244. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_large_logs.py +0 -0
  245. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_learn.py +0 -0
  246. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_learn_gui.py +0 -0
  247. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_log_editing.py +0 -0
  248. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mcp.py +0 -0
  249. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mcp_gui.py +0 -0
  250. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_measure.py +0 -0
  251. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mining.py +0 -0
  252. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_ml.py +0 -0
  253. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_multiset.py +0 -0
  254. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_ocel.py +0 -0
  255. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_packages.py +0 -0
  256. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_parallel_and_loading.py +0 -0
  257. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_parallel_boxes.py +0 -0
  258. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_performance.py +0 -0
  259. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_regions.py +0 -0
  260. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_rename.py +0 -0
  261. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_report.py +0 -0
  262. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_review_fixes.py +0 -0
  263. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_simulation.py +0 -0
  264. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_snapshots.py +0 -0
  265. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_state_space.py +0 -0
  266. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_studio_lazy.py +0 -0
  267. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_teaching.py +0 -0
  268. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_tidy.py +0 -0
  269. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_updates.py +0 -0
  270. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_wf_structure.py +0 -0
  271. {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openprocess
3
- Version: 0.13.2
3
+ Version: 0.15.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
@@ -18,6 +18,7 @@ Classifier: Topic :: Education
18
18
  Requires-Python: >=3.10
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
+ Requires-Dist: numpy>=1.24
21
22
  Provides-Extra: app
22
23
  Requires-Dist: PySide6>=6.6; extra == "app"
23
24
  Requires-Dist: certifi; extra == "app"
@@ -28,7 +29,6 @@ Provides-Extra: dev
28
29
  Requires-Dist: pytest>=8.0; extra == "dev"
29
30
  Requires-Dist: mcp>=2.3; extra == "dev"
30
31
  Provides-Extra: science
31
- Requires-Dist: numpy>=1.24; extra == "science"
32
32
  Requires-Dist: pandas>=2.0; extra == "science"
33
33
  Requires-Dist: scipy>=1.10; extra == "science"
34
34
  Requires-Dist: matplotlib>=3.7; extra == "science"
@@ -494,6 +494,9 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
494
494
  - Time unit (Auto, or seconds up to years) and a grid step you can type in.
495
495
  - Colour and shape by any attribute. The default palette can be changed
496
496
  per value: right-click a value in the legend and pick its colour.
497
+ - A log of a million events (the BPI Challenge logs) pans, zooms and
498
+ answers the pointer without a pause: the dots are numpy arrays and a
499
+ dense picture is rasterised a colour at a time, not a dot at a time.
497
500
  - **Discover** hands the log to an analysis in Mine (**Discover in Mine →**),
498
501
  where the miner is a box with its result, its *How* and its *Code*:
499
502
  - α-algorithm, which shows its eight steps;
@@ -759,7 +762,7 @@ renders every page offscreen into `docs/screenshots/`, so run it after a
759
762
  change to the app's looks.
760
763
 
761
764
  The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
762
- have **no dependencies**, so they work in a notebook or a script:
765
+ need **nothing but numpy**, so they work in a notebook or a script:
763
766
 
764
767
  ```python
765
768
  from openprocess.mining import read_xes, inductive_miner
@@ -788,14 +791,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
788
791
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
789
792
  extra) and *Predict the next activity*. Or start empty.
790
793
 
791
- | 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 |
792
795
  |---|---|
793
- | ![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) |
794
-
795
- - **The page is the canvas.** Its header has **Canvas | Summary**, then
796
- **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
797
- and Save. The sidebar beside it lists the folder's analyses and, folded
798
- 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
+ Runs**, then **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record,
800
+ Export experiment and Save. The sidebar beside it lists the folder's
801
+ 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.
799
816
  - **+ Add box** (or a double-click on the canvas) opens every box, one
800
817
  section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
801
818
  then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
@@ -809,12 +826,6 @@ extra) and *Predict the next activity*. Or start empty.
809
826
  notation…* in the sidebar land as boxes on the current analysis (or a new
810
827
  one named after the file). In a box's **Settings**, *Choose…* picks the
811
828
  file and the list beside it offers only the files the box can read.
812
- - **Summary** is the key figure of every box, in one place: a tile per box,
813
- in the order the boxes run (a score's first metric, a table's size, a
814
- log's cases, a net's places and transitions). Nothing is computed there:
815
- a tile is the box's result, as on the canvas, and a tile opens the box.
816
- The process map under the tiles is the *Directly-follows graph* box's
817
- result; an analysis without that box offers to add it in one click.
818
829
  - **Connect** by dragging from the dot on the right of a box: while you drag,
819
830
  only the inputs that take that kind of result light up, so a wrong
820
831
  connection cannot be made. Click a wire and press Delete to remove it.
@@ -884,15 +895,15 @@ extra) and *Predict the next activity*. Or start empty.
884
895
  with the course's miners and checks on the 2AMI10 exam log.
885
896
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
886
897
  draws what flows in, OpenProcess and its boxes, what flows out, and the
887
- optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
898
+ optional tools (pandas, scipy, matplotlib, …) with whether each is
888
899
  installed, how to install it and the boxes it brings. All of it is read
889
900
  from the box library. *Install…* works in the downloaded app too: it carries
890
901
  pip and installs into a folder of your own (on a Mac,
891
902
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
892
903
  need the package come alive without a restart. PM4Py needs a Python
893
904
  install (`pip install pm4py`), as parts of it are built from source.
894
- - **Motion** is kept short: a space switch, the side panel and Canvas ↔
895
- Summary settle in a fifth of a second, and a box pulses once when it
905
+ - **Motion** is kept short: a space switch, the side panel and Report ↔
906
+ Canvas settle in a fifth of a second, and a box pulses once when it
896
907
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
897
908
 
898
909
  ## Workflows from Python
@@ -949,7 +960,7 @@ See [Parallel work](docs/workflows.md#parallel-work) for the rules (each item
949
960
  from its own inputs, `flow.item_seed` for randomness). OpenProcess's own
950
961
  alignments and token replay run this way on large logs.
951
962
 
952
- `pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
963
+ `pip install -e ".[science]"` adds the pandas, SciPy and matplotlib
953
964
  boxes (describe, bootstrap intervals, tests, plots).
954
965
 
955
966
  **Large logs.** A log above 20 MB is read into columns rather than objects,
@@ -452,6 +452,9 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
452
452
  - Time unit (Auto, or seconds up to years) and a grid step you can type in.
453
453
  - Colour and shape by any attribute. The default palette can be changed
454
454
  per value: right-click a value in the legend and pick its colour.
455
+ - A log of a million events (the BPI Challenge logs) pans, zooms and
456
+ answers the pointer without a pause: the dots are numpy arrays and a
457
+ dense picture is rasterised a colour at a time, not a dot at a time.
455
458
  - **Discover** hands the log to an analysis in Mine (**Discover in Mine →**),
456
459
  where the miner is a box with its result, its *How* and its *Code*:
457
460
  - α-algorithm, which shows its eight steps;
@@ -717,7 +720,7 @@ renders every page offscreen into `docs/screenshots/`, so run it after a
717
720
  change to the app's looks.
718
721
 
719
722
  The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
720
- have **no dependencies**, so they work in a notebook or a script:
723
+ need **nothing but numpy**, so they work in a notebook or a script:
721
724
 
722
725
  ```python
723
726
  from openprocess.mining import read_xes, inductive_miner
@@ -746,14 +749,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
746
749
  confidence* (bootstrap intervals, a test and a plot; needs the `science`
747
750
  extra) and *Predict the next activity*. Or start empty.
748
751
 
749
- | 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 |
750
753
  |---|---|
751
- | ![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) |
752
-
753
- - **The page is the canvas.** Its header has **Canvas | Summary**, then
754
- **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
755
- and Save. The sidebar beside it lists the folder's analyses and, folded
756
- 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
+ Runs**, then **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record,
758
+ Export experiment and Save. The sidebar beside it lists the folder's
759
+ 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.
757
774
  - **+ Add box** (or a double-click on the canvas) opens every box, one
758
775
  section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
759
776
  then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
@@ -767,12 +784,6 @@ extra) and *Predict the next activity*. Or start empty.
767
784
  notation…* in the sidebar land as boxes on the current analysis (or a new
768
785
  one named after the file). In a box's **Settings**, *Choose…* picks the
769
786
  file and the list beside it offers only the files the box can read.
770
- - **Summary** is the key figure of every box, in one place: a tile per box,
771
- in the order the boxes run (a score's first metric, a table's size, a
772
- log's cases, a net's places and transitions). Nothing is computed there:
773
- a tile is the box's result, as on the canvas, and a tile opens the box.
774
- The process map under the tiles is the *Directly-follows graph* box's
775
- result; an analysis without that box offers to add it in one click.
776
787
  - **Connect** by dragging from the dot on the right of a box: while you drag,
777
788
  only the inputs that take that kind of result light up, so a wrong
778
789
  connection cannot be made. Click a wire and press Delete to remove it.
@@ -842,15 +853,15 @@ extra) and *Predict the next activity*. Or start empty.
842
853
  with the course's miners and checks on the 2AMI10 exam log.
843
854
  - **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
844
855
  draws what flows in, OpenProcess and its boxes, what flows out, and the
845
- optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
856
+ optional tools (pandas, scipy, matplotlib, …) with whether each is
846
857
  installed, how to install it and the boxes it brings. All of it is read
847
858
  from the box library. *Install…* works in the downloaded app too: it carries
848
859
  pip and installs into a folder of your own (on a Mac,
849
860
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
850
861
  need the package come alive without a restart. PM4Py needs a Python
851
862
  install (`pip install pm4py`), as parts of it are built from source.
852
- - **Motion** is kept short: a space switch, the side panel and Canvas ↔
853
- Summary settle in a fifth of a second, and a box pulses once when it
863
+ - **Motion** is kept short: a space switch, the side panel and Report ↔
864
+ Canvas settle in a fifth of a second, and a box pulses once when it
854
865
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
855
866
 
856
867
  ## Workflows from Python
@@ -907,7 +918,7 @@ See [Parallel work](docs/workflows.md#parallel-work) for the rules (each item
907
918
  from its own inputs, `flow.item_seed` for randomness). OpenProcess's own
908
919
  alignments and token replay run this way on large logs.
909
920
 
910
- `pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
921
+ `pip install -e ".[science]"` adds the pandas, SciPy and matplotlib
911
922
  boxes (describe, bootstrap intervals, tests, plots).
912
923
 
913
924
  **Large logs.** A log above 20 MB is read into columns rather than objects,
@@ -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.13.2"
51
+ __version__ = "0.15.0"
52
52
 
53
53
  __all__ = [
54
54
  "Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
@@ -517,8 +517,11 @@ def command_run(arguments: argparse.Namespace) -> int:
517
517
  return 1
518
518
  print(f"Every recorded result was reproduced ({len(record.results)} boxes).")
519
519
  elif arguments.save:
520
- save(workflow, path, run, folder)
521
- print(f"Recorded the results in {path.name}")
520
+ save(workflow, path, run, folder, previous=record)
521
+ if run.failed() and record.results:
522
+ print(f"A box failed, so {path.name} keeps the record of its last successful run")
523
+ else:
524
+ print(f"Recorded the results in {path.name}")
522
525
  return 0 if run.ok else 1
523
526
 
524
527
 
@@ -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.
@@ -242,8 +242,11 @@ def canonical(value: Any, _active: set[int] | None = None) -> Any:
242
242
  if isinstance(value, (list, tuple)):
243
243
  return [canonical(v, _active) for v in value]
244
244
  if dataclasses.is_dataclass(value) and not isinstance(value, type):
245
+ # A field named with a leading underscore is a cache or a working
246
+ # value (a DFG's per-arc means and medians, say), not content.
245
247
  fields = {f.name: canonical(getattr(value, f.name, None), _active)
246
- for f in dataclasses.fields(value) if f.name not in _NOT_CONTENT}
248
+ for f in dataclasses.fields(value)
249
+ if not f.name.startswith("_") and f.name not in _NOT_CONTENT}
247
250
  return {"type": type(value).__name__, **fields}
248
251
  attributes = getattr(value, "__dict__", None)
249
252
  if isinstance(attributes, dict):
@@ -456,12 +459,16 @@ def differences(record: Record, workflow: Workflow, folder: str | Path | None =
456
459
  # Files
457
460
  # ---------------------------------------------------------------------------
458
461
  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:
462
+ folder: str | Path | None = None, lock: bool = True, history: list[dict] | None = None,
463
+ previous: Record | None = None) -> Record:
460
464
  """Write the workflow and its record to ``path``; returns the record.
461
- ``history``: the analysis's snapshots (see :mod:`.snapshots`), kept in the file."""
465
+ ``history``: the analysis's snapshots (see :mod:`.snapshots`), kept in the file.
466
+ ``previous``: the file's record so far; see :func:`carried`."""
462
467
  path = Path(path)
463
468
  folder = folder if folder is not None else path.parent
464
469
  record = make_record(workflow, run, folder, lock)
470
+ if previous is not None:
471
+ record = carried(record, previous, workflow, run)
465
472
  record.history = list(history or [])
466
473
  data = {"format": FORMAT, **workflow.to_dict(), "record": record.to_dict()}
467
474
  text = json.dumps(data, indent=2, ensure_ascii=False, default=str)
@@ -471,6 +478,30 @@ def save(workflow: Workflow, path: str | Path, run: Run | None = None,
471
478
  return record
472
479
 
473
480
 
481
+ def carried(record: Record, previous: Record, workflow: Workflow, run: Run | None) -> Record:
482
+ """The record to write, given the file's ``previous`` one: the record of the
483
+ last successful run stays until a successful run replaces it.
484
+
485
+ No run, or a run in which a box failed (the input file was not found, say),
486
+ leaves the previous record as it was, results of boxes since removed aside.
487
+ A run in which no box failed replaces it; a box that could not run here (a
488
+ library it needs is missing) keeps its recorded fingerprint if the scheme is
489
+ the same, and an input file that is missing now keeps the fingerprint
490
+ recorded when it was read."""
491
+ still = {k: v for k, v in previous.results.items() if k in workflow.nodes}
492
+ if run is None or run.failed():
493
+ return Record(**{**previous.__dict__, "results": still})
494
+ if previous.scheme == record.scheme:
495
+ record.results = {**still, **record.results}
496
+ for entry in record.inputs:
497
+ if entry["sha256"] == "missing":
498
+ then = next((i for i in previous.inputs if i["node"] == entry["node"]
499
+ and i["setting"] == entry["setting"] and i["file"] == entry["file"]), None)
500
+ if then is not None:
501
+ entry["sha256"] = then["sha256"]
502
+ return record
503
+
504
+
474
505
  def load(path: str | Path, library=None) -> tuple[Workflow, Record]:
475
506
  """Read a ``.cpnflow`` file."""
476
507
  data = json.loads(Path(path).read_text(encoding="utf-8"))
@@ -348,12 +348,12 @@ class Workflow:
348
348
  "name": self.name,
349
349
  "boxes": [{"id": n.id, "box": n.box, "settings": {k: (v.to_json() if is_sweep(v) else _json_value(v))
350
350
  for k, v in n.settings.items()},
351
- "position": list(n.position), **({"title": n.title} if n.title else {}),
352
- **({"layout": {k: list(v) for k, v in n.layout.items()}} if n.layout else {})}
351
+ "position": _coordinates(n.position), **({"title": n.title} if n.title else {}),
352
+ **({"layout": {k: _coordinates(v) for k, v in n.layout.items()}} if n.layout else {})}
353
353
  for n in self.nodes.values()],
354
354
  "connections": [{"from": e.source, "output": e.output, "to": e.target, "input": e.input}
355
355
  for e in self.edges],
356
- "groups": [{"id": g.id, "name": g.name, "boxes": list(g.members), "position": list(g.position)}
356
+ "groups": [{"id": g.id, "name": g.name, "boxes": list(g.members), "position": _coordinates(g.position)}
357
357
  for g in self.groups.values()],
358
358
  }
359
359
 
@@ -387,6 +387,12 @@ class Workflow:
387
387
  return wf
388
388
 
389
389
 
390
+ def _coordinates(point) -> list:
391
+ """A position for the file: the canvas works in floats, but a whole number
392
+ is written as one (a file opened and saved keeps its ``[0, 360]``)."""
393
+ return [int(v) if isinstance(v, float) and v.is_integer() else v for v in point]
394
+
395
+
390
396
  def _stand_in(library, box_id: str) -> str | None:
391
397
  """A folder box's id holds a hash of its file's absolute path, so the same
392
398
  file in a folder that was moved, copied or imported elsewhere has another
@@ -1,9 +1,11 @@
1
- """The Workflows page: the canvas, *+ Add box*, and a side panel on demand.
2
-
3
- The page opens with the workflow alone on its canvas. *+ Add box* in the
4
- header (or a double-click on the canvas) opens the box picker
5
- (:mod:`.picker`). Click a box and the side panel appears beside the
6
- canvas, on *Result* (the viewer for its output type); its other tabs are
1
+ """The Workflows page: the Report, the canvas, *+ Add box*, and a side panel on demand.
2
+
3
+ The page opens on its *Report* (:mod:`.report`): every box's result, in run
4
+ order, on one scrolling page. *Canvas* in the header is the boxes and
5
+ wires, *Runs* every finished run. *+ Add box* (or a double-click on the
6
+ canvas) opens the box picker (:mod:`.picker`). Click a box on the canvas
7
+ (or *Open ›* under its section of the Report) and the side panel appears
8
+ beside the canvas, on *Result* (the viewer for its output type); its other tabs are
7
9
  *How* (what it reported while it ran: notes, intermediate values, the
8
10
  derivation), *Code* (its source) and *Settings* (one control per setting).
9
11
  Click the canvas, or the panel's ✕, and the panel goes again. Change a
@@ -50,11 +52,13 @@ from ..studio.widgets import (Card, ElidedLabel, NoticeBar, PageHeader, Segmente
50
52
  from ..studio.workers import run_in_background
51
53
  from .canvas import WorkflowScene, WorkflowView
52
54
  from .picker import BoxPicker, input_box_for
55
+ from .report import ReportWidget
53
56
  from .runs import RunsWidget
54
- from .summary import SummaryWidget
55
57
  from .viewers import SettingsWidget, code_widget, how_widget, pop_out, result_widget, status_text
56
58
 
57
59
  TABS = ["Result", "How", "Code", "Settings"]
60
+ #: The header switch's views, and the body's pages in the same order.
61
+ REPORT, CANVAS, RUNS = 0, 1, 2
58
62
  HINT = "Click a box to see what it gives. Drag from the dot on its right to connect it to another."
59
63
 
60
64
 
@@ -155,6 +159,7 @@ class WorkflowPage(QWidget):
155
159
  self._setting_timer.setInterval(400)
156
160
  self._setting_timer.timeout.connect(self._apply_pending_settings)
157
161
  self._pending: dict[str, dict] = {}
162
+ self._record_pending = False
158
163
 
159
164
  root = QVBoxLayout(self)
160
165
  root.setContentsMargins(0, 0, 0, 0)
@@ -175,15 +180,15 @@ class WorkflowPage(QWidget):
175
180
  self.refresh_title()
176
181
  run_in_background(lambda: reproducibility.status(workflow, record, None, folder), first_check,
177
182
  lambda _message: None)
178
- # Canvas: the boxes and wires. Summary: every result's figure, and the process map.
179
- self.summary_switch = SegmentedControl(["Canvas", "Summary", "Runs"], compact=True)
180
- self.summary_switch.setToolTip("Canvas: the boxes and wires.\nSummary: every result's key "
181
- "figure, and the process map.\nRuns: every finished run, two compared.")
183
+ # Report: every box's result, in run order. Canvas: the boxes and wires.
184
+ self.view_switch = SegmentedControl(["Report", "Canvas", "Runs"], compact=True)
185
+ self.view_switch.setToolTip("Report: every box's result, in the order the boxes run.\nCanvas: the "
186
+ "boxes and wires.\nRuns: every finished run, two compared.")
182
187
  record = getattr(document, "record", None)
183
188
  #: The last runs, with what changed before each (kept in the workflow file).
184
189
  self.history = snapshots.History.from_dict(record.history if record is not None else [])
185
- self.summary_switch.changed.connect(self.show_view)
186
- self.header.actions.addWidget(self.summary_switch)
190
+ self.view_switch.changed.connect(self.show_view)
191
+ self.header.actions.addWidget(self.view_switch)
187
192
  self.keep_button = button("Keep", self.keep_requested.emit, kind="primary",
188
193
  tooltip="Save this workflow into the folder")
189
194
  self.keep_button.setVisible(False)
@@ -258,16 +263,15 @@ class WorkflowPage(QWidget):
258
263
  splitter.reset_requested.connect(self.reset_layout)
259
264
  wrapper = QWidget()
260
265
  wrapper.setLayout(vbox(splitter, margins=(20, 0, 20, 16)))
261
- self.summary = SummaryWidget(self)
262
- self.summary.tile_clicked.connect(self._tile_clicked)
263
- summary_wrapper = QWidget()
264
- summary_wrapper.setLayout(vbox(scroll(self.summary), margins=(20, 0, 20, 16)))
266
+ self.report = ReportWidget(self)
267
+ report_wrapper = QWidget()
268
+ report_wrapper.setLayout(vbox(self.report, margins=(20, 0, 20, 16)))
265
269
  self.runs = RunsWidget(self)
266
270
  runs_wrapper = QWidget()
267
271
  runs_wrapper.setLayout(vbox(scroll(self.runs), margins=(20, 0, 20, 16)))
268
272
  self.body = QStackedWidget()
273
+ self.body.addWidget(report_wrapper)
269
274
  self.body.addWidget(wrapper)
270
- self.body.addWidget(summary_wrapper)
271
275
  self.body.addWidget(runs_wrapper)
272
276
  root.addWidget(self.body, 1)
273
277
  self._picker: BoxPicker | None = None
@@ -282,6 +286,8 @@ class WorkflowPage(QWidget):
282
286
  if self.selected in self.workflow.groups else None)
283
287
  self._refresh_custom_bar()
284
288
  self._render_panel()
289
+ # A workflow opens on its Report; an empty one on the canvas, where boxes are added.
290
+ self.show_view(REPORT if self.workflow.nodes else CANVAS)
285
291
  QTimer.singleShot(0, self.view.fit)
286
292
  if not self.workflow.nodes:
287
293
  self.status.emit("An empty workflow: press + Add box, or double-click the canvas")
@@ -302,17 +308,17 @@ class WorkflowPage(QWidget):
302
308
  self.select(None)
303
309
 
304
310
  def show_view(self, index: int) -> None:
305
- """Canvas (0), Summary (1) or Runs (2); the latter two are rebuilt when shown."""
311
+ """Report (0), Canvas (1) or Runs (2); the Report and Runs are brought up to date when shown."""
306
312
  if self.body.currentIndex() != index:
307
313
  self.body.setCurrentIndex(index)
308
314
  motion.lift(self.body)
309
- if self.summary_switch.index() != index:
310
- self.summary_switch.blockSignals(True)
311
- self.summary_switch.buttons[index].setChecked(True)
312
- self.summary_switch.blockSignals(False)
313
- if index == 1:
314
- self.summary.refresh()
315
- elif index == 2:
315
+ if self.view_switch.index() != index:
316
+ self.view_switch.blockSignals(True)
317
+ self.view_switch.buttons[index].setChecked(True)
318
+ self.view_switch.blockSignals(False)
319
+ if index == REPORT:
320
+ self.report.refresh()
321
+ elif index == RUNS:
316
322
  self.runs.refresh()
317
323
 
318
324
  def apply_snapshot(self, snapshot: snapshots.Snapshot) -> None:
@@ -335,15 +341,18 @@ class WorkflowPage(QWidget):
335
341
  return
336
342
  self._mark_edited()
337
343
  self._render_panel()
338
- self.show_view(0)
344
+ self.show_view(REPORT) # the results of those settings, as they come
339
345
  self.status.emit(f"Settings of the run at {snapshot.taken.replace('T', ' ')} are back on "
340
346
  f"{len(changed)} box{'es' if len(changed) != 1 else ''}; running")
341
347
  self.run_from(changed)
342
348
 
343
- def _tile_clicked(self, node_id: str) -> None:
344
- """A Summary tile: that box's Result, on the canvas."""
345
- self.show_view(0)
346
- self.select(node_id, 0)
349
+ def open_box(self, node_id: str, tab: int = 0) -> None:
350
+ """A Report section's *Open ›*, *How* or *Code*: that box in the side panel, on the canvas."""
351
+ self.select(node_id, tab)
352
+
353
+ def result_view(self, node, result: Result | None) -> QWidget:
354
+ """The side panel's Result tab for ``node`` showing ``result``; the Report draws each box with it."""
355
+ return self._tab_widget(node, self.workflow.spec(node), result, 0)
347
356
 
348
357
  @property
349
358
  def dock(self) -> str:
@@ -460,7 +469,8 @@ class WorkflowPage(QWidget):
460
469
  return QPointF(rect.left() + 30 + count * 24, rect.bottom() - 150 - count * 12)
461
470
 
462
471
  def add_box_in_view(self, box_id: str, settings: dict | None = None):
463
- """Add ``box_id`` where it can be seen."""
472
+ """Add ``box_id`` where it can be seen: on the canvas, brought up if another view is showing."""
473
+ self.show_view(CANVAS)
464
474
  return self.add_box(box_id, self.free_spot(), settings)
465
475
 
466
476
  def quick_add(self, where: QPointF) -> None:
@@ -491,8 +501,9 @@ class WorkflowPage(QWidget):
491
501
 
492
502
  def add_box_fed_by(self, box_id: str, source_id: str | None):
493
503
  """Add a box and connect it to ``source_id``'s output (the first input
494
- that takes it), as dropping a wire would; then it runs."""
495
- node = self.add_box_in_view(box_id)
504
+ that takes it), as dropping a wire would; then it runs. The page stays
505
+ on the view it was asked from (the Report offers the process map this way)."""
506
+ node = self.add_box(box_id, self.free_spot())
496
507
  if source_id is not None and source_id in self.workflow.nodes:
497
508
  ok, detail = self.workflow.can_connect(source_id, node.id)
498
509
  if ok:
@@ -628,6 +639,10 @@ class WorkflowPage(QWidget):
628
639
  self._show_status(result)
629
640
  if self.selected == result.node:
630
641
  self._render_panel()
642
+ if self.body.currentIndex() == REPORT:
643
+ self.report.update_box(result)
644
+ else:
645
+ self.report.stale = True
631
646
 
632
647
  def _show_status(self, result: Result) -> None:
633
648
  if result.variant != 0:
@@ -661,11 +676,16 @@ class WorkflowPage(QWidget):
661
676
  self._render_panel()
662
677
  if isinstance(outcome, Run) and not outcome.stopped and not self._has_queue:
663
678
  if self.history.add(snapshots.take(self.workflow, outcome, self.history.latest)) \
664
- and self.body.currentIndex() == 2:
679
+ and self.body.currentIndex() == RUNS:
665
680
  self.runs.refresh()
681
+ if self._record_pending:
682
+ self._record_pending = False
683
+ self.document.dirty = True
666
684
  self.ran.emit()
667
- if self.body.currentIndex() == 1:
668
- self.summary.refresh()
685
+ if self.body.currentIndex() == REPORT:
686
+ self.report.refresh()
687
+ else:
688
+ self.report.stale = True
669
689
  if self._has_queue or (isinstance(outcome, Run) and outcome.stopped):
670
690
  queued, self._queued, self._has_queue = self._queued, None, False
671
691
  if queued is not None and isinstance(outcome, Run) and outcome.stopped:
@@ -828,6 +848,8 @@ class WorkflowPage(QWidget):
828
848
  if tab is not None:
829
849
  self.tab = tab
830
850
  self._opening_tab = tab
851
+ if node_id is not None:
852
+ self.show_view(CANVAS) # the side panel lives beside the canvas
831
853
  # The scene clears its selection before selecting: without the guard,
832
854
  # the panel would close and reopen (back on Result) at every select.
833
855
  self._selecting = True
@@ -1176,11 +1198,16 @@ class WorkflowPage(QWidget):
1176
1198
  def write_to(self, path: str, quiet: bool = False) -> None:
1177
1199
  """Save the workflow and its record to ``path`` (the window's Keep / Save As)."""
1178
1200
  target = Path(path)
1179
- self.workflow.name = target.stem if target.stem else self.workflow.name
1201
+ if target.stem and (not self.document.path or Path(self.document.path) != target):
1202
+ self.workflow.name = target.stem # Keep / Save As: named after the file; a save keeps the name
1180
1203
  self.document.record = records.save(self.workflow, target, self.run, self.folder or target.parent,
1181
- history=self.history.to_dict())
1204
+ history=self.history.to_dict(),
1205
+ previous=getattr(self.document, "record", None))
1182
1206
  self.document.path = str(target)
1183
1207
  self.document.dirty = False
1208
+ # Saved while a run was going: the record is of the run before, so the one
1209
+ # finishing wants saving too.
1210
+ self._record_pending = self._running
1184
1211
  self.refresh_reproducibility()
1185
1212
  if not quiet:
1186
1213
  self.status.emit(f"Saved {target.name}")