openprocess 0.12.1__tar.gz → 0.13.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.12.1/openprocess.egg-info → openprocess-0.13.0}/PKG-INFO +35 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/README.md +31 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/__init__.py +1 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/cli.py +89 -19
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/__init__.py +2 -2
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/box.py +7 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/check.py +5 -0
- openprocess-0.13.0/openprocess/flow/boxes/cpn.py +97 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/discover.py +36 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/input.py +18 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/measure.py +2 -3
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/output.py +10 -0
- openprocess-0.13.0/openprocess/flow/changes.py +253 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/convert.py +5 -0
- openprocess-0.13.0/openprocess/flow/derivation.py +283 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/library.py +5 -2
- openprocess-0.13.0/openprocess/flow/studio.py +168 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/types.py +3 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/workflow.py +29 -3
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/page.py +13 -2
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/picker.py +1 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/viewers.py +121 -16
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/app.py +66 -5
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/definition_view.py +5 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/graph_builders.py +30 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/graph_view.py +18 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/log_page.py +2 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/model_page.py +2 -2
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/petri_page.py +2 -2
- openprocess-0.13.0/openprocess/gui/studio/remote.py +169 -0
- openprocess-0.13.0/openprocess/gui/studio/review.py +166 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/spaces.py +3 -3
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/widgets.py +5 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/workspace.py +2 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/answers.py +22 -3
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/checks.py +34 -6
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/computed.py +11 -9
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/exercise-packs.md +1 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/notation.py +9 -2
- openprocess-0.13.0/openprocess/mcp/__init__.py +29 -0
- openprocess-0.13.0/openprocess/mcp/__main__.py +5 -0
- openprocess-0.13.0/openprocess/mcp/api.py +1142 -0
- openprocess-0.13.0/openprocess/mcp/connecting.md +88 -0
- openprocess-0.13.0/openprocess/mcp/install.py +127 -0
- openprocess-0.13.0/openprocess/mcp/server.py +489 -0
- openprocess-0.13.0/openprocess/mining/bpmn.py +826 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/columns.py +2 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/alignments.py +12 -2
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/quality.py +13 -17
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/token_replay.py +104 -20
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/definitions.py +9 -5
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/alpha.py +60 -35
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/heuristics.py +6 -2
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/inductive.py +33 -3
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/state_regions.py +2 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/dotted.py +30 -4
- openprocess-0.13.0/openprocess/mining/drawing.py +471 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/filtering.py +5 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/log.py +6 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/ocel.py +1 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/pnml.py +14 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/regions.py +12 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/xes.py +2 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/references.py +15 -2
- openprocess-0.13.0/openprocess/sim/performance.py +408 -0
- {openprocess-0.12.1 → openprocess-0.13.0/openprocess.egg-info}/PKG-INFO +35 -1
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/SOURCES.txt +19 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/entry_points.txt +1 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/requires.txt +4 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/pyproject.toml +6 -1
- openprocess-0.13.0/tests/test_bpmn.py +404 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_flow_gui.py +9 -0
- openprocess-0.13.0/tests/test_mcp.py +285 -0
- openprocess-0.13.0/tests/test_mcp_gui.py +140 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_measure.py +2 -0
- openprocess-0.13.0/tests/test_performance.py +201 -0
- openprocess-0.13.0/tests/test_review_fixes.py +222 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_studio_gui.py +8 -0
- openprocess-0.12.1/openprocess/flow/boxes/cpn.py +0 -53
- {openprocess-0.12.1 → openprocess-0.13.0}/LICENSE +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/cpnpy/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/citation.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/filter.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/predict.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/science.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/explain.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/figures.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/history.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/record.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/report.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/reproducibility.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/runner.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/script.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/snapshots.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/app.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/canvas.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/canvas.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/summary.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/flow/templates.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/items.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/learn/mode.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/panning.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/alignment_view.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/connections.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/cpn_page.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/derivation_view.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/dotted_chart.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/loading.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/sidebar.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/style.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/tool_icons.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/theme.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/context.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/exam.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/importer.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/pack.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/cases.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/compare_nets.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/layout.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/playout.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/stats.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/structure.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/errors.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/parser.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/ml/values.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/declarations.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/examples.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/net.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/model/plain.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/packages.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/palette.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/binding.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/export.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/setup.cfg +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_arc_editing.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_citation.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_colorsets.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_columns.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_definitions.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_docs.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_flow.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_framework_contract.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_hierarchy.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_history.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_io.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_large_logs.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_learn.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_learn_gui.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_log_editing.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_mining.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_ml.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_multiset.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_ocel.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_packages.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_regions.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_rename.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_report.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_simulation.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_snapshots.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_state_space.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_teaching.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_tidy.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_updates.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.0}/tests/test_wf_structure.py +0 -0
- {openprocess-0.12.1 → openprocess-0.13.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.13.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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: PySide6>=6.6; extra == "gui"
|
|
|
26
26
|
Requires-Dist: certifi; extra == "gui"
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
29
|
+
Requires-Dist: mcp>=2.3; extra == "dev"
|
|
29
30
|
Provides-Extra: science
|
|
30
31
|
Requires-Dist: numpy>=1.24; extra == "science"
|
|
31
32
|
Requires-Dist: pandas>=2.0; extra == "science"
|
|
@@ -35,6 +36,8 @@ Provides-Extra: predict
|
|
|
35
36
|
Requires-Dist: scikit-learn>=1.3; extra == "predict"
|
|
36
37
|
Provides-Extra: pm4py
|
|
37
38
|
Requires-Dist: pm4py>=2.7; extra == "pm4py"
|
|
39
|
+
Provides-Extra: mcp
|
|
40
|
+
Requires-Dist: mcp>=2.3; extra == "mcp"
|
|
38
41
|
Dynamic: license-file
|
|
39
42
|
|
|
40
43
|
<p align="center">
|
|
@@ -91,6 +94,7 @@ tools (and, until version 0.7, went by the name CPNpy):
|
|
|
91
94
|
- [For developers](#for-developers)
|
|
92
95
|
- [Workflows](#workflows)
|
|
93
96
|
- [Workflows from Python](#workflows-from-python)
|
|
97
|
+
- [AI assistants](#ai-assistants)
|
|
94
98
|
- [Limitations and roadmap](#limitations-and-roadmap)
|
|
95
99
|
|
|
96
100
|
---
|
|
@@ -1017,6 +1021,36 @@ the clone is older than the latest release. See
|
|
|
1017
1021
|
|
|
1018
1022
|
---
|
|
1019
1023
|
|
|
1024
|
+
## AI assistants
|
|
1025
|
+
|
|
1026
|
+
OpenProcess comes with an **MCP server**, so Claude (Desktop or Code), Cursor
|
|
1027
|
+
or any other assistant that speaks the Model Context Protocol can use it the
|
|
1028
|
+
way you do:
|
|
1029
|
+
|
|
1030
|
+
```bash
|
|
1031
|
+
pip install "openprocess[app,mcp]"
|
|
1032
|
+
openprocess mcp install claude-desktop # or claude-code, cursor; "print" for the JSON
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
Then ask in your own words, naming your files: *"Summarise
|
|
1036
|
+
`Week 2/plane_boarding.xes` and show me its dotted chart"*, *"Discover a model
|
|
1037
|
+
with IMf at 0.2, check its fit and open it in OpenProcess"*, *"Why is
|
|
1038
|
+
`order_handling.pnml` unsound?"*, *"Write three exercises about token replay
|
|
1039
|
+
for Week 7"*. Its 30 tools cover logs, discovery, conformance, net analysis,
|
|
1040
|
+
diagrams, BPMN, workflows and boxes, exercise packs and coloured nets.
|
|
1041
|
+
|
|
1042
|
+
What the assistant is told, before anything else: **cite** the works every
|
|
1043
|
+
algorithm follows (each result carries them), say **how to reproduce** each
|
|
1044
|
+
number, keep analyses as **workflows** with their record (`openprocess run
|
|
1045
|
+
FILE --check`), write a new algorithm as a **`@box`** in the folder's
|
|
1046
|
+
`boxes/`, work in **your folder**, and **open** what it made in OpenProcess.
|
|
1047
|
+
Every file it writes is journaled with a backup: the status bar says *AI
|
|
1048
|
+
changed N files · Review*, with a diff, *Undo* and *Keep* for each.
|
|
1049
|
+
|
|
1050
|
+
The full guide is [openprocess/mcp/connecting.md](openprocess/mcp/connecting.md)
|
|
1051
|
+
(also *Help ▸ Connect an AI Assistant*). The same operations are plain Python
|
|
1052
|
+
in `openprocess.mcp.api`, without the MCP SDK.
|
|
1053
|
+
|
|
1020
1054
|
## Limitations and roadmap
|
|
1021
1055
|
|
|
1022
1056
|
Plainly stated:
|
|
@@ -52,6 +52,7 @@ tools (and, until version 0.7, went by the name CPNpy):
|
|
|
52
52
|
- [For developers](#for-developers)
|
|
53
53
|
- [Workflows](#workflows)
|
|
54
54
|
- [Workflows from Python](#workflows-from-python)
|
|
55
|
+
- [AI assistants](#ai-assistants)
|
|
55
56
|
- [Limitations and roadmap](#limitations-and-roadmap)
|
|
56
57
|
|
|
57
58
|
---
|
|
@@ -978,6 +979,36 @@ the clone is older than the latest release. See
|
|
|
978
979
|
|
|
979
980
|
---
|
|
980
981
|
|
|
982
|
+
## AI assistants
|
|
983
|
+
|
|
984
|
+
OpenProcess comes with an **MCP server**, so Claude (Desktop or Code), Cursor
|
|
985
|
+
or any other assistant that speaks the Model Context Protocol can use it the
|
|
986
|
+
way you do:
|
|
987
|
+
|
|
988
|
+
```bash
|
|
989
|
+
pip install "openprocess[app,mcp]"
|
|
990
|
+
openprocess mcp install claude-desktop # or claude-code, cursor; "print" for the JSON
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
Then ask in your own words, naming your files: *"Summarise
|
|
994
|
+
`Week 2/plane_boarding.xes` and show me its dotted chart"*, *"Discover a model
|
|
995
|
+
with IMf at 0.2, check its fit and open it in OpenProcess"*, *"Why is
|
|
996
|
+
`order_handling.pnml` unsound?"*, *"Write three exercises about token replay
|
|
997
|
+
for Week 7"*. Its 30 tools cover logs, discovery, conformance, net analysis,
|
|
998
|
+
diagrams, BPMN, workflows and boxes, exercise packs and coloured nets.
|
|
999
|
+
|
|
1000
|
+
What the assistant is told, before anything else: **cite** the works every
|
|
1001
|
+
algorithm follows (each result carries them), say **how to reproduce** each
|
|
1002
|
+
number, keep analyses as **workflows** with their record (`openprocess run
|
|
1003
|
+
FILE --check`), write a new algorithm as a **`@box`** in the folder's
|
|
1004
|
+
`boxes/`, work in **your folder**, and **open** what it made in OpenProcess.
|
|
1005
|
+
Every file it writes is journaled with a backup: the status bar says *AI
|
|
1006
|
+
changed N files · Review*, with a diff, *Undo* and *Keep* for each.
|
|
1007
|
+
|
|
1008
|
+
The full guide is [openprocess/mcp/connecting.md](openprocess/mcp/connecting.md)
|
|
1009
|
+
(also *Help ▸ Connect an AI Assistant*). The same operations are plain Python
|
|
1010
|
+
in `openprocess.mcp.api`, without the MCP SDK.
|
|
1011
|
+
|
|
981
1012
|
## Limitations and roadmap
|
|
982
1013
|
|
|
983
1014
|
Plainly stated:
|
|
@@ -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.13.0"
|
|
52
52
|
|
|
53
53
|
__all__ = [
|
|
54
54
|
"Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Command-line interface: ``openprocess <command> [options]``.
|
|
2
2
|
|
|
3
3
|
The CLI exists so that the engine is usable without the GUI -- in a script, in
|
|
4
|
-
a notebook, from a Makefile, or over SSH.
|
|
5
|
-
file
|
|
6
|
-
|
|
4
|
+
a notebook, from a Makefile, or over SSH. The first five commands work on a
|
|
5
|
+
``.cpn`` file (``example`` writes one of the built-in demo models so you have
|
|
6
|
+
something to try immediately); the others take event logs, PNML nets,
|
|
7
|
+
workflow files and exercise packs.
|
|
7
8
|
|
|
8
9
|
Commands
|
|
9
10
|
--------
|
|
@@ -24,6 +25,7 @@ Commands
|
|
|
24
25
|
``--export`` and ``--report`` write a zip and an HTML page
|
|
25
26
|
``boxes`` list the boxes (OpenProcess's own, a folder's ``boxes/``, packages)
|
|
26
27
|
``datasets`` the public logs known by name: ``list``, ``fetch``, ``where``
|
|
28
|
+
``mcp`` the MCP server for AI assistants; ``install CLIENT``, ``status``
|
|
27
29
|
"""
|
|
28
30
|
|
|
29
31
|
from __future__ import annotations
|
|
@@ -338,7 +340,7 @@ def command_mine_invariants(arguments: argparse.Namespace) -> int:
|
|
|
338
340
|
# ---------------------------------------------------------------------------
|
|
339
341
|
def command_exercises_check(arguments: argparse.Namespace) -> int:
|
|
340
342
|
"""List a pack's exercises and answer boxes; report mistakes in them."""
|
|
341
|
-
from .learn.checks import Context, TaskError,
|
|
343
|
+
from .learn.checks import Context, TaskError, computed_answer_text, validate
|
|
342
344
|
from .learn.pack import load_pack
|
|
343
345
|
pack = load_pack(arguments.folder)
|
|
344
346
|
if not pack.exercises:
|
|
@@ -357,11 +359,15 @@ def command_exercises_check(arguments: argparse.Namespace) -> int:
|
|
|
357
359
|
checked = "checked" if task.checkable else "self-checked"
|
|
358
360
|
line = f" - {task.id:<8} {task.type:<10} {checked}"
|
|
359
361
|
if arguments.answers and task.type not in ("net", "footprint", "trace", "open"):
|
|
362
|
+
# The answer the app works out (not the author's solution text),
|
|
363
|
+
# so the author can compare the two.
|
|
360
364
|
try:
|
|
361
|
-
answer =
|
|
365
|
+
answer = computed_answer_text(exercise, task, context)
|
|
362
366
|
except TaskError as error:
|
|
363
367
|
answer = f"(cannot work it out: {error})"
|
|
364
368
|
line += " → " + " ".join(answer.split())[:100]
|
|
369
|
+
if task.solution and task.type not in ("open",):
|
|
370
|
+
line += " (solution: " + " ".join(task.solution.split())[:60] + ")"
|
|
365
371
|
print(line)
|
|
366
372
|
for problem in found:
|
|
367
373
|
print(f" ! {problem}")
|
|
@@ -452,6 +458,10 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
452
458
|
target.write_text(requirements_lock(record), encoding="utf-8")
|
|
453
459
|
print(f"Wrote {target}")
|
|
454
460
|
return 0
|
|
461
|
+
if arguments.sweep and arguments.check:
|
|
462
|
+
print("openprocess: --check compares the recorded results, which a sweep changes; "
|
|
463
|
+
"run the two separately", file=sys.stderr)
|
|
464
|
+
return 2
|
|
455
465
|
for spec in arguments.sweep or []:
|
|
456
466
|
name, _, values = spec.partition("=")
|
|
457
467
|
node_id, _, setting = name.rpartition(".")
|
|
@@ -517,7 +527,10 @@ def _brief(value) -> str:
|
|
|
517
527
|
from .flow.figures import tile_parts
|
|
518
528
|
from .flow.types import Figure, Scores, Table
|
|
519
529
|
if dataclasses.is_dataclass(value) and not isinstance(value, type) and tile_parts(value):
|
|
520
|
-
|
|
530
|
+
big, small = tile_parts(value)
|
|
531
|
+
if " · " in big and big.count(" · ") == small.count(" · "): # "8 · 7" over "places · transitions"
|
|
532
|
+
return " · ".join(f"{a} {b}" for a, b in zip(big.split(" · "), small.split(" · ")))
|
|
533
|
+
return f"{big} ({small})" if small and small not in big else big
|
|
521
534
|
if isinstance(value, Scores):
|
|
522
535
|
return ", ".join(f"{k} {v:.3f}" if isinstance(v, float) else f"{k} {v}" for k, v in value.metrics.items())
|
|
523
536
|
if isinstance(value, Table):
|
|
@@ -533,26 +546,70 @@ def _brief(value) -> str:
|
|
|
533
546
|
|
|
534
547
|
|
|
535
548
|
def _write_outputs(workflow, run, folder: Path) -> None:
|
|
536
|
-
|
|
549
|
+
"""Every result that has a file form (tables, scores, figures, nets, logs,
|
|
550
|
+
texts), named after its box; the same files --export puts in the zip."""
|
|
551
|
+
from .flow.record import as_file
|
|
552
|
+
from .flow.types import Figure, Table
|
|
537
553
|
folder.mkdir(parents=True, exist_ok=True)
|
|
554
|
+
written = 0
|
|
538
555
|
for node in workflow.order():
|
|
539
556
|
value = run.value(node)
|
|
540
557
|
stem = f"{node.id} {workflow.title(node)}".replace("/", "-")
|
|
541
|
-
if isinstance(value, Table):
|
|
558
|
+
if isinstance(value, Table): # with their provenance sidecars
|
|
542
559
|
value.to_csv(folder / f"{stem}.csv")
|
|
543
|
-
elif isinstance(value,
|
|
544
|
-
Table.from_scores([value]).to_csv(folder / f"{stem}.csv")
|
|
545
|
-
elif isinstance(value, Figure):
|
|
560
|
+
elif isinstance(value, Figure) and (value.svg or value.png):
|
|
546
561
|
value.save(folder / f"{stem}.svg" if value.svg else folder / f"{stem}.png")
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
562
|
+
else:
|
|
563
|
+
found = as_file(value, stem)
|
|
564
|
+
if found is None:
|
|
565
|
+
continue
|
|
566
|
+
name, content = found
|
|
567
|
+
if isinstance(content, bytes):
|
|
568
|
+
(folder / name).write_bytes(content)
|
|
569
|
+
else:
|
|
570
|
+
(folder / name).write_text(content, encoding="utf-8")
|
|
571
|
+
written += 1
|
|
572
|
+
print(f"Wrote {written} result file(s) into {folder}")
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def command_mcp(arguments: argparse.Namespace) -> int:
|
|
576
|
+
"""``openprocess mcp``: the MCP server, and connecting assistants to it."""
|
|
577
|
+
if arguments.action == "install":
|
|
578
|
+
from .mcp.install import install
|
|
579
|
+
if not arguments.client:
|
|
580
|
+
print("openprocess: say which assistant: claude-desktop, claude-code, cursor, windsurf, or print",
|
|
581
|
+
file=sys.stderr)
|
|
582
|
+
return 2
|
|
583
|
+
try:
|
|
584
|
+
print(install(arguments.client))
|
|
585
|
+
except ValueError as error:
|
|
586
|
+
print(f"openprocess: {error}", file=sys.stderr)
|
|
587
|
+
return 1
|
|
588
|
+
return 0
|
|
589
|
+
if arguments.action == "status":
|
|
590
|
+
from .mcp.install import server_command, status
|
|
591
|
+
print("Server command: " + " ".join(server_command()))
|
|
592
|
+
for client, state in status().items():
|
|
593
|
+
print(f" {client:16} {'connected' if state['configured'] else 'not connected':14} {state['config'] or ''}")
|
|
594
|
+
return 0
|
|
595
|
+
try:
|
|
596
|
+
from .mcp.server import main as serve
|
|
597
|
+
except ImportError as error:
|
|
598
|
+
print(f"openprocess: {error}", file=sys.stderr)
|
|
599
|
+
return 1
|
|
600
|
+
argv = ["--http", "--host", arguments.host, "--port", str(arguments.port)] if arguments.http else []
|
|
601
|
+
return serve(argv)
|
|
551
602
|
|
|
552
603
|
|
|
553
604
|
def command_boxes(arguments: argparse.Namespace) -> int:
|
|
554
605
|
from .flow import library_for
|
|
555
|
-
|
|
606
|
+
folder = arguments.folder
|
|
607
|
+
if folder is not None and not (Path(folder) / "boxes").is_dir():
|
|
608
|
+
# The folder given is itself a folder of box files (Week 5/boxes).
|
|
609
|
+
library = library_for(None)
|
|
610
|
+
library.load_folder(folder)
|
|
611
|
+
else:
|
|
612
|
+
library = library_for(folder)
|
|
556
613
|
for group, specs in library.by_group().items():
|
|
557
614
|
print(f"{group}")
|
|
558
615
|
for spec in specs:
|
|
@@ -577,8 +634,9 @@ def command_datasets(arguments: argparse.Namespace) -> int:
|
|
|
577
634
|
return 0
|
|
578
635
|
try:
|
|
579
636
|
path = datasets.fetch(arguments.name)
|
|
580
|
-
except datasets.DatasetMissing as error:
|
|
581
|
-
|
|
637
|
+
except (datasets.DatasetMissing, KeyError) as error:
|
|
638
|
+
message = error.args[0] if isinstance(error, KeyError) and error.args else error
|
|
639
|
+
print(f"openprocess: {message}", file=sys.stderr)
|
|
582
640
|
return 1
|
|
583
641
|
print(f"{arguments.name}: {path}\nsha256 {datasets.sha256_file(path)}")
|
|
584
642
|
return 0
|
|
@@ -587,7 +645,8 @@ def command_datasets(arguments: argparse.Namespace) -> int:
|
|
|
587
645
|
def build_parser() -> argparse.ArgumentParser:
|
|
588
646
|
parser = argparse.ArgumentParser(
|
|
589
647
|
prog="openprocess",
|
|
590
|
-
description="
|
|
648
|
+
description="OpenProcess from the command line: process mining on event logs, Petri net "
|
|
649
|
+
"analysis, coloured net simulation, workflows, exercise packs and an MCP server.",
|
|
591
650
|
)
|
|
592
651
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
593
652
|
|
|
@@ -683,6 +742,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
683
742
|
dataset_commands.add_parser("where", help="print the cache folder")
|
|
684
743
|
datasets.set_defaults(handler=command_datasets)
|
|
685
744
|
|
|
745
|
+
mcp = subparsers.add_parser("mcp", help="serve OpenProcess to AI assistants (Model Context Protocol)",
|
|
746
|
+
description="Without a command: serve on stdio, as an assistant starts it. "
|
|
747
|
+
"`install CLIENT` connects Claude Desktop, Claude Code or Cursor; "
|
|
748
|
+
"`status` says which have it.")
|
|
749
|
+
mcp.add_argument("action", nargs="?", choices=["serve", "install", "status"], default="serve")
|
|
750
|
+
mcp.add_argument("client", nargs="?", help="for install: claude-desktop, claude-code, cursor, windsurf or print")
|
|
751
|
+
mcp.add_argument("--http", action="store_true", help="serve Streamable HTTP on localhost instead of stdio")
|
|
752
|
+
mcp.add_argument("--host", default="127.0.0.1")
|
|
753
|
+
mcp.add_argument("--port", type=int, default=8765)
|
|
754
|
+
mcp.set_defaults(handler=command_mcp)
|
|
755
|
+
|
|
686
756
|
mine = subparsers.add_parser("mine", help="process mining on event logs")
|
|
687
757
|
mining = mine.add_subparsers(dest="mining_command", required=True)
|
|
688
758
|
stats = mining.add_parser("stats", help="summarise an event log")
|
|
@@ -35,14 +35,14 @@ from .library import Library, library_for, standard_library
|
|
|
35
35
|
from .record import check, differences, load, save
|
|
36
36
|
from .runner import Cache, Result, Run, Runner
|
|
37
37
|
from .sweep import Sweep
|
|
38
|
-
from .types import (Column, AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
|
|
38
|
+
from .types import (BPMN, Column, AlignmentResult, Any, CPNet, DFG, Dataset, EventLog, Figure, Footprint, Marking,
|
|
39
39
|
PetriNet, Predictions, Predictor, ProcessTree, Regions, ReplayResult, Scores,
|
|
40
40
|
SimpleLog, Table, Text, TransitionSystem, register_type)
|
|
41
41
|
from .workflow import Edge, Group, Node, Workflow, WorkflowError, to_python, workflow
|
|
42
42
|
|
|
43
43
|
__all__ = [
|
|
44
44
|
"Column",
|
|
45
|
-
"AlignmentResult", "Any", "Box", "BoxError", "BoxSpec", "CPNet", "Cache", "DFG", "Dataset", "Edge",
|
|
45
|
+
"AlignmentResult", "Any", "BPMN", "Box", "BoxError", "BoxSpec", "CPNet", "Cache", "DFG", "Dataset", "Edge",
|
|
46
46
|
"EventLog", "Explanation", "Figure", "Footprint", "Group", "Library", "Marking", "Node", "PetriNet",
|
|
47
47
|
"Port", "Predictions", "Predictor", "ProcessTree", "Regions", "ReplayResult", "Result", "Run",
|
|
48
48
|
"Runner", "Scores", "Setting", "SimpleLog", "Sweep", "Table", "Text", "TransitionSystem", "Workflow",
|
|
@@ -255,6 +255,13 @@ class Box:
|
|
|
255
255
|
def __repr__(self) -> str:
|
|
256
256
|
return f"<box {self.spec.id}>"
|
|
257
257
|
|
|
258
|
+
def publish(self, name: str | None = None) -> dict:
|
|
259
|
+
"""Make this box, defined in a notebook or a script, a box of the folder
|
|
260
|
+
open in OpenProcess Studio: its source goes to ``boxes/<name>.py`` there
|
|
261
|
+
(see :func:`openprocess.flow.studio.publish_box`)."""
|
|
262
|
+
from .studio import publish_box
|
|
263
|
+
return publish_box(self, name)
|
|
264
|
+
|
|
258
265
|
|
|
259
266
|
# ---------------------------------------------------------------------------
|
|
260
267
|
# Reading the signature
|
|
@@ -48,6 +48,11 @@ def check_fit(model: PetriNet, log: EventLog,
|
|
|
48
48
|
scores = Scores(model.name, {"fitness": fitness, "precision": precision(model, simple),
|
|
49
49
|
"generalisation": generalisation(replay), "simplicity": simplicity(model)})
|
|
50
50
|
scores.note = f"Checked against all {replay.trace_count} cases of {log.name} ({method.lower()})."
|
|
51
|
+
if method == "Alignments" and alignments.incomplete:
|
|
52
|
+
cases = sum(a.count for a in alignments.incomplete)
|
|
53
|
+
scores.note += (f" No optimal alignment was found for {len(alignments.incomplete)} variants "
|
|
54
|
+
f"({cases:,} cases), counted as fitness 0, so this fitness is a lower bound; "
|
|
55
|
+
"token replay gives a figure for every case.")
|
|
51
56
|
return scores
|
|
52
57
|
|
|
53
58
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Coloured net boxes: play a CPN out many times, and compute its state space."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from ... import flow
|
|
8
|
+
from ...analysis.state_space import StateSpace
|
|
9
|
+
from ...mining.log import EventLog
|
|
10
|
+
from ...model.net import CPNet
|
|
11
|
+
from ...sim.export import case_variables, simulation_to_log
|
|
12
|
+
from ...sim.performance import analyse_performance
|
|
13
|
+
from ...sim.simulator import DeadMarkingError, Simulator
|
|
14
|
+
from ..box import box
|
|
15
|
+
from ..types import Scores, Table, Text
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@box(name="Simulate CPN", group="Coloured nets")
|
|
19
|
+
def simulate_cpn(net: CPNet, steps: int = 500, case_variable: str = "", seed: int = 0,
|
|
20
|
+
unit: Literal["seconds", "minutes", "hours", "days"] = "minutes") -> EventLog:
|
|
21
|
+
"""Runs the simulator for *steps* firings and turns the firing history
|
|
22
|
+
into an event log: one case per value of *case_variable* (the variable
|
|
23
|
+
that identifies a case in the inscriptions, e.g. ``p`` or ``order``),
|
|
24
|
+
one event per firing, the model time as the timestamp.
|
|
25
|
+
|
|
26
|
+
case_variable: the variable whose value is the case id (empty: the first one found)
|
|
27
|
+
"""
|
|
28
|
+
simulator = Simulator(net, seed=seed)
|
|
29
|
+
try:
|
|
30
|
+
fired = simulator.run(steps)
|
|
31
|
+
except DeadMarkingError:
|
|
32
|
+
fired = simulator.step_count
|
|
33
|
+
variable = case_variable or next(iter(case_variables(net)), "")
|
|
34
|
+
if not variable:
|
|
35
|
+
raise ValueError("No variable to use as the case id: set case_variable")
|
|
36
|
+
log = simulation_to_log(net, simulator.log, variable, unit)
|
|
37
|
+
flow.note(f"{fired} firings, model time {simulator.clock}, case variable {variable}: "
|
|
38
|
+
f"{len(log)} cases, {log.event_count} events")
|
|
39
|
+
return log
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@box(name="Performance", group="Coloured nets")
|
|
43
|
+
def performance(net: CPNet, runs: int = 10, steps: int = 1000, warm_up: float = 0.0, seed: int = 0,
|
|
44
|
+
distribution: Literal["throughput", "waiting time"] = "throughput") -> Table:
|
|
45
|
+
"""Runs a timed coloured net *runs* times (seeds *seed*, *seed* + 1, …),
|
|
46
|
+
each for *steps* firings or until it is dead, and reports one row per
|
|
47
|
+
transition (firings, throughput in firings per time unit, utilisation:
|
|
48
|
+
the share of the model time during which one of its firings was still in
|
|
49
|
+
progress) and one per place (the waiting time of its tokens from their
|
|
50
|
+
time stamp to their consumption, the time-averaged number of tokens, and
|
|
51
|
+
occupancy: the share of the time it was not empty), after the first
|
|
52
|
+
*warm_up* time units of every run, averaged over the runs. An untimed
|
|
53
|
+
net is measured in steps instead. The samples behind the table are one
|
|
54
|
+
number per run, for Compare samples and Plot.
|
|
55
|
+
|
|
56
|
+
runs: how many seeded runs to average over
|
|
57
|
+
steps: firings per run (a run stops earlier when the model is dead)
|
|
58
|
+
warm_up: model time left out at the start of every run (firings, for an untimed net)
|
|
59
|
+
seed: the first run's seed; run k uses seed + k
|
|
60
|
+
distribution: what the samples hold per run: the model's throughput, or its mean waiting time
|
|
61
|
+
"""
|
|
62
|
+
result = analyse_performance(net, runs=runs, steps=steps, warm_up=warm_up, seed=seed)
|
|
63
|
+
for note in result.notes:
|
|
64
|
+
flow.note(note)
|
|
65
|
+
for run in result.runs:
|
|
66
|
+
flow.note(result.summary(run))
|
|
67
|
+
flow.show(Table("Per run", ["run", "seed", "firings", "end", "stopped", "throughput", "mean waiting"],
|
|
68
|
+
[[k, run.seed, run.firings, run.end, "dead" if run.dead else "steps",
|
|
69
|
+
None if run.throughput is None else round(run.throughput, 4),
|
|
70
|
+
None if run.waiting_mean is None else round(run.waiting_mean, 4)]
|
|
71
|
+
for k, run in enumerate(result.runs)]), "Per run")
|
|
72
|
+
samples = result.samples(distribution)
|
|
73
|
+
dead = sum(run.dead for run in result.runs)
|
|
74
|
+
note = (f"{runs} runs of up to {steps} firings, seeds {seed} to {seed + runs - 1}, the first {warm_up:g} "
|
|
75
|
+
f"{'time units' if result.timed else 'firings'} of each left out"
|
|
76
|
+
f"{f'; {dead} of them ended in a dead marking' if dead else ''}. "
|
|
77
|
+
f"Throughput is firings per {result.unit}; waiting time runs from a token's time stamp to its "
|
|
78
|
+
f"consumption; utilisation is the share of the time a firing of the transition was in progress; "
|
|
79
|
+
f"occupancy the share of the time the place was not empty. "
|
|
80
|
+
f"The samples are the per-run {distribution} of the whole model.")
|
|
81
|
+
return Table(f"Performance of {net.name}", list(result.columns), result.rows(),
|
|
82
|
+
samples=samples or None, note=note)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@box(name="State space", group="Coloured nets", heavy=True)
|
|
86
|
+
def state_space(net: CPNet, max_nodes: int = 10000) -> Scores:
|
|
87
|
+
"""The reachability graph of a coloured net and its report: dead and
|
|
88
|
+
home markings, bounds, dead and live transitions (as CPN Tools reports
|
|
89
|
+
them). The full report is in the notes."""
|
|
90
|
+
space = StateSpace(net).generate(max_nodes=max_nodes)
|
|
91
|
+
report = space.report()
|
|
92
|
+
flow.show(Text(report, "State space report"), "Report")
|
|
93
|
+
dead = space.dead_markings()
|
|
94
|
+
return Scores(net.name, {"nodes": space.node_count, "arcs": space.arc_count,
|
|
95
|
+
"dead markings": len(dead), "home markings": len(space.home_markings()),
|
|
96
|
+
"dead transitions": len(space.dead_transitions()),
|
|
97
|
+
"complete": "No (stopped at the limit)" if space.unexplored_count else "Yes"})
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Literal
|
|
6
6
|
|
|
7
7
|
from ... import flow
|
|
8
|
+
from ...mining.bpmn import BPMN, bpmn_to_petri_net, petri_net_to_bpmn, tree_to_bpmn as _tree_to_bpmn
|
|
8
9
|
from ...mining.dfg import DFG, discover_dfg
|
|
9
10
|
from ...mining.discovery import alpha as alpha_module
|
|
10
11
|
from ...mining.discovery.heuristics import heuristics_net
|
|
@@ -62,6 +63,41 @@ def tree_to_net(tree: ProcessTree) -> PetriNet:
|
|
|
62
63
|
return to_petri_net(tree)
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
@box(name="Tree to BPMN", group="Discover")
|
|
67
|
+
def tree_to_bpmn(tree: ProcessTree) -> BPMN:
|
|
68
|
+
"""Translates a process tree into a BPMN model, block by block: a
|
|
69
|
+
sequence is a chain, a choice exclusive gateways, a parallel block
|
|
70
|
+
parallel gateways, a loop exclusive gateways with a flow back, and τ an
|
|
71
|
+
empty flow."""
|
|
72
|
+
model = _tree_to_bpmn(tree)
|
|
73
|
+
flow.note(model.summary())
|
|
74
|
+
return model
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@box(name="BPMN to net", group="Discover")
|
|
78
|
+
def bpmn_to_net(model: BPMN) -> PetriNet:
|
|
79
|
+
"""The Petri net semantics of a BPMN model (Dijkman, Dumas & Ouyang,
|
|
80
|
+
2008): tasks become transitions, sequence flows places, parallel
|
|
81
|
+
gateways silent transitions, exclusive gateways places, the start event
|
|
82
|
+
the source place and the end event the sink place."""
|
|
83
|
+
net = bpmn_to_petri_net(model)
|
|
84
|
+
flow.note(net.summary())
|
|
85
|
+
for warning in model.warnings:
|
|
86
|
+
flow.note(f"Warning: {warning}")
|
|
87
|
+
return net
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@box(name="Net to BPMN", group="Discover")
|
|
91
|
+
def net_to_bpmn(net: PetriNet) -> BPMN:
|
|
92
|
+
"""A BPMN model of a WF-net: transitions become tasks (with parallel
|
|
93
|
+
gateways where they split or join), places with several producers or
|
|
94
|
+
consumers exclusive gateways, and silent steps plain flows. Exact for
|
|
95
|
+
free-choice nets, the kind the Inductive Miner produces."""
|
|
96
|
+
model = petri_net_to_bpmn(net)
|
|
97
|
+
flow.note(model.summary())
|
|
98
|
+
return model
|
|
99
|
+
|
|
100
|
+
|
|
65
101
|
@box(name="Heuristics Miner", group="Discover")
|
|
66
102
|
def heuristics_miner(log: EventLog, dependency: float = 0.9) -> PetriNet:
|
|
67
103
|
"""Keeps the directly-follows relations whose dependency measure
|
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Literal
|
|
8
8
|
|
|
9
9
|
from ... import flow
|
|
10
|
+
from ...mining.bpmn import BPMN
|
|
10
11
|
from ...mining.log import KEY_NAME, Event, EventLog, Trace, parse_simple_log
|
|
11
12
|
from ...mining.petrinet import PetriNet
|
|
12
13
|
from ...mining.playout import play_out
|
|
@@ -95,6 +96,23 @@ def open_net(file: Path) -> PetriNet:
|
|
|
95
96
|
return net
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
@box(name="Open BPMN", group="Input")
|
|
100
|
+
def open_bpmn(file: Path) -> BPMN:
|
|
101
|
+
"""Opens a BPMN 2.0 model (a ``.bpmn`` file from Camunda Modeler, bpmn.io,
|
|
102
|
+
Signavio, ...): its start and end events, tasks, exclusive and parallel
|
|
103
|
+
gateways and sequence flows. Pools, lanes, message flows and data are
|
|
104
|
+
read past; what had to be approximated is noted.
|
|
105
|
+
|
|
106
|
+
file: the ``.bpmn`` file. Choose… picks it; a name alone means a file in the workflow's folder
|
|
107
|
+
"""
|
|
108
|
+
from ...mining.bpmn import read_bpmn
|
|
109
|
+
model = read_bpmn(_resolve(file))
|
|
110
|
+
flow.note(f"{Path(file).name}: {model.summary()}")
|
|
111
|
+
for warning in model.warnings:
|
|
112
|
+
flow.note(f"Warning: {warning}")
|
|
113
|
+
return model
|
|
114
|
+
|
|
115
|
+
|
|
98
116
|
@box(name="Open coloured net", group="Input")
|
|
99
117
|
def open_cpn(file: Path) -> CPNet:
|
|
100
118
|
"""Opens a coloured Petri net (a CPN Tools ``.cpn`` file).
|
|
@@ -20,7 +20,7 @@ from typing import Literal
|
|
|
20
20
|
|
|
21
21
|
from ... import flow
|
|
22
22
|
from ...mining.cases import case_value, cases
|
|
23
|
-
from ...mining.dotted import SORT_MODES, X_MODES, DotSettings, Dots, svg
|
|
23
|
+
from ...mining.dotted import SORT_MODES, X_MODES, DotSettings, Dots, how_to_read, svg
|
|
24
24
|
from ...mining.log import EventLog
|
|
25
25
|
from ...mining.petrinet import PetriNet
|
|
26
26
|
from ..box import box
|
|
@@ -229,8 +229,7 @@ def dotted_chart(log: EventLog,
|
|
|
229
229
|
f"{dots.origin + timedelta(seconds=dots.x_max - dots.x_min):%d %b %Y %H:%M}")
|
|
230
230
|
title = f"{log.name}: {rows.lower()} by {X_MODES[dots.mode].lower()}, colour by {colour.lower()}"
|
|
231
231
|
return Figure("Dotted chart", svg=svg(dots, title, unit),
|
|
232
|
-
caption=f"
|
|
233
|
-
f"sorted by {sort.lower()}.")
|
|
232
|
+
caption=f"{how_to_read(settings, dots.mode)} Colour: {colour}.")
|
|
234
233
|
|
|
235
234
|
|
|
236
235
|
@box(name="Who does what", group="Measure")
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from ... import flow
|
|
8
|
+
from ...mining.bpmn import BPMN
|
|
8
9
|
from ...mining.log import EventLog
|
|
9
10
|
from ...mining.petrinet import PetriNet
|
|
10
11
|
from ..box import box
|
|
@@ -25,6 +26,15 @@ def save_pnml(model: PetriNet, file: str = "model.pnml") -> None:
|
|
|
25
26
|
flow.note(f"Wrote {file}")
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
@box(name="Save as BPMN", group="Output")
|
|
30
|
+
def save_bpmn(model: BPMN, file: str = "model.bpmn") -> None:
|
|
31
|
+
"""Writes the model as BPMN 2.0 XML with a diagram (Camunda Modeler,
|
|
32
|
+
bpmn.io and Signavio open it)."""
|
|
33
|
+
from ...mining.bpmn import write_bpmn
|
|
34
|
+
write_bpmn(model, _target(file))
|
|
35
|
+
flow.note(f"Wrote {file}")
|
|
36
|
+
|
|
37
|
+
|
|
28
38
|
@box(name="Save log", group="Output")
|
|
29
39
|
def save_log(log: EventLog, file: str = "log.xes") -> None:
|
|
30
40
|
"""Writes the log as XES (``.xes`` or ``.xes.gz``) or CSV (``.csv``)."""
|