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.
- {openprocess-0.14.0/openprocess.egg-info → openprocess-0.15.0}/PKG-INFO +24 -16
- {openprocess-0.14.0 → openprocess-0.15.0}/README.md +23 -15
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/__init__.py +1 -1
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/cli.py +5 -2
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/figures.py +1 -1
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/record.py +34 -3
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/workflow.py +9 -3
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/page.py +66 -39
- openprocess-0.15.0/openprocess/gui/flow/report.py +537 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/app.py +14 -2
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/dotted_chart.py +9 -2
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/style.py +5 -1
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/dotted.py +5 -2
- {openprocess-0.14.0 → openprocess-0.15.0/openprocess.egg-info}/PKG-INFO +24 -16
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/SOURCES.txt +1 -1
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_citation.py +3 -2
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_flow_gui.py +223 -38
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_studio_gui.py +2 -0
- openprocess-0.14.0/openprocess/gui/flow/summary.py +0 -199
- {openprocess-0.14.0 → openprocess-0.15.0}/LICENSE +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/cpnpy/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/citation.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/box.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/check.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/filter.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/measure.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/predict.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/science.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/changes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/convert.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/derivation.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/explain.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/history.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/library.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/loading.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/parallel.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/report.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/reproducibility.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/runner.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/script.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/snapshots.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/studio.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/flow/types.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/app.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/canvas.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/canvas.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/picker.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/templates.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/flow/viewers.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/items.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/learn/mode.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/panning.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/alignment_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/connect.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/connections.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/cpn_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/definition_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/derivation_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/graph_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/loading.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/log_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/model_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/petri_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/remote.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/review.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/sidebar.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/tool_icons.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/theme.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/answers.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/checks.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/computed.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/context.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/exam.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/importer.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/notation.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/pack.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/__main__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/api.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/connecting.md +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/install.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mcp/server.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/bpmn.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/cases.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/columns.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/compare_nets.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/alignments.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/quality.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/conformance/token_replay.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/definitions.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/alpha.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/heuristics.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/inductive.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/drawing.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/filtering.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/layout.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/log.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/parallel.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/playout.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/regions.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/stats.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/structure.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/mining/xes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/errors.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/parser.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/ml/values.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/declarations.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/examples.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/net.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/model/plain.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/packages.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/palette.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/parallel.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/references.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/binding.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/export.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/performance.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/requires.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/pyproject.toml +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/setup.cfg +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_arc_editing.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_bpmn.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_colorsets.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_columns.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_definitions.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_docs.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_dotted_chart.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_flow.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_framework_contract.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_hierarchy.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_history.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_io.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_large_log_page.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_large_logs.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_learn.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_learn_gui.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_log_editing.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mcp.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mcp_gui.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_measure.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_mining.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_ml.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_multiset.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_ocel.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_packages.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_parallel_and_loading.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_parallel_boxes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_performance.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_regions.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_rename.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_report.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_researcher_api.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_review_fixes.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_simulation.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_snapshots.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_state_space.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_studio_lazy.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_teaching.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_tidy.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_updates.py +0 -0
- {openprocess-0.14.0 → openprocess-0.15.0}/tests/test_wf_structure.py +0 -0
- {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.
|
|
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 |
|
|
794
|
+
| The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
|
|
795
795
|
|---|---|
|
|
796
|
-
|  |  |  |
|
|
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
|
|
898
|
-
|
|
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 |
|
|
752
|
+
| The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
|
|
753
753
|
|---|---|
|
|
754
|
-
|  |  |  |
|
|
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
|
|
856
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
|
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":
|
|
352
|
-
**({"layout": {k:
|
|
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":
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
canvas
|
|
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
|
-
#
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
181
|
-
|
|
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.
|
|
186
|
-
self.header.actions.addWidget(self.
|
|
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.
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
"""
|
|
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.
|
|
310
|
-
self.
|
|
311
|
-
self.
|
|
312
|
-
self.
|
|
313
|
-
if index ==
|
|
314
|
-
self.
|
|
315
|
-
elif index ==
|
|
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(
|
|
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
|
|
344
|
-
"""A
|
|
345
|
-
self.
|
|
346
|
-
|
|
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
|
-
|
|
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() ==
|
|
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() ==
|
|
668
|
-
self.
|
|
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
|
-
|
|
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}")
|