public void InitCtx() { var dir = AppDomain.CurrentDomain.BaseDirectory; var file = Path.Combine(dir, "dmn/test.dmn"); ctx = DmnExecutionContextFactory.CreateExecutionContext(DmnParser.Parse(file)); }
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)); }
/// <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> /// 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); }
/// <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); }
/// <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);
public void InitCtx() { Ctx = CTX("test.dmn"); }
public void InitCtx() { Ctx = CTX("dynamictypes.dmn"); }
/// <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()); }
public static void InitCtxBuilder(TestContext testContext) { ctxBuilder = CTX("sfeel.dmn", SourceEnum.Builder); }