openprocess 0.11.3__tar.gz → 0.12.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.11.3/openprocess.egg-info → openprocess-0.12.0}/PKG-INFO +60 -21
- {openprocess-0.11.3 → openprocess-0.12.0}/README.md +59 -20
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/__init__.py +1 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/cli.py +21 -4
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/box.py +85 -29
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/check.py +40 -14
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/filter.py +56 -7
- openprocess-0.12.0/openprocess/flow/boxes/measure.py +195 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/predict.py +14 -8
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/science.py +102 -9
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/convert.py +7 -2
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/figures.py +14 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/library.py +3 -3
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/record.py +256 -18
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/report.py +2 -2
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/reproducibility.py +9 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/runner.py +75 -7
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/script.py +1 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/snapshots.py +9 -6
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/workflow.py +25 -10
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/canvas.py +4 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/page.py +110 -38
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/picker.py +1 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/summary.py +3 -3
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/templates.py +4 -3
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/viewers.py +37 -12
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/mode.py +1 -0
- openprocess-0.12.0/openprocess/gui/studio/alignment_view.py +235 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/app.py +15 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/dotted_chart.py +273 -80
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/graph_view.py +6 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/log_page.py +83 -28
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/model_page.py +14 -48
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/petri_page.py +8 -9
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/tool_icons.py +36 -0
- openprocess-0.12.0/openprocess/mining/cases.py +86 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/columns.py +78 -3
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/compare_nets.py +58 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/quality.py +54 -43
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/token_replay.py +91 -5
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/alpha.py +2 -2
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/heuristics.py +21 -13
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/filtering.py +32 -22
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/layout.py +43 -13
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/log.py +8 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/stats.py +50 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/xes.py +136 -56
- {openprocess-0.11.3 → openprocess-0.12.0/openprocess.egg-info}/PKG-INFO +60 -21
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/SOURCES.txt +7 -0
- openprocess-0.12.0/tests/test_docs.py +16 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_flow.py +1 -1
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_flow_gui.py +66 -0
- openprocess-0.12.0/tests/test_framework_contract.py +311 -0
- openprocess-0.12.0/tests/test_large_logs.py +179 -0
- openprocess-0.12.0/tests/test_measure.py +156 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/LICENSE +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/cpnpy/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/citation.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/explain.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/history.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/flow/types.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/app.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/canvas.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/items.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/panning.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/connections.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/cpn_page.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/definition_view.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/derivation_view.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/sidebar.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/style.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/theme.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/answers.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/checks.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/computed.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/context.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/exam.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/importer.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/notation.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/pack.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/conformance/alignments.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/definitions.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/inductive.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/playout.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/regions.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/structure.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/errors.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/parser.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/ml/values.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/declarations.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/examples.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/net.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/model/plain.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/packages.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/references.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/binding.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/export.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/requires.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/pyproject.toml +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/setup.cfg +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_arc_editing.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_citation.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_colorsets.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_columns.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_definitions.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_hierarchy.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_history.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_io.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_learn.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_learn_gui.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_log_editing.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_mining.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_ml.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_multiset.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_ocel.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_packages.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_regions.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_rename.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_report.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_simulation.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_snapshots.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_state_space.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_studio_gui.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_teaching.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_tidy.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_updates.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.0}/tests/test_wf_structure.py +0 -0
- {openprocess-0.11.3 → openprocess-0.12.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.12.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
|
|
@@ -766,9 +766,11 @@ report = check_soundness(net)
|
|
|
766
766
|
print(report.sound, report.findings)
|
|
767
767
|
```
|
|
768
768
|
|
|
769
|
-
[**docs/
|
|
770
|
-
|
|
771
|
-
and
|
|
769
|
+
[**docs/architecture.md**](docs/architecture.md) maps the code: its four
|
|
770
|
+
layers, which package imports which (drawn from the code, and checked), how a
|
|
771
|
+
workflow runs, and every module. [**docs/how-it-works.md**](docs/how-it-works.md)
|
|
772
|
+
covers the CPN ML subset, binding search, timed nets, the state space and
|
|
773
|
+
the file format. [**docs/design.md**](docs/design.md) is the *why*:
|
|
772
774
|
the problem, the claim, the three ideas and the decisions that follow from
|
|
773
775
|
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
774
776
|
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
@@ -791,8 +793,8 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
791
793
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
792
794
|
under them, its logs.
|
|
793
795
|
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
794
|
-
section per group (Input, Filter, Discover, Check, Compare, Output,
|
|
795
|
-
Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
796
|
+
section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
|
|
797
|
+
then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
796
798
|
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
797
799
|
that reads the file, already set. Type to search them all: "alpha" finds
|
|
798
800
|
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
@@ -853,8 +855,25 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
853
855
|
metadata; a CSV gets a `.provenance.json` beside it.
|
|
854
856
|
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
855
857
|
settings, groups.
|
|
856
|
-
- **The side panel** resizes by dragging the gap beside it
|
|
857
|
-
gap, or *View ▸ Reset Workflow Layout*,
|
|
858
|
+
- **The side panel** resizes by dragging the gap beside it, never narrower
|
|
859
|
+
than what it shows; double-click the gap, or *View ▸ Reset Workflow Layout*,
|
|
860
|
+
for its default size. Its dock button (or *View ▸ Workflow Panel Under the
|
|
861
|
+
Canvas*) moves it under the canvas, where a wide table or the code has the
|
|
862
|
+
window's width; the app remembers where you put it.
|
|
863
|
+
- **Alignments** are drawn as ProM draws them: the log on the model (per
|
|
864
|
+
transition, ✓ synchronous moves and » model moves, its colour from green,
|
|
865
|
+
always matched, to purple, often skipped; *n* » log moves at the activity),
|
|
866
|
+
and every variant's alignment as a row of chevrons: green synchronous,
|
|
867
|
+
yellow log move, purple model move, black τ. On a large log, the box's
|
|
868
|
+
*variants* setting aligns the most frequent ones and says what share of the
|
|
869
|
+
cases they cover.
|
|
870
|
+
- **Measure** boxes answer the questions an audit asks, for every case: *Time
|
|
871
|
+
between* two activities (throughput), *Order rule* (does the payment block
|
|
872
|
+
go before the goods receipt?), *Segregation of duties* (who both recorded
|
|
873
|
+
and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
|
|
874
|
+
share that fits, value), with *Case attribute* to keep one category and
|
|
875
|
+
*Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
|
|
876
|
+
uses them on the BPI Challenge 2019.
|
|
858
877
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
859
878
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
860
879
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -896,7 +915,7 @@ save(wf, "orders.cpnflow", run)
|
|
|
896
915
|
```bash
|
|
897
916
|
openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
|
|
898
917
|
openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
|
|
899
|
-
openprocess boxes #
|
|
918
|
+
openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
|
|
900
919
|
openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
|
|
901
920
|
```
|
|
902
921
|
|
|
@@ -912,11 +931,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
|
|
|
912
931
|
```
|
|
913
932
|
|
|
914
933
|
`pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
|
|
915
|
-
boxes (describe, bootstrap intervals, tests, plots).
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
934
|
+
boxes (describe, bootstrap intervals, tests, plots).
|
|
935
|
+
|
|
936
|
+
**Large logs.** A log above 20 MB is read into columns rather than objects,
|
|
937
|
+
and everything that looks at every event reads the columns: the BPI
|
|
938
|
+
Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
|
|
939
|
+
about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
|
|
940
|
+
the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
|
|
941
|
+
file to the last chart, runs in about two minutes.
|
|
942
|
+
|
|
943
|
+
[**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
|
|
944
|
+
the types, the *How* tab, sweeps, the prediction pipeline, the record and
|
|
945
|
+
running in CI. [**docs/framework.md**](docs/framework.md) is the
|
|
946
|
+
specification: what a box, a workflow and a run are, what a fingerprint
|
|
947
|
+
covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
|
|
948
|
+
maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
|
|
949
|
+
lists every box.
|
|
920
950
|
|
|
921
951
|
**Building the apps.** `packaging/` turns OpenProcess into a standalone app with
|
|
922
952
|
PyInstaller. In the environment:
|
|
@@ -939,11 +969,20 @@ workflow* on the Actions tab.
|
|
|
939
969
|
[CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
|
|
940
970
|
it for the people using the app: it becomes the text of the release on
|
|
941
971
|
GitHub, and the app shows it when it offers the update.
|
|
942
|
-
2.
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
972
|
+
2. Bring the documentation up to date with the code:
|
|
973
|
+
- run `python docs/make_docs.py`, which rewrites the code map in
|
|
974
|
+
[docs/architecture.md](docs/architecture.md) and the box catalogue in
|
|
975
|
+
[docs/boxes.md](docs/boxes.md) (the test suite, and so the release
|
|
976
|
+
build, fails while they are out of date, and when a package imports
|
|
977
|
+
across its layer);
|
|
978
|
+
- read [docs/framework.md](docs/framework.md) against what changed in
|
|
979
|
+
`openprocess/flow` (a test fails when a public name is not described
|
|
980
|
+
there, but only you can tell whether a description is still right).
|
|
981
|
+
3. Set `__version__` in `openprocess/__init__.py` and `version` and
|
|
982
|
+
`date-released` in `CITATION.cff` to the same version, and merge them into
|
|
983
|
+
`main`. `pyproject.toml`, the download names and the app's update check
|
|
984
|
+
all read the version from `__version__`.
|
|
985
|
+
4. Tag that commit with the same version and push the tag:
|
|
947
986
|
|
|
948
987
|
```bash
|
|
949
988
|
git pull
|
|
@@ -954,8 +993,8 @@ workflow* on the Actions tab.
|
|
|
954
993
|
About five minutes later the
|
|
955
994
|
[Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
|
|
956
995
|
macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
|
|
957
|
-
tag that does not match `__version__`,
|
|
958
|
-
`CHANGELOG.md
|
|
996
|
+
tag that does not match `__version__`, a version with no section in
|
|
997
|
+
`CHANGELOG.md`, and code whose tests fail, stale documentation included.
|
|
959
998
|
|
|
960
999
|
**Updates.** Each time it opens, the app checks the latest release on GitHub
|
|
961
1000
|
(*Settings* turns that off). If there is a newer version, a slim bar at the
|
|
@@ -727,9 +727,11 @@ report = check_soundness(net)
|
|
|
727
727
|
print(report.sound, report.findings)
|
|
728
728
|
```
|
|
729
729
|
|
|
730
|
-
[**docs/
|
|
731
|
-
|
|
732
|
-
and
|
|
730
|
+
[**docs/architecture.md**](docs/architecture.md) maps the code: its four
|
|
731
|
+
layers, which package imports which (drawn from the code, and checked), how a
|
|
732
|
+
workflow runs, and every module. [**docs/how-it-works.md**](docs/how-it-works.md)
|
|
733
|
+
covers the CPN ML subset, binding search, timed nets, the state space and
|
|
734
|
+
the file format. [**docs/design.md**](docs/design.md) is the *why*:
|
|
733
735
|
the problem, the claim, the three ideas and the decisions that follow from
|
|
734
736
|
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
735
737
|
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
@@ -752,8 +754,8 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
752
754
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
753
755
|
under them, its logs.
|
|
754
756
|
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
755
|
-
section per group (Input, Filter, Discover, Check, Compare, Output,
|
|
756
|
-
Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
757
|
+
section per group (Input, Filter, Discover, Check, Measure, Compare, Output,
|
|
758
|
+
then Science, Predict, Coloured nets, Sweep and your own boxes), in a list that
|
|
757
759
|
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
758
760
|
that reads the file, already set. Type to search them all: "alpha" finds
|
|
759
761
|
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
@@ -814,8 +816,25 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
814
816
|
metadata; a CSV gets a `.provenance.json` beside it.
|
|
815
817
|
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
816
818
|
settings, groups.
|
|
817
|
-
- **The side panel** resizes by dragging the gap beside it
|
|
818
|
-
gap, or *View ▸ Reset Workflow Layout*,
|
|
819
|
+
- **The side panel** resizes by dragging the gap beside it, never narrower
|
|
820
|
+
than what it shows; double-click the gap, or *View ▸ Reset Workflow Layout*,
|
|
821
|
+
for its default size. Its dock button (or *View ▸ Workflow Panel Under the
|
|
822
|
+
Canvas*) moves it under the canvas, where a wide table or the code has the
|
|
823
|
+
window's width; the app remembers where you put it.
|
|
824
|
+
- **Alignments** are drawn as ProM draws them: the log on the model (per
|
|
825
|
+
transition, ✓ synchronous moves and » model moves, its colour from green,
|
|
826
|
+
always matched, to purple, often skipped; *n* » log moves at the activity),
|
|
827
|
+
and every variant's alignment as a row of chevrons: green synchronous,
|
|
828
|
+
yellow log move, purple model move, black τ. On a large log, the box's
|
|
829
|
+
*variants* setting aligns the most frequent ones and says what share of the
|
|
830
|
+
cases they cover.
|
|
831
|
+
- **Measure** boxes answer the questions an audit asks, for every case: *Time
|
|
832
|
+
between* two activities (throughput), *Order rule* (does the payment block
|
|
833
|
+
go before the goods receipt?), *Segregation of duties* (who both recorded
|
|
834
|
+
and cleared an invoice?) and *Compliance by attribute* (per vendor: cases,
|
|
835
|
+
share that fits, value), with *Case attribute* to keep one category and
|
|
836
|
+
*Bubble chart* to draw it. [examples/bpi2019](examples/bpi2019/README.md)
|
|
837
|
+
uses them on the BPI Challenge 2019.
|
|
819
838
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
820
839
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
821
840
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -857,7 +876,7 @@ save(wf, "orders.cpnflow", run)
|
|
|
857
876
|
```bash
|
|
858
877
|
openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
|
|
859
878
|
openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
|
|
860
|
-
openprocess boxes #
|
|
879
|
+
openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
|
|
861
880
|
openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
|
|
862
881
|
```
|
|
863
882
|
|
|
@@ -873,11 +892,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
|
|
|
873
892
|
```
|
|
874
893
|
|
|
875
894
|
`pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
|
|
876
|
-
boxes (describe, bootstrap intervals, tests, plots).
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
895
|
+
boxes (describe, bootstrap intervals, tests, plots).
|
|
896
|
+
|
|
897
|
+
**Large logs.** A log above 20 MB is read into columns rather than objects,
|
|
898
|
+
and everything that looks at every event reads the columns: the BPI
|
|
899
|
+
Challenge 2019 log (251,734 cases, 1.6 million events, 728 MB) opens in
|
|
900
|
+
about 12 seconds and 250 MB, every tab of its log page in a few seconds, and
|
|
901
|
+
the whole [BPI 2019 analysis](examples/bpi2019/README.md), from reading the
|
|
902
|
+
file to the last chart, runs in about two minutes.
|
|
903
|
+
|
|
904
|
+
[**docs/workflows.md**](docs/workflows.md) is the tutorial: writing a box,
|
|
905
|
+
the types, the *How* tab, sweeps, the prediction pipeline, the record and
|
|
906
|
+
running in CI. [**docs/framework.md**](docs/framework.md) is the
|
|
907
|
+
specification: what a box, a workflow and a run are, what a fingerprint
|
|
908
|
+
covers and what a reproduced result promises. [**docs/architecture.md**](docs/architecture.md)
|
|
909
|
+
maps the code, drawn from its imports, and [**docs/boxes.md**](docs/boxes.md)
|
|
910
|
+
lists every box.
|
|
881
911
|
|
|
882
912
|
**Building the apps.** `packaging/` turns OpenProcess into a standalone app with
|
|
883
913
|
PyInstaller. In the environment:
|
|
@@ -900,11 +930,20 @@ workflow* on the Actions tab.
|
|
|
900
930
|
[CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
|
|
901
931
|
it for the people using the app: it becomes the text of the release on
|
|
902
932
|
GitHub, and the app shows it when it offers the update.
|
|
903
|
-
2.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
933
|
+
2. Bring the documentation up to date with the code:
|
|
934
|
+
- run `python docs/make_docs.py`, which rewrites the code map in
|
|
935
|
+
[docs/architecture.md](docs/architecture.md) and the box catalogue in
|
|
936
|
+
[docs/boxes.md](docs/boxes.md) (the test suite, and so the release
|
|
937
|
+
build, fails while they are out of date, and when a package imports
|
|
938
|
+
across its layer);
|
|
939
|
+
- read [docs/framework.md](docs/framework.md) against what changed in
|
|
940
|
+
`openprocess/flow` (a test fails when a public name is not described
|
|
941
|
+
there, but only you can tell whether a description is still right).
|
|
942
|
+
3. Set `__version__` in `openprocess/__init__.py` and `version` and
|
|
943
|
+
`date-released` in `CITATION.cff` to the same version, and merge them into
|
|
944
|
+
`main`. `pyproject.toml`, the download names and the app's update check
|
|
945
|
+
all read the version from `__version__`.
|
|
946
|
+
4. Tag that commit with the same version and push the tag:
|
|
908
947
|
|
|
909
948
|
```bash
|
|
910
949
|
git pull
|
|
@@ -915,8 +954,8 @@ workflow* on the Actions tab.
|
|
|
915
954
|
About five minutes later the
|
|
916
955
|
[Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
|
|
917
956
|
macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
|
|
918
|
-
tag that does not match `__version__`,
|
|
919
|
-
`CHANGELOG.md
|
|
957
|
+
tag that does not match `__version__`, a version with no section in
|
|
958
|
+
`CHANGELOG.md`, and code whose tests fail, stale documentation included.
|
|
920
959
|
|
|
921
960
|
**Updates.** Each time it opens, the app checks the latest release on GitHub
|
|
922
961
|
(*Settings* turns that off). If there is a newer version, a slim bar at the
|
|
@@ -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.12.0"
|
|
52
52
|
|
|
53
53
|
__all__ = [
|
|
54
54
|
"Arc", "BindingElement", "CPNet", "Marking", "Multiset", "Page", "Place",
|
|
@@ -20,7 +20,8 @@ Commands
|
|
|
20
20
|
``exercises`` ``check``, ``marks``, ``import``, ``computes``: exercise packs (OpenProcess Learn)
|
|
21
21
|
``run`` run a ``.cpnflow`` workflow headless; ``--check`` re-runs and
|
|
22
22
|
compares with the record, ``--sweep`` varies a setting,
|
|
23
|
-
``--lock`` writes the requirements the record names
|
|
23
|
+
``--lock`` writes the requirements the record names,
|
|
24
|
+
``--export`` and ``--report`` write a zip and an HTML page
|
|
24
25
|
``boxes`` list the boxes (OpenProcess's own, a folder's ``boxes/``, packages)
|
|
25
26
|
``datasets`` the public logs known by name: ``list``, ``fetch``, ``where``
|
|
26
27
|
"""
|
|
@@ -468,7 +469,7 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
468
469
|
print(f"openprocess: {line}", file=sys.stderr)
|
|
469
470
|
if problems:
|
|
470
471
|
return 2
|
|
471
|
-
run = Runner(library, folder=folder).run(workflow)
|
|
472
|
+
run = Runner(library, folder=folder, strict=arguments.strict).run(workflow) # file settings: relative to the folder
|
|
472
473
|
for node in workflow.order():
|
|
473
474
|
result = run.result(node)
|
|
474
475
|
value = run.value(node)
|
|
@@ -490,13 +491,21 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
490
491
|
if arguments.export:
|
|
491
492
|
from .flow.record import export_experiment
|
|
492
493
|
print(f"Wrote {export_experiment(workflow, run, arguments.export, folder, library)}")
|
|
494
|
+
if arguments.report:
|
|
495
|
+
from .flow.report import write_report
|
|
496
|
+
print(f"Wrote {write_report(arguments.report, workflow=workflow, run=run, folder=folder)}")
|
|
493
497
|
if arguments.check:
|
|
494
|
-
|
|
498
|
+
if not record.results:
|
|
499
|
+
# Nothing to compare is not a pass: a CI job would claim a reproduction that never happened.
|
|
500
|
+
print(f"openprocess: {path.name} has no recorded results to check against; "
|
|
501
|
+
f"record them first with: openprocess run {path.name} --save", file=sys.stderr)
|
|
502
|
+
return 1
|
|
503
|
+
report = check(workflow, record, run, complete=True)
|
|
495
504
|
if report:
|
|
496
505
|
for line in report:
|
|
497
506
|
print(f"DIFFERS: {line}")
|
|
498
507
|
return 1
|
|
499
|
-
print("Every recorded result was reproduced.")
|
|
508
|
+
print(f"Every recorded result was reproduced ({len(record.results)} boxes).")
|
|
500
509
|
elif arguments.save:
|
|
501
510
|
save(workflow, path, run, folder)
|
|
502
511
|
print(f"Recorded the results in {path.name}")
|
|
@@ -504,7 +513,11 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
504
513
|
|
|
505
514
|
|
|
506
515
|
def _brief(value) -> str:
|
|
516
|
+
import dataclasses
|
|
517
|
+
from .flow.figures import tile_parts
|
|
507
518
|
from .flow.types import Figure, Scores, Table
|
|
519
|
+
if dataclasses.is_dataclass(value) and not isinstance(value, type) and tile_parts(value):
|
|
520
|
+
return " · ".join(tile_parts(value))
|
|
508
521
|
if isinstance(value, Scores):
|
|
509
522
|
return ", ".join(f"{k} {v:.3f}" if isinstance(v, float) else f"{k} {v}" for k, v in value.metrics.items())
|
|
510
523
|
if isinstance(value, Table):
|
|
@@ -650,6 +663,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
650
663
|
run.add_argument("-o", "--output", metavar="FOLDER", help="write every result as a file")
|
|
651
664
|
run.add_argument("--export", metavar="FILE.zip",
|
|
652
665
|
help="zip the workflow, its inputs, boxes and results, with a README")
|
|
666
|
+
run.add_argument("--strict", action="store_true",
|
|
667
|
+
help="also fail a box that changes its inputs in place (slower; for tests and CI)")
|
|
668
|
+
run.add_argument("--report", metavar="FILE.html",
|
|
669
|
+
help="write the analysis as one HTML page: results, how, code, inputs, citations")
|
|
653
670
|
run.add_argument("-v", "--verbose", action="store_true", help="also print the boxes' notes")
|
|
654
671
|
run.set_defaults(handler=command_run)
|
|
655
672
|
|
|
@@ -11,18 +11,18 @@ Writing a box is writing a function with type hints, nothing more::
|
|
|
11
11
|
|
|
12
12
|
What the decorator reads, and nothing else:
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
parameters whose type is
|
|
16
|
-
``list[Type]``
|
|
17
|
-
``Type | None`` (or a default of ``None``)
|
|
18
|
-
the return type
|
|
14
|
+
==================================================== ========================================================
|
|
15
|
+
parameters whose type is an OpenProcess type input connection points, named after the parameter
|
|
16
|
+
``list[Type]`` an input that takes any number of connections
|
|
17
|
+
``Type | None`` (or a default of ``None``) an optional input
|
|
18
|
+
the return type the output; a dataclass of typed fields gives several
|
|
19
19
|
``int``, ``float``, ``bool``, ``str`` with a default a setting, with the default as its value
|
|
20
|
-
``Literal["a", "b"]``
|
|
21
|
-
``Column``
|
|
22
|
-
``Path``
|
|
23
|
-
the docstring
|
|
24
|
-
``inspect.getsource``
|
|
25
|
-
|
|
20
|
+
``Literal["a", "b"]`` a setting that is a choice
|
|
21
|
+
``Column`` a setting that is a column of a connected table
|
|
22
|
+
``Path`` a setting that is a file in the folder
|
|
23
|
+
the docstring the help text; a ``name: text`` line is a setting's help
|
|
24
|
+
``inspect.getsource`` the Code tab
|
|
25
|
+
==================================================== ========================================================
|
|
26
26
|
|
|
27
27
|
A box has no base class and no registration call: the decorator returns a
|
|
28
28
|
:class:`Box`, which is still the function (call it from a script or a
|
|
@@ -39,6 +39,7 @@ import hashlib
|
|
|
39
39
|
import inspect
|
|
40
40
|
import os
|
|
41
41
|
import importlib.util
|
|
42
|
+
import re
|
|
42
43
|
import types
|
|
43
44
|
import typing
|
|
44
45
|
from dataclasses import dataclass, field
|
|
@@ -81,10 +82,11 @@ class Port:
|
|
|
81
82
|
return "Anything" if self.type is None else getattr(self.type, "__name__", str(self.type))
|
|
82
83
|
|
|
83
84
|
def accepts(self, other: "Port") -> bool:
|
|
84
|
-
"""Can an output ``other`` be connected to this input?
|
|
85
|
+
"""Can an output ``other`` be connected to this input? The same type
|
|
86
|
+
or a subclass of it; a ``typing.Any`` point only to another one."""
|
|
85
87
|
if self.type is None or other.type is None:
|
|
86
|
-
return self.type is other.type
|
|
87
|
-
return issubclass(other.type, self.type)
|
|
88
|
+
return self.type is other.type
|
|
89
|
+
return issubclass(other.type, self.type)
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
def clean_path(value: Any) -> str:
|
|
@@ -113,8 +115,22 @@ class Setting:
|
|
|
113
115
|
help: str = ""
|
|
114
116
|
|
|
115
117
|
def coerce(self, value: Any) -> Any:
|
|
116
|
-
"""A value as the GUI or a file gives it, in the setting's type.
|
|
118
|
+
"""A value as the GUI or a file gives it, in the setting's type.
|
|
119
|
+
Raises ``ValueError`` for a value that is not one, rather than
|
|
120
|
+
rounding it: ``2.5`` is not an int setting's value, ``"2"`` and
|
|
121
|
+
``2.0`` are."""
|
|
122
|
+
if value is None and self.default is None and self.kind != "bool":
|
|
123
|
+
return None # "no value" stays no value (str | None = None)
|
|
117
124
|
if self.kind == "int":
|
|
125
|
+
if isinstance(value, (bool, int)):
|
|
126
|
+
return int(value)
|
|
127
|
+
if isinstance(value, str):
|
|
128
|
+
try:
|
|
129
|
+
return int(value.strip())
|
|
130
|
+
except ValueError:
|
|
131
|
+
value = float(value)
|
|
132
|
+
if isinstance(value, float) and not value.is_integer():
|
|
133
|
+
raise ValueError(f"{self.name!r} must be a whole number, not {value!r}")
|
|
118
134
|
return int(value)
|
|
119
135
|
if self.kind == "float":
|
|
120
136
|
return float(value)
|
|
@@ -168,6 +184,20 @@ class BoxSpec:
|
|
|
168
184
|
def defaults(self) -> dict[str, Any]:
|
|
169
185
|
return {s.name: s.default for s in self.settings}
|
|
170
186
|
|
|
187
|
+
@property
|
|
188
|
+
def description(self) -> str:
|
|
189
|
+
"""The docstring's prose: :attr:`help` without the ``name: text``
|
|
190
|
+
lines of its settings (they are shown next to each setting) and
|
|
191
|
+
without an ``Args:`` heading left with nothing under it."""
|
|
192
|
+
names = {s.name for s in self.settings} | {p.name for p in self.inputs}
|
|
193
|
+
lines = (self.help or "").splitlines()
|
|
194
|
+
dropped = {number for key, _text, first, last in _hint_blocks(self.help) if key in names
|
|
195
|
+
for number in range(first, last + 1)}
|
|
196
|
+
text = "\n".join(line for number, line in enumerate(lines) if number not in dropped)
|
|
197
|
+
for heading in ("Args:", "Arguments:", "Parameters:"):
|
|
198
|
+
text = re.sub(rf"(?m)^\s*{heading}\s*$\n?(?=\s*(\n|$))", "", text)
|
|
199
|
+
return text.strip()
|
|
200
|
+
|
|
171
201
|
@property
|
|
172
202
|
def fingerprint(self) -> str:
|
|
173
203
|
"""Changes when the code changes: part of every cache key."""
|
|
@@ -280,7 +310,7 @@ def _setting_from(name: str, annotation, default, has_default: bool, doc_hints:
|
|
|
280
310
|
kind = "str"
|
|
281
311
|
else:
|
|
282
312
|
raise BoxError(
|
|
283
|
-
f"parameter {name!r} has type {_type_text(annotation)}, which is neither
|
|
313
|
+
f"parameter {name!r} has type {_type_text(annotation)}, which is neither an OpenProcess "
|
|
284
314
|
f"type (an input) nor int, float, bool, str, Literal[...], Column or Path (a setting)")
|
|
285
315
|
if not has_default:
|
|
286
316
|
raise BoxError(f"setting {name!r} needs a default value (e.g. {name}: {kind} = ...)")
|
|
@@ -298,39 +328,65 @@ def _outputs_from(annotation) -> tuple[list[Port], type | None]:
|
|
|
298
328
|
raise BoxError("the function needs a return type hint (-> PetriNet), or -> None")
|
|
299
329
|
if annotation is None or annotation is type(None):
|
|
300
330
|
return [], None
|
|
301
|
-
annotation,
|
|
331
|
+
annotation, optional = _unwrap_optional(annotation)
|
|
302
332
|
if annotation is Any:
|
|
303
333
|
return [Port("out", None)], None
|
|
304
334
|
if isinstance(annotation, type) and is_known(annotation):
|
|
305
|
-
return [Port("out", annotation)], None
|
|
335
|
+
return [Port("out", annotation, optional=optional)], None
|
|
306
336
|
if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
|
|
307
337
|
ports = []
|
|
308
338
|
hints = get_type_hints(annotation)
|
|
309
339
|
for f in dataclasses.fields(annotation):
|
|
310
|
-
inner,
|
|
340
|
+
inner, optional = _unwrap_optional(hints.get(f.name, f.type))
|
|
311
341
|
if not (isinstance(inner, type) and is_known(inner)) and inner is not Any:
|
|
312
342
|
raise BoxError(f"result field {f.name!r} has type {_type_text(inner)}, "
|
|
313
|
-
f"which is not
|
|
314
|
-
ports.append(Port(f.name, None if inner is Any else inner,
|
|
343
|
+
f"which is not an OpenProcess type")
|
|
344
|
+
ports.append(Port(f.name, None if inner is Any else inner, optional=optional,
|
|
345
|
+
label=f.name.replace("_", " ")))
|
|
315
346
|
if not ports:
|
|
316
347
|
raise BoxError(f"result class {annotation.__name__} has no fields")
|
|
317
348
|
return ports, annotation
|
|
318
|
-
raise BoxError(f"the return type {_type_text(annotation)} is not
|
|
349
|
+
raise BoxError(f"the return type {_type_text(annotation)} is not an OpenProcess type "
|
|
319
350
|
f"(nor a dataclass of them)")
|
|
320
351
|
|
|
321
352
|
|
|
322
353
|
def _doc_hints(doc: str) -> dict[str, str]:
|
|
323
|
-
"""``name: text`` lines of a docstring, as help for settings.
|
|
354
|
+
"""``name: text`` lines of a docstring, as help for settings. The
|
|
355
|
+
Google style ``name (float): text`` is read the same way, so a function
|
|
356
|
+
documented for Sphinx needs no second docstring to be a box."""
|
|
324
357
|
hints: dict[str, str] = {}
|
|
325
|
-
for
|
|
326
|
-
|
|
327
|
-
if ":" in stripped and not stripped.startswith(":"):
|
|
328
|
-
key, _, text = stripped.partition(":")
|
|
329
|
-
if key.isidentifier() and text.strip():
|
|
330
|
-
hints[key] = text.strip()
|
|
358
|
+
for key, text, _first, _last in _hint_blocks(doc):
|
|
359
|
+
hints.setdefault(key, text)
|
|
331
360
|
return hints
|
|
332
361
|
|
|
333
362
|
|
|
363
|
+
def _hint_blocks(doc: str):
|
|
364
|
+
"""``(name, help, first line, last line)`` for each ``name: text`` block of a
|
|
365
|
+
docstring: the line and the lines under it until a blank line or the next
|
|
366
|
+
``name:``, so a help that goes on over several lines stays one help."""
|
|
367
|
+
lines = (doc or "").splitlines()
|
|
368
|
+
index = 0
|
|
369
|
+
while index < len(lines):
|
|
370
|
+
stripped = lines[index].strip()
|
|
371
|
+
key, _, text = stripped.partition(":")
|
|
372
|
+
key = key.split("(", 1)[0].strip() # "noise (float)" -> "noise"
|
|
373
|
+
if ":" in stripped and not stripped.startswith(":") and key.isidentifier() and text.strip():
|
|
374
|
+
parts, end = [text.strip()], index + 1
|
|
375
|
+
while end < len(lines) and lines[end].strip() and not _starts_hint(lines[end]):
|
|
376
|
+
parts.append(lines[end].strip())
|
|
377
|
+
end += 1
|
|
378
|
+
yield key, " ".join(parts), index, end - 1
|
|
379
|
+
index = end
|
|
380
|
+
else:
|
|
381
|
+
index += 1
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _starts_hint(line: str) -> bool:
|
|
385
|
+
stripped = line.strip()
|
|
386
|
+
key, _, text = stripped.partition(":")
|
|
387
|
+
return ":" in stripped and key.split("(", 1)[0].strip().isidentifier() and bool(text.strip())
|
|
388
|
+
|
|
389
|
+
|
|
334
390
|
def _plain_name(function_name: str) -> str:
|
|
335
391
|
words = function_name.replace("_", " ").strip()
|
|
336
392
|
return words[:1].upper() + words[1:]
|
|
@@ -6,7 +6,7 @@ from typing import Literal
|
|
|
6
6
|
|
|
7
7
|
from ... import flow
|
|
8
8
|
from ...mining.analysis import check_soundness
|
|
9
|
-
from ...mining.compare_nets import compare_nets
|
|
9
|
+
from ...mining.compare_nets import compare_nets, trace_overlap
|
|
10
10
|
from ...mining.conformance.alignments import AlignmentResult, align_log
|
|
11
11
|
from ...mining.conformance.quality import generalisation, precision, simplicity
|
|
12
12
|
from ...mining.conformance.token_replay import ReplayResult, token_replay as _replay
|
|
@@ -61,10 +61,23 @@ def token_replay(model: PetriNet, log: EventLog) -> ReplayResult:
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
@box(name="Alignments", group="Check")
|
|
64
|
-
def alignments(model: PetriNet, log: EventLog) -> AlignmentResult:
|
|
64
|
+
def alignments(model: PetriNet, log: EventLog, variants: int = 0) -> AlignmentResult:
|
|
65
65
|
"""Optimal alignments of every variant with the model (A* over the
|
|
66
|
-
synchronous product, standard cost function).
|
|
67
|
-
|
|
66
|
+
synchronous product, standard cost function). Shown as ProM shows them:
|
|
67
|
+
the log on the model, and every variant's moves.
|
|
68
|
+
|
|
69
|
+
variants: align only this many of the most frequent variants (0: all); on a large log
|
|
70
|
+
the most frequent few hundred usually cover most cases, and the notes say how many
|
|
71
|
+
"""
|
|
72
|
+
simple = log.simple_log()
|
|
73
|
+
if variants > 0 and len(simple) > variants:
|
|
74
|
+
from collections import Counter
|
|
75
|
+
kept = Counter(dict(sorted(simple.items(), key=lambda item: (-item[1], item[0]))[:variants]))
|
|
76
|
+
covered, total = sum(kept.values()), sum(simple.values())
|
|
77
|
+
flow.note(f"Aligned the {variants:,} most frequent of {len(simple):,} variants: "
|
|
78
|
+
f"{covered:,} of {total:,} cases ({100 * covered / total:.1f} %); fitness is over those cases")
|
|
79
|
+
simple = kept
|
|
80
|
+
result = align_log(model, simple)
|
|
68
81
|
rows = [["⟨" + ", ".join(a.trace) + "⟩", a.count, a.cost if a.complete else "?", round(a.fitness, 4)]
|
|
69
82
|
for a in result.alignments]
|
|
70
83
|
flow.show(Table("Alignments", ["variant", "cases", "cost", "fitness"], rows), "Alignment per variant")
|
|
@@ -94,23 +107,36 @@ def _yes(value) -> str:
|
|
|
94
107
|
|
|
95
108
|
@box(name="Compare to true net", group="Check")
|
|
96
109
|
def compare_to_true_net(model: PetriNet, true_net: PetriNet, max_length: int = 12) -> Scores:
|
|
97
|
-
"""Model to model
|
|
98
|
-
|
|
99
|
-
allows (precision)
|
|
110
|
+
"""Model to model, on behaviour: of the true net's complete traces of up
|
|
111
|
+
to *max_length* activities, the share the model allows (recall); of the
|
|
112
|
+
model's, the share the true net allows (precision). Traces are counted
|
|
113
|
+
exactly, each distinct trace once; silent steps leave no trace. Also the
|
|
114
|
+
model's simplicity and soundness.
|
|
115
|
+
|
|
116
|
+
max_length: the longest trace counted (loops make the number of traces grow with it)
|
|
117
|
+
"""
|
|
100
118
|
comparison = compare_nets(model, true_net, max_length=max_length)
|
|
101
|
-
only_model, only_truth = len(comparison.only_first), len(comparison.only_second)
|
|
102
119
|
for trace in comparison.only_first[:5]:
|
|
103
120
|
flow.note("The model allows ⟨" + ", ".join(trace) + "⟩, the true net does not")
|
|
104
121
|
for trace in comparison.only_second[:5]:
|
|
105
122
|
flow.note("The true net allows ⟨" + ", ".join(trace) + "⟩, the model does not")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
overlap = trace_overlap(model, true_net, max_length=max_length)
|
|
124
|
+
flow.note(f"Complete traces of up to {max_length} activities: the true net has {overlap.second:,}, "
|
|
125
|
+
f"the model {overlap.first:,}, both {overlap.both:,}")
|
|
126
|
+
if not overlap.complete:
|
|
127
|
+
flow.note("The nets have too many states to count every trace: the counts are a lower bound")
|
|
128
|
+
for net, count in (("true net", overlap.second), ("model", overlap.first)):
|
|
129
|
+
if not count:
|
|
130
|
+
flow.note(f"The {net} has no complete trace of up to {max_length} activities, so "
|
|
131
|
+
f"{'recall' if net == 'true net' else 'precision'} is not defined (n/a)")
|
|
109
132
|
flow.note(comparison.summary("the model", "the true net"))
|
|
110
|
-
|
|
133
|
+
sound = check_soundness(model).sound
|
|
134
|
+
return Scores(model.name, {"recall": overlap.recall if overlap.second else "n/a",
|
|
135
|
+
"precision": overlap.precision if overlap.first else "n/a",
|
|
136
|
+
"simplicity": simplicity(model),
|
|
111
137
|
"soundness": {True: "Sound", False: "Not sound", None: "Undecided"}[sound]},
|
|
112
|
-
note="Recall and precision
|
|
113
|
-
"
|
|
138
|
+
note=f"Recall and precision over the distinct complete traces of up to {max_length} "
|
|
139
|
+
"activities.")
|
|
114
140
|
|
|
115
141
|
|
|
116
142
|
@box(name="Footprint conformance", group="Check")
|