/// <summary> /// Evaluates the output expressions for positive rules and stores generates the table execution results ( (rule, output)->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()); }
/// <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)->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)))); }
/// <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); }