/// <summary> /// Create a new identity match result /// </summary> public MdmIdentityMatchResult(T input, T record, string configurationName, RecordMatchClassification classification = RecordMatchClassification.Match, float score = 1.0f) { this.Record = record; this.Method = RecordMatchMethod.Identifier; this.Score = this.Strength = score; this.Classification = classification; this.ConfigurationName = configurationName; if (input is IHasIdentifiers aIdentity && record is IHasIdentifiers bIdentity) { this.Vectors = new IRecordMatchVector[] { new MdmIdentityMatchAttribute(classification, string.Join(",", aIdentity.Identifiers.Select(o => $"{o.Value} [{o.Authority.DomainName}]")), string.Join(",", bIdentity.Identifiers.Select(o => $"{o.Value} [{o.Authority.DomainName}]"))) }; } }
/// <summary> /// Create a dummy match /// </summary> public DummyMatchResult(T input, T record) { this.m_record = record; // Patient? if (input is Patient) { var pInput = (Patient)(object)input; var pRecord = (Patient)(object)record; // Classify if (pInput.MultipleBirthOrder.HasValue && pInput.MultipleBirthOrder != pRecord.MultipleBirthOrder) { this.Classification = RecordMatchClassification.Probable; } else { this.Classification = RecordMatchClassification.Match; } } else { this.Classification = RecordMatchClassification.Match; } }
/// <summary> /// Creates a new match result /// </summary> /// <param name="record">The record that was classified</param> /// <param name="score">The assigned score</param> /// <param name="classification">The classification</param> /// <param name="method">The method that was used to establish the match</param> /// <param name="strength">The relative strength (0 .. 1) of the match given the maximum score the match could have gotten</param> /// <param name="vectors">The attributes + scores</param> /// <param name="configurationName">The name of the configuration used to match</param> public MatchResult(IdentifiedData record, double score, double strength, String configurationName, RecordMatchClassification classification, RecordMatchMethod method, IEnumerable <IRecordMatchVector> vectors) { this.Strength = strength; this.Record = record; this.Score = score; this.Classification = classification; this.ConfigurationName = configurationName; this.Vectors = vectors.Select(o => o is MatchVector mv ? mv : new MatchVector(o)).ToList(); this.Method = method; }
/// <summary> /// Create MDM match attribute /// </summary> public MdmIdentityMatchAttribute(RecordMatchClassification classification, object aValue, object bValue) { this.Score = classification == RecordMatchClassification.Match ? 1.0f : 0.0f; this.A = aValue; this.B = bValue; }
/// <summary> /// Classifies the individual record against the input and score it based on similarity rules /// </summary> /// <typeparam name="T">The type of record being classified</typeparam> /// <param name="input">The input being classified</param> /// <param name="block">The block which is being classified</param> /// <param name="attributes">The match attributes to classify on</param> /// <returns>The match classification</returns> /// <param name="configurationName">The name of the configuration used</param> /// <param name="evaluationType">The evaluation type</param> /// <param name="matchThreshold">The matching threshold</param> /// <param name="collector">The diagnostics collector to use</param> private IRecordMatchResult <T> ClassifyInternal <T>(T input, T block, List <MatchAttribute> attributes, string configurationName, ThresholdEvaluationType evaluationType, double matchThreshold, double nonMatchThreshold, IRecordMatchingDiagnosticSession collector = null) where T : IdentifiedData { try { collector?.LogStartAction(block); var attributeResult = attributes.Select(v => { try { collector?.LogStartAction(v); this.m_tracer.TraceVerbose("Initializing attribute {0}", v); // Initialize the weights and such for the attribute var attributeScores = v.GetPropertySelectors <T>().Select(selector => { Func <T, dynamic> selectorExpression = (Func <T, dynamic>)selector.Value; object aValue = selectorExpression(input), bValue = selectorExpression(block); var defaultInstance = selectorExpression.Method.ReturnType.GetConstructors().Any(c => c.GetParameters().Length == 0) ? Activator.CreateInstance(selectorExpression.Method.ReturnType) : null; var result = AssertionUtil.ExecuteAssertion(selector.Key, v.Assertion, v, aValue, bValue); return(result); }); var bestScore = attributeScores.OrderByDescending(o => o.CalculatedScore).FirstOrDefault(); if (bestScore == null || !bestScore.CalculatedScore.HasValue) { return(null); } else { var result = new MatchVector(v, v.Id ?? bestScore.PropertyName, bestScore.CalculatedScore.Value, bestScore.Evaluated, bestScore.A, bestScore.B); return(result); } } finally { collector?.LogEndAction(); } }).OfType <MatchVector>().ToList(); // Throw out attributes which are dependent however the dependent attribute was unsuccessful // So if for example: If the scoring for CITY is only counted when STATE is successful, but STATE was // unsuccessful, we want to exclude CITY. attributeResult.RemoveAll(o => !o.Attribute.When.All(w => { var attScore = attributeResult.First(r => r.Attribute.Id == w.AttributeRef); // TODO: Allow cascaded operators to specify a value //switch (w.Operator) //{ // case BinaryOperatorType.NotEqual: // return attScore != w.Value; // case BinaryOperatorType.Equal: // return attScore == w.Value; // case BinaryOperatorType.GreaterThan: // return attScore > w.Value; // case BinaryOperatorType.GreaterThanOrEqual: // return attScore >= w.Value; // case BinaryOperatorType.LessThan: // return attScore < w.Value; // case BinaryOperatorType.LessThanOrEqual: // return attScore <= w.Value; // default: // throw new InvalidOperationException($"Cannot use operator {w.Operator} on when"); //} return(attScore?.Evaluated == true && attScore?.Score > 0); })); // Remove all failed attributes var score = attributeResult.Sum(v => v.Score); // The attribute scores which are produced will be from SUM(NonMatchWeight) .. SUM(MatchWeight) double maxScore = attributeResult.Sum(o => o.Attribute.MatchWeight), minScore = attributeResult.Sum(o => o.Attribute.NonMatchWeight); // This forms a number line between -MIN .. MAX , our probability is the distance that our score // is on that line, for example: -30.392 .. 30.392 // Then the strength is 0.5 of a score of 0 , and 1.0 for a score of 30.392 var strength = (double)(score + -minScore) / (double)(maxScore + -minScore); if (Double.IsNaN(strength)) { strength = 0; } RecordMatchClassification classification = RecordMatchClassification.NonMatch; if (evaluationType == ThresholdEvaluationType.AbsoluteScore) { classification = score > matchThreshold ? RecordMatchClassification.Match : score <= nonMatchThreshold ? RecordMatchClassification.NonMatch : RecordMatchClassification.Probable; } else { classification = strength > matchThreshold ? RecordMatchClassification.Match : strength <= nonMatchThreshold ? RecordMatchClassification.NonMatch : RecordMatchClassification.Probable; } var retVal = new MatchResult <T>(block, score, strength, configurationName, classification, RecordMatchMethod.Weighted, attributeResult); return(retVal); } catch (Exception e) { this.m_tracer.TraceError("Error classifying result set (mt={0}, nmt={1}) - {2}", matchThreshold, nonMatchThreshold, e.Message); throw new MatchingException($"Error classifying result set", e); } finally { collector?.LogEndAction(); } }