====== Decision Table Mapping ====== This map defines a set of rules that must be evaluated to make a decision. The general idea comes from the Decision Model and Notation (DMN) structure used in BPM but very adapted to the specific coreBOS environment and infrastructure. You can find a few useful links about DMN here: * [[https://camunda.com/dmn/|DMN Tutorial]] * {{ :en:adminmanual:businessmappings:dmn_tutorial_-_dmn_1.1_tutorial_for_beginners_-_learn_dmn_camunda_bpm.pdf |DMN Tutorial (PDF)}} * [[https://docs.camunda.org/manual/latest/reference/dmn11/decision-table/|Decision Table Reference]] * {{ :en:adminmanual:businessmappings:dmndecisiontablereference.pdf |Decision Table Reference (PDF)}} * [[https://github.com/steffenbrand/dmn-decision-tables|Decision Table Constructor]] * [[https://www.omg.org/spec/DMN/|https://www.omg.org/spec/DMN/]] ====== Decision Table Map Format ====== The accepted format for this map is basically a set of rules which can be of three types: * Expressions * [[en:adminmanual:businessmappings:condition_expression|Condition Expression]] or [[en:adminmanual:businessmappings:condition_query|Condition Query]] business map names/IDs * Decision Tables ===== Expressions ===== Expressions contain any workflow expression that can be evaluated in the context of the Decision Map. These are exactly like [[en:adminmanual:businessmappings:condition_expression|Condition Expression]] maps but written directly inside this business map. Since an expression can return any type of value, in order to know if an expression has failed it must return the reserved string **"%%__DoesNotPass__%%"** ===== Business Map Name/ID ===== This type is simply the name or CRMID of any existing [[en:adminmanual:businessmappings:condition_expression|Condition Expression]] or [[en:adminmanual:businessmappings:condition_query|Condition Query]] business map, they will be loaded and evaluated in the context of the Decision Map Since an expression can return any type of value, in order to know if an expression has failed it must return the reserved string **"%%__DoesNotPass__%%"** ===== Decision Tables ===== This type expects to have the values to search on inside a coreBOS module. We will be able to define the conditions to filter the records in the module and then a set of search conditions to look for records. ===== Full Map Structure ===== To make this type of map easier to construct when we have a large number of input variables, the input variables accept the special value **"%%__IGNORE__%%"**. When an input variable is set to this value the whole search on that column will be ignored. This permits us to establish rules that are more generic reducing the repetition of very similar rules. ===== Hit Policy ===== * **U**nique: Only a single rule can be satisfied. The decision table result contains the output entries of the satisfied rule. If more than one rule is satisfied, the Unique hit policy is violated. * **F**irst: Multiple rules can be satisfied. The decision table result contains only the output of the first satisfied rule. * **C**ollect: Multiple rules can be satisfied. The decision table result contains the output of all satisfied rules in an arbitrary order as a list. * **A**ny: Multiple rules can be satisfied. However, all satisfied rules must generate the same output. The decision table result contains only the output of one of the satisfied rules. If multiple rules are satisfied which generate different outputs, the hit policy is violated. * **R**uleOrder: Multiple rules can be satisfied. The decision table result contains the output of all satisfied rules in the order of the rules in the decision table. * a**G**gregate: * The SUM aggregator sums up all outputs from the satisfied rules. * The MIN aggregator can be used to return the smallest output value of all satisfied rules. * The MAX aggregator can be used to return the largest output value of all satisfied rules. * The COUNT aggregator can be used to return the count of satisfied rules. [[https://docs.camunda.org/manual/latest/reference/dmn11/decision-table/hit-policy/|Read the DMN Hit Policy reference]] ===== Output Options ===== * **ExpressionResult:** whatever the expression returns will be returned * **FieldValue:** we understand that the expression returns a field name which we will evaluate in the given context * **crmObject:** we understand that the expression returns a CRM ID so we instantiate the module and return the fully-loaded object * **Row:** will return the full row of fields indicated in the "output" directive ====== Execution ====== To execute a decision map and get the result we use the [[en:devel:corebos_rules|coreBOS Rules service]]. coreBOS Rule will see that the given map is actually a decision table and will evaluate the map with the given context. $result = coreBOS_Rule::evaluate(put your decision map ID or name here, $context); ===== Web service execution ===== You can evaluate Decision Maps via the web service endpoint: **cbRule** $context = array( 'guestcount' => '4', 'season' => 'Winter', ); $context = json_encode($context); $mapid = 'SeasonDish twocolumns'; //sessionId is obtained from loginResult. $params = "sessionName=$cbSessionID"; $params.= "&operation=cbRule"; $params.= "&conditionid=".urlencode($mapid); $params.= "&context=".urlencode($context); //Retrieve must be GET Request. $response = $httpc->fetch_url("$cbURL?$params"); $dmsg.= debugmsg("Raw response (json)", $response); //decode the json encode response from the server. $jsonResponse = json_decode($response, true); $dmsg.= debugmsg("Webservice response", $jsonResponse); //operation was successful get the token from the response. if($jsonResponse['success']==false) { $dmsg.= debugmsg('failed:'.$jsonResponse['error']['message']); echo 'rule failed!'; } else { echo $jsonResponse['result']; } ====== Examples ====== ===== Select Dish with Expressions ===== {{ :en:adminmanual:businessmappings:dish-feel.png |}} U 1 ExpressionResult 2 ExpressionResult 3 ExpressionResult 4 =5, $[guestCount]<=8)) then 'Steak' else '__DoesNotPass__' end]]> ExpressionResult 5 8) then 'Stew' else '__DoesNotPass__' end]]> ExpressionResult 6 ExpressionResult ===== Select Dish with Module ===== {{ :en:adminmanual:businessmappings:dish-feel.png |}} Let's suppose we have a module called DecisionConditions with these fields: * sequence * season * guestcountmin * guestcountmax * desireddish and these records ^ sequence ^ season ^ guestcountmin ^ guestcountmax ^ desireddish ^ | 1 | Fall | 0 | 8 | Spareribs | | 2 | Winter | 0 | 8 | Roastbeef | | 3 | Spring | 0 | 4 | Dry Aged Gourmet Steak | | 4 | Spring | 5 | 8 | Steak | | 5 | Fall | 9 | 10000 | Stew | | 6 | Winter | 9 | 10000 | Stew | | 7 | Spring | 9 | 10000 | Stew | | 8 | Summer | 0 | 10000 | Light Salad and a nice Steak | U 1 DecisionConditions sequence season e season guestcount ge guestcountmin guestcount le guestcountmax desireddish FieldValue In the example above I decided to add two columns for the Guest Count, in order to convert the range [5..8] into two records. In this mindset, I also use the value 10000 as an "infinite" value. But the truth is that as the implementor of both the decision table map and module I have full control of how I want my users to define the conditions. Let's suppose that I want the users of the module to be able to define the condition with only one column for guest count like is reflected in the image. In this case, I would have implemented a module with these fields: * sequence * season * guestcount * desireddish these records ^ sequence ^ season ^ guestcount ^ desireddish ^ | 1 | Fall | 8 | Spareribs | | 2 | Fall | 10000 | Stew | | 3 | Spring | 4 | Dry Aged Gourmet Steak | | 4 | Spring | 8 | Steak | | 5 | Spring | 10000 | Stew | | 5 | Winter | 8 | Roastbeef | | 6 | Winter | 10000 | Stew | | 7 | Summer | 10000 | Light Salad and a nice Steak | and this map F 1 DecisionConditions sequence season e season guestcount le guestcount desireddish FieldValue In this case, I am playing with the **Hit Policy** which has changed to **F**irst, so now my users must understand that the ranges are defined from the previous value to the one defined in each record and that sequence is VERY important. Since, in the end, the supported operations are those of the Query Generator, which even supports ranges: QueryGenerator->addConditions(column, operator, values) where operator can be '[]', '[[', ']]', '][' among many others the possibilities that the implementor has are very big. ===== IGNORE Reserved Word ===== Finally, comment on a very important option where you can set the value of any variable in the context to **%%__IGNORE__%%** to have that variable eliminated from the decision. This permits us to create complex rules with many variables where we search on only a few of them at a time. We can also use this special reserved word in the rules table itself to create rules which cover many cases. For example, we could have a table of rules like this: ^input1^input2^output^ |e1|c1|r1| |%%__IGNORE__%%|c2|r2| |e3|c3|r3| With these rules in place, any decision that has input2==c2 will pass no matter what value is passed in as input1. Note that this can easily cause that more than one rule to pass. If we add a rule to the table above, like this: ^input1^input2^output^ |e1|c1|r1| |%%__IGNORE__%%|c2|r2| |e2|c2|r2b| |e3|c3|r3| and we pass in the values - input1=e2 - input2=c2 **both r2 and r2b will pass** and be returned, so if we have a hit policy of Unique it will fail and if we have a hit policy of First then the sequence is of utmost importance to get the correct result. ===== Default Value ===== Using the %%__IGNORE__%% reserved word we can implement a default value for our rules. If we define a rule where all the input values are set to %%__IGNORE__%%, this rule will pass for all input values. If we set the hit policy of the decision map to First and order the rules in a way that the all %%__IGNORE__%% rule is always the last to be evaluated we accomplish a decision table where any combination of input values that is not explicitly defined will return the values in the all %%__IGNORE__%% rule instead of %%__DoesNotPass__%% ===== Preprocess Directive ===== Following the new enhancements, we don't have to valorize context variables on the Decision Table map using the Execute Expression Workflow before evaluating the Decision table map. The map will be evaluated on the context of the values that are present on the screen. But there may be cases where we don’t want the context variable to hold the entire value of the field. Here comes in play the preprocess directive. The preprocess directive is optional. That being said, if it is present it will send its value to the workflow expression system, get back the result and use that result to search the Decision Table Module. If you think about it the preprocess directive carries the work of the Execute Expression workflow task. You don’t need to explicitly use the workflow system because the preprocess directive will “call” it for you. **Example:** Let's change the logic we had in the last example above. Remove all the records with the season of Spring and add this new rule on the decision table module: //If the season is Spring then regardless of how many guests there are I want the desired dish to be Beef//. That would be a record like this in the decision table module. ^ sequence ^ season ^ guestcount ^ desireddish ^ | 1 | Fall | 8 | Spareribs | | 2 | Fall | 10000 | Stew | | 3 | Spring | * | Beef | | 5 | Winter | 8 | Roastbeef | | 6 | Winter | 10000 | Stew | | 7 | Summer | 10000 | Light Salad and a nice Steak | Lets see how that will look on our Decision Table map: guestcount if '$[season]' == 'Spring' then '*' else '$[guestcount]' end e guestcount As you can see we are using the preprocess directive. What is inside the preprocess directive will be sent to the workflow system. So if the season is Spring that valorizes the context variable with the value * and then searches the decision table module for a match. Now, whenever the decision map gets evaluated on a record whose value of the Season field is Spring it will consider the value of Guest Number as * and for our Decision Table module that means that the desired dish is Beef. ===== Select Global Variable Escalation ===== A decision map that would return the value of a global variable would look something like the map below. The context would have to send in all the role and group of the current user and I'm not totally sure if the "module list" would work as it is below, but it will be VERY close and enough for you to get an idea of how this map works. F 1 GlobalVariable gvname e gvname checkboxtrue e mandatory gvname e gvname userid e assigned_user_id checkboxtrue e inmodulelist gvmodule c gvmodule gvname e gvname groupid e assigned_user_id checkboxtrue e inmodulelist gvmodule c gvmodule gvname e gvname userrole c gvrole checkboxtrue e inmodulelist gvmodule c gvmodule gvname e gvname userid e assigned_user_id checkboxfalse e inmodulelist gvname e gvname groupid e assigned_user_id checkboxfalse e inmodulelist gvname e gvname userrole c gvrole checkboxfalse e inmodulelist gvname e gvname checkboxtrue e default value FieldValue