openprocess 0.11.0__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.0/openprocess.egg-info → openprocess-0.12.0}/PKG-INFO +73 -29
- {openprocess-0.11.0 → openprocess-0.12.0}/README.md +72 -28
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/__init__.py +1 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/cli.py +23 -4
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/box.py +85 -29
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/check.py +46 -15
- {openprocess-0.11.0 → 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.0 → openprocess-0.12.0}/openprocess/flow/boxes/predict.py +14 -8
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/science.py +102 -9
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/convert.py +83 -10
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/figures.py +14 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/library.py +3 -3
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/record.py +256 -18
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/report.py +2 -2
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/reproducibility.py +9 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/runner.py +75 -7
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/script.py +1 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/snapshots.py +9 -6
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/workflow.py +25 -10
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/canvas.py +10 -5
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/canvas.py +6 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/page.py +110 -38
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/picker.py +114 -89
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/summary.py +3 -3
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/templates.py +4 -3
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/viewers.py +39 -11
- {openprocess-0.11.0 → 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.0 → openprocess-0.12.0}/openprocess/gui/studio/app.py +89 -23
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/cpn_page.py +55 -25
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/derivation_view.py +3 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/dotted_chart.py +273 -80
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/graph_view.py +52 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/log_page.py +83 -28
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/model_page.py +35 -52
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/petri_page.py +13 -9
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/sidebar.py +6 -4
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/tool_icons.py +63 -20
- openprocess-0.12.0/openprocess/mining/cases.py +86 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/columns.py +78 -3
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/compare_nets.py +58 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/conformance/alignments.py +86 -7
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/conformance/quality.py +54 -43
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/conformance/token_replay.py +91 -5
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/discovery/alpha.py +2 -2
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/discovery/heuristics.py +21 -13
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/discovery/inductive.py +133 -24
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/filtering.py +32 -22
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/layout.py +43 -13
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/log.py +8 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/stats.py +50 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/xes.py +136 -56
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/references.py +5 -2
- {openprocess-0.11.0 → openprocess-0.12.0/openprocess.egg-info}/PKG-INFO +73 -29
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess.egg-info/SOURCES.txt +7 -0
- openprocess-0.12.0/tests/test_docs.py +16 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_flow.py +21 -1
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_flow_gui.py +113 -8
- 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.0 → openprocess-0.12.0}/tests/test_mining.py +71 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_studio_gui.py +94 -3
- {openprocess-0.11.0 → openprocess-0.12.0}/LICENSE +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/cpnpy/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/analysis/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/analysis/state_space.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/analysis/state_space_process.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/citation.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/exercises/pack.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/compare.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/cpn.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/discover.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/input.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/output.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/boxes/sweeps.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/datasets.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/explain.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/history.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/provenance.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/sweep.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/flow/types.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/app.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/arc_editing.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/runs.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/flow/stepper.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/items.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/learn/answer_boxes.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/learn/concealment.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/panning.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/resources/openprocess-icon.png +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/__main__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/charts.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/cite.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/command_palette.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/compare_page.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/connections.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/definition_view.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/documents.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/file_dialogs.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/filter_dialog.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/graph_builders.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/instances.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/log_editor.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/markdown_view.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/mathtext.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/ml_highlighter.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/motion.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/net_comparison.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/notes_overlay.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/provenance.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/regions_view.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/spaces.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/style.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/updates.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/widgets.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/workers.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/studio/workspace.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/theme.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/gui/tidy.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/io/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/io/cpn_reader.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/io/cpn_writer.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/answers.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/checks.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/computed.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/context.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/exam.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/exercise-packs.md +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/importer.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/notation.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/pack.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/learn/sheet.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/analysis.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/conformance/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/csv_import.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/definitions.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/dfg.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/discovery/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/discovery/state_regions.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/footprint.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/invariants.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/ocel.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/petrinet.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/playout.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/pm4py_bridge.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/pnml.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/processtree.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/regions.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/structure.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/mining/transition_system.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/ast_nodes.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/builtins.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/colorsets.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/errors.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/evaluator.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/lexer.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/multiset.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/parser.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/ml/values.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/model/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/model/declarations.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/model/examples.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/model/net.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/model/plain.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/packages.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/sim/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/sim/binding.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/sim/export.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/sim/simulator.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/teaching/__init__.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/teaching/answers.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/teaching/checks.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/teaching/pack.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess/teaching/sheet.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess.egg-info/dependency_links.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess.egg-info/entry_points.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess.egg-info/requires.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/openprocess.egg-info/top_level.txt +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/pyproject.toml +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/setup.cfg +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_arc_editing.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_citation.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_colorsets.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_columns.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_cpn_course_model.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_definitions.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_exercises_gui.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_hierarchy.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_history.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_io.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_learn.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_learn_gui.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_log_editing.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_ml.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_multiset.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_ocel.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_packages.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_regions.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_rename.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_report.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_simulation.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_snapshots.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_state_space.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_teaching.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_tidy.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_updates.py +0 -0
- {openprocess-0.11.0 → openprocess-0.12.0}/tests/test_wf_structure.py +0 -0
- {openprocess-0.11.0 → 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
|
|
@@ -281,8 +281,9 @@ which space you were in.
|
|
|
281
281
|
state; it stays in the folder.
|
|
282
282
|
- **Flat lists, the same in both spaces.** The folder's own files first,
|
|
283
283
|
then each subfolder's under a caption naming it ("Week 5 / Part 1").
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
Click a caption to fold its files away or bring them back; the app
|
|
285
|
+
remembers which are folded, per folder. An empty folder shows in both
|
|
286
|
+
spaces until it has files.
|
|
286
287
|
- **Organise from the app**: right-click for **New Folder…**, **Rename…**,
|
|
287
288
|
**Show in Finder** and **Move to Bin** (recoverable from the Bin; never a
|
|
288
289
|
hard delete). **Drag files onto a subfolder** to move them on disk. An open
|
|
@@ -373,7 +374,9 @@ Nets drawn as in the lectures and the book:
|
|
|
373
374
|
- **Places** are circles with black tokens inside, drawn as dots up to 5 and
|
|
374
375
|
as a number above that.
|
|
375
376
|
- **Transitions** are boxes with their name inside. A **silent (τ)**
|
|
376
|
-
transition is a black bar
|
|
377
|
+
transition is a black bar: the **τ** tool (toolbar or palette) adds one
|
|
378
|
+
with a click, and the Element tab's *Silent* box turns an ordinary
|
|
379
|
+
transition into τ and back.
|
|
377
380
|
- **Arc weights** above 1 are written on the arc.
|
|
378
381
|
- **Names** go inside the places and transitions, which grow to fit them.
|
|
379
382
|
Tick **Names outside** in the toolbar to put them underneath instead; you
|
|
@@ -763,9 +766,14 @@ report = check_soundness(net)
|
|
|
763
766
|
print(report.sound, report.findings)
|
|
764
767
|
```
|
|
765
768
|
|
|
766
|
-
[**docs/
|
|
767
|
-
|
|
768
|
-
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*:
|
|
774
|
+
the problem, the claim, the three ideas and the decisions that follow from
|
|
775
|
+
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
776
|
+
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
769
777
|
|
|
770
778
|
## Workflows
|
|
771
779
|
|
|
@@ -784,13 +792,12 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
784
792
|
**+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
|
|
785
793
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
786
794
|
under them, its logs.
|
|
787
|
-
- **+ Add box** (or a double-click on the canvas) opens every box,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
first match.
|
|
795
|
+
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
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
|
|
798
|
+
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
799
|
+
that reads the file, already set. Type to search them all: "alpha" finds
|
|
800
|
+
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
794
801
|
- **Files flow into the canvas.** Drop a log, an OCEL 2.0 object-centric log
|
|
795
802
|
(`.jsonocel`), a PNML file, a .cpn or a ts.txt on the canvas and it becomes
|
|
796
803
|
its input box at that spot; an input
|
|
@@ -848,8 +855,25 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
848
855
|
metadata; a CSV gets a `.provenance.json` beside it.
|
|
849
856
|
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
850
857
|
settings, groups.
|
|
851
|
-
- **The side panel** resizes by dragging the gap beside it
|
|
852
|
-
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.
|
|
853
877
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
854
878
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
855
879
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -891,7 +915,7 @@ save(wf, "orders.cpnflow", run)
|
|
|
891
915
|
```bash
|
|
892
916
|
openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
|
|
893
917
|
openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
|
|
894
|
-
openprocess boxes #
|
|
918
|
+
openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
|
|
895
919
|
openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
|
|
896
920
|
```
|
|
897
921
|
|
|
@@ -907,11 +931,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
|
|
|
907
931
|
```
|
|
908
932
|
|
|
909
933
|
`pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
|
|
910
|
-
boxes (describe, bootstrap intervals, tests, plots).
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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.
|
|
915
950
|
|
|
916
951
|
**Building the apps.** `packaging/` turns OpenProcess into a standalone app with
|
|
917
952
|
PyInstaller. In the environment:
|
|
@@ -934,11 +969,20 @@ workflow* on the Actions tab.
|
|
|
934
969
|
[CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
|
|
935
970
|
it for the people using the app: it becomes the text of the release on
|
|
936
971
|
GitHub, and the app shows it when it offers the update.
|
|
937
|
-
2.
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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:
|
|
942
986
|
|
|
943
987
|
```bash
|
|
944
988
|
git pull
|
|
@@ -949,8 +993,8 @@ workflow* on the Actions tab.
|
|
|
949
993
|
About five minutes later the
|
|
950
994
|
[Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
|
|
951
995
|
macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
|
|
952
|
-
tag that does not match `__version__`,
|
|
953
|
-
`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.
|
|
954
998
|
|
|
955
999
|
**Updates.** Each time it opens, the app checks the latest release on GitHub
|
|
956
1000
|
(*Settings* turns that off). If there is a newer version, a slim bar at the
|
|
@@ -242,8 +242,9 @@ which space you were in.
|
|
|
242
242
|
state; it stays in the folder.
|
|
243
243
|
- **Flat lists, the same in both spaces.** The folder's own files first,
|
|
244
244
|
then each subfolder's under a caption naming it ("Week 5 / Part 1").
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
Click a caption to fold its files away or bring them back; the app
|
|
246
|
+
remembers which are folded, per folder. An empty folder shows in both
|
|
247
|
+
spaces until it has files.
|
|
247
248
|
- **Organise from the app**: right-click for **New Folder…**, **Rename…**,
|
|
248
249
|
**Show in Finder** and **Move to Bin** (recoverable from the Bin; never a
|
|
249
250
|
hard delete). **Drag files onto a subfolder** to move them on disk. An open
|
|
@@ -334,7 +335,9 @@ Nets drawn as in the lectures and the book:
|
|
|
334
335
|
- **Places** are circles with black tokens inside, drawn as dots up to 5 and
|
|
335
336
|
as a number above that.
|
|
336
337
|
- **Transitions** are boxes with their name inside. A **silent (τ)**
|
|
337
|
-
transition is a black bar
|
|
338
|
+
transition is a black bar: the **τ** tool (toolbar or palette) adds one
|
|
339
|
+
with a click, and the Element tab's *Silent* box turns an ordinary
|
|
340
|
+
transition into τ and back.
|
|
338
341
|
- **Arc weights** above 1 are written on the arc.
|
|
339
342
|
- **Names** go inside the places and transitions, which grow to fit them.
|
|
340
343
|
Tick **Names outside** in the toolbar to put them underneath instead; you
|
|
@@ -724,9 +727,14 @@ report = check_soundness(net)
|
|
|
724
727
|
print(report.sound, report.findings)
|
|
725
728
|
```
|
|
726
729
|
|
|
727
|
-
[**docs/
|
|
728
|
-
|
|
729
|
-
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*:
|
|
735
|
+
the problem, the claim, the three ideas and the decisions that follow from
|
|
736
|
+
them. `tests/cross_check_inductive_miner.py` compares the built-in Inductive
|
|
737
|
+
Miner with PM4Py's on hundreds of logs and reports where they differ.
|
|
730
738
|
|
|
731
739
|
## Workflows
|
|
732
740
|
|
|
@@ -745,13 +753,12 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
745
753
|
**+ Add box**, **Run ▶**, and **⋯** for Re-run, Record, Export experiment
|
|
746
754
|
and Save. The sidebar beside it lists the folder's analyses and, folded
|
|
747
755
|
under them, its logs.
|
|
748
|
-
- **+ Add box** (or a double-click on the canvas) opens every box,
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
first match.
|
|
756
|
+
- **+ Add box** (or a double-click on the canvas) opens every box, one
|
|
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
|
|
759
|
+
scrolls. Under *Input*, **the folder's own files**: one click adds the box
|
|
760
|
+
that reads the file, already set. Type to search them all: "alpha" finds
|
|
761
|
+
the α-algorithm, "boarding" the log; Enter adds the first match.
|
|
755
762
|
- **Files flow into the canvas.** Drop a log, an OCEL 2.0 object-centric log
|
|
756
763
|
(`.jsonocel`), a PNML file, a .cpn or a ts.txt on the canvas and it becomes
|
|
757
764
|
its input box at that spot; an input
|
|
@@ -809,8 +816,25 @@ extra) and *Predict the next activity*. Or start empty.
|
|
|
809
816
|
metadata; a CSV gets a `.provenance.json` beside it.
|
|
810
817
|
- **⌘Z and ⇧⌘Z** undo and redo on the canvas: boxes, connections, moves,
|
|
811
818
|
settings, groups.
|
|
812
|
-
- **The side panel** resizes by dragging the gap beside it
|
|
813
|
-
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.
|
|
814
838
|
- **Connections** (at the bottom of the sidebar, or *View ▸ Connections*)
|
|
815
839
|
draws what flows in, OpenProcess and its boxes, what flows out, and the
|
|
816
840
|
optional tools (pandas, numpy, scipy, matplotlib, …) with whether each is
|
|
@@ -852,7 +876,7 @@ save(wf, "orders.cpnflow", run)
|
|
|
852
876
|
```bash
|
|
853
877
|
openprocess run orders.cpnflow --check # re-run; fails if any result differs from the record
|
|
854
878
|
openprocess run orders.cpnflow --sweep noise=0..0.5 step 0.1
|
|
855
|
-
openprocess boxes #
|
|
879
|
+
openprocess boxes # every box (listed in docs/boxes.md), with a folder's boxes/
|
|
856
880
|
openprocess datasets list # the BPI Challenge, Sepsis, ... logs by name
|
|
857
881
|
```
|
|
858
882
|
|
|
@@ -868,11 +892,22 @@ def last_two(log: EventLog, representation: str = "multiset") -> TransitionSyste
|
|
|
868
892
|
```
|
|
869
893
|
|
|
870
894
|
`pip install -e ".[science]"` adds the pandas, NumPy, SciPy and matplotlib
|
|
871
|
-
boxes (describe, bootstrap intervals, tests, plots).
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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.
|
|
876
911
|
|
|
877
912
|
**Building the apps.** `packaging/` turns OpenProcess into a standalone app with
|
|
878
913
|
PyInstaller. In the environment:
|
|
@@ -895,11 +930,20 @@ workflow* on the Actions tab.
|
|
|
895
930
|
[CHANGELOG.md](CHANGELOG.md) (`## 0.4.0`, then a few bullet points). Write
|
|
896
931
|
it for the people using the app: it becomes the text of the release on
|
|
897
932
|
GitHub, and the app shows it when it offers the update.
|
|
898
|
-
2.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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:
|
|
903
947
|
|
|
904
948
|
```bash
|
|
905
949
|
git pull
|
|
@@ -910,8 +954,8 @@ workflow* on the Actions tab.
|
|
|
910
954
|
About five minutes later the
|
|
911
955
|
[Releases page](https://github.com/s4mstruthers/openprocess/releases) has the
|
|
912
956
|
macOS (Apple silicon and Intel), Windows and Linux apps. The build refuses a
|
|
913
|
-
tag that does not match `__version__`,
|
|
914
|
-
`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.
|
|
915
959
|
|
|
916
960
|
**Updates.** Each time it opens, the app checks the latest release on GitHub
|
|
917
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
|
"""
|
|
@@ -249,6 +250,8 @@ def command_mine_conform(arguments: argparse.Namespace) -> int:
|
|
|
249
250
|
print(f"Generalisation {generalisation(replay):.4f}")
|
|
250
251
|
print(f"Simplicity {simplicity(net):.4f}")
|
|
251
252
|
print(f"Fitting cases {alignments.fitting_traces} / {alignments.trace_count}")
|
|
253
|
+
for note in alignments.notes:
|
|
254
|
+
print(f"Note: {note}")
|
|
252
255
|
return 0
|
|
253
256
|
|
|
254
257
|
|
|
@@ -466,7 +469,7 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
466
469
|
print(f"openprocess: {line}", file=sys.stderr)
|
|
467
470
|
if problems:
|
|
468
471
|
return 2
|
|
469
|
-
run = Runner(library, folder=folder).run(workflow)
|
|
472
|
+
run = Runner(library, folder=folder, strict=arguments.strict).run(workflow) # file settings: relative to the folder
|
|
470
473
|
for node in workflow.order():
|
|
471
474
|
result = run.result(node)
|
|
472
475
|
value = run.value(node)
|
|
@@ -488,13 +491,21 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
488
491
|
if arguments.export:
|
|
489
492
|
from .flow.record import export_experiment
|
|
490
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)}")
|
|
491
497
|
if arguments.check:
|
|
492
|
-
|
|
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)
|
|
493
504
|
if report:
|
|
494
505
|
for line in report:
|
|
495
506
|
print(f"DIFFERS: {line}")
|
|
496
507
|
return 1
|
|
497
|
-
print("Every recorded result was reproduced.")
|
|
508
|
+
print(f"Every recorded result was reproduced ({len(record.results)} boxes).")
|
|
498
509
|
elif arguments.save:
|
|
499
510
|
save(workflow, path, run, folder)
|
|
500
511
|
print(f"Recorded the results in {path.name}")
|
|
@@ -502,7 +513,11 @@ def command_run(arguments: argparse.Namespace) -> int:
|
|
|
502
513
|
|
|
503
514
|
|
|
504
515
|
def _brief(value) -> str:
|
|
516
|
+
import dataclasses
|
|
517
|
+
from .flow.figures import tile_parts
|
|
505
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))
|
|
506
521
|
if isinstance(value, Scores):
|
|
507
522
|
return ", ".join(f"{k} {v:.3f}" if isinstance(v, float) else f"{k} {v}" for k, v in value.metrics.items())
|
|
508
523
|
if isinstance(value, Table):
|
|
@@ -648,6 +663,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
648
663
|
run.add_argument("-o", "--output", metavar="FOLDER", help="write every result as a file")
|
|
649
664
|
run.add_argument("--export", metavar="FILE.zip",
|
|
650
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")
|
|
651
670
|
run.add_argument("-v", "--verbose", action="store_true", help="also print the boxes' notes")
|
|
652
671
|
run.set_defaults(handler=command_run)
|
|
653
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:]
|