ripple-down-rules 0.6.28__tar.gz → 0.6.29__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 (163) hide show
  1. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/PKG-INFO +1 -1
  2. ripple_down_rules-0.6.29/src/ripple_down_rules/__init__.py +5 -0
  3. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/datastructures/callable_expression.py +4 -2
  4. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/experts.py +43 -28
  5. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/rdr.py +7 -3
  6. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/utils.py +21 -8
  7. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  8. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules.egg-info/SOURCES.txt +9 -0
  9. ripple_down_rules-0.6.29/test/test_expert_answers/grdr_expert_answers_fit.py +105 -0
  10. ripple_down_rules-0.6.29/test/test_expert_answers/grdr_expert_answers_fit_no_targets.py +224 -0
  11. ripple_down_rules-0.6.29/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.py +573 -0
  12. ripple_down_rules-0.6.29/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.py +378 -0
  13. ripple_down_rules-0.6.29/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.py +133 -0
  14. ripple_down_rules-0.6.29/test/test_expert_answers/scrdr_expert_answers_fit.py +252 -0
  15. ripple_down_rules-0.6.29/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.py +552 -0
  16. ripple_down_rules-0.6.29/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.py +111 -0
  17. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_helpers/helpers.py +3 -3
  18. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr.py +1 -0
  19. ripple_down_rules-0.6.29/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +1 -0
  20. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/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
  21. ripple_down_rules-0.6.28/src/ripple_down_rules/__init__.py +0 -5
  22. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.github/workflows/build_and_deploy_doc.yml +0 -0
  23. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.github/workflows/ci.yml +0 -0
  24. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.github/workflows/publish-to-test-pypi.yml +0 -0
  25. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.gitignore +0 -0
  26. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
  27. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
  28. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/LICENSE +0 -0
  29. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/README.md +0 -0
  30. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/_config.yml +0 -0
  31. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/_toc.yml +0 -0
  32. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/bibliography.md +0 -0
  33. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/intro.md +0 -0
  34. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/references.bib +0 -0
  35. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/doc/requirements.txt +0 -0
  36. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/__init__.py +0 -0
  37. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/animal_species.py +0 -0
  38. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/part_containment_rdr/__init__.py +0 -0
  39. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -0
  40. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -0
  41. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -0
  42. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/part_containment_rdr/robot_rdr.py +0 -0
  43. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/examples/relational_example.py +0 -0
  44. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/images/scrdr.dot +0 -0
  45. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/images/scrdr.png +0 -0
  46. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/images/thinking_pr2.jpg +0 -0
  47. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/pyproject.toml +0 -0
  48. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/pytest.ini +0 -0
  49. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/requirements-dev-ci.txt +0 -0
  50. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/requirements-dev.txt +0 -0
  51. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/requirements-gui.txt +0 -0
  52. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/requirements-viz.txt +0 -0
  53. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/requirements.txt +0 -0
  54. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_extra.dot +0 -0
  55. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_extra.png +0 -0
  56. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_stop_only.dot +0 -0
  57. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_stop_only.png +0 -0
  58. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
  59. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_mcrdr_stop_plus_rule.png +0 -0
  60. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr.dot +0 -0
  61. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr.png +0 -0
  62. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr_2.dot +0 -0
  63. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr_2.png +0 -0
  64. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr_3.dot +0 -0
  65. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/complete_scrdr_3.png +0 -0
  66. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/grdr_Habitat.dot +0 -0
  67. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/grdr_Habitat.png +0 -0
  68. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/grdr_Species.dot +0 -0
  69. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/grdr_Species.png +0 -0
  70. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_extra.dot +0 -0
  71. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_extra.png +0 -0
  72. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_extra_classify.dot +0 -0
  73. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_extra_classify.png +0 -0
  74. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
  75. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/mcrdr_stop_plus_rule_combined.png +0 -0
  76. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/partial_mcrdr_extra.dot +0 -0
  77. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/partial_mcrdr_extra.png +0 -0
  78. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/relational_scrdr_classify.dot +0 -0
  79. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/results/relational_scrdr_classify.png +0 -0
  80. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/scripts/live_dot_server_client.py +0 -0
  81. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/setup.cfg +0 -0
  82. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  83. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/datastructures/case.py +0 -0
  84. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/datastructures/dataclasses.py +0 -0
  85. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/datastructures/enums.py +0 -0
  86. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/helpers.py +0 -0
  87. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/rdr_decorators.py +0 -0
  88. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/rules.py +0 -0
  89. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/start-code-server.sh +0 -0
  90. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/__init__.py +0 -0
  91. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/gui.py +0 -0
  92. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +0 -0
  93. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
  94. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/prompt.py +0 -0
  95. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules/user_interface/template_file_creator.py +0 -0
  96. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  97. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules.egg-info/requires.txt +0 -0
  98. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  99. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/__init__.py +0 -0
  100. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/conf/__init__.py +0 -0
  101. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/conf/world/__init__.py +0 -0
  102. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/conf/world/base_config.py +0 -0
  103. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/conf/world/handles_and_containers.py +0 -0
  104. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/conftest.py +0 -0
  105. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/datasets.py +0 -0
  106. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/factories/__init__.py +0 -0
  107. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/factories/world/__init__.py +0 -0
  108. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/factories/world/handles_and_containers.py +0 -0
  109. {ripple_down_rules-0.6.28/test/test_results/datasets_physical_object_is_a_robot → ripple_down_rules-0.6.29/test/test_expert_answers}/__init__.py +0 -0
  110. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
  111. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
  112. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
  113. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
  114. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
  115. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
  116. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
  117. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
  118. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
  119. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
  120. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
  121. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
  122. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
  123. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
  124. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
  125. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
  126. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
  127. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
  128. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
  129. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
  130. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
  131. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
  132. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
  133. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_generated_rdrs/__init__.py +0 -0
  134. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_helpers/__init__.py +0 -0
  135. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_helpers/object_diagram_case_query.png +0 -0
  136. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_helpers/object_diagram_person.png +0 -0
  137. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_json_serialization.py +0 -0
  138. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_object_diagram.py +0 -0
  139. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_on_mutagenic.py +0 -0
  140. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_alchemy.py +0 -0
  141. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_decorators.py +0 -0
  142. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_helpers_rdrs.py +0 -0
  143. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_world/__init__.py +0 -0
  144. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_world/conftest.py +0 -0
  145. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_rdr_world/test_rdr_world.py +0 -0
  146. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_relational_rdr.py +0 -0
  147. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_relational_rdr_alchemy.py +0 -0
  148. {ripple_down_rules-0.6.28/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot → ripple_down_rules-0.6.29/test/test_results/datasets_physical_object_is_a_robot}/__init__.py +0 -0
  149. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +0 -0
  150. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +0 -0
  151. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +0 -0
  152. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata/datasets_physical_object_is_a_robot.json +0 -0
  153. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/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 +0 -0
  154. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_rdr.py +0 -0
  155. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/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 +0 -0
  156. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_sql_model.py +0 -0
  157. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_template_file_creator.py +0 -0
  158. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_user_interface/__init__.py +0 -0
  159. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_user_interface/test_ipython.py +0 -0
  160. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_user_interface/test_ipython_copilot.py +0 -0
  161. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_user_interface/test_prompt.py +0 -0
  162. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/test/test_user_interface/test_qt_gui_inline.py +0 -0
  163. {ripple_down_rules-0.6.28 → ripple_down_rules-0.6.29}/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.6.28
3
+ Version: 0.6.29
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
@@ -0,0 +1,5 @@
1
+ __version__ = "0.6.29"
2
+
3
+ import logging
4
+ logger = logging.Logger("rdr")
5
+ logger.setLevel(logging.INFO)
@@ -120,7 +120,9 @@ class CallableExpression(SubclassJSONSerializer):
120
120
  self.user_defined_name = user_input.split('(')[0].replace('def ', '')
121
121
  else:
122
122
  self.user_defined_name = user_input
123
- self._user_input: str = encapsulate_user_input(user_input, self.get_encapsulating_function())
123
+ if f"def {self.encapsulating_function_name}" not in user_input:
124
+ user_input = encapsulate_user_input(user_input, self.get_encapsulating_function())
125
+ self._user_input: str = user_input
124
126
  if conclusion_type is not None:
125
127
  if is_iterable(conclusion_type):
126
128
  conclusion_type = tuple(conclusion_type)
@@ -308,7 +310,7 @@ def parse_string_to_expression(expression_str: str) -> AST:
308
310
  :param expression_str: The string which will be parsed.
309
311
  :return: The parsed expression.
310
312
  """
311
- if not expression_str.startswith(CallableExpression.get_encapsulating_function()):
313
+ if not expression_str.startswith(f"def {CallableExpression.encapsulating_function_name}"):
312
314
  expression_str = encapsulate_user_input(expression_str, CallableExpression.get_encapsulating_function())
313
315
  mode = 'exec' if expression_str.startswith('def') else 'eval'
314
316
  tree = ast.parse(expression_str, mode=mode)
@@ -6,6 +6,8 @@ import logging
6
6
  import os
7
7
  import uuid
8
8
  from abc import ABC, abstractmethod
9
+ from textwrap import dedent, indent
10
+ from typing import Tuple, Dict
9
11
 
10
12
  from typing_extensions import Optional, TYPE_CHECKING, List
11
13
 
@@ -13,6 +15,7 @@ from .datastructures.callable_expression import CallableExpression
13
15
  from .datastructures.enums import PromptFor
14
16
  from .datastructures.dataclasses import CaseQuery
15
17
  from .datastructures.case import show_current_and_corner_cases
18
+ from .user_interface.template_file_creator import TemplateFileCreator
16
19
  from .utils import extract_imports, extract_function_source, get_imports_from_scope, encapsulate_user_input
17
20
 
18
21
  try:
@@ -46,14 +49,12 @@ class Expert(ABC):
46
49
  answers_save_path: Optional[str] = None):
47
50
  self.all_expert_answers = []
48
51
  self.use_loaded_answers = use_loaded_answers
49
- self.append = append
52
+ self.append = True
50
53
  self.answers_save_path = answers_save_path
51
54
  if answers_save_path is not None and os.path.exists(answers_save_path + '.py'):
52
55
  if use_loaded_answers:
53
56
  self.load_answers(answers_save_path)
54
- else:
55
- os.remove(answers_save_path + '.py')
56
- self.append = True
57
+ os.remove(answers_save_path + '.py')
57
58
 
58
59
  @abstractmethod
59
60
  def ask_for_conditions(self, case_query: CaseQuery, last_evaluated_rule: Optional[Rule] = None) \
@@ -89,28 +90,26 @@ class Expert(ABC):
89
90
  "answers_save_path attribute.")
90
91
  if path is None:
91
92
  path = self.answers_save_path
92
- if os.path.exists(path + '.json'):
93
- os.remove(path + '.json')
94
93
  if os.path.exists(path + '.py'):
95
94
  os.remove(path + '.py')
96
95
  self.all_expert_answers = []
97
96
 
98
- def save_answers(self, path: Optional[str] = None):
97
+ def save_answers(self, path: Optional[str] = None, expert_answers: Optional[List[Tuple[Dict, str]]] = None):
99
98
  """
100
99
  Save the expert answers to a file.
101
100
 
102
101
  :param path: The path to save the answers to.
102
+ :param expert_answers: The expert answers to save.
103
103
  """
104
+ expert_answers = expert_answers if expert_answers else self.all_expert_answers
105
+ if not any(expert_answers):
106
+ return
104
107
  if path is None and self.answers_save_path is None:
105
108
  raise ValueError("No path provided to save expert answers, either provide a path or set the "
106
109
  "answers_save_path attribute.")
107
110
  if path is None:
108
111
  path = self.answers_save_path
109
- is_json = os.path.exists(path + '.json')
110
- if is_json:
111
- self._save_to_json(path)
112
- else:
113
- self._save_to_python(path)
112
+ self._save_to_python(path, expert_answers=expert_answers)
114
113
 
115
114
  def _save_to_json(self, path: str):
116
115
  """
@@ -127,12 +126,14 @@ class Expert(ABC):
127
126
  with open(path + '.json', "w") as f:
128
127
  json.dump(all_answers, f)
129
128
 
130
- def _save_to_python(self, path: str):
129
+ def _save_to_python(self, path: str, expert_answers: Optional[List[Tuple[Dict, str]]] = None):
131
130
  """
132
131
  Save the expert answers to a Python file.
133
132
 
134
133
  :param path: The path to save the answers to.
134
+ :param expert_answers: The expert answers to save.
135
135
  """
136
+ expert_answers = expert_answers if expert_answers else self.all_expert_answers
136
137
  dir_name = os.path.dirname(path)
137
138
  if not os.path.exists(dir_name + '/__init__.py'):
138
139
  os.makedirs(dir_name, exist_ok=True)
@@ -145,18 +146,13 @@ class Expert(ABC):
145
146
  current_file_data = f.read()
146
147
  action = 'a' if self.append and current_file_data is not None else 'w'
147
148
  with open(path + '.py', action) as f:
148
- for scope, func_source in self.all_expert_answers:
149
+ for scope, func_source in expert_answers:
149
150
  if len(scope) > 0:
150
151
  imports = '\n'.join(get_imports_from_scope(scope)) + '\n\n\n'
151
152
  else:
152
153
  imports = ''
153
- if func_source is not None:
154
- uid = uuid.uuid4().hex
155
- func_source = encapsulate_user_input(func_source, CallableExpression.get_encapsulating_function(f'_{uid}'))
156
- else:
154
+ if func_source is None:
157
155
  func_source = 'pass # No user input provided for this case.\n'
158
- if current_file_data is not None and func_source[1:] in current_file_data:
159
- continue
160
156
  f.write(imports + func_source + '\n' + '\n\n\n\'===New Answer===\'\n\n\n')
161
157
 
162
158
  def load_answers(self, path: Optional[str] = None):
@@ -170,11 +166,10 @@ class Expert(ABC):
170
166
  "answers_save_path attribute.")
171
167
  if path is None:
172
168
  path = self.answers_save_path
173
- is_json = os.path.exists(path + '.json')
174
- if is_json:
175
- self._load_answers_from_json(path)
176
- elif os.path.exists(path + '.py'):
169
+ if os.path.exists(path + '.py'):
177
170
  self._load_answers_from_python(path)
171
+ elif os.path.exists(path + '.json'):
172
+ self._load_answers_from_json(path)
178
173
 
179
174
  def _load_answers_from_json(self, path: str):
180
175
  """
@@ -195,15 +190,15 @@ class Expert(ABC):
195
190
  file_path = path + '.py'
196
191
  with open(file_path, "r") as f:
197
192
  all_answers = f.read().split('\n\n\n\'===New Answer===\'\n\n\n')[:-1]
198
- all_function_sources = list(extract_function_source(file_path, []).values())
199
- all_function_sources_names = list(extract_function_source(file_path, []).keys())
193
+ all_function_sources = extract_function_source(file_path, [], as_list=True)
200
194
  for i, answer in enumerate(all_answers):
201
195
  answer = answer.strip('\n').strip()
202
196
  if 'def ' not in answer and 'pass' in answer:
203
197
  self.all_expert_answers.append(({}, None))
204
198
  continue
205
199
  scope = extract_imports(tree=ast.parse(answer))
206
- function_source = all_function_sources[i].replace(all_function_sources_names[i],
200
+ func_name = all_function_sources[i].split('def ')[1].split('(')[0]
201
+ function_source = all_function_sources[i].replace(func_name,
207
202
  CallableExpression.encapsulating_function_name)
208
203
  self.all_expert_answers.append((scope, function_source))
209
204
 
@@ -249,6 +244,8 @@ class Human(Expert):
249
244
  if user_input is not None:
250
245
  case_query.scope.update(loaded_scope)
251
246
  condition = CallableExpression(user_input, bool, scope=case_query.scope)
247
+ if self.answers_save_path is not None and not any(loaded_scope):
248
+ self.convert_json_answer_to_python_answer(case_query, user_input, condition, PromptFor.Conditions)
252
249
  else:
253
250
  user_input, condition = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conditions, prompt_str=data_to_show)
254
251
  if user_input == 'exit':
@@ -260,6 +257,20 @@ class Human(Expert):
260
257
  case_query.conditions = condition
261
258
  return condition
262
259
 
260
+ def convert_json_answer_to_python_answer(self, case_query: CaseQuery, user_input: str,
261
+ callable_expression: CallableExpression,
262
+ prompt_for: PromptFor):
263
+ case_query.scope['case'] = case_query.case
264
+ tfc = TemplateFileCreator(case_query, prompt_for=prompt_for)
265
+ code = tfc.build_boilerplate_code()
266
+ if user_input.startswith('def'):
267
+ user_input = '\n'.join(user_input.split('\n')[1:])
268
+ user_input = indent(dedent(user_input), " " * 4).strip()
269
+ code = code.replace('pass', user_input)
270
+ else:
271
+ code = code.replace('pass', f"return {user_input}")
272
+ self.save_answers(expert_answers=[({}, code)])
273
+
263
274
  def ask_for_conclusion(self, case_query: CaseQuery) -> Optional[CallableExpression]:
264
275
  """
265
276
  Ask the expert to provide a conclusion for the case.
@@ -279,13 +290,17 @@ class Human(Expert):
279
290
  expression = CallableExpression(expert_input, case_query.attribute_type,
280
291
  scope=case_query.scope,
281
292
  mutually_exclusive=case_query.mutually_exclusive)
293
+ if self.answers_save_path is not None and not any(loaded_scope):
294
+ self.convert_json_answer_to_python_answer(case_query, expert_input, expression,
295
+ PromptFor.Conclusion)
282
296
  except IndexError:
283
297
  self.use_loaded_answers = False
284
298
  if not self.use_loaded_answers:
285
299
  data_to_show = None
286
300
  if self.user_prompt.viewer is None:
287
301
  data_to_show = show_current_and_corner_cases(case_query.case)
288
- expert_input, expression = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conclusion, prompt_str=data_to_show)
302
+ expert_input, expression = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conclusion,
303
+ prompt_str=data_to_show)
289
304
  if expert_input is None:
290
305
  self.all_expert_answers.append(({}, None))
291
306
  elif expert_input != 'exit':
@@ -230,7 +230,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
230
230
  num_rules: int = 0
231
231
  while not stop_iterating:
232
232
  for case_query in case_queries:
233
- pred_cat = self.fit_case(case_query, expert=expert, **kwargs_for_fit_case)
233
+ pred_cat = self.fit_case(case_query, expert=expert, clear_expert_answers=False,
234
+ **kwargs_for_fit_case)
234
235
  if case_query.target is None:
235
236
  continue
236
237
  target = {case_query.attribute_name: case_query.target(case_query.case)}
@@ -308,6 +309,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
308
309
  update_existing_rules: bool = True,
309
310
  scenario: Optional[Callable] = None,
310
311
  ask_now: Callable = lambda _: True,
312
+ clear_expert_answers: bool = True,
311
313
  **kwargs) \
312
314
  -> Union[CallableExpression, Dict[str, CallableExpression]]:
313
315
  """
@@ -319,7 +321,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
319
321
  :param update_existing_rules: Whether to update the existing same conclusion type rules that already gave
320
322
  some conclusions with the type required by the case query.
321
323
  :param scenario: The scenario at which the case was created, this is used to recreate the case if needed.
322
- :ask_now: Whether to ask the expert for refinements or alternatives.
324
+ :param ask_now: Whether to ask the expert for refinements or alternatives.
325
+ :param clear_expert_answers: Whether to clear expert answers after saving the new rule.
323
326
  :return: The category that the case belongs to.
324
327
  """
325
328
  if case_query is None:
@@ -348,7 +351,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
348
351
 
349
352
  if self.save_dir is not None:
350
353
  self.save()
351
- expert.clear_answers()
354
+ if clear_expert_answers:
355
+ expert.clear_answers()
352
356
 
353
357
  return fit_case_result
354
358
 
@@ -146,7 +146,8 @@ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = N
146
146
  def extract_function_source(file_path: str,
147
147
  function_names: List[str], join_lines: bool = True,
148
148
  return_line_numbers: bool = False,
149
- include_signature: bool = True) \
149
+ include_signature: bool = True,
150
+ as_list: bool = False) \
150
151
  -> Union[Dict[str, Union[str, List[str]]],
151
152
  Tuple[Dict[str, Union[str, List[str]]], Dict[str, Tuple[int, int]]]]:
152
153
  """
@@ -157,6 +158,8 @@ def extract_function_source(file_path: str,
157
158
  :param join_lines: Whether to join the lines of the function.
158
159
  :param return_line_numbers: Whether to return the line numbers of the function.
159
160
  :param include_signature: Whether to include the function signature in the source code.
161
+ :param as_list: Whether to return a list of function sources instead of dict (useful when there is multiple
162
+ functions with same name).
160
163
  :return: A dictionary mapping function names to their source code as a string if join_lines is True,
161
164
  otherwise as a list of strings.
162
165
  """
@@ -167,7 +170,9 @@ def extract_function_source(file_path: str,
167
170
  tree = ast.parse(source)
168
171
  function_names = make_list(function_names)
169
172
  functions_source: Dict[str, Union[str, List[str]]] = {}
173
+ functions_source_list: List[Union[str, List[str]]] = []
170
174
  line_numbers: Dict[str, Tuple[int, int]] = {}
175
+ line_numbers_list: List[Tuple[int, int]] = []
171
176
  for node in tree.body:
172
177
  if isinstance(node, ast.FunctionDef) and (node.name in function_names or len(function_names) == 0):
173
178
  # Get the line numbers of the function
@@ -175,16 +180,24 @@ def extract_function_source(file_path: str,
175
180
  func_lines = lines[node.lineno - 1:node.end_lineno]
176
181
  if not include_signature:
177
182
  func_lines = func_lines[1:]
178
- line_numbers[node.name] = (node.lineno, node.end_lineno)
179
- functions_source[node.name] = dedent("\n".join(func_lines)) if join_lines else func_lines
180
- if (len(functions_source) >= len(function_names)) and (not len(function_names) == 0):
181
- break
182
- if len(functions_source) < len(function_names):
183
+ if as_list:
184
+ line_numbers_list.append((node.lineno, node.end_lineno))
185
+ else:
186
+ line_numbers[node.name] = (node.lineno, node.end_lineno)
187
+ parsed_function = dedent("\n".join(func_lines)) if join_lines else func_lines
188
+ if as_list:
189
+ functions_source_list.append(parsed_function)
190
+ else:
191
+ functions_source[node.name] = parsed_function
192
+ if len(function_names) > 0:
193
+ if len(functions_source) >= len(function_names) or len(functions_source_list) >= len(function_names):
194
+ break
195
+ if len(functions_source) < len(function_names) and len(functions_source_list) < len(function_names):
183
196
  logger.warning(f"Could not find all functions in {file_path}: {function_names} not found, "
184
197
  f"functions not found: {set(function_names) - set(functions_source.keys())}")
185
198
  if return_line_numbers:
186
- return functions_source, line_numbers
187
- return functions_source
199
+ return functions_source if not as_list else functions_source_list, line_numbers if not as_list else line_numbers_list
200
+ return functions_source if not as_list else functions_source_list
188
201
 
189
202
 
190
203
  def encapsulate_user_input(user_input: str, func_signature: str, func_doc: Optional[str] = None) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.6.28
3
+ Version: 0.6.29
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
@@ -103,18 +103,24 @@ test/conf/world/handles_and_containers.py
103
103
  test/factories/__init__.py
104
104
  test/factories/world/__init__.py
105
105
  test/factories/world/handles_and_containers.py
106
+ test/test_expert_answers/__init__.py
106
107
  test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json
107
108
  test/test_expert_answers/drawer_cabinet_expert_answers_fit.json
108
109
  test/test_expert_answers/grdr_expert_answers_classify.json
109
110
  test/test_expert_answers/grdr_expert_answers_fit.json
111
+ test/test_expert_answers/grdr_expert_answers_fit.py
110
112
  test/test_expert_answers/grdr_expert_answers_fit_extra.json
111
113
  test/test_expert_answers/grdr_expert_answers_fit_no_targets.json
114
+ test/test_expert_answers/grdr_expert_answers_fit_no_targets.py
112
115
  test/test_expert_answers/mcrdr_expert_answers_classify.json
113
116
  test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json
117
+ test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.py
114
118
  test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json
119
+ test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.py
115
120
  test/test_expert_answers/mcrdr_extra_expert_answers_classify.json
116
121
  test/test_expert_answers/mcrdr_extra_expert_answers_fit.json
117
122
  test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json
123
+ test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.py
118
124
  test/test_expert_answers/mcrdr_stop_only_answers_fit.json
119
125
  test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json
120
126
  test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json
@@ -123,8 +129,11 @@ test/test_expert_answers/mutagenic_expert_answers.json
123
129
  test/test_expert_answers/relational_scrdr_expert_answers_classify.json
124
130
  test/test_expert_answers/scrdr_expert_answers_classify.json
125
131
  test/test_expert_answers/scrdr_expert_answers_fit.json
132
+ test/test_expert_answers/scrdr_expert_answers_fit.py
126
133
  test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json
134
+ test/test_expert_answers/scrdr_expert_answers_fit_no_targets.py
127
135
  test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json
136
+ test/test_expert_answers/scrdr_multi_line_expert_answers_fit.py
128
137
  test/test_expert_answers/scrdr_world_expert_answers_fit.json
129
138
  test/test_generated_rdrs/__init__.py
130
139
  test/test_helpers/__init__.py
@@ -0,0 +1,105 @@
1
+ from typing_extensions import Any, Callable, List, Optional, Tuple, Type
2
+ from test.datasets import Habitat, Species, load_zoo_cases
3
+ from ripple_down_rules.datastructures.case import Case
4
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
5
+ from ripple_down_rules.datastructures.enums import Category
6
+ from ripple_down_rules.experts import Human
7
+ from ripple_down_rules.rdr import GeneralRDR, MultiClassRDR, SingleClassRDR
8
+ from ripple_down_rules.utils import make_set
9
+ from test.test_helpers.helpers import get_fit_grdr, get_fit_mcrdr, get_fit_scrdr, get_habitat
10
+ from pandas.core.frame import DataFrame
11
+
12
+ def conditions_for_animal_habitats_of_type_habitat(case: DataFrame) -> bool:
13
+ """Get conditions on whether it's possible to conclude a value for Animal.habitats of type Habitat."""
14
+ # Write your code here
15
+ return case.species=='mammal' and case.aquatic==0
16
+
17
+
18
+
19
+ '===New Answer==='
20
+
21
+
22
+ from typing_extensions import Any, Callable, List, Optional, Tuple, Type
23
+ from test.datasets import Habitat, Species, load_zoo_cases
24
+ from ripple_down_rules.datastructures.case import Case
25
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
26
+ from ripple_down_rules.datastructures.enums import Category
27
+ from ripple_down_rules.experts import Human
28
+ from ripple_down_rules.rdr import GeneralRDR, MultiClassRDR, SingleClassRDR
29
+ from ripple_down_rules.utils import make_set
30
+ from test.test_helpers.helpers import get_fit_grdr, get_fit_mcrdr, get_fit_scrdr, get_habitat
31
+ from pandas.core.frame import DataFrame
32
+
33
+ def conditions_for_animal_habitats_of_type_habitat(case: DataFrame) -> bool:
34
+ """Get conditions on whether it's possible to conclude a value for Animal.habitats of type Habitat."""
35
+ # Write your code here
36
+ return case.species=='fish'
37
+
38
+
39
+
40
+ '===New Answer==='
41
+
42
+
43
+ from typing_extensions import Any, Callable, List, Optional, Tuple, Type
44
+ from test.datasets import Habitat, Species, load_zoo_cases
45
+ from ripple_down_rules.datastructures.case import Case
46
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
47
+ from ripple_down_rules.datastructures.enums import Category
48
+ from ripple_down_rules.experts import Human
49
+ from ripple_down_rules.rdr import GeneralRDR, MultiClassRDR, SingleClassRDR
50
+ from ripple_down_rules.utils import make_set
51
+ from test.test_helpers.helpers import get_fit_grdr, get_fit_mcrdr, get_fit_scrdr, get_habitat
52
+ from pandas.core.frame import DataFrame
53
+
54
+ def conditions_for_animal_habitats_of_type_habitat(case: DataFrame) -> bool:
55
+ """Get conditions on whether it's possible to conclude a value for Animal.habitats of type Habitat."""
56
+ # Write your code here
57
+ return case.species=='bird' and case.legs>0
58
+
59
+
60
+
61
+ '===New Answer==='
62
+
63
+
64
+ from typing_extensions import Any, Callable, List, Optional, Tuple, Type
65
+ from test.datasets import Habitat, Species, load_zoo_cases
66
+ from ripple_down_rules.datastructures.case import Case
67
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
68
+ from ripple_down_rules.datastructures.enums import Category
69
+ from ripple_down_rules.experts import Human
70
+ from ripple_down_rules.rdr import GeneralRDR, MultiClassRDR, SingleClassRDR
71
+ from ripple_down_rules.utils import make_set
72
+ from test.test_helpers.helpers import get_fit_grdr, get_fit_mcrdr, get_fit_scrdr, get_habitat
73
+ from pandas.core.frame import DataFrame
74
+
75
+ def conditions_for_animal_habitats_of_type_habitat(case: DataFrame) -> bool:
76
+ """Get conditions on whether it's possible to conclude a value for Animal.habitats of type Habitat."""
77
+ # Write your code here
78
+ return case.species=='molusc' and case.aquatic==0
79
+
80
+
81
+
82
+ '===New Answer==='
83
+
84
+
85
+ from typing_extensions import Any, Callable, List, Optional, Tuple, Type
86
+ from test.datasets import Habitat, Species, load_zoo_cases
87
+ from ripple_down_rules.datastructures.case import Case
88
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
89
+ from ripple_down_rules.datastructures.enums import Category
90
+ from ripple_down_rules.experts import Human
91
+ from ripple_down_rules.rdr import GeneralRDR, MultiClassRDR, SingleClassRDR
92
+ from ripple_down_rules.utils import make_set
93
+ from test.test_helpers.helpers import get_fit_grdr, get_fit_mcrdr, get_fit_scrdr, get_habitat
94
+ from pandas.core.frame import DataFrame
95
+
96
+ def conditions_for_animal_habitats_of_type_habitat(case: DataFrame) -> bool:
97
+ """Get conditions on whether it's possible to conclude a value for Animal.habitats of type Habitat."""
98
+ # Write your code here
99
+ return case.species=='molusc' and case.aquatic==1
100
+
101
+
102
+
103
+ '===New Answer==='
104
+
105
+