/// <summary> /// Calculates the distinct count of output values for Collect hit policy /// </summary> /// <remarks>Based on the collect hit policy, the aggregated count is calculated for the first output.</remarks> /// <param name="positiveRules">Rules evaluates as positive match</param> /// <param name="results">Evaluated (calculated) results (rule outputs)</param> /// <returns><see cref="PositiveRulesCollectValues"/> container where the <see cref="PositiveRulesCollectValues.Count"/> contains the /// distinct count of values, the other properties are zeroed</returns> private static PositiveRulesCollectValues CalculatePositiveRulesCollectCountValue( IEnumerable <DmnDecisionTableRule> positiveRules, DmnDecisionTableRuleExecutionResults results) { var count = positiveRules.ToList() .Select(r => results.GetResult(r, r.Outputs[0])?.Value?.ToString()) .Where(v => v != null) .Distinct() .ToList() .Count; return(new PositiveRulesCollectValues(0, 0, 0, count)); }
//------------------------------------------------------------------------ // Collect hit policy helpers //------------------------------------------------------------------------ /// <summary> /// Calculates the aggregate output values for Collect hit policy /// </summary> /// <remarks>Based on the collect hit policy, the aggregates are calculated for the first output</remarks> /// <param name="positiveRules">Rules evaluates as positive match</param> /// <param name="results">Evaluated (calculated) results (rule outputs)</param> /// <returns><see cref="PositiveRulesCollectValues"/> container with aggregated output values</returns> private static PositiveRulesCollectValues CalculatePositiveRulesCollectValues( IEnumerable <DmnDecisionTableRule> positiveRules, DmnDecisionTableRuleExecutionResults results) { double sum = 0; var min = double.MaxValue; var max = double.MinValue; var distinctValues = new List <double>(); foreach (var positiveRule in positiveRules) { var valueRaw = results.GetResult(positiveRule, positiveRule.Outputs[0])?.Value; if (valueRaw == null) { continue; //ignore null results/values } double value; var isBool = positiveRule.Outputs[0].Output.Variable.Type == typeof(bool); if (isBool) { value = (bool)valueRaw ? 1 : 0; } else { value = (double)Convert.ChangeType(valueRaw, typeof(double)); } sum += value; if (value < min) { min = value; } if (value > max) { max = value; } if (!distinctValues.Contains(value)) { distinctValues.Add(value); } } var count = distinctValues.Count; return(new PositiveRulesCollectValues(sum, min, max, count)); }
/// <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); }
//------------------------------------------------------------------------ // Priority and Output order hit policies helpers //------------------------------------------------------------------------ /// <summary> /// Orders the positive rules by output priorities for the Priority and Output order hit policies. /// </summary> /// <remarks> /// For the Priority and Output order hit policies, priority is decided in compound output tables over all the outputs for which output values have been provided. /// The priority for each output is specified in the ordered list of output values in decreasing order of priority, /// and the overall priority is established by considering the ordered outputs from left to right. /// Outputs for which no output values are provided are not taken into account in the ordering, /// although their output entries are included in the ordered compound output. /// </remarks> /// <param name="positiveRules">Rules evaluates as positive match</param> /// <param name="results">Evaluated (calculated) results (rule outputs)</param> /// <param name="correlationId">Correlation ID used for logging</param> /// <returns>Positive rules ordered by output priorities for the Priority and Output order hit policies</returns> private IOrderedEnumerable <DmnDecisionTableRule> OrderPositiveRulesByOutputPriority( IReadOnlyCollection <DmnDecisionTableRule> positiveRules, DmnDecisionTableRuleExecutionResults results, string correlationId) { IOrderedEnumerable <DmnDecisionTableRule> ordered = null; foreach (var output in Outputs) { //outputs without allowed values list are not part of ordering if (output.AllowedValues == null || output.AllowedValues.Count <= 0) { continue; } if (ordered == null) { ordered = positiveRules.OrderBy(i => { var ruleOutput = i.Outputs.FirstOrDefault(o => o.Output.Index == output.Index); //no such output in positive rule (ruleOutput==null) - at the end of the list //no output result in positive rule (ruleOutput.ResultOutput==null) - at the end of the list //no output result value in positive rule (ruleOutput.ResultOutput.Value==null) - at the end of the list //all handled in GetIndexOfOutputValue: if (value == null) return int.MaxValue; return(GetIndexOfOutputValue(output.AllowedValues, results.GetResult(i, ruleOutput)?.Value, correlationId)); }); } else { ordered = ordered.ThenBy(i => { var ruleOutput = i.Outputs.FirstOrDefault(o => o.Output.Index == output.Index); return(GetIndexOfOutputValue(output.AllowedValues, results.GetResult(i, ruleOutput)?.Value, correlationId)); }); } } return(ordered); }
//------------------------------------------------------------------------ // Any hit policy helpers //------------------------------------------------------------------------ /// <summary> /// Checks whether all positive rule outputs match as required by Any hit policy /// </summary> /// <param name="positiveRules">Rules evaluates as positive match</param> /// <param name="results">Evaluated (calculated) results (rule outputs)</param> /// <returns>True when all positive rule outputs match</returns> private static bool MatchRuleOutputs(IEnumerable <DmnDecisionTableRule> positiveRules, DmnDecisionTableRuleExecutionResults results) { var array = positiveRules.ToArray(); if (array.Length < 2) { return(true); } var firstItem = results.GetResultsHashCode(array[0]); var allEqual = array.Skip(1).All(i => Equals(firstItem, results.GetResultsHashCode(i))); return(allEqual); }