Skip to content

raiderrobert/amino

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

amino

A toolkit and DSL for custom rules engines. Much focus has been given toward AI and machine learning tooling to help take humans out of the loop. However, there are exist a wide variety of current and future applications for custom rules engines.

We propose inverting the problem space and placing a schema at the center, not unlike how GraphQL has done so for APIs.

Amino has three parts:

  • a schema definition like graphql or protobuf for the data space it operates on
  • a pre-built small and extensible DSL for conditional logic to operate on these schemas
  • a runtime to evaluate the rules against the data set

How to Use

Basic Example

Declare a schema

schema.amn

amount: int
state_code: str

Import schema and evaluate a rule to see if it matches the matching variables passed in.

>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> amn.eval("amount > 0 and state_code = 'CA'", {"amount": 100, "state_code": "CA"})
True
>>> amn.eval("amount > 0 and state_code = 'CA'", {"amount": 0, "state_code": "CA"})
False

More likely Runtime Example

Declare a schema

schema.amn

amount: int
state_code: str

Import schema and use it in your code. ( Note: You don't need to specify id for just one data set or one rule, but you do need an id for more than one of either, and each id must unique. )

>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> compiled = amn.compile(
...    [
...        {"id": 1, "rule":"amount > 0 and state_code = 'CA'"},
...        {"id": 2, "rule":"amount > 10 and state_code = 'CA'"},
...        {"id": 3, "rule":"amount >= 100"},
...    ]
... )
>>> compiled.eval([
...    {"id": 45, "amount": 100, "state_code": "CA"},
...    {"id": 46, "amount": 50, "state_code": "CA"},
...    {"id": 47, "amount": 100, "state_code": "NY"},
...    {"id": 48, "amount": 10, "state_code": "NY"},
... ])

[
    {"id": 45, "results": [1, 2, 3]}, 
    {"id": 46, "results": [1, 2]},
    {"id": 47, "results": [3]},
    {"id": 48, "results": []},
]

We also support returning just one match.

>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> compiled = amn.compile(
...    [
...        {"id": 1, "rule":"amount > 0 and state_code = 'CA'", "ordering": 3},
...        {"id": 2, "rule":"amount > 10 and state_code = 'CA'", "ordering": 2},
...        {"id": 3, "rule":"amount >= 100", "ordering": 1},
...    ],
...    match={"option": "first", "key": "ordering", "ordering": "asc"}
... )
>>> compiled.eval([
...    {"id": 100, "amount": 100, "state_code": "CA"},
...    {"id": 101, "amount": 50, "state_code": "CA"},
...    {"id": 102, "amount": 50, "state_code": "NY"},
... ])

[
    {"id": 100, "results": [3]}, 
    {"id": 101, "results": [2]},
    {"id": 102, "results": []}
]

Schema Elaboration

Comments

We support comments with the # symbol. Anything to the right of the comment symbol is disregarded at runtime.

schema.amn

# this is a comment
amount: int  # this is too
state_code: str

Structs

We support C-like structs with the struct keyword

schema.amn

struct applicant {
    state_code: str,
}

struct loan {
    amount: int
}

>>> data = {"loan": {"amount": 100}, "applicant": "state_code": "CA"
>>> rule = "loan.amount > 0 and applicant.state_code = 'CA'"
>>> amn.eval(rule, data)
True

Functions

We support function declarations; you declare the inputs and output, and you implement the function in your own language. These aren't true functions. It may be more appropriate to call it a foreign function interface declaration. That is, amino is the host language, and your implementation language in your project (e.g. Python, TypeScript, etc.) is the guest language.

schema.amn

amount: int
state_code: str

smallest_number: (int, int) -> int

Note the passing of min and passing it into the funcs argument while loading the schema. This provides the DSL host language access to calling out to the guest function min while the host function in the DSL uses smallest_number.

>>> amn = amino.load_schema("schema.amn", funcs={'smallest_number': min})
>>> data = {"amount": 100, "state_code": "CA"}
>>> rule = "smallest_number(amount, 1000) < 1000 and state_code = 'CA'"
>>> amn.eval(rule, data)
True

Default Arguments

Functions also support more complex cases, such as referencing other variables in the schema:

schema.amn

COMPANY_MAX_LOAN_AMT: int = 100_000

loan_amount: int
approved_amount: int
state_code: str

within_tolerances: (COMPANY_MAX_LOAN_AMT)(loan_amount, approved_amount) -> bool

Note the passing of within_tolerances and passing it into the funcs argument while loading the schema.

At runtime, three variables in the order provided will be passed to within_tolerances

>>> import custom_module
>>> amn = amino.load_schema("schema.amn", {'within_tolerances': custom_module.within_tolerances})
>>> data = {"amount": 100, "state_code": "CA"}
>>> rule = "within_tolerances(10_000, 90_000) and state_code = 'CA'"
>>> amn.eval(rule, data)
True

Lists

We support homogenous or heterogeneous arrays with the list keyword

schema.amn

state_code: str
amounts: list[int]
things: list[int|str|float]
>>> data = {"amount": 100, "state_code": "CA", "things": ["CA", 1, 1.0] }
>>> rule = "amount > 0 and state_code = 'CA' or state_code in things"
>>> amn.eval(rule, data)
True

Operators

Built-in operators.

!=
=
>
<
>=
<=
in
not in
not
and
or

About

A toolkit for custom rules engines

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages