public static List <EvalEvidence> GetEvidence <T>(this IConditionExpression condition, T instance) { var leafEvaluators = new List <LeafExpression>(); PopulateLeafFieldEvaluators(condition, leafEvaluators); var list = new List <EvalEvidence>(); foreach (var leafExpr in leafEvaluators) { var ctxParameter = Expression.Parameter(typeof(T), "ctx"); var leftExpression = ctxParameter.EvaluateExpression(leafExpr.Left, leafExpr.Operator != Operator.IsNull && leafExpr.Operator != Operator.NotIsNull); var leftType = leftExpression.Type; var lambda = Expression.Lambda(leftExpression, ctxParameter); var getValue = lambda.Compile(); var actualObj = getValue.DynamicInvoke(instance); object expected = leafExpr.Right; Type rightType = typeof(string); if (leafExpr.RightSideIsExpression) { var rightExpression = ctxParameter.EvaluateExpression(leafExpr.Right); lambda = Expression.Lambda(rightExpression, ctxParameter); getValue = lambda.Compile(); var expectedObj = getValue.DynamicInvoke(instance); expected = expectedObj; rightType = rightExpression.Type; } var evidence = new EvalEvidence() { Expression = leafExpr, LeftType = leftType, RightType = rightType, Actual = actualObj == null ? null : JToken.FromObject(actualObj), Expected = JToken.FromObject(expected) }; var getScore = evidence.GetScore <T>(); evidence.Score = getScore(instance); list.Add(evidence); } return(list); }
public static Func <T, double> GetScore <T>(this EvalEvidence evidence) { if (evidence.LeftType.IsNullableType() && Nullable.GetUnderlyingType(evidence.LeftType).IsNumericType() && (evidence.Expression.Operator == Operator.IsNull || evidence.Expression.Operator == Operator.NotIsNull)) { double?actual = string.IsNullOrEmpty(evidence.Actual?.ToString()) ? default(double?) : double.Parse(evidence.Actual.ToString()); double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.IsNull: return(actual.HasValue? 0.0 : 1.0); case Operator.NotIsNull: return(actual.HasValue? 1.0 : 0.0); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for numeric type"); } } return(getScore); } if (evidence.LeftType.IsNumericType()) { double actual = double.Parse(evidence.Actual.ToString()); double expected = double.Parse(evidence.Expected.ToString()); double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.GreaterThan: return(actual > expected ? 1.0 : (actual / expected)); case Operator.GreaterOrEqual: return(actual >= expected ? 1.0 : actual / expected); case Operator.LessThan: return(actual < expected ? 1.0 : (actual - expected) / expected); case Operator.LessOrEqual: return(actual <= expected ? 1.0 : (actual - expected) / expected); case Operator.Equals: return(Math.Abs(actual - expected) < TOLERANCE ? 1.0 : 0.0); case Operator.NotEquals: return(Math.Abs(actual - expected) > TOLERANCE ? 1.0 : 0.0); case Operator.DiffWithinPct: double tolerance = double.Parse(evidence.Expression.OperatorArgs[0]) * expected / 100; return(Math.Abs(actual - expected) < tolerance ? 1.0 : 0.0); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for numeric type"); } } return(getScore); } if (evidence.LeftType.IsGenericType && evidence.LeftType.GetGenericArguments()[0].IsNumericType()) { var actualNumberList = ((JArray)evidence.Actual).Select(t => double.Parse(t.ToString())).ToList(); var expectedArray = evidence.Expression.Right.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(double.Parse).ToArray(); double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.IsEmpty: return(actualNumberList.Count == 0 ? 1.0 : 0.0); case Operator.NotIsEmpty: return(actualNumberList.Count > 0 ? 1.0 : 0.0); case Operator.AllInRangePct: double pct = double.Parse(evidence.Expression.OperatorArgs[0]) / 100; double min = expectedArray[0] * (1 - pct); double max = expectedArray[1] * (1 + pct); return(actualNumberList.All(n => n >= min && n <= max) ? 1.0 : 0.0); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for numeric list"); } } return(getScore); } if (evidence.LeftType == typeof(string)) { var actual = evidence.Actual.ToString(); var expectedString = evidence.RightType == typeof(string) ? evidence.Expected.ToString() : null; var expectedStringArray = evidence.Expression.Right .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()).ToArray(); double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.Equals: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(actual.Equals(expectedString, StringComparison.InvariantCultureIgnoreCase) ? 1.0 : 0.0); case Operator.NotEquals: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(actual.Equals(expectedString, StringComparison.InvariantCultureIgnoreCase) ? 0.0 : 1.0); case Operator.StartsWith: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(actual.StartsWith(expectedString) ? 1.0 : 0.0); case Operator.NotStartsWith: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(!actual.StartsWith(expectedString) ? 1.0 : 0.0); case Operator.Contains: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(actual.Contains(expectedString) ? 1.0 : 0.0); case Operator.NotContains: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(!actual.Contains(expectedString) ? 1.0 : 0.0); case Operator.In: if (expectedStringArray.Length == 0) { return(0.0); } return(expectedStringArray.Contains(actual) ? 1.0 : 0.0); case Operator.NotIn: if (expectedStringArray.Length == 0) { return(0.0); } return(!expectedStringArray.Contains(actual) ? 1.0 : 0.0); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for string type"); } } return(getScore); } if (evidence.LeftType == typeof(string[]) || evidence.LeftType == typeof(IEnumerable <string>)) { var actualStringList = ((JArray)evidence.Actual).Select(i => i.ToString()).ToList(); var expectedString = evidence.RightType == typeof(string) ? evidence.Expected.ToString() : null; var expectedStringArray = evidence.Expression.Right .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()).ToArray(); var commonCount = actualStringList.Distinct().Intersect(expectedStringArray).Count(); var actualCount = actualStringList.Count; var expectedCount = expectedStringArray.Length; double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.Contains: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(actualStringList.Contains(expectedString) ? 1.0 : 0.0); case Operator.NotContains: if (string.IsNullOrEmpty(expectedString)) { return(0.0); } return(!actualStringList.Contains(expectedString) ? 1.0 : 0.0); case Operator.ContainsAll: if (expectedStringArray.Length == 0) { return(0.0); } //return expectedStringArray.All(s => actualStringList.Contains(s)) ? 1.0 : 0.0; return(commonCount < expectedCount ? (double)commonCount / expectedCount : 1.0); case Operator.NotContainsAll: if (expectedStringArray.Length == 0) { return(0.0); } //return expectedStringArray.Any(s => !actualStringList.Contains(s)) ? 1.0 : 0.0; return(commonCount < expectedCount ? 1.0 : (double)commonCount / expectedCount); case Operator.AllIn: if (expectedStringArray.Length == 0) { return(0.0); } //return actualStringList.All(s => expectedStringArray.Contains(s)) ? 1.0 : 0.0; return(commonCount < actualCount ? (double)commonCount / expectedCount : 1.0); case Operator.NotAllIn: if (expectedStringArray.Length == 0) { return(0.0); } //return actualStringList.Any(s => !expectedStringArray.Contains(s)) ? 1.0 : 0.0; return(commonCount < actualCount ? 1.0 : (double)commonCount / expectedCount); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for numeric type"); } } return(getScore); } if (evidence.LeftType == typeof(bool)) { bool actual = bool.Parse(evidence.Actual.ToString()); bool expected = bool.Parse(evidence.Expected.ToString()); double getScore(T t) { return(actual == expected ? 1.0 : 0.0); } return(getScore); } if (Nullable.GetUnderlyingType(evidence.LeftType) != null && ( evidence.Expression.Operator == Operator.IsNull || evidence.Expression.Operator == Operator.NotIsNull)) { double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.IsNull: return(evidence.Actual == null ? 1.0 : 0.0); case Operator.NotIsNull: return(evidence.Actual == null ? 0.0 : 1.0); default: return(0.0); } } return(getScore); } if (!evidence.LeftType.IsPrimitiveType()) { var actualObject = evidence.LeftType == typeof(Enumerable) ? null : evidence.Actual; var actualList = evidence.LeftType == typeof(Enumerable) ? ((JArray)evidence.Actual).ToList() : null; var actualCount = actualList?.Count ?? 0; double getScore(T t) { switch (evidence.Expression.Operator) { case Operator.IsNull: return(actualObject == null ? 1.0 : 0.0); case Operator.NotIsNull: return(actualObject == null ? 0.0 : 1.0); case Operator.IsEmpty: return(actualCount == 0 ? 1.0 : 0.0); case Operator.NotIsEmpty: return(actualCount > 0 ? 1.0 : 0.0); default: throw new NotSupportedException($"operator '{evidence.Expression.Operator}' is not supported for type '{evidence.LeftType.Name}'"); } } return(getScore); } throw new NotSupportedException($"expression with type '{evidence.LeftType.Name}' is not supported"); }