Simple Rule Engine

Simple Rule Engine is a Python Library that provides simple yet powerful Python APIs for composing rules.

Salient Features

  • Ability to declaratively author both Scoring and Decision Rules.

  • The library offers clear segregation of rules vs. facts. Once a rule has been composed, it can be used against any fact set and evaulated.

  • The library offers composable functional syntax that can be extended with various format adapters. For example, developers can compose their rules in SQL (persist them in the database) and use an adapter to compose rules and execute them against data. See here for an example of such an extension.

  • Ability to version control rule declarations thus enabling auditing of rule changes over a period of time.

  • Ability to author chained rules. Evaluation of one rule can refer to the result of another rule, thus enabling modular, hierarchical rules.

Rule Grammar

At it's heart, the simple rule engine is composed of the following constructs:

  • Token

  • Operator

  • Expression

  • Conditional

They are described below.

Token:

An entity that is representative of a fact, and also guides clients on the data type of fact to be supplied.

Types of Tokens:

  • NumericToken

  • StringToken

  • BooleanToken

  • RuleToken: A rule itself, composed as a Token

Example:

  • StringToken("pet") represents a token pet of type String.

  • NumericToken("age") represents a token age of type Numeric.

A rule can be a Token too. A RuleToken implements Token and composes a Rule. When asked for value, a RuleToken executes the rule it composes and provides the value.

A Token in itself is not of much value, but when combined with an Operator, allows the end user to compose rules.

Operator:

An Operator composes a base value and evaluates against a value supplied.

Example: Gte(35).evaulate(15) returns False. Gte(35.0).evaulate(40.0) returns True.

Expression:

An Expression composes a Token on the left hand side (LHS), Operator in the middle and the data to be evaulated on the right hand side (RHS).

Example:

Expression(NumericToken("cibil_score"), Between(floor=650, ceiling=800)) represents an evaluation that compares whether the fact cibil_score is between 650 and 800 or not.

Expression(NumericToken("cibil_score"), Between(floor=650, ceiling=800)).evaluate(dict(cibil_score=700)) evaluates to True.

An Expression is at the core of simple-rule-engine. Multiple Expressions strung together with And or Or conditions make up for a full-fledged rule set.

Conditional:

  • A Conditional composes a list of Expressions.

  • WhenAll evaluates to True when all expressions evaluate to True.

  • WhenAny evaluates to True when any expression evaluates to True.

  • A Conditional can compose a Conditional - this enables clients to express complex conditions.

  • A Conditional can be further extended to implement NotWhenAll or NotWhenAny etc. as well.

Example:

cibil_score_between_650_800 = Expression(
    NumericToken("cibil_score"), 
    Between(floor=650, ceiling=800)
    )
marital_status_in_married_unspecified = Expression(
    StringToken("marital_status"), 
    In("Married", "Unspecified")
    )
business_owned_by_self_family = Expression(
    StringToken("business_ownership"), 
    In("Owned by Self", "Owned by Family")
    )

# A Conditional that composes three expressions into an and condition
WhenAll(
    cibil_score_between_650_800,
    marital_status_in_married_unspecified,
    business_owned_by_self_family
)

# A Conditional composing another Conditional. The below statement is equivalent of 
# WHERE applicant_age >= 35 AND ( business_ownership in ('SELF', 'FAMILY') OR applicant_ownership in ('SELF', 'FAMILY') )
WhenAll(
    applicant_age_gte_35,
    WhenAny(
        business_owned_by_self_family,
        applicant_owned_by_self_family
    )
),

With the use of Conditional and Expression, you can weave any complex rule.

Rule Row:

  • A RuleRow composes a Conditional (as an antecedent (i.e. when)) and specifies a consequent (result (i.e. then)) when the antecedent evaluates to True.

For a Score, the consequent must be a float.

For a Decision, the consequent can be anything.

Rule Set:

  • A Rule Set composes a set of RuleRows.

For a score, each rule set carries a weight and the total weight of all rule sets must be equal to 1.

For a decision, there must be only one rule set.

Rule:

  • A rule composes one or many Rule sets.

For a score, the total score is calculated as sum(rule set score * weight).

A rule exposes get_token_dict_structure function that returns a dictionary of all facts required for the rule to be executed successfully.

Last updated