|
1 | | -# PROJECT NAME |
2 | | -Standard Project Template for Databricks Labs Projects |
| 1 | +# Rules Engine |
| 2 | +Simplified Validation for Production Workloads |
3 | 3 |
|
4 | 4 | ## Project Description |
5 | | -Short description of project's purpose |
| 5 | +As pipelines move from bronze to gold, it's very common that some level of governance be performed in |
| 6 | +Silver or at various places in the pipeline. The need for business rule validation is very common. |
| 7 | +Databricks recognizes this and as such is building Delta Pipelines with Expectations are coming soon |
| 8 | +and will likely reduce the need for a rules engine like this but it's possible that simple rules are needed |
| 9 | +where Delta Pipelines are a bit overkill or not in line with the overall workload. After the release |
| 10 | +of Delta Pipelines and Expectations the code base will be reviewed and adjusted appropriately. That may mean to |
| 11 | +extend Expectations, add a simplified wrapper, or both, or none. We'll have to wait and see what Delta |
| 12 | +Pipelines and Expectations looks like when it's released. |
6 | 13 |
|
7 | | -## Project Support |
8 | | -Please note that all projects in the /databrickslabs github account are provided for your exploration only, and are not formally supported by Databricks with Service Level Agreements (SLAs). They are provided AS-IS and we do not make any guarantees of any kind. Please do not submit a support ticket relating to any issues arising from the use of these projects. |
| 14 | +Introducing Databricks Labs - Rules Engine, a simple solution for validating data in dataframes before you |
| 15 | +move the data to production and/or in-line (coming soon). |
9 | 16 |
|
10 | | -Any issues discovered through the use of this project should be filed as GitHub Issues on the Repo. They will be reviewed as time permits, but there are no formal SLAs for support. |
| 17 | + |
| 18 | +## Using The Rules Engine In Your Project |
| 19 | +* Pull the latest release from the releases |
| 20 | +* Add it as a dependency (will be in Maven eventually) |
| 21 | +* Reference it in your imports |
11 | 22 |
|
| 23 | +## Getting Started |
| 24 | +A list of usage examples is available in the `demo` folder of this repo in [html](demo/Rules_Engine_Examples.html) |
| 25 | +and as a [Databricks Notebook DBC](demo/Rules_Engine_Examples.dbc). |
12 | 26 |
|
13 | | -## Building the Project |
14 | | -Instructions for how to build the project |
| 27 | +The process simple: |
| 28 | +* Define Rules |
| 29 | +* Build a RuleSet from your Dataframe using your Rules you built |
| 30 | +```scala |
| 31 | +import com.databricks.labs.validation.utils.Structures._ |
| 32 | +import com.databricks.labs.validation._ |
| 33 | +``` |
| 34 | + |
| 35 | +As of version 0.1 There are three primary rule types |
| 36 | +* Boundary Rules |
| 37 | +* Categorical Rules (Strings and Numerical) |
| 38 | +* Date Rules (in progress) |
| 39 | + |
| 40 | +Rules can be composed of: |
| 41 | +* simple column references `col("my_column_name")` |
| 42 | +* complex columns `col("Revenue") - col("Cost")` |
| 43 | +* aggregate columns `min("ColumnName")` |
| 44 | + |
| 45 | +Rules can be applied to simple DataFrames or grouped Dataframes. To use a grouped dataframe simply pass |
| 46 | +your dataframe into the RuleSet and pass one or more columns in as `by` columns. This will apply the rule |
| 47 | +at the group level which can be helpful at times. |
| 48 | + |
| 49 | +### Simple Rule |
| 50 | +`val validateRetailPrice = Rule("Retail_Price_Validation", col("retail_price"), Bounds(0.0, 6.99))` |
| 51 | + |
| 52 | +### List of Rules |
| 53 | +NOTE: While validations can be performed on aggregate cols (whether the DF is grouped or not) aggregate columns |
| 54 | +only return a single value - as such the failed count will be set to 1 for failures so for aggregate columns |
| 55 | +the `Invalid_Count` is rendered somewhat useless. Better granularity can be seen in the report when not using |
| 56 | +aggregates. |
| 57 | +```scala |
| 58 | +val specializedRules = Array( |
| 59 | + // Example of aggregate column |
| 60 | + Rule("Reasonable_sku_counts", count(col("sku")), Bounds(lower = 20.0, upper = 200.0)), |
| 61 | + // Example of calculated column from optimized UDF |
| 62 | + Rule("Max_allowed_discount", |
| 63 | + max(getDiscountPercentage(col("retail_price"), col("scan_price"))), |
| 64 | + Bounds(upper = 90.0)), |
| 65 | + // Example distinct values rule |
| 66 | + Rule("Unique_Skus", countDistinct("sku"), Bounds(upper = 1.0)) |
| 67 | +) |
| 68 | +``` |
| 69 | + |
| 70 | +### MinMax Rules |
| 71 | +It's very common to build rules to validate min and max allowable values so there's a helper function |
| 72 | +to speed up this process. It really only makes sense to use minmax when specifying both an upper and a lower bound |
| 73 | +in the Bounds object. Using this method in the example below will only require three lines of code instead of the 6 |
| 74 | +if each rule was built manually |
| 75 | +```scala |
| 76 | +val minMaxPriceDefs = Array( |
| 77 | + MinMaxRuleDef("MinMax_Sku_Price", col("retail_price"), Bounds(0.0, 29.99)), |
| 78 | + MinMaxRuleDef("MinMax_Scan_Price", col("scan_price"), Bounds(0.0, 29.99)), |
| 79 | + MinMaxRuleDef("MinMax_Cost", col("cost"), Bounds(0.0, 12.0)) |
| 80 | +) |
| 81 | + |
| 82 | +// Generate the array of Rules from the minmax generator |
| 83 | +val minMaxPriceRules = RuleSet.generateMinMaxRules(minMaxPriceDefs: _*) |
| 84 | +``` |
| 85 | +OR -- simply add the list of minmax rules or simple individual rule definitions |
| 86 | +to an existing RuleSet (if not using builder pattern) |
| 87 | +```scala |
| 88 | +val someRuleSet = RuleSet(df) |
| 89 | +someRuleSet.addMinMaxRules(minMaxPriceDefs: _*) |
| 90 | +someRuleSet.addMinMaxRules("Retail_Price_Validation", col("retail_price"), Bounds(0.0, 6.99)) |
| 91 | +``` |
| 92 | + |
| 93 | +### Categorical Rules |
| 94 | +There are two types of categorical rules which are used to validate against a pre-defined list of valid |
| 95 | +values. Currently (as of 0.1) accepted categorical types are String, Double, Int, Long |
| 96 | +```scala |
| 97 | +val catNumerics = Array( |
| 98 | +Rule("Valid_Stores", col("store_id"), Lookups.validStoreIDs), |
| 99 | +Rule("Valid_Skus", col("sku"), Lookups.validSkus) |
| 100 | +) |
| 101 | + |
| 102 | +val catStrings = Array( |
| 103 | +Rule("Valid_Regions", col("region"), Lookups.validRegions) |
| 104 | +) |
| 105 | +``` |
15 | 106 |
|
16 | | -## Deploying / Installing the Project |
17 | | -Instructions for how to deploy the project, or install it |
| 107 | +### Validation |
| 108 | +Now that you have some rules built up... it's time to build the ruleset and validate it. As mentioned above, |
| 109 | +the dataframe can be simple or groupBy column[s] can be passed in (as string) to perform validation at the |
| 110 | +grouped level. |
| 111 | +```scala |
| 112 | +val (rulesReport, passed) = RuleSet(df) |
| 113 | +.add(specializedRules) |
| 114 | +.add(minMaxPriceRules) |
| 115 | +.add(catNumerics) |
| 116 | +.add(catStrings) |
| 117 | +.validate() |
18 | 118 |
|
19 | | -## Releasing the Project |
20 | | -Instructions for how to release a version of the project |
| 119 | +val (rulesReport, passed) = RuleSet(df, Array("store_id")) |
| 120 | +.add(specializedRules) |
| 121 | +.add(minMaxPriceRules) |
| 122 | +.add(catNumerics) |
| 123 | +.add(catStrings) |
| 124 | +.validate() |
| 125 | +``` |
| 126 | +The validation returns two items, a boolean (true/false) as to whether all rules passed or not. If a single rule |
| 127 | +fails the `passed` value above will return false. The `rulesReport` is a summary of which rules failed and, |
| 128 | +if the input column was not an aggregate column, the number of failed records. An image of the report is below. |
| 129 | + |
21 | 130 |
|
22 | | -## Using the Project |
23 | | -Simple examples on how to use the project |
| 131 | +## Next Steps |
| 132 | +Clearly, this is just a start. This is a small package and, as such, a GREAT place to start if you've never |
| 133 | +contributed to a project before. Please feel free to fork the repo and/or submit PRs. I'd love to see what |
| 134 | +you come up with. If you're not much of a developer or don't have the time you can still contribute! Please |
| 135 | +post your ideas in the issues and label them appropriately (i.e. bug/enhancement) and someone will review it |
| 136 | +and add it as soon as possible. |
| 137 | + |
| 138 | +Some ideas of great adds are: |
| 139 | +* Add a Python wrapper |
| 140 | +* Refactor Rule and/or Validator to implement an Abstract class or trait |
| 141 | + * There's a clear opportunity to abstract away some of the redundancy between rule types. |
| 142 | +* Implement a fast runner |
| 143 | + * Optimize performance by failing fast for big data. Smart sampling could be implemented to review subsets |
| 144 | + of columns/records and look for failures to enable a faster failure. |
| 145 | +* Implement tests |
| 146 | + * Yeah, I know...I should have done this on day 0...but...time is always an issue. I plan to come back and add |
| 147 | + tests but if you'd like to add tests, that's a great way to learn code base (especially one this small) |
| 148 | +* Implement the date time rule (or somet other custom rule) |
| 149 | + * The date time rule has already been scaffolded, it just needs to be built out |
| 150 | + * What kind of complex rules does your business require that isn't possible here |
| 151 | +* Add a quarantine pattern |
| 152 | + * Enable a configuration to a Ruleset to identify records that didn't pass the validations and add |
| 153 | + them to a predefined quarantine zone. |
| 154 | +* Add logic to attempt to auto-handle certain types of failures based on common business patterns |
| 155 | + |
| 156 | + |
| 157 | +## Legal Information |
| 158 | +This software is provided as-is and is not officially supported by Databricks through customer technical support channels. |
| 159 | +Support, questions, and feature requests can be submitted through the Issues page of this repo. |
| 160 | +Please see the [legal agreement](LICENSE.txt) and understand that issues with the use of this code will |
| 161 | +not be answered or investigated by Databricks Support. |
| 162 | + |
| 163 | +## Core Contribution team |
| 164 | +* Lead Developer: [Daniel Tomes](https://www.linkedin.com/in/tomes/), Practice Leader, Databricks |
| 165 | +* Developer: <b> your name here </b> Contribute to the project |
| 166 | + |
| 167 | + |
| 168 | +## Project Support |
| 169 | +Please note that all projects in the /databrickslabs github account are provided for your exploration only, |
| 170 | +and are not formally supported by Databricks with Service Level Agreements (SLAs). |
| 171 | +They are provided AS-IS and we do not make any guarantees of any kind. |
| 172 | +Please do not submit a support ticket relating to any issues arising from the use of these projects. |
| 173 | + |
| 174 | +Any issues discovered through the use of this project should be filed as GitHub Issues on the Repo. |
| 175 | +They will be reviewed as time permits, but there are no formal SLAs for support. |
| 176 | + |
| 177 | + |
| 178 | +## Building the Project |
| 179 | +To build the project: <br> |
| 180 | +``` |
| 181 | +cd Downloads |
| 182 | +git pull repo |
| 183 | +sbt clean package |
| 184 | +``` |
0 commit comments