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:
record_id
first_name
last_name
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
- 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.
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.