• Stars
    star
    891
  • Rank 51,222 (Top 2 %)
  • Language
    Python
  • License
    MIT License
  • Created over 10 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Python DSL for setting up business intelligence rules that can be configured without code

business-rules

As a software system grows in complexity and usage, it can become burdensome if every change to the logic/behavior of the system also requires you to write and deploy new code. The goal of this business rules engine is to provide a simple interface allowing anyone to capture new rules and logic defining the behavior of a system, and a way to then process those rules on the backend.

You might, for example, find this is a useful way for analysts to define marketing logic around when certain customers or items are eligible for a discount or to automate emails after users enter a certain state or go through a particular sequence of events.

Usage

1. Define Your set of variables

Variables represent values in your system, usually the value of some particular object. You create rules by setting threshold conditions such that when a variable is computed that triggers the condition some action is taken.

You define all the available variables for a certain kind of object in your code, and then later dynamically set the conditions and thresholds for those.

For example:

class ProductVariables(BaseVariables):

    def __init__(self, product):
        self.product = product

    @numeric_rule_variable
    def current_inventory(self):
        return self.product.current_inventory

    @numeric_rule_variable(label='Days until expiration')
    def expiration_days(self)
        last_order = self.product.orders[-1]
        return (last_order.expiration_date - datetime.date.today()).days

    @string_rule_variable()
    def current_month(self):
        return datetime.datetime.now().strftime("%B")

    @select_rule_variable(options=Products.top_holiday_items())
    def goes_well_with(self):
        return products.related_products

2. Define your set of actions

These are the actions that are available to be taken when a condition is triggered.

For example:

class ProductActions(BaseActions):

    def __init__(self, product):
        self.product = product

    @rule_action(params={"sale_percentage": FIELD_NUMERIC})
    def put_on_sale(self, sale_percentage):
        self.product.price = (1.0 - sale_percentage) * self.product.price
        self.product.save()

    @rule_action(params={"number_to_order": FIELD_NUMERIC})
    def order_more(self, number_to_order):
        ProductOrder.objects.create(product_id=self.product.id,
                                    quantity=number_to_order)

If you need a select field for an action parameter, another -more verbose- syntax is available:

class ProductActions(BaseActions):

    def __init__(self, product):
        self.product = product

    @rule_action(params=[{'fieldType': FIELD_SELECT,
                          'name': 'stock_state',
                          'label': 'Stock state',
                          'options': [
                            {'label': 'Available', 'name': 'available'},
                            {'label': 'Last items', 'name': 'last_items'},
                            {'label': 'Out of stock', 'name': 'out_of_stock'}
                        ]}])
    def change_stock_state(self, stock_state):
        self.product.stock_state = stock_state
        self.product.save()

3. Build the rules

A rule is just a JSON object that gets interpreted by the business-rules engine.

Note that the JSON is expected to be auto-generated by a UI, which makes it simple for anyone to set and tweak business rules without knowing anything about the code. The javascript library used for generating these on the web can be found here.

An example of the resulting python lists/dicts is:

rules = [
# expiration_days < 5 AND current_inventory > 20
{ "conditions": { "all": [
      { "name": "expiration_days",
        "operator": "less_than",
        "value": 5,
      },
      { "name": "current_inventory",
        "operator": "greater_than",
        "value": 20,
      },
  ]},
  "actions": [
      { "name": "put_on_sale",
        "params": {"sale_percentage": 0.25},
      },
  ],
},

# current_inventory < 5 OR (current_month = "December" AND current_inventory < 20)
{ "conditions": { "any": [
      { "name": "current_inventory",
        "operator": "less_than",
        "value": 5,
      },
    ]},
      { "all": [
        {  "name": "current_month",
          "operator": "equal_to",
          "value": "December",
        },
        { "name": "current_inventory",
          "operator": "less_than",
          "value": 20,
        }
      ]},
  },
  "actions": [
    { "name": "order_more",
      "params":{"number_to_order": 40},
    },
  ],
}]

Export the available variables, operators and actions

To e.g. send to your client so it knows how to build rules

from business_rules import export_rule_data
export_rule_data(ProductVariables, ProductActions)

that returns

{"variables": [
    { "name": "expiration_days",
      "label": "Days until expiration",
      "field_type": "numeric",
      "options": []},
    { "name": "current_month",
      "label": "Current Month",
      "field_type": "string",
      "options": []},
    { "name": "goes_well_with",
      "label": "Goes Well With",
      "field_type": "select",
      "options": ["Eggnog", "Cookies", "Beef Jerkey"]}
                ],
  "actions": [
    { "name": "put_on_sale",
      "label": "Put On Sale",
      "params": {"sale_percentage": "numeric"}},
    { "name": "order_more",
      "label": "Order More",
      "params": {"number_to_order": "numeric"}}
  ],
  "variable_type_operators": {
    "numeric": [ {"name": "equal_to",
                  "label": "Equal To",
                  "input_type": "numeric"},
                 {"name": "less_than",
                  "label": "Less Than",
                  "input_type": "numeric"},
                 {"name": "greater_than",
                  "label": "Greater Than",
                  "input_type": "numeric"}],
    "string": [ { "name": "equal_to",
                  "label": "Equal To",
                  "input_type": "text"},
                { "name": "non_empty",
                  "label": "Non Empty",
                  "input_type": "none"}]
  }
}

Run your rules

from business_rules import run_all

rules = _some_function_to_receive_from_client()

for product in Products.objects.all():
    run_all(rule_list=rules,
            defined_variables=ProductVariables(product),
            defined_actions=ProductActions(product),
            stop_on_first_trigger=True
           )

API

Variable Types and Decorators:

The type represents the type of the value that will be returned for the variable and is necessary since there are different available comparison operators for different types, and the front-end that's generating the rules needs to know which operators are available.

All decorators can optionally take a label:

  • label - A human-readable label to show on the frontend. By default we just split the variable name on underscores and capitalize the words.

The available types and decorators are:

numeric - an integer, float, or python Decimal.

@numeric_rule_variable operators:

  • equal_to
  • greater_than
  • less_than
  • greater_than_or_equal_to
  • less_than_or_equal_to

Note: to compare floating point equality we just check that the difference is less than some small epsilon

string - a python bytestring or unicode string.

@string_rule_variable operators:

  • equal_to
  • starts_with
  • ends_with
  • contains
  • matches_regex
  • non_empty

boolean - a True or False value.

@boolean_rule_variable operators:

  • is_true
  • is_false

select - a set of values, where the threshold will be a single item.

@select_rule_variable operators:

  • contains
  • does_not_contain

select_multiple - a set of values, where the threshold will be a set of items.

@select_multiple_rule_variable operators:

  • contains_all
  • is_contained_by
  • shares_at_least_one_element_with
  • shares_exactly_one_element_with
  • shares_no_elements_with

Returning data to your client

Contributing

Open up a pull request, making sure to add tests for any new functionality. To set up the dev environment (assuming you're using virtualenvwrapper):

$ python -m virtualenv venv
$ source ./venv/bin/activate
$ pip install -r dev-requirements.txt -e .
$ pytest

Alternatively, you can also use Tox:

$ pip install "tox<4"
$ tox -p auto --skip-missing-interpreters

More Repositories

1

synx

A command-line tool that reorganizes your Xcode project folder to match your Xcode groups
Ruby
6,082
star
2

Static

Simple static table views for iOS in Swift.
Swift
1,250
star
3

VENTouchLock

A Touch ID and Passcode framework used in the Venmo app.
Objective-C
965
star
4

VENTokenField

Easy-to-use token field that is used in the Venmo app.
Objective-C
793
star
5

VENCalculatorInputView

Calculator keyboard used in the Venmo iOS app
Objective-C
763
star
6

DVR

Network testing for Swift
Swift
651
star
7

tooltip-view

Dead simple Android Tooltip Views
Java
486
star
8

DryDock-iOS

DEPRECATED: An open-source internal installer app
433
star
9

VENSeparatorView

Jagged border separators on UIViews used in the Venmo app.
Objective-C
379
star
10

VENVersionTracker

Objective-C
300
star
11

business-rules-ui

A JavaScript library for building out the logic and UI for business rules.
JavaScript
197
star
12

VENPromotionsManager

iOS Library to perform location & time-based promotions.
Objective-C
197
star
13

cursor-utils

A library that encapsulates the repeatable actions of Android Cursors.
Java
195
star
14

venmo-ios-sdk

Make and accept payments in your iOS app via Venmo
Objective-C
176
star
15

react-html-document

A foundational React component useful for rendering full html documents on the server.
JavaScript
155
star
16

VENExperimentsManager

An Objective-C library enabling easy implementation of feature experiments on iOS allowing users to opt in and out of experiments at will.
Objective-C
76
star
17

slouch

A lightweight Python framework for building cli-inspired Slack bots.
Python
71
star
18

xcode_server

Xcode Bot client
Ruby
58
star
19

android-pin

An easy drop-in PIN controller for Android
Java
42
star
20

app-switch-android

Java
35
star
21

VENCore

Core Objective-C client library for the Venmo API
Objective-C
27
star
22

QuizTrain

Swift Framework for TestRail's API
Swift
19
star
23

feature_ramp

Toggling and ramping features via a lightweight Redis backend.
Python
18
star
24

btnamespace

A Python library to isolate state on the Braintree sandbox during testing.
Python
17
star
25

flaskeleton

Python
13
star
26

swaggergenerator

Create swagger / OpenAPI schemas from example interactions.
Python
11
star
27

tornado-stub-client

A stub library for making requests with tornado's AsyncHTTPClient
Python
9
star
28

venmo.github.io

Old Venmo Engineering Blog
CSS
8
star
29

nose-detecthttp

A nose plugin that can detect tests making external http calls.
Python
7
star
30

puppet-consulr

Dynamic puppet manifests using consul
Ruby
5
star
31

puppet-sentry

Puppet module for managing the Sentry realtime event logging and aggregation platform
Ruby
4
star
32

python3-avro

Copies the python3 client implementation from our fork of apache's avro project.
Python
4
star
33

nose-seed-faker

A nose plugin that seeds the faker package
Python
4
star
34

QuizTrainExample

Example of how to use QuizTrain with Unit Tests and UI Tests on iOS
Swift
3
star
35

single-click-highlightable

HOC for React that allows users to highlight text on elements without triggering the onClick handler
JavaScript
3
star
36

puppet-hound

Puppet hound module
Puppet
2
star
37

nose-timeout

Nose plugin for aborting long running tests
Python
1
star
38

foundations-interview

This is a test repository used in Foundations team interview process
JavaScript
1
star