{"id":25770845,"url":"https://github.com/borfast/decision-engine","last_synced_at":"2025-02-27T02:38:37.407Z","repository":{"id":50156238,"uuid":"119777525","full_name":"borfast/decision-engine","owner":"borfast","description":"A simple rules-based decision engine.","archived":false,"fork":false,"pushed_at":"2024-07-15T20:49:55.000Z","size":175,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-16T01:19:45.499Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/borfast.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-01T03:26:49.000Z","updated_at":"2024-07-15T20:49:57.000Z","dependencies_parsed_at":"2024-07-16T00:55:21.315Z","dependency_job_id":null,"html_url":"https://github.com/borfast/decision-engine","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borfast%2Fdecision-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borfast%2Fdecision-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borfast%2Fdecision-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borfast%2Fdecision-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/borfast","download_url":"https://codeload.github.com/borfast/decision-engine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240967287,"owners_count":19886215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-27T02:38:36.754Z","updated_at":"2025-02-27T02:38:37.393Z","avatar_url":"https://github.com/borfast.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"A simple rules-based decision engine.\n\n[![Build Status](https://travis-ci.org/borfast/decision-engine.svg?branch=master)](https://travis-ci.org/borfast/decision-engine)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/0859ce60678b42d0a230c0819e5a6b5c)](https://www.codacy.com/app/borfast/decision-engine?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=borfast/decision-engine\u0026amp;utm_campaign=Badge_Grade)\n[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/0859ce60678b42d0a230c0819e5a6b5c)](https://www.codacy.com/manual/borfast/decision-engine?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=borfast/decision-engine\u0026utm_campaign=Badge_Coverage)\n[![Maintainability](https://api.codeclimate.com/v1/badges/b752f611baf5bc9fa2a7/maintainability)](https://codeclimate.com/github/borfast/decision-engine/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/b752f611baf5bc9fa2a7/test_coverage)](https://codeclimate.com/github/borfast/decision-engine/test_coverage)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/borfast/decision-engine/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/borfast/decision-engine/?branch=master)\n\n## Concepts\n\nThere are three base concepts: Sources, Comparisons and Rules:\n\n* Sources are sources of data to be tested.\n* Comparisons are algorithms to compare sources of data.\n* Rules are where you combine Sources and Comparisons to create logic decisions.\n\nThere's also the Engine but it's pretty simple, as all it does is validate all the rules you feed into it. Its \nmain logic is literally one line of code: `return all([rule.check(data) for rule in self.rules])` \n\nThe way this works is you define Sources for data, Rules that use those Sources and make Comparisons between them, \nEngines that make use of rules, and then you call `Engine.decide()` and pass it some real/raw data that will be \nprocessed and fed to rules by the Sources.\n\n\n### Sources\n\nSources may be a little tricky to understand at first. When you define a Source, you don't necessarily define the \ndata or where it is fetched. Sources are more like placeholders that yield the necessary data when Rules are checked.\n\nYou can have Sources that always yield the same value, like the `FixedValueSource`, or Sources that will do some \nsort of processing on the data you feed the engine, like the `DictSource` which fetches the value of a given key in \nthe dictionary.\n\nThe included Sources are very basic but the idea is that the end user can implement their own Sources that fetch or\ngenerate data in any way they like. For example, getting something from an API using HTTP, generating a value depending\non the time of the day, or fetching data from a database.\n\n#### Currently available Sources\n\n* DictSource - yields the value of a given key in a dictionary.\n* FixedValueSource - always yields the same value, which you set up when instantiating it.\n* PercentageSource - yields a percentage of the value returned by another Source.\n* RandomIntSource - yields a random integer between the values you specify when instantiating it.\n\n\n### Comparisons\n\nThese are pretty simple: they take two Sources and compare them in some way, like \"is A equal to B\", or \"is X greater\nthan or equal to Y\". The values to be compared are passed to the Comparison automatically when `Rule.check()` is called.\n\n#### Currently available Comparisons\n\nThe names should be self-explanatory.\n\n* Equal\n* NotEqual\n* GreaterThan\n* GreaterThanOrEqual\n* LessThan\n* LessThanOrEqual\n\n### Rules\n\nThis is where you combine Sources and Comparisons to create actual decision logic. When you instantiate a Rule, you \ntell it which Sources you want to compare, as well as the Comparison you want it to use. Rules have a `check()` \nmethod which runs the rule logic. You can call it manually but it is normally called automatically by the Engine. Just\nas with Sources, you can also implement custom Rules with custom logic in this method. For example, you could implement\na Rule that receives a list of other Rules, and a float value to act as a percentage, and the rule would only pass if\nat least that percentage of the given rules passed. \n\nOne important note: the Rules pass the Sources to the Comparisons in the same order you pass them to the Rule. For \nexample, if you instantiate this rule: `rule = SimpleRule(source1, source2, GreaterThanOrEqual())`, the comparison \nmade will be equivalent to `source1 \u003e= source2`, whereas if you do `rule = SimpleRule(source2, source1, \nGreaterThanOrEqual())` it will result in `source2 \u003e= source1`.\n\n#### Currently available rules\n\n* SimpleRule - Uses the provided Comparison to compare the two given Sources.\n* BooleanOrRule - Takes two other Rules and returns True if **at least one of them** returns True.\n* BooleanAndRule - Takes two other Rules and returns True if **both of them** return True.\n\n\n### Engine\n\nThe Engine is currently the simplest part of the system. When you instantiate it, you pass it all the rules you want \nit to use and when you call `Engine.decide()` it runs the `check()` method on all the rules. If all return True, the \nengine returns True, otherwise it returns False.\n\n\n## Example\n\nLet's start with a very simple example and say you want to ensure someone is at least 18 years old. This could be \nexpressed like so:\n\n```\n# First we define the data Sources\nage = DictSource('age')\nminimum_age = FixedValueSource(18)\n\n# Then we create our rule and the engine that will use it.\n# This rule is equivalent to age \u003e= minimum_age\nage_rule = SimpleComparisonRule(age, minimum_age, GreaterThanOrEqual())\n\nengine = Engine([age_rule])\n\n# Now let's come up with some data\nbob = {\n    'name': 'Bob',\n    'age': 17,\n}\n\nalice = {\n    'name': 'Alice',\n    'age': 32,\n}\n\npeter = {\n    'name': 'Peter Parker',\n    'age': 18,\n}\n\n# Finally, let's make decisions about our three personas\nengine.decide(bob)  # This returns False\nengine.decide(alice)  # This returns True\nengine.decide(peter)  # This returns True\n```\n\nAs you can see, the `DictSource` knows how to fetch the age from the data dictionaries. You can check this tiny bit by \nrunning `age.get_value(bob)` and it should return `17`. \n\nWhen passing the data dictionaries to the `Engine` along with the rules we created, the `Engine` will pass the data\nto each `Rule`, which in turn will pass it to each `Source` that requires data (in this case, only the `DictSource`,\nsince `FixedValueSource` doesn't need input data), which will then fetch the value from the key you instantiated it\nwith.\n\nTo put it another way, a DictSource only knows you want it to fetch data from a dictionary that will show up sometime\nin the future, and it knows which key in the dictionary you want it to fetch the data from.\n\n`FixedValueSource` is an exception, as it is initialized with a specific value right from the start but just like any \nother `Source`, this value is only used when a `Rule` asks for its value.\n\nWhen `engine.decide()` is called, it iterates over the list of rules and calls `check()` on each of them. So when\n`age_rule.check()` is called, the rule will use the `GreaterThanOrEqual` `Comparison` to make sure that the value\nwe get from the `age` `Source` (which gets its value from data['age']) is greater than or equal to the value we get\nfrom the `minimum_age` `Source` (which always has the same value)`.\n\n\n---\n\nI suggest you read the tests to better understand how this works. Start with test_sources.py, which is pretty \nsimple and should make things a lot more obvious. Then, look at test_comparisons.py, proceed to test_rules.py, and \nfinally look at test_engine.py to see how everything can be put together. \n\nPull requests for improvements are welcome! ;)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborfast%2Fdecision-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborfast%2Fdecision-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborfast%2Fdecision-engine/lists"}