ripple-down-rules 0.5.64__tar.gz → 0.5.71__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.
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/PKG-INFO +2 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/requirements.txt +2 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/__init__.py +1 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/datastructures/dataclasses.py +61 -2
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/helpers.py +8 -4
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/rdr.py +88 -44
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/rdr_decorators.py +7 -6
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/rules.py +55 -10
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/utils.py +62 -4
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules.egg-info/PKG-INFO +2 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules.egg-info/SOURCES.txt +11 -2
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules.egg-info/requires.txt +1 -0
- ripple_down_rules-0.5.71/test/conf/world/base_config.py +48 -0
- ripple_down_rules-0.5.71/test/conf/world/handles_and_containers.py +50 -0
- ripple_down_rules-0.5.71/test/conftest.py +112 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/datasets.py +82 -0
- ripple_down_rules-0.5.71/test/factories/world/__init__.py +0 -0
- ripple_down_rules-0.5.71/test/factories/world/handles_and_containers.py +22 -0
- ripple_down_rules-0.5.71/test/test_generated_rdrs/__init__.py +1 -0
- ripple_down_rules-0.5.71/test/test_helpers/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_helpers/helpers.py +21 -11
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_on_mutagenic.py +12 -8
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_rdr.py +47 -33
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_rdr_helpers_rdrs.py +2 -8
- ripple_down_rules-0.5.71/test/test_rdr_world/__init__.py +0 -0
- ripple_down_rules-0.5.71/test/test_rdr_world/conftest.py +147 -0
- ripple_down_rules-0.5.71/test/test_rdr_world/test_rdr_world.py +52 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +1 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +2 -2
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata/datasets_physical_object_is_a_robot.json +4 -4
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr.py +1 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_rdr.py +2 -2
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/rdr_metadata/datasets_physical_object_select_objects_that_are_parts_of_robot.json +4 -4
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_template_file_creator.py +2 -2
- ripple_down_rules-0.5.71/test/test_user_interface/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_user_interface/test_prompt.py +1 -1
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_user_interface/test_qt_gui_inline.py +3 -0
- ripple_down_rules-0.5.64/test/conftest.py +0 -23
- ripple_down_rules-0.5.64/test/test_rdr_world.py +0 -191
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/.github/workflows/build_and_deploy_doc.yml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/.github/workflows/ci.yml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/.github/workflows/publish-to-test-pypi.yml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/LICENSE +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/README.md +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/_config.yml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/_toc.yml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/bibliography.md +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/intro.md +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/references.bib +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/doc/requirements.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/animal_species.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/part_containment_rdr/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/part_containment_rdr/robot_rdr.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/examples/relational_example.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/images/scrdr.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/images/scrdr.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/images/thinking_pr2.jpg +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/pyproject.toml +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/pytest.ini +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/requirements-dev-ci.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/requirements-dev.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/requirements-gui.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/requirements-viz.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_stop_only.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_stop_only.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_mcrdr_stop_plus_rule.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr_2.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr_2.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr_3.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/complete_scrdr_3.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/grdr_Habitat.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/grdr_Habitat.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/grdr_Species.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/grdr_Species.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_extra_classify.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_extra_classify.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/mcrdr_stop_plus_rule_combined.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/partial_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/partial_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/relational_scrdr_classify.dot +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/results/relational_scrdr_classify.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/setup.cfg +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/datastructures/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/datastructures/case.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/datastructures/enums.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/experts.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/start-code-server.sh +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/gui.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/prompt.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/user_interface/template_file_creator.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
- {ripple_down_rules-0.5.64/test/test_generated_rdrs → ripple_down_rules-0.5.71/test/conf}/__init__.py +0 -0
- {ripple_down_rules-0.5.64/test/test_helpers → ripple_down_rules-0.5.71/test/conf/world}/__init__.py +0 -0
- {ripple_down_rules-0.5.64/test/test_user_interface → ripple_down_rules-0.5.71/test/factories}/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
- ripple_down_rules-0.5.64/test/test_expert_answers/view_rdr_expert_answers_fit.json → ripple_down_rules-0.5.71/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_helpers/object_diagram_case_query.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_helpers/object_diagram_person.png +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_json_serialization.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_object_diagram.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_rdr_decorators.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_relational_rdr.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_relational_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_is_a_robot/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr_defs.py +2 -2
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_sql_model.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_user_interface/test_ipython.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_user_interface/test_ipython_copilot.py +0 -0
- {ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.71
|
4
4
|
Summary: Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning.
|
5
5
|
Author-email: Abdelrhman Bassiouny <abassiou@uni-bremen.de>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -694,6 +694,7 @@ Requires-Dist: pygments
|
|
694
694
|
Requires-Dist: sqlalchemy
|
695
695
|
Requires-Dist: pandas
|
696
696
|
Requires-Dist: pyparsing>=3.2.3
|
697
|
+
Requires-Dist: omegaconf
|
697
698
|
Provides-Extra: viz
|
698
699
|
Requires-Dist: networkx>=3.1; extra == "viz"
|
699
700
|
Requires-Dist: matplotlib>=3.7.5; extra == "viz"
|
@@ -5,9 +5,11 @@ import typing
|
|
5
5
|
from dataclasses import dataclass, field
|
6
6
|
|
7
7
|
import typing_extensions
|
8
|
+
from omegaconf import MISSING
|
8
9
|
from sqlalchemy.orm import DeclarativeBase as SQLTable
|
9
|
-
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List, get_origin, Set
|
10
|
+
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List, get_origin, Set, Callable
|
10
11
|
|
12
|
+
from ..utils import get_method_name, get_function_import_path_and_representation
|
11
13
|
from .callable_expression import CallableExpression
|
12
14
|
from .case import create_case, Case
|
13
15
|
from ..utils import copy_case, make_list, make_set, get_origin_and_args_from_type_hint, get_value_type_from_type_hint, \
|
@@ -37,6 +39,24 @@ class CaseQuery:
|
|
37
39
|
"""
|
38
40
|
Whether the attribute can only take one value (i.e. True) or multiple values (i.e. False).
|
39
41
|
"""
|
42
|
+
case_factory: Optional[Callable[[], Any]] = None
|
43
|
+
"""
|
44
|
+
The factory method that can be used to recreate the original case.
|
45
|
+
"""
|
46
|
+
case_factory_idx: Optional[int] = None
|
47
|
+
"""
|
48
|
+
This is used when the case factory is a list of cases, this index is used to select the case from the list.
|
49
|
+
"""
|
50
|
+
case_conf: Optional[CaseConf] = None
|
51
|
+
"""
|
52
|
+
The case configuration that is used to (re)create the original case, recommended to be used when you want to
|
53
|
+
the case to persist in the rule base, this would allow it to be used for merging with other similar conclusion RDRs.
|
54
|
+
"""
|
55
|
+
scenario: Optional[Callable] = None
|
56
|
+
"""
|
57
|
+
The executable scenario is the root callable that recreates the situation that the case is
|
58
|
+
created in, for example, when the case is created from a test function, this would be the test function itself.
|
59
|
+
"""
|
40
60
|
_target: Optional[CallableExpression] = None
|
41
61
|
"""
|
42
62
|
The target expression of the attribute.
|
@@ -225,4 +245,43 @@ class CaseQuery:
|
|
225
245
|
self.mutually_exclusive, _target=self.target, default_value=self.default_value,
|
226
246
|
scope=self.scope, _case=copy_case(self.case), _target_value=self.target_value,
|
227
247
|
conditions=self.conditions, is_function=self.is_function,
|
228
|
-
function_args_type_hints=self.function_args_type_hints
|
248
|
+
function_args_type_hints=self.function_args_type_hints,
|
249
|
+
case_factory=self.case_factory, case_factory_idx=self.case_factory_idx,
|
250
|
+
case_conf=self.case_conf, scenario=self.scenario)
|
251
|
+
|
252
|
+
|
253
|
+
@dataclass
|
254
|
+
class CaseConf:
|
255
|
+
factory_method: Callable[[Any], Any] = MISSING
|
256
|
+
|
257
|
+
def create(self) -> Any:
|
258
|
+
return self.factory_method()
|
259
|
+
|
260
|
+
|
261
|
+
@dataclass
|
262
|
+
class CaseFactoryMetaData:
|
263
|
+
factory_method: Optional[Callable[[Optional[CaseConf]], Any]] = None
|
264
|
+
factory_idx: Optional[int] = None
|
265
|
+
case_conf: Optional[CaseConf] = None
|
266
|
+
scenario: Optional[Callable] = None
|
267
|
+
|
268
|
+
@classmethod
|
269
|
+
def from_case_query(cls, case_query: CaseQuery) -> CaseFactoryMetaData:
|
270
|
+
return cls(factory_method=case_query.case_factory, factory_idx=case_query.case_factory_idx,
|
271
|
+
case_conf=case_query.case_conf, scenario=case_query.scenario)
|
272
|
+
|
273
|
+
def __repr__(self):
|
274
|
+
factory_method_repr = None
|
275
|
+
scenario_repr = None
|
276
|
+
if self.factory_method is not None:
|
277
|
+
_, factory_method_repr = get_function_import_path_and_representation(self.factory_method)
|
278
|
+
if self.scenario is not None:
|
279
|
+
_, scenario_repr = get_function_import_path_and_representation(self.scenario)
|
280
|
+
return (f"CaseFactoryMetaData("
|
281
|
+
f"factory_method={factory_method_repr}, "
|
282
|
+
f"factory_idx={self.factory_idx}, "
|
283
|
+
f"case_conf={self.case_conf},"
|
284
|
+
f" scenario={scenario_repr})")
|
285
|
+
|
286
|
+
def __str__(self):
|
287
|
+
return self.__repr__()
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
3
3
|
import os
|
4
4
|
from types import ModuleType
|
5
5
|
|
6
|
+
from ripple_down_rules.datastructures.dataclasses import CaseFactoryMetaData
|
7
|
+
|
6
8
|
from .datastructures.case import create_case
|
7
9
|
from .datastructures.dataclasses import CaseQuery
|
8
10
|
from typing_extensions import Type, Optional, Callable, Any, Dict, TYPE_CHECKING, Union
|
@@ -15,7 +17,8 @@ if TYPE_CHECKING:
|
|
15
17
|
|
16
18
|
|
17
19
|
def general_rdr_classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDownRules]],
|
18
|
-
case: Any, modify_original_case: bool = False
|
20
|
+
case: Any, modify_original_case: bool = False,
|
21
|
+
case_query: Optional[CaseQuery] = None) -> Dict[str, Any]:
|
19
22
|
"""
|
20
23
|
Classify a case by going through all classifiers and adding the categories that are classified,
|
21
24
|
and then restarting the classification until no more categories can be added.
|
@@ -23,6 +26,7 @@ def general_rdr_classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDow
|
|
23
26
|
:param classifiers_dict: A dictionary mapping conclusion types to the classifiers that produce them.
|
24
27
|
:param case: The case to classify.
|
25
28
|
:param modify_original_case: Whether to modify the original case or create a copy and modify it.
|
29
|
+
:param case_query: The case query to extract metadata from if needed.
|
26
30
|
:return: The categories that the case belongs to.
|
27
31
|
"""
|
28
32
|
conclusions = {}
|
@@ -31,7 +35,7 @@ def general_rdr_classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDow
|
|
31
35
|
while True:
|
32
36
|
new_conclusions = {}
|
33
37
|
for attribute_name, rdr in classifiers_dict.items():
|
34
|
-
pred_atts = rdr.classify(case_cp)
|
38
|
+
pred_atts = rdr.classify(case_cp, case_query=case_query)
|
35
39
|
if pred_atts is None:
|
36
40
|
continue
|
37
41
|
if rdr.mutually_exclusive:
|
@@ -49,8 +53,8 @@ def general_rdr_classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDow
|
|
49
53
|
conclusions[attribute_name] = set()
|
50
54
|
conclusions[attribute_name].update(pred_atts)
|
51
55
|
if attribute_name in new_conclusions:
|
52
|
-
|
53
|
-
update_case(
|
56
|
+
temp_case_query = CaseQuery(case_cp, attribute_name, rdr.conclusion_type, rdr.mutually_exclusive)
|
57
|
+
update_case(temp_case_query, new_conclusions)
|
54
58
|
if len(new_conclusions) == 0:
|
55
59
|
break
|
56
60
|
return conclusions
|
@@ -4,6 +4,8 @@ import copyreg
|
|
4
4
|
import importlib
|
5
5
|
import os
|
6
6
|
|
7
|
+
from ripple_down_rules.datastructures.dataclasses import CaseFactoryMetaData
|
8
|
+
|
7
9
|
from . import logger
|
8
10
|
import sys
|
9
11
|
from abc import ABC, abstractmethod
|
@@ -37,7 +39,7 @@ except ImportError as e:
|
|
37
39
|
from .utils import draw_tree, make_set, copy_case, \
|
38
40
|
SubclassJSONSerializer, make_list, get_type_from_string, \
|
39
41
|
is_conflicting, get_imports_from_scope, extract_function_source, extract_imports, get_full_class_name, \
|
40
|
-
is_iterable, str_to_snake_case
|
42
|
+
is_iterable, str_to_snake_case, get_import_path_from_path
|
41
43
|
|
42
44
|
|
43
45
|
class RippleDownRules(SubclassJSONSerializer, ABC):
|
@@ -112,7 +114,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
112
114
|
if not os.path.exists(save_dir + '/__init__.py'):
|
113
115
|
os.makedirs(save_dir, exist_ok=True)
|
114
116
|
with open(save_dir + '/__init__.py', 'w') as f:
|
115
|
-
f.write("
|
117
|
+
f.write("from . import *\n")
|
116
118
|
if model_name is not None:
|
117
119
|
self.model_name = model_name
|
118
120
|
elif self.model_name is None:
|
@@ -136,7 +138,11 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
136
138
|
model_dir = os.path.join(load_dir, model_name)
|
137
139
|
json_file = os.path.join(model_dir, cls.metadata_folder, model_name)
|
138
140
|
rdr = cls.from_json_file(json_file)
|
139
|
-
|
141
|
+
try:
|
142
|
+
rdr.update_from_python(model_dir)
|
143
|
+
except (FileNotFoundError, ValueError) as e:
|
144
|
+
logger.warning(f"Could not load the python file for the model {model_name} from {model_dir}. "
|
145
|
+
f"Make sure the file exists and is valid.")
|
140
146
|
rdr.save_dir = load_dir
|
141
147
|
rdr.model_name = model_name
|
142
148
|
return rdr
|
@@ -215,13 +221,15 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
215
221
|
return self.classify(case)
|
216
222
|
|
217
223
|
@abstractmethod
|
218
|
-
def classify(self, case: Union[Case, SQLTable], modify_case: bool = False
|
224
|
+
def classify(self, case: Union[Case, SQLTable], modify_case: bool = False,
|
225
|
+
case_query: Optional[CaseQuery] = None) \
|
219
226
|
-> Optional[Union[CallableExpression, Dict[str, CallableExpression]]]:
|
220
227
|
"""
|
221
228
|
Classify a case.
|
222
229
|
|
223
230
|
:param case: The case to classify.
|
224
231
|
:param modify_case: Whether to modify the original case attributes with the conclusion or not.
|
232
|
+
:param case_query: The case query containing the case to classify and the target category to compare the case with.
|
225
233
|
:return: The category that the case belongs to.
|
226
234
|
"""
|
227
235
|
pass
|
@@ -229,6 +237,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
229
237
|
def fit_case(self, case_query: CaseQuery,
|
230
238
|
expert: Optional[Expert] = None,
|
231
239
|
update_existing_rules: bool = True,
|
240
|
+
scenario: Optional[Callable] = None,
|
232
241
|
**kwargs) \
|
233
242
|
-> Union[CallableExpression, Dict[str, CallableExpression]]:
|
234
243
|
"""
|
@@ -239,6 +248,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
239
248
|
:param expert: The expert to ask for differentiating features as new rule conditions.
|
240
249
|
:param update_existing_rules: Whether to update the existing same conclusion type rules that already gave
|
241
250
|
some conclusions with the type required by the case query.
|
251
|
+
:param scenario: The scenario at which the case was created, this is used to recreate the case if needed.
|
242
252
|
:return: The category that the case belongs to.
|
243
253
|
"""
|
244
254
|
if case_query is None:
|
@@ -247,14 +257,14 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
247
257
|
self.name = case_query.attribute_name if self.name is None else self.name
|
248
258
|
self.case_type = case_query.case_type if self.case_type is None else self.case_type
|
249
259
|
self.case_name = case_query.case_name if self.case_name is None else self.case_name
|
260
|
+
case_query.scenario = scenario if case_query.scenario is None else case_query.scenario
|
250
261
|
|
251
262
|
expert = expert or Human(viewer=self.viewer,
|
252
263
|
answers_save_path=self.save_dir + '/expert_answers'
|
253
264
|
if self.save_dir else None)
|
254
|
-
|
255
265
|
if case_query.target is None:
|
256
266
|
case_query_cp = copy(case_query)
|
257
|
-
conclusions = self.classify(case_query_cp.case, modify_case=True)
|
267
|
+
conclusions = self.classify(case_query_cp.case, modify_case=True, case_query=case_query_cp)
|
258
268
|
if self.should_i_ask_the_expert_for_a_target(conclusions, case_query_cp, update_existing_rules):
|
259
269
|
expert.ask_for_conclusion(case_query_cp)
|
260
270
|
case_query.target = case_query_cp.target
|
@@ -389,7 +399,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
389
399
|
:return: The module that contains the rdr classifier function.
|
390
400
|
"""
|
391
401
|
# remove from imports if exists first
|
392
|
-
|
402
|
+
package_name = get_import_path_from_path(package_name)
|
403
|
+
name = f"{package_name}.{self.generated_python_file_name}" if package_name else self.generated_python_file_name
|
393
404
|
try:
|
394
405
|
module = importlib.import_module(name)
|
395
406
|
del sys.modules[name]
|
@@ -411,6 +422,10 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
411
422
|
conclusion_func_names = [f'conclusion_{rid}' for rid in rules_dict.keys() if not isinstance(rules_dict[rid], MultiClassStopRule)]
|
412
423
|
all_func_names = condition_func_names + conclusion_func_names
|
413
424
|
filepath = f"{model_dir}/{self.generated_python_defs_file_name}.py"
|
425
|
+
cases_path = f"{model_dir}/{self.generated_python_cases_file_name}.py"
|
426
|
+
cases_import_path = get_import_path_from_path(model_dir)
|
427
|
+
cases_import_path = f"{cases_import_path}.{self.generated_python_cases_file_name}" if cases_import_path\
|
428
|
+
else self.generated_python_cases_file_name
|
414
429
|
functions_source = extract_function_source(filepath, all_func_names, include_signature=False)
|
415
430
|
# get the scope from the imports in the file
|
416
431
|
scope = extract_imports(filepath)
|
@@ -418,13 +433,15 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
418
433
|
if rule.conditions is not None:
|
419
434
|
rule.conditions.user_input = functions_source[f"conditions_{rule.uid}"]
|
420
435
|
rule.conditions.scope = scope
|
436
|
+
if os.path.exists(cases_path):
|
437
|
+
rule.corner_case_metadata = importlib.import_module(cases_import_path).__dict__.get(f"corner_case_{rule.uid}", None)
|
421
438
|
if rule.conclusion is not None and not isinstance(rule, MultiClassStopRule):
|
422
439
|
rule.conclusion.user_input = functions_source[f"conclusion_{rule.uid}"]
|
423
440
|
rule.conclusion.scope = scope
|
424
441
|
|
425
442
|
@abstractmethod
|
426
443
|
def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = "",
|
427
|
-
defs_file: Optional[str] = None):
|
444
|
+
defs_file: Optional[str] = None, cases_file: Optional[str] = None):
|
428
445
|
"""
|
429
446
|
Write the rules as source code to a file.
|
430
447
|
|
@@ -432,6 +449,7 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
432
449
|
:param file: The file to write the source code to.
|
433
450
|
:param parent_indent: The indentation of the parent rule.
|
434
451
|
:param defs_file: The file to write the definitions to.
|
452
|
+
:param cases_file: The file to write the cases to.
|
435
453
|
"""
|
436
454
|
pass
|
437
455
|
|
@@ -444,14 +462,17 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
444
462
|
os.makedirs(model_dir, exist_ok=True)
|
445
463
|
if not os.path.exists(model_dir + '/__init__.py'):
|
446
464
|
with open(model_dir + '/__init__.py', 'w') as f:
|
447
|
-
f.write("
|
448
|
-
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.conclusion_type_hint}:\n"
|
465
|
+
f.write("from . import *\n")
|
466
|
+
func_def = f"def classify(case: {self.case_type.__name__}, **kwargs) -> {self.conclusion_type_hint}:\n"
|
449
467
|
file_name = model_dir + f"/{self.generated_python_file_name}.py"
|
450
468
|
defs_file_name = model_dir + f"/{self.generated_python_defs_file_name}.py"
|
469
|
+
cases_file_name = model_dir + f"/{self.generated_python_cases_file_name}.py"
|
451
470
|
imports, defs_imports = self._get_imports()
|
452
471
|
# clear the files first
|
453
472
|
with open(defs_file_name, "w") as f:
|
454
473
|
f.write(defs_imports + "\n\n")
|
474
|
+
with open(cases_file_name, "w") as cases_f:
|
475
|
+
cases_f.write("# This file contains the corner cases for the rules.\n")
|
455
476
|
with open(file_name, "w") as f:
|
456
477
|
imports += f"from .{self.generated_python_defs_file_name} import *\n"
|
457
478
|
f.write(imports + "\n\n")
|
@@ -461,7 +482,8 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
461
482
|
f.write(f"\n\n{func_def}")
|
462
483
|
f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
|
463
484
|
f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
|
464
|
-
self.write_rules_as_source_code_to_file(self.start_rule, f, " " * 4, defs_file=defs_file_name
|
485
|
+
self.write_rules_as_source_code_to_file(self.start_rule, f, " " * 4, defs_file=defs_file_name,
|
486
|
+
cases_file=cases_file_name)
|
465
487
|
|
466
488
|
@property
|
467
489
|
@abstractmethod
|
@@ -510,6 +532,10 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
510
532
|
def generated_python_defs_file_name(self) -> str:
|
511
533
|
return f"{self.generated_python_file_name}_defs"
|
512
534
|
|
535
|
+
@property
|
536
|
+
def generated_python_cases_file_name(self) -> str:
|
537
|
+
return f"{self.generated_python_file_name}_cases"
|
538
|
+
|
513
539
|
|
514
540
|
@property
|
515
541
|
def conclusion_type(self) -> Tuple[Type]:
|
@@ -592,7 +618,7 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
592
618
|
pred = self.evaluate(case_query.case)
|
593
619
|
if pred.conclusion(case_query.case) != case_query.target_value:
|
594
620
|
expert.ask_for_conditions(case_query, pred)
|
595
|
-
pred.fit_rule(case_query
|
621
|
+
pred.fit_rule(case_query)
|
596
622
|
|
597
623
|
return self.classify(case_query.case)
|
598
624
|
|
@@ -605,18 +631,24 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
605
631
|
"""
|
606
632
|
if not self.start_rule:
|
607
633
|
expert.ask_for_conditions(case_query)
|
608
|
-
self.start_rule = SingleClassRule(case_query
|
609
|
-
conclusion_name=case_query.attribute_name)
|
634
|
+
self.start_rule = SingleClassRule.from_case_query(case_query)
|
610
635
|
|
611
|
-
def classify(self, case: Case, modify_case: bool = False
|
636
|
+
def classify(self, case: Case, modify_case: bool = False,
|
637
|
+
case_query: Optional[CaseQuery] = None) -> Optional[Any]:
|
612
638
|
"""
|
613
639
|
Classify a case by recursively evaluating the rules until a rule fires or the last rule is reached.
|
614
640
|
|
615
641
|
:param case: The case to classify.
|
616
642
|
:param modify_case: Whether to modify the original case attributes with the conclusion or not.
|
643
|
+
:param case_query: The case query containing the case and the target category to compare the case with.
|
617
644
|
"""
|
618
645
|
pred = self.evaluate(case)
|
619
|
-
|
646
|
+
conclusion = pred.conclusion(case) if pred is not None else None
|
647
|
+
if pred is not None and pred.fired and case_query is not None:
|
648
|
+
if pred.corner_case_metadata is None and conclusion is not None\
|
649
|
+
and type(conclusion) in case_query.core_attribute_type:
|
650
|
+
pred.corner_case_metadata = CaseFactoryMetaData.from_case_query(case_query)
|
651
|
+
return conclusion if pred is not None and pred.fired else self.default_conclusion
|
620
652
|
|
621
653
|
def evaluate(self, case: Case) -> SingleClassRule:
|
622
654
|
"""
|
@@ -632,22 +664,24 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
632
664
|
f.write(f"{' ' * 4}else:\n{' ' * 8}return {self.default_conclusion}\n")
|
633
665
|
|
634
666
|
def write_rules_as_source_code_to_file(self, rule: SingleClassRule, file: TextIOWrapper, parent_indent: str = "",
|
635
|
-
defs_file: Optional[str] = None):
|
667
|
+
defs_file: Optional[str] = None, cases_file: Optional[str] = None):
|
636
668
|
"""
|
637
669
|
Write the rules as source code to a file.
|
638
670
|
"""
|
639
671
|
if rule.conditions:
|
672
|
+
rule.write_corner_case_as_source_code(cases_file)
|
640
673
|
if_clause = rule.write_condition_as_source_code(parent_indent, defs_file)
|
641
674
|
file.write(if_clause)
|
642
675
|
if rule.refinement:
|
643
676
|
self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ",
|
644
|
-
defs_file=defs_file)
|
677
|
+
defs_file=defs_file, cases_file=cases_file)
|
645
678
|
|
646
679
|
conclusion_call = rule.write_conclusion_as_source_code(parent_indent, defs_file)
|
647
680
|
file.write(conclusion_call)
|
648
681
|
|
649
682
|
if rule.alternative:
|
650
|
-
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file
|
683
|
+
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file,
|
684
|
+
cases_file=cases_file)
|
651
685
|
|
652
686
|
@property
|
653
687
|
def conclusion_type_hint(self) -> str:
|
@@ -699,13 +733,19 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
699
733
|
super(MultiClassRDR, self).__init__(start_rule, **kwargs)
|
700
734
|
self.mode: MCRDRMode = mode
|
701
735
|
|
702
|
-
def classify(self, case: Union[Case, SQLTable], modify_case: bool = False
|
736
|
+
def classify(self, case: Union[Case, SQLTable], modify_case: bool = False,
|
737
|
+
case_query: Optional[CaseQuery] = None) -> Set[Any]:
|
703
738
|
evaluated_rule = self.start_rule
|
704
739
|
self.conclusions = []
|
705
740
|
while evaluated_rule:
|
706
741
|
next_rule = evaluated_rule(case)
|
707
742
|
if evaluated_rule.fired:
|
708
|
-
|
743
|
+
rule_conclusion = evaluated_rule.conclusion(case)
|
744
|
+
if evaluated_rule.corner_case_metadata is None and case_query is not None:
|
745
|
+
if rule_conclusion is not None and len(make_list(rule_conclusion)) > 0\
|
746
|
+
and any(ct in case_query.core_attribute_type for ct in map(type, make_list(rule_conclusion))):
|
747
|
+
evaluated_rule.corner_case_metadata = CaseFactoryMetaData.from_case_query(case_query)
|
748
|
+
self.add_conclusion(rule_conclusion)
|
709
749
|
evaluated_rule = next_rule
|
710
750
|
return make_set(self.conclusions)
|
711
751
|
|
@@ -733,7 +773,7 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
733
773
|
self.stop_wrong_conclusion_else_add_it(case_query, expert, evaluated_rule)
|
734
774
|
else:
|
735
775
|
# Rule fired and target is correct or there is no target to compare
|
736
|
-
self.add_conclusion(
|
776
|
+
self.add_conclusion(rule_conclusion)
|
737
777
|
|
738
778
|
if not next_rule:
|
739
779
|
if not make_set(target_value).issubset(make_set(self.conclusions)):
|
@@ -745,16 +785,18 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
745
785
|
return self.conclusions
|
746
786
|
|
747
787
|
def write_rules_as_source_code_to_file(self, rule: Union[MultiClassTopRule, MultiClassStopRule],
|
748
|
-
file, parent_indent: str = "", defs_file: Optional[str] = None
|
788
|
+
file, parent_indent: str = "", defs_file: Optional[str] = None,
|
789
|
+
cases_file: Optional[str] = None):
|
749
790
|
if rule == self.start_rule:
|
750
791
|
file.write(f"{parent_indent}conclusions = set()\n")
|
751
792
|
if rule.conditions:
|
793
|
+
rule.write_corner_case_as_source_code(cases_file)
|
752
794
|
if_clause = rule.write_condition_as_source_code(parent_indent, defs_file)
|
753
795
|
file.write(if_clause)
|
754
796
|
conclusion_indent = parent_indent
|
755
797
|
if hasattr(rule, "refinement") and rule.refinement:
|
756
798
|
self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ",
|
757
|
-
defs_file=defs_file)
|
799
|
+
defs_file=defs_file, cases_file=cases_file)
|
758
800
|
conclusion_indent = parent_indent + " " * 4
|
759
801
|
file.write(f"{conclusion_indent}else:\n")
|
760
802
|
|
@@ -762,7 +804,8 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
762
804
|
file.write(conclusion_call)
|
763
805
|
|
764
806
|
if rule.alternative:
|
765
|
-
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file
|
807
|
+
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file,
|
808
|
+
cases_file=cases_file)
|
766
809
|
|
767
810
|
@property
|
768
811
|
def conclusion_type_hint(self) -> str:
|
@@ -788,8 +831,7 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
788
831
|
"""
|
789
832
|
if not self.start_rule:
|
790
833
|
conditions = expert.ask_for_conditions(case_query)
|
791
|
-
self.start_rule = MultiClassTopRule(
|
792
|
-
conclusion_name=case_query.attribute_name)
|
834
|
+
self.start_rule = MultiClassTopRule.from_case_query(case_query)
|
793
835
|
|
794
836
|
@property
|
795
837
|
def last_top_rule(self) -> Optional[MultiClassTopRule]:
|
@@ -810,7 +852,7 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
810
852
|
if is_conflicting(rule_conclusion, case_query.target_value):
|
811
853
|
self.stop_conclusion(case_query, expert, evaluated_rule)
|
812
854
|
else:
|
813
|
-
self.add_conclusion(
|
855
|
+
self.add_conclusion(rule_conclusion)
|
814
856
|
|
815
857
|
def stop_conclusion(self, case_query: CaseQuery,
|
816
858
|
expert: Expert, evaluated_rule: MultiClassTopRule):
|
@@ -822,12 +864,13 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
822
864
|
:param evaluated_rule: The evaluated rule to ask the expert about.
|
823
865
|
"""
|
824
866
|
conditions = expert.ask_for_conditions(case_query, evaluated_rule)
|
825
|
-
evaluated_rule.fit_rule(case_query
|
867
|
+
evaluated_rule.fit_rule(case_query)
|
826
868
|
if self.mode == MCRDRMode.StopPlusRule:
|
827
869
|
self.stop_rule_conditions = conditions
|
828
870
|
if self.mode == MCRDRMode.StopPlusRuleCombined:
|
829
871
|
new_top_rule_conditions = conditions.combine_with(evaluated_rule.conditions)
|
830
|
-
|
872
|
+
case_query.conditions = new_top_rule_conditions
|
873
|
+
self.add_top_rule(case_query)
|
831
874
|
|
832
875
|
def add_rule_for_case(self, case_query: CaseQuery, expert: Expert):
|
833
876
|
"""
|
@@ -839,19 +882,19 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
839
882
|
if self.stop_rule_conditions and self.mode == MCRDRMode.StopPlusRule:
|
840
883
|
conditions = self.stop_rule_conditions
|
841
884
|
self.stop_rule_conditions = None
|
885
|
+
case_query.conditions = conditions
|
842
886
|
else:
|
843
887
|
conditions = expert.ask_for_conditions(case_query)
|
844
|
-
self.add_top_rule(
|
888
|
+
self.add_top_rule(case_query)
|
845
889
|
|
846
|
-
def add_conclusion(self,
|
890
|
+
def add_conclusion(self, rule_conclusion: List[Any]) -> None:
|
847
891
|
"""
|
848
892
|
Add the conclusion of the evaluated rule to the list of conclusions.
|
849
893
|
|
850
|
-
:param
|
851
|
-
|
894
|
+
:param rule_conclusion: The conclusion of the evaluated rule, which can be a single conclusion
|
895
|
+
or a set of conclusions.
|
852
896
|
"""
|
853
897
|
conclusion_types = [type(c) for c in self.conclusions]
|
854
|
-
rule_conclusion = evaluated_rule.conclusion(case)
|
855
898
|
if type(rule_conclusion) not in conclusion_types:
|
856
899
|
self.conclusions.extend(make_list(rule_conclusion))
|
857
900
|
else:
|
@@ -864,15 +907,13 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
864
907
|
self.conclusions.remove(c)
|
865
908
|
self.conclusions.extend(make_list(combined_conclusion))
|
866
909
|
|
867
|
-
def add_top_rule(self,
|
910
|
+
def add_top_rule(self, case_query: CaseQuery):
|
868
911
|
"""
|
869
912
|
Add a top rule to the classifier, which is a rule that is always checked and is part of the start_rules list.
|
870
913
|
|
871
|
-
:param
|
872
|
-
:param conclusion: The conclusion of the rule.
|
873
|
-
:param corner_case: The corner case of the rule.
|
914
|
+
:param case_query: The case query to add the top rule for.
|
874
915
|
"""
|
875
|
-
self.start_rule.alternative = MultiClassTopRule(
|
916
|
+
self.start_rule.alternative = MultiClassTopRule.from_case_query(case_query)
|
876
917
|
|
877
918
|
@staticmethod
|
878
919
|
def start_rule_type() -> Type[Rule]:
|
@@ -933,16 +974,19 @@ class GeneralRDR(RippleDownRules):
|
|
933
974
|
def start_rules(self) -> List[Union[SingleClassRule, MultiClassTopRule]]:
|
934
975
|
return [rdr.start_rule for rdr in self.start_rules_dict.values()]
|
935
976
|
|
936
|
-
def classify(self, case: Any, modify_case: bool = False
|
977
|
+
def classify(self, case: Any, modify_case: bool = False,
|
978
|
+
case_query: Optional[CaseQuery] = None) -> Optional[Dict[str, Any]]:
|
937
979
|
"""
|
938
980
|
Classify a case by going through all RDRs and adding the categories that are classified, and then restarting
|
939
981
|
the classification until no more categories can be added.
|
940
982
|
|
941
983
|
:param case: The case to classify.
|
942
984
|
:param modify_case: Whether to modify the original case or create a copy and modify it.
|
985
|
+
:param case_query: The case query containing the case and the target category to compare the case with.
|
943
986
|
:return: The categories that the case belongs to.
|
944
987
|
"""
|
945
|
-
return general_rdr_classify(self.start_rules_dict, case, modify_original_case=modify_case
|
988
|
+
return general_rdr_classify(self.start_rules_dict, case, modify_original_case=modify_case,
|
989
|
+
case_query=case_query)
|
946
990
|
|
947
991
|
def _fit_case(self, case_query: CaseQuery, expert: Optional[Expert] = None, **kwargs) \
|
948
992
|
-> Dict[str, Any]:
|
@@ -1029,7 +1073,7 @@ class GeneralRDR(RippleDownRules):
|
|
1029
1073
|
"""
|
1030
1074
|
for rdr in self.start_rules_dict.values():
|
1031
1075
|
rdr._write_to_python(model_dir)
|
1032
|
-
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.conclusion_type_hint}:\n"
|
1076
|
+
func_def = f"def classify(case: {self.case_type.__name__}, **kwargs) -> {self.conclusion_type_hint}:\n"
|
1033
1077
|
with open(model_dir + f"/{self.generated_python_file_name}.py", "w") as f:
|
1034
1078
|
f.write(self._get_imports() + "\n\n")
|
1035
1079
|
f.write("classifiers_dict = dict()\n")
|
@@ -1039,7 +1083,7 @@ class GeneralRDR(RippleDownRules):
|
|
1039
1083
|
f.write(func_def)
|
1040
1084
|
f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
|
1041
1085
|
f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
|
1042
|
-
f.write(f"{' ' * 4}return general_rdr_classify(classifiers_dict, case)\n")
|
1086
|
+
f.write(f"{' ' * 4}return general_rdr_classify(classifiers_dict, case, **kwargs)\n")
|
1043
1087
|
|
1044
1088
|
@property
|
1045
1089
|
def _default_generated_python_file_name(self) -> Optional[str]:
|
{ripple_down_rules-0.5.64 → ripple_down_rules-0.5.71}/src/ripple_down_rules/rdr_decorators.py
RENAMED
@@ -6,17 +6,18 @@ of the RDRs.
|
|
6
6
|
import os.path
|
7
7
|
from functools import wraps
|
8
8
|
|
9
|
-
from pyparsing.tools.cvt_pyparsing_pep8_names import camel_to_snake
|
10
9
|
from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union, Sequence
|
11
10
|
|
12
|
-
from ripple_down_rules.datastructures.case import
|
11
|
+
from ripple_down_rules.datastructures.case import Case
|
13
12
|
from ripple_down_rules.datastructures.dataclasses import CaseQuery
|
14
|
-
from ripple_down_rules.datastructures.enums import Category
|
15
13
|
from ripple_down_rules.experts import Expert, Human
|
16
|
-
from ripple_down_rules.rdr import GeneralRDR
|
17
|
-
|
14
|
+
from ripple_down_rules.rdr import GeneralRDR
|
15
|
+
try:
|
16
|
+
from ripple_down_rules.user_interface.gui import RDRCaseViewer
|
17
|
+
except ImportError:
|
18
|
+
RDRCaseViewer = None
|
18
19
|
from ripple_down_rules.utils import get_method_args_as_dict, get_func_rdr_model_name, make_set, \
|
19
|
-
get_method_class_if_exists,
|
20
|
+
get_method_class_if_exists, str_to_snake_case
|
20
21
|
|
21
22
|
|
22
23
|
class RDRDecorator:
|