from core.builtin_concepts import BuiltinConcepts from core.builtin_helpers import expect_one from core.global_symbols import EVENT_RULE_CREATED, EVENT_RULE_DELETED, EVENT_RULE_ID_DELETED from core.sheerka.services.sheerka_service import BaseService from sheerkarete.network import ReteNetwork DISABLED_RULES = "#disabled#" LOW_PRIORITY_RULES = "#low_priority#" class SheerkaEvaluateRules(BaseService): NAME = "EvaluateRules" def __init__(self, sheerka): # order must be before RuleManager because of event subscription super().__init__(sheerka, 4) self.evaluators_by_name = None self.network = ReteNetwork() def initialize(self): self.sheerka.bind_service_method(self.NAME, self.evaluate_format_rules, False, visible=False) self.sheerka.bind_service_method(self.NAME, self.evaluate_exec_rules, False, visible=False) self.reset_evaluators() self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_rule_created) self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_rule_deleted) self.sheerka.subscribe(EVENT_RULE_ID_DELETED, self.on_rule_deleted) self.sheerka.register_debug_vars(self.NAME, "evaluate_rules", "results") self.sheerka.register_debug_rules(self.NAME, "evaluate_rule", "*") def reset_evaluators(self): # instantiate evaluators, once for all, only keep when it's enabled evaluators = [e_class() for e_class in self.sheerka.evaluators] evaluators = [e for e in evaluators if e.enabled] self.evaluators_by_name = {e.short_name: e for e in evaluators} def evaluate_exec_rules(self, context, return_values): # self.network.add_obj("__rets", return_values) for ret in return_values: self.network.add_obj("__ret", ret) results = [] # list of return values, for activated rules for match in self.network.matches: for rule in match.pnode.rules: body = context.sheerka.new(BuiltinConcepts.RULE_EVALUATION_RESULT, rule=rule) return_value = context.sheerka.ret(self.NAME, True, body) results.append(return_value) return results def evaluate_format_rules(self, context, bag, disabled): return self.evaluate_rules(context, self.sheerka.get_format_rules(), bag, disabled) def evaluate_rules(self, context, rules, bag, disabled): """ evaluate the format rules, in the context of 'bag' CAUTION : the rules MUST be sorted by priority :param context: :param rules: :param bag: :param disabled: disabled rules (because they have already been fired or whatever) :return: { True : list of success, False :list of failed, '#disabled"': list of disabled...} """ with context.push(BuiltinConcepts.RULES_EVALUATION, bag, desc="Evaluating rules...") as sub_context: sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED) sub_context.add_inputs(bag=bag) debugger = sub_context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rules") debugger.debug_entering(bag=bag) results = {} sub_context.sheerka.add_many_to_short_term_memory(sub_context, bag) success_priority = None for rule in rules: if not rule.metadata.is_enabled or rule.id in disabled: results.setdefault(DISABLED_RULES, []).append(rule) continue if success_priority and rule.priority != success_priority: results.setdefault(LOW_PRIORITY_RULES, []).append(rule) continue res = self.evaluate_rule(sub_context, rule, bag) ok = res.status and self.sheerka.is_success(self.sheerka.objvalue(res)) results.setdefault(ok, []).append(rule) if ok and success_priority is None: success_priority = rule.priority debugger.debug_var("results", self.get_debug_format(results)) sub_context.add_values(rules_result=results) return results def evaluate_rule(self, context, rule, bag): """ Evaluate the conditions :param context: :param rule: :param bag: :return: """ results = self.evaluate_conditions(context, rule.compiled_conditions, bag) debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule", new_debug_id=False) debugger.debug_rule(rule, results) return expect_one(context, results) def evaluate_conditions(self, context, conditions, bag, missing_vars=None): """ Evaluate the conditions :param context: :param conditions: :param bag: variables that are supposed to be in short term memory :param missing_vars: if initialized to a set, keeps tracks of the missing variables :return: """ bag_variables = set(bag.keys()) for k, v in bag.items(): context.add_to_short_term_memory(k, v) results = [] for compiled_condition in conditions: if compiled_condition.variables.intersection(bag_variables) != compiled_condition.variables: if isinstance(missing_vars, set): missing_vars.update(compiled_condition.variables - bag_variables) continue if compiled_condition.not_variables.intersection(bag_variables): continue if compiled_condition.return_value is None: # We only want to test the existence of a data results.append(context.sheerka.ret(self.NAME, True, True)) else: # do not forget to reset the 'is_evaluated' in the case of a concept for concept in compiled_condition.concepts_to_reset: concept.get_hints().is_evaluated = False evaluator = self.evaluators_by_name[compiled_condition.evaluator_type] res = evaluator.eval(context, compiled_condition.return_value) value = context.sheerka.objvalue(res.body) if res.status and isinstance(value, bool) and value: # one successful value found. No need to look any further results = [res] # don't we care about the other failing results ? break else: results.append(res) return results def remove_from_rete_memory(self, lst): if lst is None: return for obj in lst: self.network.remove_obj(obj) def on_rule_created(self, context, rule): """ When a new rule is added to the system, update the network """ if rule.metadata.is_enabled and rule.rete_disjunctions: self.network.add_rule(rule) def on_rule_deleted(self, context, rule): """ When a rule is deleted from the system, remove it from the network """ if isinstance(rule, str): rule = self.sheerka.get_rule_by_id(rule) if not self.sheerka.is_known(rule): return self.network.remove_rule(rule) @staticmethod def get_debug_format(result): """ Return the same dictionary, the with the short formatting of the rules eg without the action clause :param result: :return: """ return {key: [str(r) if key == True else r.short_str() for r in rules] for key, rules in result.items()}