openprocess 0.13.2__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openprocess-0.13.2/openprocess.egg-info → openprocess-0.15.0}/PKG-INFO +31 -20
- {openprocess-0.13.2 → openprocess-0.15.0}/README.md +29 -18
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/__init__.py +1 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/cli.py +5 -2
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/figures.py +1 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/record.py +34 -3
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/workflow.py +9 -3
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/page.py +66 -39
- openprocess-0.15.0/openprocess/gui/flow/report.py +537 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/app.py +21 -5
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/compare_page.py +10 -2
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/connections.py +1 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/documents.py +21 -14
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/dotted_chart.py +229 -98
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/filter_dialog.py +80 -26
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/log_editor.py +203 -45
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/log_page.py +160 -29
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/style.py +5 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/workers.py +18 -4
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/columns.py +231 -17
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/csv_import.py +65 -8
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/dfg.py +57 -25
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/dotted.py +200 -155
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/filtering.py +36 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/log.py +17 -14
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/stats.py +15 -11
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/xes.py +115 -23
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/packages.py +1 -1
- {openprocess-0.13.2 → openprocess-0.15.0/openprocess.egg-info}/PKG-INFO +31 -20
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/SOURCES.txt +3 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/pyproject.toml +8 -6
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_citation.py +3 -2
- openprocess-0.15.0/tests/test_dotted_chart.py +109 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_flow_gui.py +223 -38
- openprocess-0.15.0/tests/test_large_log_page.py +353 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_researcher_api.py +3 -2
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_studio_gui.py +2 -0
- openprocess-0.13.2/openprocess/gui/flow/summary.py +0 -199
- {openprocess-0.13.2 → openprocess-0.15.0}/LICENSE +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/cpnpy/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/citation.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/box.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/check.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/filter.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/measure.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/predict.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/science.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/changes.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/convert.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/derivation.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/explain.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/history.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/library.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/loading.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/parallel.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/report.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/reproducibility.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/runner.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/script.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/snapshots.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/studio.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/flow/types.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/app.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/canvas.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/canvas.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/picker.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/templates.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/flow/viewers.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/items.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/learn/mode.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/panning.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/alignment_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/connect.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/cpn_page.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/definition_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/derivation_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/graph_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/loading.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/model_page.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/petri_page.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/remote.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/review.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/sidebar.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/tool_icons.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/theme.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/answers.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/checks.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/computed.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/context.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/exam.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/importer.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/notation.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/pack.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/__main__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/api.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/connecting.md +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/install.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mcp/server.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/bpmn.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/cases.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/compare_nets.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/alignments.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/quality.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/conformance/token_replay.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/definitions.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/alpha.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/heuristics.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/inductive.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/drawing.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/layout.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/parallel.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/playout.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/regions.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/structure.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/errors.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/parser.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/ml/values.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/declarations.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/examples.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/net.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/model/plain.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/palette.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/parallel.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/references.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/binding.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/export.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/performance.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/requires.txt +1 -1
- {openprocess-0.13.2 → openprocess-0.15.0}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/setup.cfg +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_arc_editing.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_bpmn.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_colorsets.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_columns.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_definitions.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_docs.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_flow.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_framework_contract.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_hierarchy.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_history.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_io.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_large_logs.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_learn.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_learn_gui.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_log_editing.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mcp.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mcp_gui.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_measure.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_mining.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_ml.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_multiset.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_ocel.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_packages.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_parallel_and_loading.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_parallel_boxes.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_performance.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_regions.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_rename.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_report.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_review_fixes.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_simulation.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_snapshots.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_state_space.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_studio_lazy.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_teaching.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_tidy.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_updates.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_wf_structure.py +0 -0
- {openprocess-0.13.2 → openprocess-0.15.0}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openprocess
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Process mining, Petri nets and workflows in one open app: every algorithm visible, every result reproducible, every step teachable
|
|
5
5
|
Author: Sam Struthers
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,7 @@ Classifier: Topic :: Education
|
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: numpy>=1.24
|
|
21
22
|
Provides-Extra: app
|
|
22
23
|
Requires-Dist: PySide6>=6.6; extra == "app"
|
|
23
24
|
Requires-Dist: certifi; extra == "app"
|
|
@@ -28,7 +29,6 @@ Provides-Extra: dev
|
|
|
28
29
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
29
30
|
Requires-Dist: mcp>=2.3; extra == "dev"
|
|
30
31
|
Provides-Extra: science
|
|
31
|
-
Requires-Dist: numpy>=1.24; extra == "science"
|
|
32
32
|
Requires-Dist: pandas>=2.0; extra == "science"
|
|
33
33
|
Requires-Dist: scipy>=1.10; extra == "science"
|
|
34
34
|
Requires-Dist: matplotlib>=3.7; extra == "science"
|
|
@@ -494,6 +494,9 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
|
|
|
494
494
|
- Time unit (Auto, or seconds up to years) and a grid step you can type in.
|
|
495
495
|
- Colour and shape by any attribute. The default palette can be changed
|
|
496
496
|
per value: right-click a value in the legend and pick its colour.
|
|
497
|
+
- A log of a million events (the BPI Challenge logs) pans, zooms and
|
|
498
|
+
answers the pointer without a pause: the dots are numpy arrays and a
|
|
499
|
+
dense picture is rasterised a colour at a time, not a dot at a time.
|
|
497
500
|
- **Discover** hands the log to an analysis in Mine (**Discover in Mine →**),
|
|
498
501
|
where the miner is a box with its result, its *How* and its *Code*:
|
|
499
502
|
- α-algorithm, which shows its eight steps;
|
|
@@ -759,7 +762,7 @@ renders every page offscreen into `docs/screenshots/`, so run it after a
|
|
|
759
762
|
change to the app's looks.
|
|
760
763
|
|
|
761
764
|
The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
|
|
762
|
-
|
|
765
|
+
need **nothing but numpy**, so they work in a notebook or a script:
|
|
763
766
|
|
|
764
767
|
```python
|
|
765
768
|
from openprocess.mining import read_xes, inductive_miner
|
|
@@ -788,14 +791,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
|
|
|
788
791
|
confidence* (bootstrap intervals, a test and a plot; needs the `science`
|
|
789
792
|
extra) and *Predict the next activity*. Or start empty.
|
|
790
793
|
|
|
791
|
-
| The canvas: *Compare discovery*, three miners side by side |
|
|
794
|
+
| The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
|
|
792
795
|
|---|---|
|
|
793
|
-
|  |  |  |
|
|
797
|
+
|
|
798
|
+
- **An analysis opens on its Report.** Its header has **Report | Canvas |
|
|
799
|
+
Runs**, then **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record,
|
|
800
|
+
Export experiment and Save. The sidebar beside it lists the folder's
|
|
801
|
+
analyses and, folded under them, its logs.
|
|
802
|
+
- **Report** is every box's result, in the order the boxes run, on one
|
|
803
|
+
scrolling page: a section per box with the group it is in, its name, the
|
|
804
|
+
line the canvas shows under it, its status, the result as the side panel
|
|
805
|
+
shows it (a table scrolls sideways inside its section when it is wider; a
|
|
806
|
+
figure keeps its proportions; a score gets a key-figure tile), its
|
|
807
|
+
settings as chips, and *Open ›*, *How* and *Code*, which open the box in
|
|
808
|
+
the side panel on the canvas. A slim *Steps* outline on the left lists
|
|
809
|
+
the sections, grouped as the canvas groups the boxes, with each box's
|
|
810
|
+
key figure; a click scrolls to the section, and on a narrow window the
|
|
811
|
+
outline folds into a *Steps* menu. Nothing is computed there: while a
|
|
812
|
+
run goes, each section is redrawn as its box reports; a box that has not
|
|
813
|
+
run says so, a failed one shows its error. The process map is the
|
|
814
|
+
*Directly-follows graph* box's result; an analysis without that box is
|
|
815
|
+
offered one in one click.
|
|
799
816
|
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
800
817
|
section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
|
|
801
818
|
then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
@@ -809,12 +826,6 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
809
826
|
notation…* in the sidebar land as boxes on the current analysis (or a new
|
|
810
827
|
one named after the file). In a box's **Settings**, *Choose…* picks the
|
|
811
828
|
file and the list beside it offers only the files the box can read.
|
|
812
|
-
- **Summary** is the key figure of every box, in one place: a tile per box,
|
|
813
|
-
in the order the boxes run (a score's first metric, a table's size, a
|
|
814
|
-
log's cases, a net's places and transitions). Nothing is computed there:
|
|
815
|
-
a tile is the box's result, as on the canvas, and a tile opens the box.
|
|
816
|
-
The process map under the tiles is the *Directly-follows graph* box's
|
|
817
|
-
result; an analysis without that box offers to add it in one click.
|
|
818
829
|
- **Connect** by dragging from the dot on the right of a box: while you drag,
|
|
819
830
|
only the inputs that take that kind of result light up, so a wrong
|
|
820
831
|
connection cannot be made. Click a wire and press Delete to remove it.
|
|
@@ -884,15 +895,15 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
884
895
|
with the course's miners and checks on the 2AMI10 exam log.
|
|
885
896
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
886
897
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
887
|
-
optional tools (pandas,
|
|
898
|
+
optional tools (pandas, scipy, matplotlib, …) with whether each is
|
|
888
899
|
installed, how to install it and the boxes it brings. All of it is read
|
|
889
900
|
from the box library. *Install…* works in the downloaded app too: it carries
|
|
890
901
|
pip and installs into a folder of your own (on a Mac,
|
|
891
902
|
`~/Library/Application Support/OpenProcess/packages`), and the boxes that
|
|
892
903
|
need the package come alive without a restart. PM4Py needs a Python
|
|
893
904
|
install (`pip install pm4py`), as parts of it are built from source.
|
|
894
|
-
- **Motion** is kept short: a space switch, the side panel and
|
|
895
|
-
|
|
905
|
+
- **Motion** is kept short: a space switch, the side panel and Report ↔
|
|
906
|
+
Canvas settle in a fifth of a second, and a box pulses once when it
|
|
896
907
|
finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
|
|
897
908
|
|
|
898
909
|
## Workflows from Python
|
|
@@ -949,7 +960,7 @@ See [Parallel work](docs/workflows.md#parallel-work) for the rules (each item
|
|
|
949
960
|
from its own inputs, `flow.item_seed` for randomness). OpenProcess's own
|
|
950
961
|
alignments and token replay run this way on large logs.
|
|
951
962
|
|
|
952
|
-
`pip install -e ".[science]"` adds the pandas,
|
|
963
|
+
`pip install -e ".[science]"` adds the pandas, SciPy and matplotlib
|
|
953
964
|
boxes (describe, bootstrap intervals, tests, plots).
|
|
954
965
|
|
|
955
966
|
**Large logs.** A log above 20 MB is read into columns rather than objects,
|
|
@@ -452,6 +452,9 @@ links to the definitions it builds on; **Help ▸ Definitions** lists them all.
|
|
|
452
452
|
- Time unit (Auto, or seconds up to years) and a grid step you can type in.
|
|
453
453
|
- Colour and shape by any attribute. The default palette can be changed
|
|
454
454
|
per value: right-click a value in the legend and pick its colour.
|
|
455
|
+
- A log of a million events (the BPI Challenge logs) pans, zooms and
|
|
456
|
+
answers the pointer without a pause: the dots are numpy arrays and a
|
|
457
|
+
dense picture is rasterised a colour at a time, not a dot at a time.
|
|
455
458
|
- **Discover** hands the log to an analysis in Mine (**Discover in Mine →**),
|
|
456
459
|
where the miner is a box with its result, its *How* and its *Code*:
|
|
457
460
|
- α-algorithm, which shows its eight steps;
|
|
@@ -717,7 +720,7 @@ renders every page offscreen into `docs/screenshots/`, so run it after a
|
|
|
717
720
|
change to the app's looks.
|
|
718
721
|
|
|
719
722
|
The engines (`openprocess.mining`, `openprocess.ml`, `openprocess.sim`, `openprocess.analysis`)
|
|
720
|
-
|
|
723
|
+
need **nothing but numpy**, so they work in a notebook or a script:
|
|
721
724
|
|
|
722
725
|
```python
|
|
723
726
|
from openprocess.mining import read_xes, inductive_miner
|
|
@@ -746,14 +749,28 @@ Miner, a fitness check), *Compare discovery* (three miners side by side),
|
|
|
746
749
|
confidence* (bootstrap intervals, a test and a plot; needs the `science`
|
|
747
750
|
extra) and *Predict the next activity*. Or start empty.
|
|
748
751
|
|
|
749
|
-
| The canvas: *Compare discovery*, three miners side by side |
|
|
752
|
+
| The canvas: *Compare discovery*, three miners side by side | Report: every box's result, in run order, on one page |
|
|
750
753
|
|---|---|
|
|
751
|
-
|  |  |  |
|
|
755
|
+
|
|
756
|
+
- **An analysis opens on its Report.** Its header has **Report | Canvas |
|
|
757
|
+
Runs**, then **+ Add box**, **Run ▶**, and **⋯** for Re-run, Record,
|
|
758
|
+
Export experiment and Save. The sidebar beside it lists the folder's
|
|
759
|
+
analyses and, folded under them, its logs.
|
|
760
|
+
- **Report** is every box's result, in the order the boxes run, on one
|
|
761
|
+
scrolling page: a section per box with the group it is in, its name, the
|
|
762
|
+
line the canvas shows under it, its status, the result as the side panel
|
|
763
|
+
shows it (a table scrolls sideways inside its section when it is wider; a
|
|
764
|
+
figure keeps its proportions; a score gets a key-figure tile), its
|
|
765
|
+
settings as chips, and *Open ›*, *How* and *Code*, which open the box in
|
|
766
|
+
the side panel on the canvas. A slim *Steps* outline on the left lists
|
|
767
|
+
the sections, grouped as the canvas groups the boxes, with each box's
|
|
768
|
+
key figure; a click scrolls to the section, and on a narrow window the
|
|
769
|
+
outline folds into a *Steps* menu. Nothing is computed there: while a
|
|
770
|
+
run goes, each section is redrawn as its box reports; a box that has not
|
|
771
|
+
run says so, a failed one shows its error. The process map is the
|
|
772
|
+
*Directly-follows graph* box's result; an analysis without that box is
|
|
773
|
+
offered one in one click.
|
|
757
774
|
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
758
775
|
section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
|
|
759
776
|
then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
@@ -767,12 +784,6 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
767
784
|
notation…* in the sidebar land as boxes on the current analysis (or a new
|
|
768
785
|
one named after the file). In a box's **Settings**, *Choose…* picks the
|
|
769
786
|
file and the list beside it offers only the files the box can read.
|
|
770
|
-
- **Summary** is the key figure of every box, in one place: a tile per box,
|
|
771
|
-
in the order the boxes run (a score's first metric, a table's size, a
|
|
772
|
-
log's cases, a net's places and transitions). Nothing is computed there:
|
|
773
|
-
a tile is the box's result, as on the canvas, and a tile opens the box.
|
|
774
|
-
The process map under the tiles is the *Directly-follows graph* box's
|
|
775
|
-
result; an analysis without that box offers to add it in one click.
|
|
776
787
|
- **Connect** by dragging from the dot on the right of a box: while you drag,
|
|
777
788
|
only the inputs that take that kind of result light up, so a wrong
|
|
778
789
|
connection cannot be made. Click a wire and press Delete to remove it.
|
|
@@ -842,15 +853,15 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
842
853
|
with the course's miners and checks on the 2AMI10 exam log.
|
|
843
854
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
844
855
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
845
|
-
optional tools (pandas,
|
|
856
|
+
optional tools (pandas, scipy, matplotlib, …) with whether each is
|
|
846
857
|
installed, how to install it and the boxes it brings. All of it is read
|
|
847
858
|
from the box library. *Install…* works in the downloaded app too: it carries
|
|
848
859
|
pip and installs into a folder of your own (on a Mac,
|
|
849
860
|
`~/Library/Application Support/OpenProcess/packages`), and the boxes that
|
|
850
861
|
need the package come alive without a restart. PM4Py needs a Python
|
|
851
862
|
install (`pip install pm4py`), as parts of it are built from source.
|
|
852
|
-
- **Motion** is kept short: a space switch, the side panel and
|
|
853
|
-
|
|
863
|
+
- **Motion** is kept short: a space switch, the side panel and Report ↔
|
|
864
|
+
Canvas settle in a fifth of a second, and a box pulses once when it
|
|
854
865
|
finishes. `OPENPROCESS_NO_MOTION=1` turns it off.
|
|
855
866
|
|
|
856
867
|
## Workflows from Python
|
|
@@ -907,7 +918,7 @@ See [Parallel work](docs/workflows.md#parallel-work) for the rules (each item
|
|
|
907
918
|
from its own inputs, `flow.item_seed` for randomness). OpenProcess's own
|
|
908
919
|
alignments and token replay run this way on large logs.
|
|
909
920
|
|
|
910
|
-
`pip install -e ".[science]"` adds the pandas,
|
|
921
|
+
`pip install -e ".[science]"` adds the pandas, SciPy and matplotlib
|
|
911
922
|
boxes (describe, bootstrap intervals, tests, plots).
|
|
912
923
|
|
|
913
924
|
**Large logs.** A log above 20 MB is read into columns rather than objects,
|
|
@@ -48,7 +48,7 @@ from .sim.simulator import Simulator
|
|
|
48
48
|
#: The one place the version number is written (pyproject.toml, the update
|
|
49
49
|
#: check and packaging/build.py read it from here). A release is tagged
|
|
50
50
|
#: "v" + this, e.g. v0.2.0.
|
|
51
|
-
__version__ = "0.
|
|
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}")
|