예제 #1
0
        public void InitCtx()
        {
            var dir  = AppDomain.CurrentDomain.BaseDirectory;
            var file = Path.Combine(dir, "dmn/test.dmn");

            ctx = DmnExecutionContextFactory.CreateExecutionContext(DmnParser.Parse(file));
        }
예제 #2
0
        public static void InitCtx(TestContext testContext)
        {
            var dir  = AppDomain.CurrentDomain.BaseDirectory;
            var file = Path.Combine(dir, "dmn/dmn1.3/sfeel.dmn");

            ctx = DmnExecutionContextFactory.CreateExecutionContext(DmnParser.Parse13(file));
        }
예제 #3
0
        /// <summary>
        /// Evaluates the decision.
        /// </summary>
        /// <param name="context">DMN Engine execution context</param>
        /// <param name="correlationId">Optional correlation ID used while logging</param>
        /// <returns>Decision result</returns>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> is nul</exception>
        protected override DmnDecisionResult Evaluate(DmnExecutionContext context, string correlationId = null)
        {
            if (context == null)
            {
                throw Logger.FatalCorr <ArgumentNullException>(correlationId, $"{nameof(context)} is null");
            }

            Logger.InfoCorr(correlationId, $"Evaluating expressiong decision {Name} with expression {Expression}...");
            var result = context.EvalExpression(Expression, Output.Type);

            Logger.InfoCorr(correlationId, $"Evaluated expressiong decision {Name} with expression {Expression}");
            var outVariable = context.GetVariable(Output);

            outVariable.Value = result;
            return(new DmnDecisionResult() + (new DmnDecisionSingleResult() + outVariable.Clone()));
        }
        /// <summary>
        /// Evaluates the output expressions for  positive rules and stores generates the table execution results ( (rule, output)-&gt;temp variable with result)
        /// </summary>
        /// <param name="context">Engine execution context</param>
        /// <param name="correlationId">Correlation ID used for logging</param>
        /// <param name="positiveRules">List of positive rules</param>
        /// <returns>Table execution results</returns>
        private DmnDecisionTableRuleExecutionResults EvaluateOutputsForPositiveRules(DmnExecutionContext context, string correlationId, IEnumerable <DmnDecisionTableRule> positiveRules)
        {
            Logger.InfoCorr(correlationId, $"Evaluating decision table {Name} positive rules outputs...");
            var results = new DmnDecisionTableRuleExecutionResults();

            foreach (var positiveRule in positiveRules)
            {
                //outputs
                foreach (var ruleOutput in positiveRule.Outputs)
                {
                    if (string.IsNullOrWhiteSpace(ruleOutput.Expression))
                    {
                        results.SetResult(positiveRule, ruleOutput, null);
                        continue;
                    }

                    var result = context.EvalExpression(ruleOutput.Expression, ruleOutput.Output.Variable.Type ?? typeof(object));

                    // check allowed output values
                    var allowedValues = ruleOutput.Output.AllowedValues;
                    if (allowedValues != null && allowedValues.Count > 0 &&
                        !string.IsNullOrWhiteSpace(result?.ToString()))
                    {
                        if (!ruleOutput.Output.AllowedValues.Contains(result.ToString()))
                        {
                            throw Logger.ErrorCorr <DmnExecutorException>(
                                      correlationId,
                                      $"Decision table {Name}, rule {positiveRule}: Output value '{result}' is not in allowed values list ({string.Join(",", allowedValues)})");
                        }
                    }

                    var output = new DmnExecutionVariable(ruleOutput.Output.Variable)
                    {
                        Value = result
                    };
                    results.SetResult(positiveRule, ruleOutput, output);
                    if (Logger.IsTraceEnabled)
                    {
                        Logger.TraceCorr(correlationId,
                                         $"Positive decision table {Name} rule {positiveRule}: output {output.Name}, value {output.Value}");
                    }
                }
            }

            Logger.InfoCorr(correlationId, $"Evaluated decision table {Name} positive rules outputs");
            return(results);
        }
        protected override DmnDecisionResult Evaluate(DmnExecutionContext context, string executionId)
        {
            //Get execution variable value - use it here as sample to get input1 (the input2 will be directly referenced "by name" in the expression)
            var i1Bool = bool.TryParse(context.GetVariable(InputVar1).Value?.ToString() ?? "false", out var tmpOp) &&
                         tmpOp;


            //Build and evaluate an expression
            var expr   = $"{(Negate ? "!" : "")}({i1Bool.ToString().ToLower()} && {InputVar2.Name})";
            var result = context.EvalExpression <int>(expr, executionId);

            //Set execution variable value
            context.GetVariable(OutputVar).Value = result;

            //Build the decision result
            return(new DmnDecisionResult(new DmnDecisionSingleResult(context.GetVariable(OutputVar))));
        }
예제 #6
0
        /// <summary>
        /// Executes the decision. The execution wrapper around <see cref="Evaluate"/>.
        /// </summary>
        /// <param name="context">DMN Engine execution context</param>
        /// <param name="correlationId">Optional correlation ID used while logging</param>
        /// <returns>Decision result</returns>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> is null</exception>
        public DmnDecisionResult Execute(DmnExecutionContext context, string correlationId = null)
        {
            if (context == null)
            {
                throw Logger.Fatal <ArgumentNullException>($"{nameof(context)} is null");
            }

            Logger.InfoCorr(correlationId, $"Executing decision {Name}...");
            if (Logger.IsTraceEnabled)
            {
                foreach (var input in RequiredInputs)
                {
                    Logger.TraceCorr(correlationId, $"Decision {Name}, input {input.Name}, value {context.GetVariable(input).Value}");
                }
            }

            // ReSharper disable once InvertIf
            if (RequiredDecisions != null)
            {
                Logger.InfoCorr(correlationId, $"Resolving dependencies for decision {Name}...");
                foreach (var requiredDecision in RequiredDecisions)
                {
                    Logger.InfoCorr(correlationId, $"Executing dependency  {requiredDecision.Name} for decision {Name}...");
                    requiredDecision.Execute(context, correlationId);
                    Logger.InfoCorr(correlationId, $"Executed dependency  {requiredDecision.Name} for decision {Name}");
                }
                Logger.InfoCorr(correlationId, $"Resolved dependencies for decision {Name}");
            }

            var result = Evaluate(context, correlationId);

            Logger.InfoCorr(correlationId, $"Executed decision {Name}");
            // ReSharper disable once InvertIf
            if (Logger.IsTraceEnabled)
            {
                if (!result.HasResult)
                {
                    Logger.TraceCorr(correlationId, $"Decision {Name} returned no result");
                }
                else
                {
                    if (result.IsSingleResult)
                    {
                        foreach (var output in result.SingleResult)
                        {
                            Logger.TraceCorr(correlationId, $"Decision {Name} single result, output {output.Name}, value {output.Value}");
                        }
                    }
                    else
                    {
                        var idx = 0;
                        foreach (var singleResult in result.Results)
                        {
                            idx++;
                            foreach (var output in singleResult.Variables)
                            {
                                Logger.TraceCorr(correlationId, $"Decision {Name} result #{idx}, output {output.Name}, value {output.Value}");
                            }
                        }
                    }
                }
            }
            return(result);
        }
예제 #7
0
 /// <summary>
 /// Evaluates the decision.
 /// </summary>
 /// <param name="context">DMN Engine execution context</param>
 /// <param name="correlationId">Optional correlation ID used while logging</param>
 /// <returns>Decision result</returns>
 protected abstract DmnDecisionResult Evaluate(DmnExecutionContext context, string correlationId = null);
        /// <summary>
        /// Evaluates the rules and return the list of positive rules (rules that match the input)
        /// </summary>
        /// <remarks>
        /// Decision table defines the set of rules - "when the input values matches all input conditions, provide defined outputs".
        /// The input data are compared with corresponding rule expressions and when all match (comparison is true), the rule is evaluated
        /// as positive.
        /// Technically, when there is a negative comparison result, the rule is evaluates as negative and the rest of inputs is not evaluated.
        /// It's possible to omit all input expressions, so the rule will be always evaluated as positive match.
        /// </remarks>
        /// <param name="context">Engine execution context</param>
        /// <param name="correlationId">Correlation ID used for logging</param>
        /// <returns>List of positive rules (rules that match the input)</returns>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> is nul</exception>
        private List <DmnDecisionTableRule> EvaluateRules(DmnExecutionContext context, string correlationId)
        {
            if (context == null)
            {
                throw Logger.FatalCorr <ArgumentNullException>(correlationId, $"{nameof(context)} is null");
            }

            var positiveRules = new List <DmnDecisionTableRule>();

            //EVALUATE RULES
            Logger.InfoCorr(correlationId, $"Evaluating decision table {Name} rules...");
            foreach (var rule in Rules)
            {
                var match = true;
                foreach (var ruleInput in rule.Inputs)
                {
                    //check allowed input values
                    var allowedValues = ruleInput.Input.AllowedValues;
                    if (allowedValues != null && allowedValues.Count > 0)
                    {
                        string value = null;
                        if (!string.IsNullOrWhiteSpace(ruleInput.Input.Expression))
                        {
                            //input is mapped to expression, so evaluate it to ger the value
                            var valueObj = context.EvalExpression <object>(ruleInput.Input.Expression);
                            if (valueObj != null)
                            {
                                value = valueObj.ToString();
                            }
                        }
                        else
                        {
                            //input is mapped to variable
                            value = context.GetVariable(ruleInput.Input.Variable).Value?.ToString();
                        }
                        if (!allowedValues.Contains(value))
                        {
                            throw Logger.ErrorCorr <DmnExecutorException>(
                                      correlationId,
                                      $"Decision table {Name}, rule #{rule.Index}: Input value '{value}' is not in allowed values list ({string.Join(",", allowedValues)})");
                        }
                    }
                    if (Logger.IsTraceEnabled)
                    {
                        Logger.TraceCorr(correlationId, $"Evaluating decision table {Name} rule {rule} input #{ruleInput.Input.Index}: {ruleInput.Expression}... ");
                    }
                    var result =
                        context.EvalExpression <bool>(ruleInput.Expression); //TODO ?pre-parse and use the inputs as parameters?
                    if (Logger.IsTraceEnabled)
                    {
                        Logger.TraceCorr(correlationId, $"Evaluated decision table {Name} rule {rule} input #{ruleInput.Input.Index}: {ruleInput.Expression} with result {result}");
                    }

                    // ReSharper disable once InvertIf
                    if (!result)
                    {
                        match = false;
                        break;
                    }
                }

                Logger.InfoCorr(correlationId,
                                $"Evaluated decision table {Name} rule: {(match ? "POSITIVE" : "NEGATIVE")} - {rule}");
                if (match)
                {
                    positiveRules.Add(rule);
                }
            }

            Logger.InfoCorr(correlationId, $"Evaluated decision table {Name} rules");
            return(positiveRules);
        }
        /// <summary>
        /// Evaluates the decision table.
        /// </summary>
        /// <remarks>
        /// While evaluating the decision table, the <seealso cref="Rules"/> are evaluated first.
        /// Then it evaluates (calculates) the outputs for positive rules and applies the hit policy.
        /// </remarks>
        /// <param name="context">DMN Engine execution context</param>
        /// <param name="correlationId">Optional correlation ID used while logging</param>
        /// <returns>Decision result</returns>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> is null</exception>
        protected override DmnDecisionResult Evaluate(DmnExecutionContext context, string correlationId = null)
        {
            if (context == null)
            {
                throw Logger.FatalCorr <ArgumentNullException>(correlationId, $"{nameof(context)} is null");
            }

            //EVALUATE RULES
            var positiveRules = EvaluateRules(context, correlationId);

            //EVALUATE OUTPUTS for positive rules
            var results = EvaluateOutputsForPositiveRules(context, correlationId, positiveRules);

            //APPLY HIT POLICY
            if (positiveRules.Count <= 0)
            {
                return(new DmnDecisionResult());
            }

            var retVal = new DmnDecisionResult();
            var positiveMatchOutputRules = new List <DmnDecisionTableRule>();

            // ReSharper disable once SwitchStatementMissingSomeCases
            switch (HitPolicy)
            {
            case HitPolicyEnum.Unique:
                // No overlap is possible and all rules are disjoint. Only a single rule can be matched
                // Overlapping rules represent an error.
                if (positiveRules.Count > 1)
                {
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"UNIQUE hit policy violation - {positiveRules.Count} matches");
                }
                positiveMatchOutputRules.Add(positiveRules[0]);
                break;

            case HitPolicyEnum.First:
                //Multiple(overlapping) rules can match, with different output entries. The first hit by rule order is returned
                positiveMatchOutputRules.Add(positiveRules[0]);
                break;

            case HitPolicyEnum.Priority:
                // Multiple rules can match, with different output entries. This policy returns the matching rule with the highest output priority.
                //  Output priorities are specified in the ordered list of output values, in decreasing order of priority. Note that priorities are independent from rule sequence
                // A P table that omits allowed output values is an error.
                var orderedPositiveRules = OrderPositiveRulesByOutputPriority(positiveRules, results, correlationId);
                if (orderedPositiveRules == null)
                {
                    //A P table that omits allowed output values is an error.
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"PRIORITY hit policy violation - no outputs with Allowed Values");
                }
                positiveMatchOutputRules.Add(orderedPositiveRules.ToArray()[0]);
                break;

            case HitPolicyEnum.Any:
                //  There may be overlap, but all of the matching rules show equal output entries for each output, so any match can be used.
                //  If the output entries are non-equal, the hit policy is incorrect and the result is an error.
                if (!MatchRuleOutputs(positiveRules, results))
                {
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"ANY hit policy violation - the outputs don't match");
                }
                positiveMatchOutputRules.Add(positiveRules[0]);
                break;


            case HitPolicyEnum.Collect:
                // Multiple rules can be satisfied. The decision table result contains the output of all satisfied rules in an arbitrary order as a list.
                // If an aggregator is specified, the decision table result will only contain a single output entry. The aggregator will generate the output entry from all satisfied rules.
                // Except for C-count and C-list, the rules must have numeric output values (bool is also allowed - 0=false, 1=true).
                // If the Collect hit policy is used with an aggregator, the decision table can only have one output.
                if (Outputs.Count > 1 && Aggregation != CollectHitPolicyAggregationEnum.List)
                {
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"COLLECT hit policy violation - multiple outputs not allowed for aggregation {Aggregation}");
                }

                var outType          = Outputs[0].Variable.Type;
                var outTypeIsNumeric = outType == typeof(int) || outType == typeof(long) || outType == typeof(double);
                var outTypeIsBool    = outType == typeof(bool);
                var outTypeIsStr     = outType == typeof(string);

                var isAllowedForCount     = outTypeIsNumeric || outTypeIsStr;
                var isAllowedForSumMinMax = outTypeIsNumeric || outTypeIsBool;

                if (!
                    (Aggregation == CollectHitPolicyAggregationEnum.List ||
                     (Aggregation == CollectHitPolicyAggregationEnum.Count && isAllowedForCount) ||
                     Aggregation != CollectHitPolicyAggregationEnum.List && Aggregation != CollectHitPolicyAggregationEnum.Count && isAllowedForSumMinMax))
                {
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"COLLECT hit policy violation - type {outType.Name} not allowed for aggregation {Aggregation}");
                }


                if (Aggregation == CollectHitPolicyAggregationEnum.List)
                {
                    positiveMatchOutputRules.AddRange(positiveRules);
                }
                else
                {
                    var collectValues = isAllowedForSumMinMax ?
                                        CalculatePositiveRulesCollectValues(positiveRules, results) :
                                        CalculatePositiveRulesCollectCountValue(positiveRules, results);

                    var output         = Outputs[0];
                    var outputVariable = context.GetVariable(output.Variable);
                    if (collectValues.Count == 0 && Aggregation != CollectHitPolicyAggregationEnum.Count)
                    {
                        //no value for sum, min, max
                        outputVariable.Value = null;
                        retVal += new DmnDecisionSingleResult() + outputVariable.Clone();
                        return(retVal);
                    }
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (Aggregation)
                    {
                    case CollectHitPolicyAggregationEnum.Sum:
                        outputVariable.Value = collectValues.Sum;
                        break;

                    case CollectHitPolicyAggregationEnum.Min:
                        outputVariable.Value = collectValues.Min;
                        break;

                    case CollectHitPolicyAggregationEnum.Max:
                        outputVariable.Value = collectValues.Max;
                        break;

                    case CollectHitPolicyAggregationEnum.Count:
                        outputVariable.Value = collectValues.Count;
                        break;
                    }

                    retVal += new DmnDecisionSingleResult() + outputVariable.Clone();
                    return(retVal);
                }

                break;

            case HitPolicyEnum.RuleOrder:
                //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.
                positiveMatchOutputRules.AddRange(positiveRules);

                break;

            case HitPolicyEnum.OutputOrder:
                //Returns all hits in decreasing output priority order.
                // Output priorities are specified in the ordered list of output values in decreasing order of priority
                var orderedPositiveRules2 = OrderPositiveRulesByOutputPriority(positiveRules, results, correlationId);
                if (orderedPositiveRules2 == null)
                {
                    //A P table that omits allowed output values is an error.
                    throw Logger.ErrorCorr <DmnExecutorException>(
                              correlationId,
                              $"OUTPUT ORDER hit policy violation - no outputs with Allowed Values");
                }
                positiveMatchOutputRules.AddRange(orderedPositiveRules2);
                break;
            }

            //Return (multiple) matches
            foreach (var multiMatchOutputRule in positiveMatchOutputRules)
            {
                var singleResult = new DmnDecisionSingleResult();
                foreach (var ruleOutput in multiMatchOutputRule.Outputs)
                {
                    var resultOutput = results.GetResult(multiMatchOutputRule, ruleOutput);
                    if (resultOutput == null)
                    {
                        continue;
                    }

                    context.GetVariable(ruleOutput.Output.Variable).Value = resultOutput.Value;
                    singleResult += resultOutput;
                }

                retVal += singleResult;
            }

            return(retVal);
        }
 public static void InitCtxFile13(TestContext testContext)
 {
     ctxFile13 = CTX("sfeel.dmn", SourceEnum.File13);
 }
예제 #11
0
 /// <summary>
 /// Evaluates the decision.
 /// </summary>
 /// <param name="context">DMN Engine execution context</param>
 /// <param name="executionId">Identifier of the execution run</param>
 /// <returns>Decision result</returns>
 protected abstract DmnDecisionResult Evaluate(DmnExecutionContext context, string executionId);
예제 #12
0
 public void InitCtx()
 {
     Ctx = CTX("test.dmn");
 }
 public void InitCtx()
 {
     Ctx = CTX("dynamictypes.dmn");
 }
예제 #14
0
        /// <summary>
        /// Evaluates the output expressions for  positive rules and stores generates the table execution results ( (rule, output)-&gt;temp variable with result)
        /// </summary>
        /// <param name="context">Engine execution context</param>
        /// <param name="executionId">Identifier of the execution run</param>
        /// <param name="positiveRules">List of positive rules</param>
        /// <returns>Table execution results</returns>
        private DmnDecisionTableRuleExecutionResults EvaluateOutputsForPositiveRulesParallel(DmnExecutionContext context, string executionId, IEnumerable <DmnDecisionTableRule> positiveRules)
        {
            Logger.InfoCorr(executionId, $"Evaluating decision table {Name} positive rules outputs...");
            var results         = new DmnDecisionTableRuleExecutionResultsParallel();
            var rulesAndOutputs = positiveRules.SelectMany(
                r => r.Outputs,
                (rule, output) => new { rule, output });

            Parallel.ForEach(rulesAndOutputs, ruleAndOutput =>


            {
                var positiveRule = ruleAndOutput.rule;
                var ruleOutput   = ruleAndOutput.output;

                if (string.IsNullOrWhiteSpace(ruleOutput.Expression))
                {
                    results.SetResult(positiveRule, ruleOutput, null);
                }
                else
                {
                    var result = context.EvalExpression(ruleOutput.Expression,
                                                        ruleOutput.Output.Variable.Type ?? typeof(object),
                                                        executionId);

                    // check allowed output values
                    var allowedValues = ruleOutput.Output.AllowedValues;
                    if (allowedValues != null && allowedValues.Length > 0 &&
                        !string.IsNullOrWhiteSpace(result?.ToString()))
                    {
                        if (!ruleOutput.Output.AllowedValues.Contains(result.ToString()))
                        {
                            throw Logger.ErrorCorr <DmnExecutorException>(
                                executionId,
                                $"Decision table {Name}, rule {positiveRule}: Output value '{result}' is not in allowed values list ({string.Join(",", allowedValues)})");
                        }
                    }

                    var output = new DmnExecutionVariable(ruleOutput.Output.Variable)
                    {
                        Value = result
                    };
                    results.SetResult(positiveRule, ruleOutput, output);
                    if (Logger.IsTraceEnabled)
                    {
                        Logger.TraceCorr(executionId,
                                         $"Positive decision table {Name} rule {positiveRule}: output {output.Name}, value {output.Value}");
                    }
                }
            });

            Logger.InfoCorr(executionId, $"Evaluated decision table {Name} positive rules outputs");
            return(results.FinalizeConcurrentResults());
        }
 public static void InitCtxBuilder(TestContext testContext)
 {
     ctxBuilder = CTX("sfeel.dmn", SourceEnum.Builder);
 }