rule

Functions/classes for Rule Engine

Overview

The rule module is intended to evaluate rule(s) against record(s) to determine if there are any matches, and which rules matched. This can be useful when you have a list of records and want to perform targeted filtering (potentially complex) for the purposes of segmentation or targeting.

The rule is structured in the following format:

{
    "CONDITION": {
        "OPERATOR": {
            "RECORD_KEY": "VALUE"
        }
    }
}

where CONDITION is either “AND” or “OR” and used to combine multiple operators. The OPERATOR is one of:

  • ==: Equals

  • !=: Not Equals

  • <=: Less than or equal to

  • >=: Greater than or equal to

  • <: Less than

  • >: Greater than

  • in: Value is in a list of possible values

  • !in: Value is not in a list of possible values

RECORD_KEY is the dictionary key in the list of records to retrieve the value from and value is a single value to compare to.

Note

If OPERATOR is in or !in, then VALUE should be a list of values

The rules can support layered logic, for example:

RULE = {
    "AND": {
        "OR": {
            "==": [
                {
                    "key1": "test"
                },
                {
                    "key1": None
                }
            ]
        },
        {
            "!=": {
                "key2": None
            }
        }
    }
}

This layered logic can continue further to create extremely complex rules as long as it follows the same formatting of CONDITION, OPERATOR, RECORD_KEY, VALUE.

To simplify usage, if there is only one OPERATOR, you can exclude the CONDITION. For example:

RULE = {
    "==": {
        "key1": None
    }
}

The rule engine will validate the structure of your rules as you add them and raise an error if any are formatted incorrectly. Additionally, you can use the elevaso_spine.rule.engine.RuleEngine.explain_rule() to have a specific rule output in simplified terms.

engine

RuleEngine

The elevaso_spine.rule.engine.RuleEngine() class provides a way to perform evaluation against a set or rules and records.

For example, you have multiple records in dictionary format that contain the following keys:

  1. record_id

  2. first_name

  3. last_name

  4. state

You want to find all individuals with the first name of John living in the state of Utah. Separately, you want to find all the individuals living in the state of Nevada.

The rules would look like:

[
    {
        "rule_num": 1,
        "rule": {
        "AND": {
                "==": [
                    {
                        "first_name": "John"
                    },
                    {
                        "state": "Utah"
                    }
                ]
            }
        }
    },
    {
        "rule_num": 2,
        "rule": {
        "==": {
                "state": "Nevada"
            }
        }
    }
]

Your code would look like the following:

from elevaso_spine.rule.engine import RuleEngine

RULES = [
    {
        "rule_num": 1,
        "rule": {
        "AND": {
                "==": [
                    {
                        "first_name": "John"
                    },
                    {
                        "state": "Utah"
                    }
                ]
            }
        }
    },
    {
        "rule_num": 2,
        "rule": {
        "==": {
                "state": "Nevada"
            }
        }
    }
]

RECORDS = [
    {
        "record_id": 1,
        "first_name": "John",
        "last_name": "Smith",
        "state": "Utah",
    },
    {
        "record_id": 2,
        "first_name": "John",
        "last_name": "Johnson",
        "state": "Nevada",
    },
    {
        "record_id": 3,
        "first_name": "Jane",
        "last_name": "Smith",
        "state": "Nevada",
    },
]

rule_engine = RuleEngine()

rule_engine.add_rule_set(RULES)

rule_engine.add_record_set(RECORDS)

rule_engine.eval()

print(rule_engine.output)

This will print the following:

[
    (
        {
            'record_id': 1,
            'first_name': 'John',
            'last_name': 'Smith',
            'state': 'Utah'
        },
        [1]
    ),
    (
        {
            'record_id': 2,
            'first_name': 'John',
            'last_name': 'Johnson',
            'state': 'Nevada'
        },
        [2]
    ),
    (
        {
            'record_id': 3,
            'first_name': 'Jane',
            'last_name': 'Smith',
            'state': 'Nevada'
        },
        [2]
    )
]

The output contains a list of tuple results where the tuple contains the original record and a list of rule numbers that match.

class elevaso_spine.rule.engine.RuleEngine(**kwargs)[source]

A class to evaluate a set of records against a complex set of rules to identify matches

__init__(**kwargs)[source]

Initialize an instance of the class

Kwargs:

case_sensitive (bool, Optional): True/False if values should be case sensitive, defaults to True

trim_strings (bool, Optional): True/False if strings should be trimmed (remove head/trail spaces) before comparing rules, defaults to True

empty_equals_none (bool, Optional): True/False if an empty string (even after trimming [if set]) == None or Null, defaults to True

eval_if_no_key_found (bool, Optional): True/False if the rule should be evaluated even if the key does not exist in the record, defaults to True

default_value_no_key_found (object, Optional): Default value to use if the key does not exist in the record (only applies if eval_if_no_key_found == True), defaults to None

exit_on_error (bool, Optional): True/False if RuleEngine class should raise exception and exit on error, defaults to True

add_record(record: dict)[source]

Add a single record

Args:

record (dict): Single record in dictionary format

add_record_set(record_set: list)[source]

Add list of records to the rule engine

Args:

record_set (list): List of records in dictionary format

add_rule(rule: dict, rule_num: str = None)[source]

Add a single rule

Args:

rule (dict): Single rule in dictionary format

rule_num (str, Optional): Rule number, defaults to the order number when added

add_rule_set(rule_set: list)[source]

Add a list of rules pre-formatted

Args:

rule_set (list): List of dictionary containing rule_num, and rule

Raises:

TypeError if rule_set is not in list format

eval()[source]

Perform evaluation of rule_set against record_set

explain_rule(rule_num: object) str[source]

Explain a single rule in simple terms

Args:

rule_num (object): Rule number to explain

Returns:

str representing the explained rule

property output: list

Return output from eval function

Returns:

list of tuple containing the record and list of rules matched

property record_len: int

Number of records in the engine

Returns:

int representing the number of records

property rule_len: int

Number of rules in the engine

Returns:

int representing the number of rules

operators

contains

The elevaso_spine.rule.operators.contains() function checks if the value exists in the values list and supports the in operator of rule engine.

elevaso_spine.rule.operators.contains(value: str, values: list) bool[source]

Check if a value is in a list

Args:

values (list): List of values to check

value (str): Value to check in the list

Returns:

bool if value is in list

not_contains

The elevaso_spine.rule.operators.not_contains() function checks if the value exists in the values list and supports the !in operator of rule engine.

elevaso_spine.rule.operators.not_contains(value: str, values: list) bool[source]

Check if a value is not in a list

Args:

values (list): List of values to check

value (str): Value to check in the list

Returns:

bool if value is not in list