openprocess 0.12.0__tar.gz → 0.12.1__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.12.0/openprocess.egg-info → openprocess-0.12.1}/PKG-INFO +6 -2
- {openprocess-0.12.0 → openprocess-0.12.1}/README.md +5 -1
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/__init__.py +1 -1
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/measure.py +73 -3
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/app.py +102 -8
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/dotted_chart.py +69 -313
- openprocess-0.12.1/openprocess/gui/studio/loading.py +166 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/style.py +3 -6
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/analysis.py +22 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/definitions.py +11 -1
- openprocess-0.12.1/openprocess/mining/dotted.py +455 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/xes.py +46 -6
- openprocess-0.12.1/openprocess/palette.py +14 -0
- {openprocess-0.12.0 → openprocess-0.12.1/openprocess.egg-info}/PKG-INFO +6 -2
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess.egg-info/SOURCES.txt +3 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_large_logs.py +25 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_measure.py +25 -1
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_mining.py +19 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_studio_gui.py +96 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/LICENSE +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/cpnpy/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/citation.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/cli.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/box.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/check.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/filter.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/predict.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/science.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/convert.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/explain.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/figures.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/history.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/library.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/record.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/report.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/reproducibility.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/runner.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/script.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/snapshots.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/types.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/flow/workflow.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/app.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/canvas.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/canvas.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/picker.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/summary.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/templates.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/flow/viewers.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/items.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/learn/mode.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/panning.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/alignment_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/connections.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/cpn_page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/definition_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/derivation_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/graph_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/log_page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/model_page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/petri_page.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/sidebar.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/tool_icons.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/theme.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/io/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/answers.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/checks.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/computed.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/context.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/exam.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/importer.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/notation.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/pack.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/cases.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/columns.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/compare_nets.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/conformance/alignments.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/conformance/quality.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/conformance/token_replay.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/discovery/alpha.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/discovery/heuristics.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/discovery/inductive.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/filtering.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/layout.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/log.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/playout.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/regions.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/stats.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/structure.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/errors.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/parser.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/ml/values.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/model/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/model/declarations.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/model/examples.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/model/net.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/model/plain.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/packages.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/references.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/sim/binding.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/sim/export.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess.egg-info/requires.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/pyproject.toml +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/setup.cfg +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_arc_editing.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_citation.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_colorsets.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_columns.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_definitions.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_docs.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_flow.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_flow_gui.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_framework_contract.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_hierarchy.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_history.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_io.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_learn.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_learn_gui.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_log_editing.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_ml.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_multiset.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_ocel.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_packages.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_regions.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_rename.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_report.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_simulation.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_snapshots.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_state_space.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_teaching.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_tidy.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_updates.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_wf_structure.py +0 -0
- {openprocess-0.12.0 → openprocess-0.12.1}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openprocess
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.1
|
|
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
|
|
@@ -873,7 +873,11 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
873
873
|
and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
|
|
874
874
|
share that fits, value), with *Case attribute* to keep one category and
|
|
875
875
|
*Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
|
|
876
|
-
uses them on the BPI Challenge 2019.
|
|
876
|
+
uses them on the BPI Challenge 2019. *Dotted chart* saves a dotted chart
|
|
877
|
+
as a figure (the same dots as the log page's tab), and *Who does what*
|
|
878
|
+
counts each activity per resource;
|
|
879
|
+
[examples/energy_contracts](examples/energy_contracts/README.md) uses them
|
|
880
|
+
with the course's miners and checks on the 2AMI10 exam log.
|
|
877
881
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
878
882
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
879
883
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -834,7 +834,11 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
834
834
|
and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
|
|
835
835
|
share that fits, value), with *Case attribute* to keep one category and
|
|
836
836
|
*Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
|
|
837
|
-
uses them on the BPI Challenge 2019.
|
|
837
|
+
uses them on the BPI Challenge 2019. *Dotted chart* saves a dotted chart
|
|
838
|
+
as a figure (the same dots as the log page's tab), and *Who does what*
|
|
839
|
+
counts each activity per resource;
|
|
840
|
+
[examples/energy_contracts](examples/energy_contracts/README.md) uses them
|
|
841
|
+
with the course's miners and checks on the 2AMI10 exam log.
|
|
838
842
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
839
843
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
840
844
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -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.12.
|
|
51
|
+
__version__ = "0.12.1"
|
|
52
52
|
|
|
53
53
|
__all__ = [
|
|
54
54
|
"Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
|
|
@@ -5,7 +5,9 @@ the goods receipt? Did one user record an invoice and also clear it? How
|
|
|
5
5
|
compliant is each vendor, and with how much money? Each box answers one such
|
|
6
6
|
question for every case, in a table (and, where it helps, the cases
|
|
7
7
|
themselves, to analyse further), and works on a large log without building
|
|
8
|
-
its events (:func:`openprocess.mining.cases.cases`). The
|
|
8
|
+
its events (:func:`openprocess.mining.cases.cases`). The dotted chart and
|
|
9
|
+
*Who does what* are the first look at a log: when things happen, and who
|
|
10
|
+
does them. The BPI Challenge 2019
|
|
9
11
|
analysis in ``examples/bpi2019`` is built from them.
|
|
10
12
|
"""
|
|
11
13
|
|
|
@@ -18,10 +20,11 @@ from typing import Literal
|
|
|
18
20
|
|
|
19
21
|
from ... import flow
|
|
20
22
|
from ...mining.cases import case_value, cases
|
|
23
|
+
from ...mining.dotted import SORT_MODES, X_MODES, DotSettings, Dots, svg
|
|
21
24
|
from ...mining.log import EventLog
|
|
22
25
|
from ...mining.petrinet import PetriNet
|
|
23
26
|
from ..box import box
|
|
24
|
-
from ..types import Table
|
|
27
|
+
from ..types import Figure, Table
|
|
25
28
|
|
|
26
29
|
UNITS = {"hours": 3600.0, "days": 86400.0}
|
|
27
30
|
|
|
@@ -192,4 +195,71 @@ def compliance_by(model: PetriNet, log: EventLog, attribute: str = "Vendor",
|
|
|
192
195
|
return Table(f"Compliance by {attribute}", columns, rows)
|
|
193
196
|
|
|
194
197
|
|
|
195
|
-
|
|
198
|
+
@box(name="Dotted chart", group="Measure")
|
|
199
|
+
def dotted_chart(log: EventLog,
|
|
200
|
+
x: Literal["Actual time", "Time since case start", "Relative to case duration (%)",
|
|
201
|
+
"Logical: order in log", "Logical: position in case"] = "Actual time",
|
|
202
|
+
rows: str = "Case", colour: str = "Activity",
|
|
203
|
+
sort: Literal["First event", "Last event", "Duration", "Number of events", "Name"] = "First event",
|
|
204
|
+
unit: Literal["Auto", "Seconds", "Minutes", "Hours", "Days", "Weeks"] = "Auto") -> Figure:
|
|
205
|
+
"""Every event as a dot (Song & van der Aalst, 2007): *x* says when, the
|
|
206
|
+
row what it belongs to, the colour one more attribute; the same dots as
|
|
207
|
+
the dotted chart in Mine, saved as a figure. Patterns to look for: the
|
|
208
|
+
arrival rate (the slope of the first dots), batches (vertical lines),
|
|
209
|
+
waiting (long gaps in a row), and in Time since case start, how long
|
|
210
|
+
each step takes after the case begins.
|
|
211
|
+
|
|
212
|
+
x: when: actual time, time since the case started, the share of the case's duration, or a logical position
|
|
213
|
+
rows: Case, Activity, Resource, Lifecycle or any event attribute
|
|
214
|
+
colour: Activity, Resource, Lifecycle, Case, None or any event attribute
|
|
215
|
+
sort: the order of the rows
|
|
216
|
+
unit: the time unit of the x axis (Auto: dates and durations)
|
|
217
|
+
"""
|
|
218
|
+
settings = DotSettings(x_mode=X_MODES.index(x), rows=rows, colour_by=colour, sort=SORT_MODES.index(sort))
|
|
219
|
+
dots = Dots(log, log.default_classifier(), settings)
|
|
220
|
+
if not len(dots):
|
|
221
|
+
raise ValueError(f"{log.name} has no events to draw")
|
|
222
|
+
if dots.mode != settings.x_mode:
|
|
223
|
+
flow.note(f"The log has no timestamps, so x is {X_MODES[dots.mode].lower()}")
|
|
224
|
+
flow.note(f"{len(dots):,} events in {len(dots.row_values):,} rows ({rows}), "
|
|
225
|
+
f"{len(dots.colour_values):,} colours ({colour})")
|
|
226
|
+
if dots.mode == 0 and dots.origin is not None:
|
|
227
|
+
from datetime import timedelta
|
|
228
|
+
flow.note(f"From {dots.origin:%d %b %Y %H:%M} to "
|
|
229
|
+
f"{dots.origin + timedelta(seconds=dots.x_max - dots.x_min):%d %b %Y %H:%M}")
|
|
230
|
+
title = f"{log.name}: {rows.lower()} by {X_MODES[dots.mode].lower()}, colour by {colour.lower()}"
|
|
231
|
+
return Figure("Dotted chart", svg=svg(dots, title, unit),
|
|
232
|
+
caption=f"Dotted chart: rows {rows}, x {X_MODES[dots.mode]}, colour {colour}, "
|
|
233
|
+
f"sorted by {sort.lower()}.")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@box(name="Who does what", group="Measure")
|
|
237
|
+
def who_does_what(log: EventLog, by: str = "org:resource") -> Table:
|
|
238
|
+
"""How often each activity is done by each value of the event attribute
|
|
239
|
+
*by* (each resource, say): one row per activity, one column per value,
|
|
240
|
+
most frequent first. It shows the roles in a process: who only does one
|
|
241
|
+
step, who does several, and which steps nobody else does.
|
|
242
|
+
|
|
243
|
+
by: the event attribute to count by (org:resource, or any other)
|
|
244
|
+
"""
|
|
245
|
+
counts: dict[str, Counter] = {}
|
|
246
|
+
totals: Counter = Counter()
|
|
247
|
+
for case in cases(log, (by,)):
|
|
248
|
+
for activity, value in zip(case.activities, case.columns[by]):
|
|
249
|
+
name = "–" if value is None else str(value)
|
|
250
|
+
counts.setdefault(activity, Counter())[name] += 1
|
|
251
|
+
totals[name] += 1
|
|
252
|
+
if not totals or set(totals) == {"–"}:
|
|
253
|
+
raise ValueError(f"No event of {log.name} has the attribute {by!r}")
|
|
254
|
+
values = [v for v, _ in totals.most_common()]
|
|
255
|
+
activities = sorted(counts, key=lambda a: -sum(counts[a].values()))
|
|
256
|
+
rows = [[a, *[counts[a][v] for v in values], sum(counts[a].values())] for a in activities]
|
|
257
|
+
for value in values:
|
|
258
|
+
done = [a for a in activities if counts[a][value]]
|
|
259
|
+
if len(done) == 1:
|
|
260
|
+
flow.note(f"{value} only does {done[0]}")
|
|
261
|
+
return Table(f"Who does what ({by})", ["activity", *values, "events"], rows)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = ["RuleResult", "compliance_by", "dotted_chart", "order_rule", "segregation_of_duties",
|
|
265
|
+
"time_between", "who_does_what"]
|
|
@@ -101,6 +101,7 @@ from .workspace import (
|
|
|
101
101
|
exercise_files, file_kind, file_stem, file_suffix,
|
|
102
102
|
made_by_openprocess, safe_file_name, unique_path,
|
|
103
103
|
)
|
|
104
|
+
from .loading import Loading, LoadingPage
|
|
104
105
|
from .workers import run_in_background
|
|
105
106
|
|
|
106
107
|
APPLICATION_NAME = "OpenProcess Studio"
|
|
@@ -181,6 +182,17 @@ def _same_file(a: Path, b: Path) -> bool:
|
|
|
181
182
|
return False
|
|
182
183
|
|
|
183
184
|
|
|
185
|
+
#: How long a read may take before the loading page shows (a small file never flashes it).
|
|
186
|
+
LOADING_DELAY_MS = 350
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _file_size(path: str) -> int:
|
|
190
|
+
try:
|
|
191
|
+
return Path(path).stat().st_size
|
|
192
|
+
except OSError:
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
|
|
184
196
|
def _signature(path: str | Path) -> tuple[int, int] | None:
|
|
185
197
|
"""(modification time, size) of a file, or None when it is not there."""
|
|
186
198
|
try:
|
|
@@ -581,6 +593,12 @@ class StudioWindow(QMainWindow):
|
|
|
581
593
|
self.connections_page = ConnectionsPage()
|
|
582
594
|
self.connections_page.install_requested.connect(self.install_tool)
|
|
583
595
|
self.content.addWidget(self.connections_page)
|
|
596
|
+
#: What the main area shows while a file you opened is read (see _start_loading).
|
|
597
|
+
self.loading_page = LoadingPage()
|
|
598
|
+
self.loading_page.cancel_requested.connect(self._cancel_loading)
|
|
599
|
+
self.content.addWidget(self.loading_page)
|
|
600
|
+
self._loads: dict[str, Loading] = {}
|
|
601
|
+
self._before_loading = None
|
|
584
602
|
# Above the pages: "a new version is out" (see check_automatically).
|
|
585
603
|
from .updates import UpdateBar
|
|
586
604
|
self.update_bar = UpdateBar()
|
|
@@ -1572,8 +1590,7 @@ class StudioWindow(QMainWindow):
|
|
|
1572
1590
|
page = ComparePage(document)
|
|
1573
1591
|
page.tabs.changed.connect(lambda i: self.remembered.__setitem__("compare_tab", i))
|
|
1574
1592
|
elif isinstance(document, WorkflowDocument):
|
|
1575
|
-
page = WorkflowPage(document, self.library(),
|
|
1576
|
-
self.workspace.folder if self.workspace is not None else None)
|
|
1593
|
+
page = WorkflowPage(document, self.library(), self._workflow_folder(document))
|
|
1577
1594
|
page.open_document.connect(lambda doc, source=document: self.add_document(doc, near=source.path))
|
|
1578
1595
|
page.edit_requested.connect(lambda net, origin, doc=document: self.edit_petri_net(net, origin, doc))
|
|
1579
1596
|
page.keep_requested.connect(lambda doc=document: self.keep_workflow(doc))
|
|
@@ -2527,14 +2544,24 @@ class StudioWindow(QMainWindow):
|
|
|
2527
2544
|
self._rebuild_sidebar()
|
|
2528
2545
|
return
|
|
2529
2546
|
mapping = dialog.mapping()
|
|
2530
|
-
|
|
2531
|
-
self.
|
|
2532
|
-
|
|
2547
|
+
self._start_loading(path, "Reading the event log")
|
|
2548
|
+
folder = self._folder_switches
|
|
2549
|
+
|
|
2550
|
+
def csv_loaded(log, mapping=mapping) -> None:
|
|
2551
|
+
self._finish_loading(path)
|
|
2552
|
+
if folder != self._folder_switches:
|
|
2553
|
+
self._opening.discard(self._key(path))
|
|
2554
|
+
return
|
|
2555
|
+
self.add_document(LogDocument(log, path=path), options={"csv_mapping": asdict(mapping)})
|
|
2556
|
+
|
|
2557
|
+
run_in_background(lambda: read_csv(path, mapping), csv_loaded,
|
|
2558
|
+
lambda message: self._load_failed(path, message, size))
|
|
2533
2559
|
elif lower.endswith((".xes", ".xes.gz", ".gz")):
|
|
2534
|
-
self.
|
|
2560
|
+
loading = self._start_loading(path, "Reading the event log")
|
|
2535
2561
|
folder = self._folder_switches
|
|
2536
2562
|
|
|
2537
2563
|
def loaded(log) -> None:
|
|
2564
|
+
self._finish_loading(path)
|
|
2538
2565
|
if folder != self._folder_switches:
|
|
2539
2566
|
# The folder was switched or closed while this was
|
|
2540
2567
|
# loading: it belongs to the folder that was left.
|
|
@@ -2543,8 +2570,8 @@ class StudioWindow(QMainWindow):
|
|
|
2543
2570
|
self.add_document(LogDocument(log, path=path))
|
|
2544
2571
|
self.statusBar().showMessage(f"Opened {name}: {len(log):,} cases", 6000)
|
|
2545
2572
|
|
|
2546
|
-
run_in_background(lambda: read_xes(path), loaded,
|
|
2547
|
-
lambda message: self.
|
|
2573
|
+
run_in_background(lambda: read_xes(path, progress=loading.report), loaded,
|
|
2574
|
+
lambda message: self._load_failed(path, message, size))
|
|
2548
2575
|
else:
|
|
2549
2576
|
QMessageBox.information(self, "Unsupported file",
|
|
2550
2577
|
f"{name}: open .xes, .csv, .pnml or .cpn files (or a "
|
|
@@ -2554,6 +2581,61 @@ class StudioWindow(QMainWindow):
|
|
|
2554
2581
|
except Exception as error: # noqa: BLE001 - surface any import problem
|
|
2555
2582
|
self._could_not_open(path, str(error), size)
|
|
2556
2583
|
|
|
2584
|
+
# -- reading a large file: the loading page --------------------------------------------------
|
|
2585
|
+
def _start_loading(self, path: str, title: str) -> Loading:
|
|
2586
|
+
"""Start reading ``path`` on a worker. For a file the user opened (not
|
|
2587
|
+
one reopened with its folder), the main area shows the loading page
|
|
2588
|
+
if the read takes more than a moment, so a large log never leaves the
|
|
2589
|
+
window looking stuck; the window keeps working meanwhile."""
|
|
2590
|
+
key = self._key(path)
|
|
2591
|
+
loading = Loading(path, title, size=_file_size(path))
|
|
2592
|
+
self._loads[key] = loading
|
|
2593
|
+
self.statusBar().showMessage(f"Reading {Path(path).name}…")
|
|
2594
|
+
if key not in self._restore_paths:
|
|
2595
|
+
origin = self.content.currentWidget()
|
|
2596
|
+
QTimer.singleShot(LOADING_DELAY_MS, lambda: self._show_loading(key, origin))
|
|
2597
|
+
return loading
|
|
2598
|
+
|
|
2599
|
+
def _show_loading(self, key: str, origin) -> None:
|
|
2600
|
+
loading = self._loads.get(key)
|
|
2601
|
+
if loading is None or self.in_learn:
|
|
2602
|
+
return # done already, or Learn has the window
|
|
2603
|
+
if self.content.currentWidget() not in (origin, self.loading_page):
|
|
2604
|
+
return # you went to another page meanwhile: stay there
|
|
2605
|
+
if self.content.currentWidget() is not self.loading_page:
|
|
2606
|
+
self._before_loading = self.content.currentWidget()
|
|
2607
|
+
self.loading_page.show_loading(loading)
|
|
2608
|
+
self.content.setCurrentWidget(self.loading_page)
|
|
2609
|
+
|
|
2610
|
+
def _finish_loading(self, path: str) -> None:
|
|
2611
|
+
"""The read ended (done, failed or stopped): leave the loading page if it shows this file."""
|
|
2612
|
+
loading = self._loads.pop(self._key(path), None)
|
|
2613
|
+
if loading is None or self.loading_page.loading is not loading:
|
|
2614
|
+
return
|
|
2615
|
+
self.loading_page.stop()
|
|
2616
|
+
self.loading_page.loading = None
|
|
2617
|
+
waiting = next(iter(self._loads.values()), None)
|
|
2618
|
+
if waiting is not None and self.content.currentWidget() is self.loading_page:
|
|
2619
|
+
self.loading_page.show_loading(waiting) # another file you opened is still coming
|
|
2620
|
+
elif self.content.currentWidget() is self.loading_page:
|
|
2621
|
+
back = self._before_loading
|
|
2622
|
+
self.content.setCurrentWidget(back if back is not None and self.content.indexOf(back) >= 0
|
|
2623
|
+
else self.content.widget(0))
|
|
2624
|
+
|
|
2625
|
+
def _cancel_loading(self, path: str) -> None:
|
|
2626
|
+
self.statusBar().showMessage(f"Stopping {Path(path).name}…")
|
|
2627
|
+
|
|
2628
|
+
def _load_failed(self, path: str, message: str, before: tuple | None) -> None:
|
|
2629
|
+
loading = self._loads.get(self._key(path))
|
|
2630
|
+
cancelled = loading is not None and loading.cancelled
|
|
2631
|
+
self._finish_loading(path)
|
|
2632
|
+
if cancelled:
|
|
2633
|
+
self._opening.discard(self._key(path))
|
|
2634
|
+
self._rebuild_sidebar()
|
|
2635
|
+
self.statusBar().showMessage(f"Stopped reading {Path(path).name}", 6000)
|
|
2636
|
+
return
|
|
2637
|
+
self._could_not_open(path, message, before)
|
|
2638
|
+
|
|
2557
2639
|
def _could_not_open(self, path: str, message: str, before: tuple | None) -> None:
|
|
2558
2640
|
self._opening.discard(self._key(path))
|
|
2559
2641
|
self._rebuild_sidebar()
|
|
@@ -2762,6 +2844,18 @@ class StudioWindow(QMainWindow):
|
|
|
2762
2844
|
self.statusBar().showMessage(f"{document.name} against {Path(path).name}: click Check fit for the "
|
|
2763
2845
|
"scores and how they were computed", 10000)
|
|
2764
2846
|
|
|
2847
|
+
def _workflow_folder(self, document) -> str | None:
|
|
2848
|
+
"""The folder a workflow's files are relative to: the open folder, or
|
|
2849
|
+
the workflow file's own folder when it was opened from somewhere else
|
|
2850
|
+
(an example double-clicked, a file from a download), as
|
|
2851
|
+
``openprocess run`` reads it."""
|
|
2852
|
+
folder = self.workspace.folder if self.workspace is not None else None
|
|
2853
|
+
if document.path:
|
|
2854
|
+
own = Path(document.path).resolve().parent
|
|
2855
|
+
if folder is None or not own.is_relative_to(Path(folder).resolve()):
|
|
2856
|
+
return str(own)
|
|
2857
|
+
return folder
|
|
2858
|
+
|
|
2765
2859
|
def library(self):
|
|
2766
2860
|
"""The boxes available in this window (the folder's boxes/ included)."""
|
|
2767
2861
|
from ...flow.library import library_for
|