openprocess 0.14.0__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.14.0/openprocess.egg-info → openprocess-0.15.0}/PKG-INFO +24 -16
  2. {openprocess-0.14.0 → openprocess-0.15.0}/README.md +23 -15
  3. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/__init__.py +1 -1
  4. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/cli.py +5 -2
  5. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/figures.py +1 -1
  6. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/record.py +34 -3
  7. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/workflow.py +9 -3
  8. {openprocess-0.14.0 → 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.14.0 → openprocess-0.15.0}/openprocess/gui/studio/app.py +14 -2
  11. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/dotted_chart.py +9 -2
  12. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/style.py +5 -1
  13. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/dotted.py +5 -2
  14. {openprocess-0.14.0 → openprocess-0.15.0/openprocess.egg-info}/PKG-INFO +24 -16
  15. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/SOURCES.txt +1 -1
  16. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_citation.py +3 -2
  17. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_flow_gui.py +223 -38
  18. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_studio_gui.py +2 -0
  19. openprocess-0.14.0/openprocess/gui/flow/summary.py +0 -199
  20. {openprocess-0.14.0 → openprocess-0.15.0}/LICENSE +0 -0
  21. {openprocess-0.14.0 → openprocess-0.15.0}/cpnpy/__init__.py +0 -0
  22. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/__init__.py +0 -0
  23. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/state_space.py +0 -0
  24. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/state_space_process.py +0 -0
  25. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/citation.py +0 -0
  26. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
  27. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
  28. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
  29. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
  30. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
  31. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
  32. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
  33. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
  34. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
  35. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
  36. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
  37. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
  38. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
  39. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
  40. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
  41. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
  42. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
  43. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
  44. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
  45. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/pack.md +0 -0
  46. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/__init__.py +0 -0
  47. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/box.py +0 -0
  48. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/__init__.py +0 -0
  49. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/check.py +0 -0
  50. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/compare.py +0 -0
  51. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/cpn.py +0 -0
  52. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/discover.py +0 -0
  53. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/filter.py +0 -0
  54. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/input.py +0 -0
  55. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/measure.py +0 -0
  56. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/output.py +0 -0
  57. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/predict.py +0 -0
  58. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/science.py +0 -0
  59. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/sweeps.py +0 -0
  60. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/changes.py +0 -0
  61. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/convert.py +0 -0
  62. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/datasets.py +0 -0
  63. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/derivation.py +0 -0
  64. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/explain.py +0 -0
  65. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/history.py +0 -0
  66. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/library.py +0 -0
  67. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/loading.py +0 -0
  68. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/parallel.py +0 -0
  69. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/provenance.py +0 -0
  70. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/report.py +0 -0
  71. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/reproducibility.py +0 -0
  72. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/runner.py +0 -0
  73. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/script.py +0 -0
  74. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/snapshots.py +0 -0
  75. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/studio.py +0 -0
  76. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/sweep.py +0 -0
  77. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/types.py +0 -0
  78. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/__init__.py +0 -0
  79. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/app.py +0 -0
  80. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/arc_editing.py +0 -0
  81. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/canvas.py +0 -0
  82. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/__init__.py +0 -0
  83. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/canvas.py +0 -0
  84. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/picker.py +0 -0
  85. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/runs.py +0 -0
  86. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/stepper.py +0 -0
  87. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/templates.py +0 -0
  88. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/viewers.py +0 -0
  89. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/items.py +0 -0
  90. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/answer_boxes.py +0 -0
  91. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/concealment.py +0 -0
  92. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/mode.py +0 -0
  93. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/panning.py +0 -0
  94. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
  95. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/__init__.py +0 -0
  96. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/__main__.py +0 -0
  97. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/alignment_view.py +0 -0
  98. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/charts.py +0 -0
  99. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/cite.py +0 -0
  100. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/command_palette.py +0 -0
  101. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/compare_page.py +0 -0
  102. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/connect.py +0 -0
  103. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/connections.py +0 -0
  104. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/cpn_page.py +0 -0
  105. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/definition_view.py +0 -0
  106. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/derivation_view.py +0 -0
  107. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/documents.py +0 -0
  108. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/file_dialogs.py +0 -0
  109. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/filter_dialog.py +0 -0
  110. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/graph_builders.py +0 -0
  111. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/graph_view.py +0 -0
  112. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/instances.py +0 -0
  113. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/loading.py +0 -0
  114. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/log_editor.py +0 -0
  115. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/log_page.py +0 -0
  116. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/markdown_view.py +0 -0
  117. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/mathtext.py +0 -0
  118. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
  119. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/model_page.py +0 -0
  120. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/motion.py +0 -0
  121. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/net_comparison.py +0 -0
  122. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/notes_overlay.py +0 -0
  123. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/petri_page.py +0 -0
  124. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/provenance.py +0 -0
  125. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/regions_view.py +0 -0
  126. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/remote.py +0 -0
  127. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/review.py +0 -0
  128. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/sidebar.py +0 -0
  129. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/spaces.py +0 -0
  130. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/tool_icons.py +0 -0
  131. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/updates.py +0 -0
  132. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/widgets.py +0 -0
  133. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/workers.py +0 -0
  134. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/workspace.py +0 -0
  135. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/theme.py +0 -0
  136. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/tidy.py +0 -0
  137. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/__init__.py +0 -0
  138. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/cpn_reader.py +0 -0
  139. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/cpn_writer.py +0 -0
  140. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/__init__.py +0 -0
  141. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/answers.py +0 -0
  142. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/checks.py +0 -0
  143. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/computed.py +0 -0
  144. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/context.py +0 -0
  145. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/exam.py +0 -0
  146. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/exercise-packs.md +0 -0
  147. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/importer.py +0 -0
  148. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/notation.py +0 -0
  149. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/pack.py +0 -0
  150. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/sheet.py +0 -0
  151. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/__init__.py +0 -0
  152. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/__main__.py +0 -0
  153. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/api.py +0 -0
  154. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/connecting.md +0 -0
  155. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/install.py +0 -0
  156. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/server.py +0 -0
  157. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/__init__.py +0 -0
  158. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/analysis.py +0 -0
  159. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/bpmn.py +0 -0
  160. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/cases.py +0 -0
  161. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/columns.py +0 -0
  162. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/compare_nets.py +0 -0
  163. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/__init__.py +0 -0
  164. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/alignments.py +0 -0
  165. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/quality.py +0 -0
  166. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/token_replay.py +0 -0
  167. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/csv_import.py +0 -0
  168. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/definitions.py +0 -0
  169. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/dfg.py +0 -0
  170. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/__init__.py +0 -0
  171. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/alpha.py +0 -0
  172. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/heuristics.py +0 -0
  173. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/inductive.py +0 -0
  174. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/state_regions.py +0 -0
  175. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/drawing.py +0 -0
  176. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/filtering.py +0 -0
  177. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/footprint.py +0 -0
  178. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/invariants.py +0 -0
  179. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/layout.py +0 -0
  180. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/log.py +0 -0
  181. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/ocel.py +0 -0
  182. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/parallel.py +0 -0
  183. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/petrinet.py +0 -0
  184. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/playout.py +0 -0
  185. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/pm4py_bridge.py +0 -0
  186. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/pnml.py +0 -0
  187. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/processtree.py +0 -0
  188. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/regions.py +0 -0
  189. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/stats.py +0 -0
  190. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/structure.py +0 -0
  191. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/transition_system.py +0 -0
  192. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/xes.py +0 -0
  193. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/__init__.py +0 -0
  194. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/ast_nodes.py +0 -0
  195. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/builtins.py +0 -0
  196. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/colorsets.py +0 -0
  197. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/errors.py +0 -0
  198. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/evaluator.py +0 -0
  199. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/lexer.py +0 -0
  200. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/multiset.py +0 -0
  201. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/parser.py +0 -0
  202. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/values.py +0 -0
  203. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/__init__.py +0 -0
  204. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/declarations.py +0 -0
  205. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/examples.py +0 -0
  206. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/net.py +0 -0
  207. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/plain.py +0 -0
  208. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/packages.py +0 -0
  209. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/palette.py +0 -0
  210. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/parallel.py +0 -0
  211. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/references.py +0 -0
  212. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/__init__.py +0 -0
  213. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/binding.py +0 -0
  214. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/export.py +0 -0
  215. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/performance.py +0 -0
  216. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/simulator.py +0 -0
  217. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/__init__.py +0 -0
  218. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/answers.py +0 -0
  219. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/checks.py +0 -0
  220. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/pack.py +0 -0
  221. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/sheet.py +0 -0
  222. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/dependency_links.txt +0 -0
  223. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/entry_points.txt +0 -0
  224. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/requires.txt +0 -0
  225. {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/top_level.txt +0 -0
  226. {openprocess-0.14.0 → openprocess-0.15.0}/pyproject.toml +0 -0
  227. {openprocess-0.14.0 → openprocess-0.15.0}/setup.cfg +0 -0
  228. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_arc_editing.py +0 -0
  229. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_bpmn.py +0 -0
  230. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_colorsets.py +0 -0
  231. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_columns.py +0 -0
  232. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_cpn_course_model.py +0 -0
  233. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_definitions.py +0 -0
  234. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_docs.py +0 -0
  235. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_dotted_chart.py +0 -0
  236. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_exercises_gui.py +0 -0
  237. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_flow.py +0 -0
  238. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_framework_contract.py +0 -0
  239. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_hierarchy.py +0 -0
  240. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_history.py +0 -0
  241. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_io.py +0 -0
  242. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_large_log_page.py +0 -0
  243. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_large_logs.py +0 -0
  244. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_learn.py +0 -0
  245. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_learn_gui.py +0 -0
  246. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_log_editing.py +0 -0
  247. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mcp.py +0 -0
  248. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mcp_gui.py +0 -0
  249. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_measure.py +0 -0
  250. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mining.py +0 -0
  251. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_ml.py +0 -0
  252. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_multiset.py +0 -0
  253. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_ocel.py +0 -0
  254. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_packages.py +0 -0
  255. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_parallel_and_loading.py +0 -0
  256. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_parallel_boxes.py +0 -0
  257. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_performance.py +0 -0
  258. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_regions.py +0 -0
  259. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_rename.py +0 -0
  260. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_report.py +0 -0
  261. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_researcher_api.py +0 -0
  262. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_review_fixes.py +0 -0
  263. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_simulation.py +0 -0
  264. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_snapshots.py +0 -0
  265. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_state_space.py +0 -0
  266. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_studio_lazy.py +0 -0
  267. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_teaching.py +0 -0
  268. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_tidy.py +0 -0
  269. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_updates.py +0 -0
  270. {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_wf_structure.py +0 -0
  271. {openprocess-0.14.0 → 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.14.0
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
@@ -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
+ 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.
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.
@@ -894,8 +902,8 @@ extra) and *Predict the next activity*. Or start empty.
894
902
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
895
903
  need the package come alive without a restart. PM4Py needs a Python
896
904
  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
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
899
907
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
900
908
 
901
909
  ## Workflows from Python
@@ -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
+ 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.
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.
@@ -852,8 +860,8 @@ extra) and *Predict the next activity*. Or start empty.
852
860
  `~/Library/Application Support/OpenProcess/packages`), and the boxes that
853
861
  need the package come alive without a restart. PM4Py needs a Python
854
862
  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
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
857
865
  finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
858
866
 
859
867
  ## Workflows from Python
@@ -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.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}")