Home
Softono

Simple Rule Engine Client

Open source MIT Python
21
Stars
6
Forks
0
Issues
1
Watchers
3 years
Last Commit

 About Simple Rule Engine Client

An extension to simple-rule-engine that illustrates how rules can be declaratively specified (json etc.) and de-serialized into simple-rule-engine constructs.

Platforms

Web Self-hosted

Languages

Python

Need Help Installing Simple Rule Engine Client?

We provide expert installation service for this software. Our team will install, configure, and secure Simple Rule Engine Client on your server. plans start at just $30.

Simple Rule Engine Client

View on GitHub

simple-rule-engine-client

An extension to Simple Rule Engine that illustrates how rules can be declaratively specified (json, yaml, custom grammar etc.), stored, and later de-serialized into simple-rule-engine constructs and executed with data.

CircleCI

Table of Contents

Examples

A simple decision tree involving facts

Decision matrix

Bureau Score Business Ownership Decision
between 650 and 800 in [Owned by Self, Owned by Family] GO

JSON Rule specification

{
    "RuleDecision": {
        "RuleRows": [
            {
                "WhenAll": [
                    {
                        "NumericToken": "cibil_score",
                        "Between": {
                            "floor": 650,
                            "ceiling": 800
                        }
                    },
                    {
                        "StringToken": "business_ownership",
                        "In": [
                            "Owned by Self",
                            "Owned by Family"
                        ]
                    }
                ],
                "Consequent": "GO"
            }
        ]
    }
}

Test Harness

from unittest import TestCase

from services.adapter.simple_rule_engine_dict_adapter import SimpleRuleEngineDictAdapter
from services.util.json_file_util import JsonFileUtil


class TestSimpleRuleEngineAdapter(TestCase):
    def test_rule_simple_decision(self):
        json_file_util = JsonFileUtil(file_name_with_path="./examples/simple_decision.json")
        decision_rule_dict = json_file_util.read_file()

        rule_engine_adapter = SimpleRuleEngineDictAdapter(rule_dict=decision_rule_dict)
        decision_rule = rule_engine_adapter.get_rule()

        assert type(decision_rule).__name__ == "RuleDecision"

        fact = dict(cibil_score=700, business_ownership="Owned by Self")
        assert decision_rule.execute(token_dict=fact) == "GO"

A simple scoring rule

Scoring Rule

  • If age >= 35 and pet in dog, score is 10, with a weight of 0.5
  • If domicile is in KA, score is 5, with a weight of 0.5

JSON Rule specification

{
  "RuleScore": {
    "RuleSets": [
      {
        "RuleRows": [
          {
            "WhenAll": [
              {
                "NumericToken": "age",
                "Gte": 35
              },
              {
                "StringToken": "pet",
                "In": [
                  "dog"
                ]
              }
            ],
            "Consequent": 10
          }
        ],
        "Weight": 0.5
      },
      {
        "RuleRows": [
          {
            "WhenAll": [
              {
                "StringToken": "domicile",
                "In": [
                  "KA"
                ]
              }
            ],
            "Consequent": 5
          }
        ],
        "Weight": 0.5
      }
    ]
  }
}

Test Harness

from unittest import TestCase

from services.adapter.simple_rule_engine_dict_adapter import SimpleRuleEngineDictAdapter
from services.util.json_file_util import JsonFileUtil


class TestSimpleRuleEngineAdapter(TestCase):
    def test_rule_simple_score(self):
        json_file_util = JsonFileUtil(file_name_with_path="./examples/simple_score.json")
        score_rule_dict = json_file_util.read_file()

        rule_engine_adapter = SimpleRuleEngineDictAdapter(rule_dict=score_rule_dict)
        score_rule = rule_engine_adapter.get_rule()

        assert type(score_rule).__name__ == "RuleScore"

        fact = dict(age=40, pet="dog", domicile="TN")
        assert score_rule.execute(token_dict=fact) == 5.0

        fact = dict(age=40, pet="dog", domicile="KA")
        assert score_rule.execute(token_dict=fact) == 7.5

Custom SQL Like Rule Grammar

Here's an illustration of a rule that's based on a custom grammar written in EBNF and parsed by Lark.

Sample Rule

my_rule {
    when {
        cibil_score between 650 and 750 and 
        age > 35 and 
        house_ownership in (owned, rented) and
        (
            total_overdue_amount == 0 or 
            number_of_overdue_loans < 2 or
            (
                number_of_overdue_loans >= 2 and
                big_shot == true
            )
        ) and
        pet == dog
    }
    then true
    when {
        cibil_score < 650
    }
    then false
}

Parse Tree

start
  decisionrule
    my_rule
    rulerow
      when
      condition
        expression
          token	cibil_score
          between
          number	650
          number	750
        conditional	and
        expression
          token	age
          gt
          number	35
        conditional	and
        expression
          token	house_ownership
          in
          word_list
            owned
            rented
        conditional	and
        expression
          expression
            token	total_overdue_amount
            eq
            number	0
          conditional	or
          expression
            token	number_of_overdue_loans
            lt
            number	2
          conditional	or
          expression
            expression
              token	number_of_overdue_loans
              gte
              number	2
            conditional	and
            expression
              token	big_shot
              eq
              boolean	true
        conditional	and
        expression
          token	pet
          eq
          string	dog
      then
      decision
        boolean	true
    rulerow
      when
      condition
        expression
          token	cibil_score
          lt
          number	650
      then
      decision
        boolean	false

Test Harness

import pytest
from lark import Lark

from services.adapter.simple_rule_engine_lark_tree_adapter import SimpleRuleEngineLarkTreeAdapter


@pytest.fixture
def decision_rule_grammar():
    with open("./decision_rule.lark") as rule_grammar_file:
        rule_grammar = rule_grammar_file.read()

    return rule_grammar

def test_rule_complex_decision(decision_rule_grammar):
    parser = Lark(decision_rule_grammar)

    custom_rule = """
        my_rule {
            when {
                cibil_score between 650 and 750 and 
                age > 35 and 
                house_ownership in (owned, rented) and
                (
                    total_overdue_amount == 0 or 
                    number_of_overdue_loans < 2 or
                    (
                        number_of_overdue_loans >= 2 and
                        big_shot == true
                    )
                ) and
                pet == dog
            }
            then true
            when {
                cibil_score < 650
            }
            then false
        }
        """

    tree = parser.parse(custom_rule)
    print(tree.pretty())

    decision_rule = SimpleRuleEngineLarkTreeAdapter(tree).get_rule()

    # Evaluate the Decision Rule by passing data
    facts = dict(
        cibil_score=700,
        age=40,
        house_ownership="owned",
        total_overdue_amount=0,
        pet="dog"
    )
    assert decision_rule.execute(token_dict=facts) is True

    facts = dict(
        cibil_score=700,
        age=40,
        house_ownership="owned",
        total_overdue_amount=100,
        number_of_overdue_loans=1,
        pet="dog"
    )
    assert decision_rule.execute(token_dict=facts) is True

    facts = dict(
        cibil_score=700,
        age=40,
        house_ownership="owned",
        total_overdue_amount=100,
        number_of_overdue_loans=2,
        big_shot="true",
        pet="dog"
    )
    assert decision_rule.execute(token_dict=facts) is True

    facts = dict(
        cibil_score=600,
        age=40,
        house_ownership="owned",
        total_overdue_amount=100,
        number_of_overdue_loans=2,
        big_shot="false",
        pet="dog"
    )
    assert decision_rule.execute(token_dict=facts) is False