def _find_matching_child(node, context):
        for child in node["children"]:
            property_name = child["decision_rule"]["property"]
            operand = child["decision_rule"]["operand"]
            operator = child["decision_rule"]["operator"]
            context_value = context.get(property_name)
            # If there is no context value:
            if context_value is None:
                raise CraftAiDecisionError(
                    """Unable to take decision, property '{}' is missing from the given context."""
                    .format(property_name))
            if (not isinstance(operator, six.string_types)
                    or not operator in OPERATORS.values()):
                raise CraftAiDecisionError(
                    """Invalid decision tree format, {} is not a valid"""
                    """ decision operator.""".format(operator))

            # To be compared, continuous parameters should not be strings
            if TYPES["continuous"] in operator:
                context_value = float(context_value)
                operand = float(operand)

            if OPERATORS_FUNCTION[operator](context_value, operand):
                return child
        return {}
Exemple #2
0
  def _distribution(node):
    # If it is a leaf
    if not (node.get("children") is not None and len(node.get("children"))):
      prediction = node["prediction"]
      value_distribution = prediction["distribution"]
      nb_samples = prediction["nb_samples"]
      # It is a classification problem
      if isinstance(value_distribution, list):
        return [value_distribution, nb_samples]

      # It is a regression problem
      predicted_value = prediction.get("value")
      if predicted_value is not None:
        return [predicted_value, nb_samples]

      raise CraftAiDecisionError(
        """Unable to take decision: the decision tree has no valid"""
        """ predicted value for the given context."""
      )

    # If it is not a leaf, we recurse into the children and store
    # the distributions/means and sizes of each child branch.
    def recurse(_child):
      return InterpreterV2._distribution(_child)
    values_sizes = map(recurse, node.get("children"))
    values, sizes = zip(*values_sizes)
    if isinstance(values[0], list):
      return InterpreterV2.compute_mean_distributions(values, sizes)
    return InterpreterV2.compute_mean_values(values, sizes)
Exemple #3
0
    def decide(tree, args):
        bare_tree, configuration, tree_version = Interpreter._parse_tree(tree)
        if configuration != {}:
            time = None if len(args) == 1 else args[1]
            context_result = Interpreter._rebuild_context(
                configuration, args[0], time)
            context = context_result["context"]
        else:
            context = Interpreter.join_decide_args(args)

        # Convert timezones as integers into standard +/hh:mm format
        # This should only happen when no time generated value is required
        context = Interpreter._convert_timezones_to_standard_format(
            configuration, context)

        if semver.match(tree_version, ">=1.0.0") and semver.match(
                tree_version, "<2.0.0"):
            decision = InterpreterV1.decide(configuration, bare_tree, context)
        elif semver.match(tree_version, ">=2.0.0") and semver.match(
                tree_version, "<3.0.0"):
            decision = InterpreterV2.decide(configuration, bare_tree, context)
        else:
            raise CraftAiDecisionError(
                """Invalid decision tree format, "{}" is currently not a valid version."""
                .format(tree_version))

        decision["context"] = context

        return decision
    def _check_context(configuration, context):
        # Extract the required properties (i.e. those that are not the output)
        expected_properties = [
            p for p in configuration["context"]
            if not p in configuration["output"]
        ]

        # Retrieve the missing properties
        missing_properties = [
            p for p in expected_properties if not p in context
        ]

        # Validate the values
        bad_properties = [
            p for p in expected_properties
            if p in context and not _VALUE_VALIDATORS[configuration["context"]
                                                      [p]["type"]](context[p])
        ]

        if missing_properties or bad_properties:
            missing_properties_messages = [
                "expected property '{}' is not defined".format(p)
                for p in missing_properties
            ]
            bad_properties_messages = [
                "'{}' is not a valid value for property '{}' of type '{}'".
                format(context[p], p, configuration["context"][p]["type"])
                for p in bad_properties
            ]

            raise CraftAiDecisionError(
                """Unable to take decision, the given context is not valid: {}."""
                .format(", ".join(missing_properties_messages +
                                  bad_properties_messages)))
    def _rebuild_context(configuration, state, time=None):
        # Model should come from _parse_tree and is assumed to be checked upon
        # already
        output = configuration["output"]
        context = configuration["context"]

        # We should not use the output key(s) to compare against
        configuration_ctx = {
            key: context[key]
            for (key, value) in context.items() if (key not in output)
        }

        # Check if we need the time object
        to_generate = []
        for prop in configuration_ctx.items():
            prop_name = prop[0]
            prop_attributes = prop[1]
            if prop_attributes["type"] in [
                    "time_of_day", "day_of_week", "day_of_month",
                    "month_of_year", "timezone"
            ]:
                # is_generated is at True, we must generate the time for the associated context property
                case_1 = "is_generated" in list(
                    prop_attributes.keys()) and prop[1]["is_generated"]
                # is_generated is not given, by default at True, so we must generate it as well
                case_2 = "is_generated" not in list(prop_attributes.keys())
                if case_1 or case_2:
                    to_generate.append(prop_name)

        # Raise an exception if a time object is not provided but needed
        if to_generate and not isinstance(time, Time):

            # Check for missings (not provided and need to be generated)
            missings = []
            for prop in to_generate:
                if prop not in list(state.keys()):
                    missings.append(prop_name)

            # Raise an error if some need to be generated but not provided and no Time object
            if missings:
                raise CraftAiDecisionError(
                    """you must provide a Time object to decide() because"""
                    """ context properties {} need to be generated.""".format(
                        missings))
            else:
                to_generate = []

        # Generate context properties which need to
        if to_generate:
            for prop in to_generate:
                state[prop] = time.to_dict()[configuration_ctx[prop]["type"]]

        # Rebuild the context with generated and non-generated values
        context = {
            feature: state.get(feature)
            for feature, properties in configuration_ctx.items()
        }

        return context
Exemple #6
0
 def join_decide_args(args):
     joined_args = {}
     for arg in args:
         if isinstance(arg, Time):
             joined_args.update(arg.to_dict())
         try:
             joined_args.update(arg)
         except TypeError:
             raise CraftAiDecisionError(
                 """Invalid context args, the given objects aren't dicts"""
                 """ or Time instances.""")
     return joined_args
Exemple #7
0
    def _parse_tree(tree_object):
        # Checking definition of tree_object
        if not isinstance(tree_object, dict):
            raise CraftAiDecisionError(
                "Invalid decision tree format, the given json is not an object."
            )

        # Checking version existence
        tree_version = tree_object.get("_version")
        if not tree_version:
            raise CraftAiDecisionError(
                """Invalid decision tree format, unable to find the version"""
                """ informations.""")

        # Checking version and tree validity according to version
        if re.compile(r"\d+.\d+.\d+").match(tree_version) is None:
            raise CraftAiDecisionError(
                """Invalid decision tree format, "{}" is not a valid version."""
                .format(tree_version))
        elif semver.match(tree_version, ">=1.0.0") and semver.match(
                tree_version, "<3.0.0"):
            if tree_object.get("configuration") is None:
                raise CraftAiDecisionError(
                    """Invalid decision tree format, no configuration found""")
            if tree_object.get("trees") is None:
                raise CraftAiDecisionError(
                    """Invalid decision tree format, no tree found.""")
            bare_tree = tree_object.get("trees")
            configuration = tree_object.get("configuration")
        else:
            raise CraftAiDecisionError(
                """Invalid decision tree format, {} is not a supported"""
                """ version.""".format(tree_version))
        return bare_tree, configuration, tree_version
Exemple #8
0
  def _find_matching_child(node, context, deactivate_missing_values=True):
    for child in node["children"]:
      property_name = child["decision_rule"]["property"]
      operand = child["decision_rule"]["operand"]
      operator = child["decision_rule"]["operator"]
      context_value = context.get(property_name)

      # If there is no context value:
      if context_value is None:
        if deactivate_missing_values:
          raise CraftAiDecisionError(
            """Unable to take decision, property '{}' is missing from the given context.""".
            format(property_name)
          )
      if (not isinstance(operator, six.string_types) or
          not operator in OPERATORS.values()):
        raise CraftAiDecisionError(
          """Invalid decision tree format, {} is not a valid"""
          """ decision operator.""".format(operator)
        )

      if OPERATORS_FUNCTION[operator](context_value, operand):
        return child
    return {}
    def _check_context(configuration, context):
        # Extract the required properties (i.e. those that are not the output)
        expected_properties = [
            p for p in configuration["context"]
            if not p in configuration["output"]
        ]

        # Retrieve the missing properties
        missing_properties = [
            p for p in expected_properties
            if not p in context or context[p] is None
        ]

        # Validate the values
        bad_properties = [
            p for p in expected_properties
            if not InterpreterV1.validate_property_value(
                configuration, context, p)
        ]

        if missing_properties or bad_properties:
            missing_properties = sorted(missing_properties)
            missing_properties_messages = [
                "expected property '{}' is not defined".format(p)
                for p in missing_properties
            ]
            bad_properties = sorted(bad_properties)
            bad_properties_messages = [
                "'{}' is not a valid value for property '{}' of type '{}'".
                format(context[p], p, configuration["context"][p]["type"])
                for p in bad_properties
            ]

            errors = missing_properties_messages + bad_properties_messages

            # deal with missing properties
            if errors:
                message = "Unable to take decision, the given context is not valid: " + errors.pop(
                    0)

                for error in errors:
                    message = "".join((message, ", ", error))
                message = message + "."

                raise CraftAiDecisionError(message)