ripple-down-rules 0.5.63__tar.gz → 0.5.64__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.
Files changed (143) hide show
  1. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/PKG-INFO +1 -1
  2. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/part_containment_rdr/robot_rdr.py +3 -2
  3. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/__init__.py +1 -1
  4. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/datastructures/case.py +10 -4
  5. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/datastructures/dataclasses.py +1 -1
  6. ripple_down_rules-0.5.64/src/ripple_down_rules/helpers.py +93 -0
  7. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/rdr.py +55 -59
  8. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/rdr_decorators.py +48 -18
  9. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/rules.py +9 -4
  10. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/gui.py +9 -7
  11. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +1 -1
  12. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/object_diagram.py +9 -1
  13. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/template_file_creator.py +17 -22
  14. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/utils.py +174 -59
  15. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  16. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules.egg-info/SOURCES.txt +2 -0
  17. ripple_down_rules-0.5.64/test/conftest.py +23 -0
  18. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_helpers/helpers.py +9 -9
  19. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_rdr.py +6 -6
  20. ripple_down_rules-0.5.64/test/test_rdr_helpers_rdrs.py +65 -0
  21. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_rdr_world.py +2 -2
  22. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +1 -2
  23. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +2 -2
  24. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/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 -2
  25. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/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
  26. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/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
  27. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_template_file_creator.py +3 -4
  28. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_user_interface/test_qt_gui_inline.py +1 -1
  29. ripple_down_rules-0.5.64/test/test_utils.py +38 -0
  30. ripple_down_rules-0.5.63/src/ripple_down_rules/helpers.py +0 -51
  31. ripple_down_rules-0.5.63/test/test_utils.py +0 -23
  32. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/.github/workflows/build_and_deploy_doc.yml +0 -0
  33. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/.github/workflows/ci.yml +0 -0
  34. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/.github/workflows/publish-to-test-pypi.yml +0 -0
  35. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
  36. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
  37. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/LICENSE +0 -0
  38. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/README.md +0 -0
  39. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/_config.yml +0 -0
  40. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/_toc.yml +0 -0
  41. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/bibliography.md +0 -0
  42. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/intro.md +0 -0
  43. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/references.bib +0 -0
  44. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/doc/requirements.txt +0 -0
  45. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/__init__.py +0 -0
  46. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/animal_species.py +0 -0
  47. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/part_containment_rdr/__init__.py +0 -0
  48. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -0
  49. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -0
  50. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -0
  51. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/examples/relational_example.py +0 -0
  52. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/images/scrdr.dot +0 -0
  53. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/images/scrdr.png +0 -0
  54. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/images/thinking_pr2.jpg +0 -0
  55. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/pyproject.toml +0 -0
  56. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/pytest.ini +0 -0
  57. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/requirements-dev-ci.txt +0 -0
  58. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/requirements-dev.txt +0 -0
  59. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/requirements-gui.txt +0 -0
  60. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/requirements-viz.txt +0 -0
  61. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/requirements.txt +0 -0
  62. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_extra.dot +0 -0
  63. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_extra.png +0 -0
  64. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_stop_only.dot +0 -0
  65. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_stop_only.png +0 -0
  66. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
  67. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_mcrdr_stop_plus_rule.png +0 -0
  68. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr.dot +0 -0
  69. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr.png +0 -0
  70. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr_2.dot +0 -0
  71. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr_2.png +0 -0
  72. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr_3.dot +0 -0
  73. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/complete_scrdr_3.png +0 -0
  74. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/grdr_Habitat.dot +0 -0
  75. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/grdr_Habitat.png +0 -0
  76. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/grdr_Species.dot +0 -0
  77. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/grdr_Species.png +0 -0
  78. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_extra.dot +0 -0
  79. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_extra.png +0 -0
  80. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_extra_classify.dot +0 -0
  81. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_extra_classify.png +0 -0
  82. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
  83. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/mcrdr_stop_plus_rule_combined.png +0 -0
  84. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/partial_mcrdr_extra.dot +0 -0
  85. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/partial_mcrdr_extra.png +0 -0
  86. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/relational_scrdr_classify.dot +0 -0
  87. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/results/relational_scrdr_classify.png +0 -0
  88. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/setup.cfg +0 -0
  89. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  90. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
  91. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/datastructures/enums.py +0 -0
  92. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/experts.py +0 -0
  93. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/start-code-server.sh +0 -0
  94. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/__init__.py +0 -0
  95. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules/user_interface/prompt.py +0 -0
  96. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  97. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules.egg-info/requires.txt +0 -0
  98. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  99. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/datasets.py +0 -0
  100. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
  101. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
  102. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
  103. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
  104. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
  105. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
  106. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
  107. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
  108. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
  109. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
  110. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
  111. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
  112. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
  113. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
  114. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
  115. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
  116. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
  117. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
  118. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
  119. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
  120. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
  121. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
  122. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_expert_answers/view_rdr_expert_answers_fit.json +0 -0
  123. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_generated_rdrs/__init__.py +0 -0
  124. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_helpers/__init__.py +0 -0
  125. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_helpers/object_diagram_case_query.png +0 -0
  126. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_helpers/object_diagram_person.png +0 -0
  127. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_json_serialization.py +0 -0
  128. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_object_diagram.py +0 -0
  129. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_on_mutagenic.py +0 -0
  130. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_rdr_alchemy.py +0 -0
  131. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_rdr_decorators.py +0 -0
  132. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_relational_rdr.py +0 -0
  133. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_relational_rdr_alchemy.py +0 -0
  134. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_is_a_robot/__init__.py +0 -0
  135. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +1 -1
  136. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata/datasets_physical_object_is_a_robot.json +0 -0
  137. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +0 -0
  138. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/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
  139. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_sql_model.py +0 -0
  140. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_user_interface/__init__.py +0 -0
  141. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_user_interface/test_ipython.py +0 -0
  142. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_user_interface/test_ipython_copilot.py +0 -0
  143. {ripple_down_rules-0.5.63 → ripple_down_rules-0.5.64}/test/test_user_interface/test_prompt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.5.63
3
+ Version: 0.5.64
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
@@ -1,5 +1,6 @@
1
1
  from typing_extensions import Dict, Any
2
- from ripple_down_rules.rdr import GeneralRDR
2
+
3
+ from ripple_down_rules.helpers import general_rdr_classify
3
4
  from ripple_down_rules.datastructures.case import Case, create_case
4
5
  from __main__ import Robot
5
6
  from . import robot_contained_objects_mcrdr as contained_objects_classifier
@@ -12,4 +13,4 @@ classifiers_dict['contained_objects'] = contained_objects_classifier
12
13
  def classify(case: Robot) -> Dict[str, Any]:
13
14
  if not isinstance(case, Case):
14
15
  case = create_case(case, max_recursion_idx=3)
15
- return GeneralRDR._classify(classifiers_dict, case)
16
+ return general_rdr_classify(classifiers_dict, case)
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.63"
1
+ __version__ = "0.5.64"
2
2
 
3
3
  import logging
4
4
  logger = logging.Logger("rdr")
@@ -84,7 +84,7 @@ class Case(UserDict, SubclassJSONSerializer):
84
84
  def _to_json(self) -> Dict[str, Any]:
85
85
  serializable = {k: v for k, v in self.items() if not k.startswith("_")}
86
86
  serializable["_id"] = self._id
87
- serializable["_obj_type"] = get_full_class_name(self._obj_type)
87
+ serializable["_obj_type"] = get_full_class_name(self._obj_type) if self._obj_type is not None else None
88
88
  serializable["_name"] = self._name
89
89
  for k, v in serializable.items():
90
90
  if isinstance(v, set):
@@ -96,7 +96,7 @@ class Case(UserDict, SubclassJSONSerializer):
96
96
  @classmethod
97
97
  def _from_json(cls, data: Dict[str, Any]) -> Case:
98
98
  id_ = data.pop("_id")
99
- obj_type = get_type_from_string(data.pop("_obj_type"))
99
+ obj_type = get_type_from_string(data.pop("_obj_type")) if data["_obj_type"] is not None else None
100
100
  name = data.pop("_name")
101
101
  for k, v in data.items():
102
102
  data[k] = SubclassJSONSerializer.from_json(v)
@@ -308,7 +308,10 @@ def create_case_attribute_from_iterable_attribute(attr_value: Any, name: str, ob
308
308
  :return: A case attribute that represents the original iterable attribute.
309
309
  """
310
310
  values = list(attr_value.values()) if isinstance(attr_value, (dict, UserDict)) else attr_value
311
- _type = type(list(values)[0]) if len(values) > 0 else get_value_type_from_type_hint(name, obj)
311
+ try:
312
+ _type = type(list(values)[0]) if len(values) > 0 else get_value_type_from_type_hint(name, obj)
313
+ except ValueError:
314
+ _type = None
312
315
  attr_case = Case(_type, _id=id(attr_value), _name=name, original_object=attr_value)
313
316
  case_attr = CaseAttribute(values)
314
317
  for idx, val in enumerate(values):
@@ -317,7 +320,10 @@ def create_case_attribute_from_iterable_attribute(attr_value: Any, name: str, ob
317
320
  obj_name=name, parent_is_iterable=True)
318
321
  attr_case.update(sub_attr_case)
319
322
  for sub_attr, val in attr_case.items():
320
- setattr(case_attr, sub_attr, val)
323
+ try:
324
+ setattr(case_attr, sub_attr, val)
325
+ except AttributeError:
326
+ pass
321
327
  return case_attr
322
328
 
323
329
 
@@ -95,7 +95,7 @@ class CaseQuery:
95
95
  """
96
96
  if self._case is not None:
97
97
  return self._case
98
- elif not isinstance(self.original_case, (Case, SQLTable)):
98
+ elif not isinstance(self.original_case, Case):
99
99
  self._case = create_case(self.original_case, max_recursion_idx=3)
100
100
  else:
101
101
  self._case = self.original_case
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from types import ModuleType
5
+
6
+ from .datastructures.case import create_case
7
+ from .datastructures.dataclasses import CaseQuery
8
+ from typing_extensions import Type, Optional, Callable, Any, Dict, TYPE_CHECKING, Union
9
+
10
+ from .utils import get_func_rdr_model_name, copy_case, make_set, update_case
11
+ from .utils import calculate_precision_and_recall
12
+
13
+ if TYPE_CHECKING:
14
+ from .rdr import RippleDownRules
15
+
16
+
17
+ def general_rdr_classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDownRules]],
18
+ case: Any, modify_original_case: bool = False) -> Dict[str, Any]:
19
+ """
20
+ Classify a case by going through all classifiers and adding the categories that are classified,
21
+ and then restarting the classification until no more categories can be added.
22
+
23
+ :param classifiers_dict: A dictionary mapping conclusion types to the classifiers that produce them.
24
+ :param case: The case to classify.
25
+ :param modify_original_case: Whether to modify the original case or create a copy and modify it.
26
+ :return: The categories that the case belongs to.
27
+ """
28
+ conclusions = {}
29
+ case = create_case(case)
30
+ case_cp = copy_case(case) if not modify_original_case else case
31
+ while True:
32
+ new_conclusions = {}
33
+ for attribute_name, rdr in classifiers_dict.items():
34
+ pred_atts = rdr.classify(case_cp)
35
+ if pred_atts is None:
36
+ continue
37
+ if rdr.mutually_exclusive:
38
+ if attribute_name not in conclusions or \
39
+ (attribute_name in conclusions and conclusions[attribute_name] != pred_atts):
40
+ conclusions[attribute_name] = pred_atts
41
+ new_conclusions[attribute_name] = pred_atts
42
+ else:
43
+ pred_atts = make_set(pred_atts)
44
+ if attribute_name in conclusions:
45
+ pred_atts = {p for p in pred_atts if p not in conclusions[attribute_name]}
46
+ if len(pred_atts) > 0:
47
+ new_conclusions[attribute_name] = pred_atts
48
+ if attribute_name not in conclusions:
49
+ conclusions[attribute_name] = set()
50
+ conclusions[attribute_name].update(pred_atts)
51
+ if attribute_name in new_conclusions:
52
+ case_query = CaseQuery(case_cp, attribute_name, rdr.conclusion_type, rdr.mutually_exclusive)
53
+ update_case(case_query, new_conclusions)
54
+ if len(new_conclusions) == 0:
55
+ break
56
+ return conclusions
57
+
58
+
59
+ def is_matching(classifier: Callable[[Any], Any], case_query: CaseQuery, pred_cat: Optional[Dict[str, Any]] = None) -> bool:
60
+ """
61
+ :param classifier: The RDR classifier to check the prediction of.
62
+ :param case_query: The case query to check.
63
+ :param pred_cat: The predicted category.
64
+ :return: Whether the classifier prediction is matching case_query target or not.
65
+ """
66
+ if case_query.target is None:
67
+ return False
68
+ if pred_cat is None:
69
+ pred_cat = classifier(case_query.case)
70
+ if not isinstance(pred_cat, dict):
71
+ pred_cat = {case_query.attribute_name: pred_cat}
72
+ target = {case_query.attribute_name: case_query.target_value}
73
+ precision, recall = calculate_precision_and_recall(pred_cat, target)
74
+ return all(recall) and all(precision)
75
+
76
+
77
+ def load_or_create_func_rdr_model(func, model_dir: str, rdr_type: Type[RippleDownRules],
78
+ **rdr_kwargs) -> RippleDownRules:
79
+ """
80
+ Load the RDR model of the function if it exists, otherwise create a new one.
81
+
82
+ :param func: The function to load the model for.
83
+ :param model_dir: The directory where the model is stored.
84
+ :param rdr_type: The type of the RDR model to load.
85
+ :param rdr_kwargs: Additional arguments to pass to the RDR constructor in the case of a new model.
86
+ """
87
+ model_name = get_func_rdr_model_name(func)
88
+ model_path = os.path.join(model_dir, model_name, "rdr_metadata", f"{model_name}.json")
89
+ if os.path.exists(model_path):
90
+ rdr = rdr_type.load(load_dir=model_dir, model_name=model_name)
91
+ else:
92
+ rdr = rdr_type(**rdr_kwargs)
93
+ return rdr
@@ -28,7 +28,7 @@ from .datastructures.case import Case, CaseAttribute, create_case
28
28
  from .datastructures.dataclasses import CaseQuery
29
29
  from .datastructures.enums import MCRDRMode
30
30
  from .experts import Expert, Human
31
- from .helpers import is_matching
31
+ from .helpers import is_matching, general_rdr_classify
32
32
  from .rules import Rule, SingleClassRule, MultiClassTopRule, MultiClassStopRule
33
33
  try:
34
34
  from .user_interface.gui import RDRCaseViewer
@@ -36,7 +36,7 @@ except ImportError as e:
36
36
  RDRCaseViewer = None
37
37
  from .utils import draw_tree, make_set, copy_case, \
38
38
  SubclassJSONSerializer, make_list, get_type_from_string, \
39
- is_conflicting, update_case, get_imports_from_scope, extract_function_source, extract_imports, get_full_class_name, \
39
+ is_conflicting, get_imports_from_scope, extract_function_source, extract_imports, get_full_class_name, \
40
40
  is_iterable, str_to_snake_case
41
41
 
42
42
 
@@ -76,16 +76,18 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
76
76
  """
77
77
  The name of the model. If None, the model name will be the generated python file name.
78
78
  """
79
+ mutually_exclusive: Optional[bool] = None
80
+ """
81
+ Whether the output of the classification of this rdr allows only one possible conclusion or not.
82
+ """
79
83
 
80
84
  def __init__(self, start_rule: Optional[Rule] = None, viewer: Optional[RDRCaseViewer] = None,
81
- save_dir: Optional[str] = None, ask_always: bool = False, model_name: Optional[str] = None):
85
+ save_dir: Optional[str] = None, model_name: Optional[str] = None):
82
86
  """
83
87
  :param start_rule: The starting rule for the classifier.
84
88
  :param viewer: The viewer gui to use for the classifier. If None, no viewer is used.
85
89
  :param save_dir: The directory to save the classifier to.
86
- :param ask_always: Whether to always ask the expert (True) or only ask when classification fails (False).
87
90
  """
88
- self.ask_always: bool = ask_always
89
91
  self.model_name: Optional[str] = model_name
90
92
  self.save_dir = save_dir
91
93
  self.start_rule = start_rule
@@ -224,7 +226,10 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
224
226
  """
225
227
  pass
226
228
 
227
- def fit_case(self, case_query: CaseQuery, expert: Optional[Expert] = None, **kwargs) \
229
+ def fit_case(self, case_query: CaseQuery,
230
+ expert: Optional[Expert] = None,
231
+ update_existing_rules: bool = True,
232
+ **kwargs) \
228
233
  -> Union[CallableExpression, Dict[str, CallableExpression]]:
229
234
  """
230
235
  Fit the classifier to a case and ask the expert for refinements or alternatives if the classification is
@@ -232,6 +237,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
232
237
 
233
238
  :param case_query: The query containing the case to classify and the target category to compare the case with.
234
239
  :param expert: The expert to ask for differentiating features as new rule conditions.
240
+ :param update_existing_rules: Whether to update the existing same conclusion type rules that already gave
241
+ some conclusions with the type required by the case query.
235
242
  :return: The category that the case belongs to.
236
243
  """
237
244
  if case_query is None:
@@ -248,11 +255,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
248
255
  if case_query.target is None:
249
256
  case_query_cp = copy(case_query)
250
257
  conclusions = self.classify(case_query_cp.case, modify_case=True)
251
- if (self.ask_always or conclusions is None
252
- or is_iterable(conclusions) and len(conclusions) == 0
253
- or (isinstance(conclusions, dict) and (case_query_cp.attribute_name not in conclusions
254
- or not any(type(c) in case_query_cp.core_attribute_type
255
- for c in make_list(conclusions[case_query_cp.attribute_name]))))):
258
+ if self.should_i_ask_the_expert_for_a_target(conclusions, case_query_cp, update_existing_rules):
256
259
  expert.ask_for_conclusion(case_query_cp)
257
260
  case_query.target = case_query_cp.target
258
261
  if case_query.target is None:
@@ -268,6 +271,34 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
268
271
 
269
272
  return fit_case_result
270
273
 
274
+ @staticmethod
275
+ def should_i_ask_the_expert_for_a_target(conclusions: Union[Any, Dict[str, Any]],
276
+ case_query: CaseQuery,
277
+ update_existing: bool) -> bool:
278
+ """
279
+ Determine if the rdr should ask the expert for the target of a given case query.
280
+
281
+ :param conclusions: The conclusions of the case.
282
+ :param case_query: The query containing the case to classify.
283
+ :param update_existing: Whether to update rules that gave the required type of conclusions.
284
+ :return: True if the rdr should ask the expert, False otherwise.
285
+ """
286
+ if conclusions is None:
287
+ return True
288
+ elif is_iterable(conclusions) and len(conclusions) == 0:
289
+ return True
290
+ elif isinstance(conclusions, dict):
291
+ if case_query.attribute_name not in conclusions:
292
+ return True
293
+ conclusions = conclusions[case_query.attribute_name]
294
+ conclusion_types = map(type, make_list(conclusions))
295
+ if not any(ct in case_query.core_attribute_type for ct in conclusion_types):
296
+ return True
297
+ elif update_existing:
298
+ return True
299
+ else:
300
+ return False
301
+
271
302
  @abstractmethod
272
303
  def _fit_case(self, case_query: CaseQuery, expert: Optional[Expert] = None, **kwargs) \
273
304
  -> Union[CallableExpression, Dict[str, CallableExpression]]:
@@ -423,11 +454,10 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
423
454
  f.write(defs_imports + "\n\n")
424
455
  with open(file_name, "w") as f:
425
456
  imports += f"from .{self.generated_python_defs_file_name} import *\n"
426
- imports += f"from ripple_down_rules.rdr import {self.__class__.__name__}\n"
427
457
  f.write(imports + "\n\n")
428
458
  f.write(f"attribute_name = '{self.attribute_name}'\n")
429
459
  f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
430
- f.write(f"type_ = {self.__class__.__name__}\n")
460
+ f.write(f"mutually_exclusive = {self.mutually_exclusive}\n")
431
461
  f.write(f"\n\n{func_def}")
432
462
  f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
433
463
  f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
@@ -533,6 +563,11 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
533
563
 
534
564
  class SingleClassRDR(RDRWithCodeWriter):
535
565
 
566
+ mutually_exclusive: bool = True
567
+ """
568
+ The output of the classification of this rdr negates all other possible outputs, there can only be one true value.
569
+ """
570
+
536
571
  def __init__(self, default_conclusion: Optional[Any] = None, **kwargs):
537
572
  """
538
573
  :param start_rule: The starting rule for the classifier.
@@ -650,6 +685,10 @@ class MultiClassRDR(RDRWithCodeWriter):
650
685
  """
651
686
  The conditions of the stopping rule if needed.
652
687
  """
688
+ mutually_exclusive: bool = False
689
+ """
690
+ The output of the classification of this rdr allows for more than one true value as conclusion.
691
+ """
653
692
 
654
693
  def __init__(self, start_rule: Optional[MultiClassTopRule] = None,
655
694
  mode: MCRDRMode = MCRDRMode.StopOnly, **kwargs):
@@ -903,50 +942,7 @@ class GeneralRDR(RippleDownRules):
903
942
  :param modify_case: Whether to modify the original case or create a copy and modify it.
904
943
  :return: The categories that the case belongs to.
905
944
  """
906
- return self._classify(self.start_rules_dict, case, modify_original_case=modify_case)
907
-
908
- @staticmethod
909
- def _classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDownRules]],
910
- case: Any, modify_original_case: bool = False) -> Dict[str, Any]:
911
- """
912
- Classify a case by going through all classifiers and adding the categories that are classified,
913
- and then restarting the classification until no more categories can be added.
914
-
915
- :param classifiers_dict: A dictionary mapping conclusion types to the classifiers that produce them.
916
- :param case: The case to classify.
917
- :param modify_original_case: Whether to modify the original case or create a copy and modify it.
918
- :return: The categories that the case belongs to.
919
- """
920
- conclusions = {}
921
- case = case if isinstance(case, (Case, SQLTable)) else create_case(case)
922
- case_cp = copy_case(case) if not modify_original_case else case
923
- while True:
924
- new_conclusions = {}
925
- for attribute_name, rdr in classifiers_dict.items():
926
- pred_atts = rdr.classify(case_cp)
927
- if pred_atts is None:
928
- continue
929
- if rdr.type_ is SingleClassRDR:
930
- if attribute_name not in conclusions or \
931
- (attribute_name in conclusions and conclusions[attribute_name] != pred_atts):
932
- conclusions[attribute_name] = pred_atts
933
- new_conclusions[attribute_name] = pred_atts
934
- else:
935
- pred_atts = make_set(pred_atts)
936
- if attribute_name in conclusions:
937
- pred_atts = {p for p in pred_atts if p not in conclusions[attribute_name]}
938
- if len(pred_atts) > 0:
939
- new_conclusions[attribute_name] = pred_atts
940
- if attribute_name not in conclusions:
941
- conclusions[attribute_name] = set()
942
- conclusions[attribute_name].update(pred_atts)
943
- if attribute_name in new_conclusions:
944
- mutually_exclusive = True if rdr.type_ is SingleClassRDR else False
945
- case_query = CaseQuery(case_cp, attribute_name, rdr.conclusion_type, mutually_exclusive)
946
- update_case(case_query, new_conclusions)
947
- if len(new_conclusions) == 0:
948
- break
949
- return conclusions
945
+ return general_rdr_classify(self.start_rules_dict, case, modify_original_case=modify_case)
950
946
 
951
947
  def _fit_case(self, case_query: CaseQuery, expert: Optional[Expert] = None, **kwargs) \
952
948
  -> Dict[str, Any]:
@@ -1043,7 +1039,7 @@ class GeneralRDR(RippleDownRules):
1043
1039
  f.write(func_def)
1044
1040
  f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
1045
1041
  f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
1046
- f.write(f"{' ' * 4}return GeneralRDR._classify(classifiers_dict, case)\n")
1042
+ f.write(f"{' ' * 4}return general_rdr_classify(classifiers_dict, case)\n")
1047
1043
 
1048
1044
  @property
1049
1045
  def _default_generated_python_file_name(self) -> Optional[str]:
@@ -1068,7 +1064,7 @@ class GeneralRDR(RippleDownRules):
1068
1064
  # add type hints
1069
1065
  imports += f"from typing_extensions import Dict, Any\n"
1070
1066
  # import rdr type
1071
- imports += f"from ripple_down_rules.rdr import GeneralRDR\n"
1067
+ imports += f"from ripple_down_rules.helpers import general_rdr_classify\n"
1072
1068
  # add case type
1073
1069
  imports += f"from ripple_down_rules.datastructures.case import Case, create_case\n"
1074
1070
  imports += f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
@@ -7,13 +7,14 @@ import os.path
7
7
  from functools import wraps
8
8
 
9
9
  from pyparsing.tools.cvt_pyparsing_pep8_names import camel_to_snake
10
- from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union
10
+ from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union, Sequence
11
11
 
12
12
  from ripple_down_rules.datastructures.case import create_case, Case
13
13
  from ripple_down_rules.datastructures.dataclasses import CaseQuery
14
14
  from ripple_down_rules.datastructures.enums import Category
15
15
  from ripple_down_rules.experts import Expert, Human
16
16
  from ripple_down_rules.rdr import GeneralRDR, RippleDownRules
17
+ from ripple_down_rules.user_interface.gui import RDRCaseViewer
17
18
  from ripple_down_rules.utils import get_method_args_as_dict, get_func_rdr_model_name, make_set, \
18
19
  get_method_class_if_exists, get_method_name, str_to_snake_case
19
20
 
@@ -26,7 +27,10 @@ class RDRDecorator:
26
27
  mutual_exclusive: bool,
27
28
  output_name: str = "output_",
28
29
  fit: bool = True,
29
- expert: Optional[Expert] = None):
30
+ expert: Optional[Expert] = None,
31
+ ask_always: bool = False,
32
+ update_existing_rules: bool = True,
33
+ viewer: Optional[RDRCaseViewer] = None):
30
34
  """
31
35
  :param models_dir: The directory to save/load the RDR models.
32
36
  :param output_type: The type of the output. This is used to create the RDR model.
@@ -38,6 +42,9 @@ class RDRDecorator:
38
42
  classification mode. This means that the RDR will classify the function's output based on the RDR model.
39
43
  :param expert: The expert that will be used to prompt the user for the correct output. If None, a Human
40
44
  expert will be used.
45
+ :param ask_always: If True, the function will ask the user for a target if it doesn't exist.
46
+ :param update_existing_rules: If True, the function will update the existing RDR rules
47
+ even if they gave an output.
41
48
  :return: A decorator to use a GeneralRDR as a classifier that monitors and modifies the function's output.
42
49
  """
43
50
  self.rdr_models_dir = models_dir
@@ -48,6 +55,9 @@ class RDRDecorator:
48
55
  self.output_name = output_name
49
56
  self.fit: bool = fit
50
57
  self.expert: Optional[Expert] = expert
58
+ self.ask_always = ask_always
59
+ self.update_existing_rules = update_existing_rules
60
+ self.viewer = viewer
51
61
  self.load()
52
62
 
53
63
  def decorator(self, func: Callable) -> Callable:
@@ -59,59 +69,77 @@ class RDRDecorator:
59
69
  self.parsed_output_type = self.parse_output_type(func, self.output_type, *args)
60
70
  if self.model_name is None:
61
71
  self.initialize_rdr_model_name_and_load(func)
72
+ if self.expert is None:
73
+ self.expert = Human(viewer=self.viewer,
74
+ answers_save_path=self.rdr_models_dir + f'/expert_answers')
75
+
76
+ func_output = {self.output_name: func(*args, **kwargs)}
62
77
 
63
78
  if self.fit:
64
- case_query = self.create_case_query_from_method(func, self.parsed_output_type,
65
- self.mutual_exclusive, self.output_name,
79
+ case_query = self.create_case_query_from_method(func, func_output,
80
+ self.parsed_output_type,
81
+ self.mutual_exclusive,
66
82
  *args, **kwargs)
67
- output = self.rdr.fit_case(case_query, expert=self.expert)
83
+ output = self.rdr.fit_case(case_query, expert=self.expert,
84
+ ask_always_for_target=self.ask_always,
85
+ update_existing_rules=self.update_existing_rules,
86
+ viewer=self.viewer)
87
+ else:
88
+ case, case_dict = self.create_case_from_method(func, func_output, *args, **kwargs)
89
+ output = self.rdr.classify(case)
90
+
91
+ if self.output_name in output:
68
92
  return output[self.output_name]
69
93
  else:
70
- case, case_dict = self.create_case_from_method(func, self.output_name, *args, **kwargs)
71
- return self.rdr.classify(case)[self.output_name]
94
+ return func_output[self.output_name]
72
95
 
73
96
  return wrapper
74
97
 
75
98
  @staticmethod
76
- def create_case_query_from_method(func: Callable, output_type, mutual_exclusive: bool,
77
- output_name: str = 'output_', *args, **kwargs) -> CaseQuery:
99
+ def create_case_query_from_method(func: Callable,
100
+ func_output: Dict[str, Any],
101
+ output_type: Sequence[Type],
102
+ mutual_exclusive: bool,
103
+ *args, **kwargs) -> CaseQuery:
78
104
  """
79
105
  Create a CaseQuery from the function and its arguments.
80
106
 
81
107
  :param func: The function to create a case from.
82
- :param output_type: The type of the output.
108
+ :param func_output: The output of the function as a dictionary, where the key is the output name.
109
+ :param output_type: The type of the output as a sequence of types.
83
110
  :param mutual_exclusive: If True, the output types are mutually exclusive.
84
- :param output_name: The name of the output in the case. Defaults to 'output_'.
85
111
  :param args: The positional arguments of the function.
86
112
  :param kwargs: The keyword arguments of the function.
87
113
  :return: A CaseQuery object representing the case.
88
114
  """
89
115
  output_type = make_set(output_type)
90
- case, case_dict = RDRDecorator.create_case_from_method(func, output_name, *args, **kwargs)
116
+ case, case_dict = RDRDecorator.create_case_from_method(func, func_output, *args, **kwargs)
91
117
  scope = func.__globals__
92
118
  scope.update(case_dict)
93
119
  func_args_type_hints = get_type_hints(func)
120
+ output_name = list(func_output.keys())[0]
94
121
  func_args_type_hints.update({output_name: Union[tuple(output_type)]})
95
122
  return CaseQuery(case, output_name, Union[tuple(output_type)],
96
123
  mutual_exclusive, scope=scope,
97
124
  is_function=True, function_args_type_hints=func_args_type_hints)
98
125
 
99
126
  @staticmethod
100
- def create_case_from_method(func: Callable, output_name: str = "output_", *args, **kwargs) -> Tuple[Case, Dict[str, Any]]:
127
+ def create_case_from_method(func: Callable,
128
+ func_output: Dict[str, Any],
129
+ *args, **kwargs) -> Tuple[Case, Dict[str, Any]]:
101
130
  """
102
131
  Create a Case from the function and its arguments.
103
132
 
104
133
  :param func: The function to create a case from.
105
- :param output_name: The name of the output in the case. Defaults to 'output_'.
134
+ :param func_output: A dictionary containing the output of the function, where the key is the output name.
106
135
  :param args: The positional arguments of the function.
107
136
  :param kwargs: The keyword arguments of the function.
108
137
  :return: A Case object representing the case.
109
138
  """
110
139
  case_dict = get_method_args_as_dict(func, *args, **kwargs)
111
- func_output = func(*args, **kwargs)
112
- case_dict.update({output_name: func_output})
140
+ case_dict.update(func_output)
113
141
  case_name = get_func_rdr_model_name(func)
114
- return create_case(case_dict, obj_name=case_name, max_recursion_idx=3), case_dict
142
+ return Case(dict, id(case_dict), case_name, case_dict, **case_dict), case_dict
115
143
 
116
144
  def initialize_rdr_model_name_and_load(self, func: Callable) -> None:
117
145
  model_file_name = get_func_rdr_model_name(func, include_file_name=True)
@@ -148,8 +176,10 @@ class RDRDecorator:
148
176
  model_path = os.path.join(self.rdr_models_dir, self.model_name + f"/rdr_metadata/{self.model_name}.json")
149
177
  if os.path.exists(os.path.join(self.rdr_models_dir, model_path)):
150
178
  self.rdr = GeneralRDR.load(self.rdr_models_dir, self.model_name)
179
+ self.rdr.set_viewer(self.viewer)
151
180
  if self.rdr is None:
152
- self.rdr = GeneralRDR(save_dir=self.rdr_models_dir, model_name=self.model_name)
181
+ self.rdr = GeneralRDR(save_dir=self.rdr_models_dir, model_name=self.model_name,
182
+ viewer=self.viewer)
153
183
 
154
184
  def update_from_python(self):
155
185
  """
@@ -12,7 +12,7 @@ from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple
12
12
  from .datastructures.callable_expression import CallableExpression
13
13
  from .datastructures.case import Case
14
14
  from .datastructures.enums import RDREdge, Stop
15
- from .utils import SubclassJSONSerializer, conclusion_to_json
15
+ from .utils import SubclassJSONSerializer, conclusion_to_json, get_full_class_name
16
16
 
17
17
 
18
18
  class Rule(NodeMixin, SubclassJSONSerializer, ABC):
@@ -150,11 +150,16 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
150
150
  pass
151
151
 
152
152
  def _to_json(self) -> Dict[str, Any]:
153
- json_serialization = {"conditions": self.conditions.to_json(),
153
+ try:
154
+ corner_case = SubclassJSONSerializer.to_json_static(self.corner_case) if self.corner_case else None
155
+ except Exception as e:
156
+ logging.debug("Failed to serialize corner case to json, setting it to None. Error: %s", e)
157
+ corner_case = None
158
+ json_serialization = {"_type": get_full_class_name(type(self)),
159
+ "conditions": self.conditions.to_json(),
154
160
  "conclusion": conclusion_to_json(self.conclusion),
155
161
  "parent": self.parent.json_serialization if self.parent else None,
156
- "corner_case": SubclassJSONSerializer.to_json_static(self.corner_case)
157
- if self.corner_case else None,
162
+ "corner_case": corner_case,
158
163
  "conclusion_name": self.conclusion_name,
159
164
  "weight": self.weight,
160
165
  "uid": self.uid}
@@ -281,12 +281,14 @@ class RDRCaseViewer(QMainWindow):
281
281
  main_obj: Optional[Dict[str, Any]] = None
282
282
  user_input: Optional[str] = None
283
283
  attributes_widget: Optional[QWidget] = None
284
- save_function: Optional[Callable[str], None] = None
284
+ save_function: Optional[Callable[str, str], None] = None
285
285
 
286
-
287
- def __init__(self, parent=None, save_file: Optional[str] = None):
286
+ def __init__(self, parent=None,
287
+ save_dir: Optional[str] = None,
288
+ save_model_name: Optional[str] = None):
288
289
  super().__init__(parent)
289
- self.save_file = save_file
290
+ self.save_dir = save_dir
291
+ self.save_model_name = save_model_name
290
292
 
291
293
  self.setWindowTitle("RDR Case Viewer")
292
294
 
@@ -323,17 +325,17 @@ class RDRCaseViewer(QMainWindow):
323
325
 
324
326
  # Add both to main layout
325
327
  main_layout.addWidget(self.attributes_widget, stretch=1)
326
- main_layout.addWidget(middle_widget, stretch=2)
328
+ main_layout.addWidget(middle_widget, stretch=1)
327
329
  main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
328
330
 
329
- def set_save_function(self, save_function: Callable[[str], None]) -> None:
331
+ def set_save_function(self, save_function: Callable[[str, str], None]) -> None:
330
332
  """
331
333
  Set the function to save the file.
332
334
 
333
335
  :param save_function: The function to save the file.
334
336
  """
335
337
  self.save_function = save_function
336
- self.save_btn.clicked.connect(lambda: self.save_function(self.save_file))
338
+ self.save_btn.clicked.connect(lambda: self.save_function(self.save_dir, self.save_model_name))
337
339
 
338
340
  def print(self, msg):
339
341
  """
@@ -134,7 +134,7 @@ class IPythonShell:
134
134
  """
135
135
  Update the user input from the code lines captured in the shell.
136
136
  """
137
- if len(self.shell.all_lines) == 1 and self.shell.all_lines[0].replace('return', '').strip() == '':
137
+ if self.shell.all_lines[0].replace('return', '').strip() == '':
138
138
  self.user_input = None
139
139
  else:
140
140
  self.all_code_lines = extract_dependencies(self.shell.all_lines)
@@ -1,5 +1,9 @@
1
1
  import logging
2
2
 
3
+ from ripple_down_rules.datastructures.case import Case
4
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
5
+ from ripple_down_rules.utils import SubclassJSONSerializer
6
+
3
7
  try:
4
8
  import graphviz
5
9
  except ImportError:
@@ -77,7 +81,11 @@ def generate_object_graph(obj, name='root', seen=None, graph=None, current_depth
77
81
  for attr in dir(obj):
78
82
  if attr.startswith('_'):
79
83
  continue
80
- if attr == 'scope':
84
+ if isinstance(obj, CaseQuery) and attr == 'scope':
85
+ continue
86
+ if isinstance(obj, Case) and attr in ['data']:
87
+ continue
88
+ if isinstance(obj, SubclassJSONSerializer) and attr == 'data_class_refs':
81
89
  continue
82
90
  value = getattr(obj, attr)
83
91
  if callable(value):