From 170cf245cccddbf8cab357bd5e789d83d4b2c483 Mon Sep 17 00:00:00 2001 From: Nick Goupinets Date: Tue, 27 Oct 2020 15:50:29 -0400 Subject: [PATCH 1/2] #2184 Initial implementation --- .../jpa/empi/svc/EmpiMatchFinderSvcImpl.java | 10 +---- .../provider/EmpiProviderMatchR4Test.java | 33 +++++++++++++++- .../uhn/fhir/empi/api/EmpiMatchOutcome.java | 15 ++++++++ .../fhir/empi/api/IEmpiMatchFinderSvc.java | 12 ------ .../fhir/empi/provider/EmpiProviderDstu3.java | 35 +++++++++++++++-- .../fhir/empi/provider/EmpiProviderR4.java | 38 +++++++++++++++++-- 6 files changed, 114 insertions(+), 29 deletions(-) diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java index 66286163891..f8260b00469 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java @@ -35,6 +35,7 @@ import java.util.stream.Collectors; @Service public class EmpiMatchFinderSvcImpl implements IEmpiMatchFinderSvc { + @Autowired private EmpiCandidateSearchSvc myEmpiCandidateSearchSvc; @Autowired @@ -50,13 +51,4 @@ public class EmpiMatchFinderSvcImpl implements IEmpiMatchFinderSvc { .collect(Collectors.toList()); } - @Override - @Nonnull - public List findMatches(String theResourceType, IAnyResource theResource) { - List targetCandidates = getMatchedTargets(theResourceType, theResource); - return targetCandidates.stream() - .filter(candidate -> candidate.isMatch()) - .map(MatchedTarget::getTarget) - .collect(Collectors.toList()); - } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java index d8f4dca618b..88a853fcff2 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java @@ -1,14 +1,22 @@ package ca.uhn.fhir.jpa.empi.provider; +import ca.uhn.fhir.parser.IParser; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.codesystems.MatchGrade; +import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class EmpiProviderMatchR4Test extends BaseProviderR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderMatchR4Test.class); @Override @BeforeEach @@ -26,7 +34,30 @@ public class EmpiProviderMatchR4Test extends BaseProviderR4Test { Bundle result = myEmpiProviderR4.match(newJane); assertEquals(1, result.getEntry().size()); - assertEquals(createdJane.getId(), result.getEntryFirstRep().getResource().getId()); + + Bundle.BundleEntryComponent entry0 = result.getEntry().get(0); + assertEquals(createdJane.getId(), entry0.getResource().getId()); + + Bundle.BundleEntrySearchComponent searchComponent = entry0.getSearch(); + assertEquals(Bundle.SearchEntryMode.MATCH, searchComponent.getMode()); + + assertEquals(2.0/3.0, searchComponent.getScore().doubleValue(), 0.01); + Extension matchGradeExtension = searchComponent.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/match-grade"); + assertNotNull(matchGradeExtension); + assertEquals(MatchGrade.CERTAIN.toCode(), matchGradeExtension.getValue().toString()); + } + + @Test + public void testMismatch() throws Exception { + Patient jane = buildJanePatient(); + jane.setActive(true); + Patient createdJane = createPatient(jane); + + Patient paul = buildPaulPatient(); + paul.setActive(true); + + Bundle result = myEmpiProviderR4.match(paul); + assertEquals(0, result.getEntry().size()); } @Test diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java index 366504e2bcf..e0c68ce025c 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java @@ -105,6 +105,21 @@ public final class EmpiMatchOutcome { return this; } + /** + * Gets normalized score that is in the range from zero to one + * + * @return + * Returns the normalized score + */ + public Double getNormalizedScore() { + if (vector == 0) { + return 0.0; + } else if (score > vector) { + return 1.0; + } + return score / vector; + } + @Override public String toString() { return new ToStringBuilder(this) diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java index 9a9e46adf7f..3304b8e9a23 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java @@ -37,16 +37,4 @@ public interface IEmpiMatchFinderSvc { */ @Nonnull List getMatchedTargets(String theResourceType, IAnyResource theResource); - - /** - * Used by the $match operation. - * Retrieve a list of Patient/Practitioner matches, based on the given {@link IAnyResource} - * Internally, performs all EMPI matching rules on the type of the resource then returns only those - * with a match result of MATCHED. - * - * @param theResourceType the type of the resource. - * @param theResource the resource that we are attempting to find matches for. - * @return a List of {@link IAnyResource} representing all people who had a MATCH outcome. - */ - List findMatches(String theResourceType, IAnyResource theResource); } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java index 6c17e4517f8..80d4a8f8170 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.empi.api.IEmpiControllerSvc; import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; import ca.uhn.fhir.empi.api.IEmpiSubmitSvc; +import ca.uhn.fhir.empi.api.MatchedTarget; import ca.uhn.fhir.empi.model.EmpiTransactionContext; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -37,6 +38,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ParametersUtil; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.DecimalType; import org.hl7.fhir.dstu3.model.InstantType; import org.hl7.fhir.dstu3.model.Parameters; @@ -45,10 +47,12 @@ import org.hl7.fhir.dstu3.model.Person; import org.hl7.fhir.dstu3.model.Practitioner; import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.codesystems.MatchGrade; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -77,20 +81,45 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider { if (thePatient == null) { throw new InvalidRequestException("resource may not be null"); } - Collection matches = myEmpiMatchFinderSvc.findMatches("Patient", thePatient); + List matches = myEmpiMatchFinderSvc.getMatchedTargets("Patient", thePatient); Bundle retVal = new Bundle(); retVal.setType(Bundle.BundleType.SEARCHSET); retVal.setId(UUID.randomUUID().toString()); retVal.getMeta().setLastUpdatedElement(InstantType.now()); - for (IAnyResource next : matches) { - retVal.addEntry().setResource((Resource) next); + for (MatchedTarget next : matches) { + boolean shouldKeepThisEntry = next.isMatch() || next.isPossibleMatch(); + if (!shouldKeepThisEntry) { + continue; + } + + Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); + entry.setResource((Resource) next.getTarget()); + entry.setSearch(toBundleEntrySearchComponent(next)); + + retVal.addEntry(entry); } return retVal; } + private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget next) { + Bundle.BundleEntrySearchComponent searchComponent = new Bundle.BundleEntrySearchComponent(); + searchComponent.setMode(Bundle.SearchEntryMode.MATCH); + searchComponent.setScore(next.getMatchResult().getNormalizedScore()); + + MatchGrade matchGrade = MatchGrade.PROBABLE; + if (next.isMatch()) { + matchGrade = MatchGrade.CERTAIN; + } else if (next.isPossibleMatch()) { + matchGrade = MatchGrade.POSSIBLE; + } + searchComponent.addExtension("http://hl7.org/fhir/StructureDefinition/match-grade", + new CodeType(matchGrade.toCode())); + return searchComponent; + } + @Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class) public Person mergePerson(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, min = 1, max = 1) StringType theFromPersonId, @OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, min = 1, max = 1) StringType theToPersonId, diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java index 8d0d3b9291a..22dd53992dc 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.empi.api.IEmpiControllerSvc; import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; import ca.uhn.fhir.empi.api.IEmpiSubmitSvc; +import ca.uhn.fhir.empi.api.MatchedTarget; import ca.uhn.fhir.empi.model.EmpiTransactionContext; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -39,7 +40,9 @@ import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.Parameters; @@ -48,8 +51,10 @@ import org.hl7.fhir.r4.model.Person; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.codesystems.MatchGrade; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -74,25 +79,50 @@ public class EmpiProviderR4 extends BaseEmpiProvider { } @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) - public Bundle match(@OperationParam(name=ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { + public Bundle match(@OperationParam(name = ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { if (thePatient == null) { throw new InvalidRequestException("resource may not be null"); } - Collection matches = myEmpiMatchFinderSvc.findMatches("Patient", thePatient); + List matches = myEmpiMatchFinderSvc.getMatchedTargets("Patient", thePatient); Bundle retVal = new Bundle(); retVal.setType(Bundle.BundleType.SEARCHSET); retVal.setId(UUID.randomUUID().toString()); retVal.getMeta().setLastUpdatedElement(InstantType.now()); - for (IAnyResource next : matches) { - retVal.addEntry().setResource((Resource) next); + for (MatchedTarget next : matches) { + boolean shouldKeepThisEntry = next.isMatch() || next.isPossibleMatch(); + if (!shouldKeepThisEntry) { + continue; + } + + Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); + entry.setResource((Resource) next.getTarget()); + entry.setSearch(toBundleEntrySearchComponent(next)); + + retVal.addEntry(entry); } return retVal; } + private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget next) { + Bundle.BundleEntrySearchComponent searchComponent = new Bundle.BundleEntrySearchComponent(); + searchComponent.setMode(Bundle.SearchEntryMode.MATCH); + searchComponent.setScore(next.getMatchResult().getNormalizedScore()); + + MatchGrade matchGrade = MatchGrade.PROBABLE; + if (next.isMatch()) { + matchGrade = MatchGrade.CERTAIN; + } else if (next.isPossibleMatch()) { + matchGrade = MatchGrade.POSSIBLE; + } + searchComponent.addExtension("http://hl7.org/fhir/StructureDefinition/match-grade", + new CodeType(matchGrade.toCode())); + return searchComponent; + } + @Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class) public Person mergePersons(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, min = 1, max = 1) StringType theFromPersonId, @OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, min = 1, max = 1) StringType theToPersonId, From 954fbf054ff36efc99b11fa107555a2e2da788e7 Mon Sep 17 00:00:00 2001 From: Nick Goupinets Date: Wed, 28 Oct 2020 15:09:15 -0400 Subject: [PATCH 2/2] Fixed implementation based on the feedback. --- .../provider/EmpiProviderMatchR4Test.java | 43 ++++++++++++++++--- .../ca/uhn/fhir/empi/api/EmpiConstants.java | 1 + .../uhn/fhir/empi/api/EmpiMatchOutcome.java | 7 ++- .../fhir/empi/provider/EmpiProviderDstu3.java | 18 +++++--- .../fhir/empi/provider/EmpiProviderR4.java | 15 ++++--- .../fhir/empi/api/EmpiMatchOutcomeTest.java | 27 ++++++++++++ 6 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/api/EmpiMatchOutcomeTest.java diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java index 88a853fcff2..f7785341f1e 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMatchR4Test.java @@ -1,23 +1,28 @@ package ca.uhn.fhir.jpa.empi.provider; -import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.empi.api.EmpiConstants; +import com.google.common.collect.Ordering; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.codesystems.MatchGrade; -import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; public class EmpiProviderMatchR4Test extends BaseProviderR4Test { private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderMatchR4Test.class); + public static final String NAME_GIVEN_JANET = NAME_GIVEN_JANE + "t"; + @Override @BeforeEach public void before() { @@ -41,12 +46,38 @@ public class EmpiProviderMatchR4Test extends BaseProviderR4Test { Bundle.BundleEntrySearchComponent searchComponent = entry0.getSearch(); assertEquals(Bundle.SearchEntryMode.MATCH, searchComponent.getMode()); - assertEquals(2.0/3.0, searchComponent.getScore().doubleValue(), 0.01); - Extension matchGradeExtension = searchComponent.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/match-grade"); + assertEquals(2.0 / 3.0, searchComponent.getScore().doubleValue(), 0.01); + Extension matchGradeExtension = searchComponent.getExtensionByUrl(EmpiConstants.FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE); assertNotNull(matchGradeExtension); assertEquals(MatchGrade.CERTAIN.toCode(), matchGradeExtension.getValue().toString()); } + @Test + public void testMatchOrder() throws Exception { + Patient jane0 = buildJanePatient(); + Patient createdJane1 = createPatient(jane0); + + Patient jane1 = buildPatientWithNameAndId(NAME_GIVEN_JANET, JANE_ID); + jane1.setActive(true); + Patient createdJane2 = createPatient(jane1); + + Patient newJane = buildJanePatient(); + + Bundle result = myEmpiProviderR4.match(newJane); + assertEquals(2, result.getEntry().size()); + + Bundle.BundleEntryComponent entry0 = result.getEntry().get(0); + assertTrue(jane0.getId().equals(((Patient) entry0.getResource()).getId()), "First match should be Jane"); + Bundle.BundleEntryComponent entry1 = result.getEntry().get(1); + assertTrue(jane1.getId().equals(((Patient) entry1.getResource()).getId()), "Second match should be Janet"); + + List scores = result.getEntry() + .stream() + .map(bec -> bec.getSearch().getScore().doubleValue()) + .collect(Collectors.toList()); + assertTrue(Ordering.natural().reverse().isOrdered(scores), "Match scores must be descending"); + } + @Test public void testMismatch() throws Exception { Patient jane = buildJanePatient(); diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java index 4b3a134f124..1a1d53eabc0 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java @@ -32,5 +32,6 @@ public class EmpiConstants { public static final String HAPI_ENTERPRISE_IDENTIFIER_SYSTEM = "http://hapifhir.io/fhir/NamingSystem/empi-person-enterprise-id"; public static final String ALL_RESOURCE_SEARCH_PARAM_TYPE = "*"; + public static final String FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE = "http://hl7.org/fhir/StructureDefinition/match-grade"; } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java index e0c68ce025c..9f9c7348977 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java @@ -117,7 +117,12 @@ public final class EmpiMatchOutcome { } else if (score > vector) { return 1.0; } - return score / vector; + + double retVal = score / vector; + if (retVal < 0) { + retVal = 0.0; + } + return retVal; } @Override diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java index 80d4a8f8170..5f85300d643 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.empi.provider; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiConstants; import ca.uhn.fhir.empi.api.EmpiLinkJson; import ca.uhn.fhir.empi.api.IEmpiControllerSvc; import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; @@ -52,6 +53,7 @@ import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -77,11 +79,13 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider { } @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) - public Bundle match(@OperationParam(name=ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { + public Bundle match(@OperationParam(name = ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { if (thePatient == null) { throw new InvalidRequestException("resource may not be null"); } + List matches = myEmpiMatchFinderSvc.getMatchedTargets("Patient", thePatient); + matches.sort(Comparator.comparing((MatchedTarget m) -> m.getMatchResult().getNormalizedScore()).reversed()); Bundle retVal = new Bundle(); retVal.setType(Bundle.BundleType.SEARCHSET); @@ -104,19 +108,19 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider { return retVal; } - private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget next) { + private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget theMatchedTarget) { Bundle.BundleEntrySearchComponent searchComponent = new Bundle.BundleEntrySearchComponent(); searchComponent.setMode(Bundle.SearchEntryMode.MATCH); - searchComponent.setScore(next.getMatchResult().getNormalizedScore()); + searchComponent.setScore(theMatchedTarget.getMatchResult().getNormalizedScore()); MatchGrade matchGrade = MatchGrade.PROBABLE; - if (next.isMatch()) { + if (theMatchedTarget.isMatch()) { matchGrade = MatchGrade.CERTAIN; - } else if (next.isPossibleMatch()) { + } else if (theMatchedTarget.isPossibleMatch()) { matchGrade = MatchGrade.POSSIBLE; } - searchComponent.addExtension("http://hl7.org/fhir/StructureDefinition/match-grade", - new CodeType(matchGrade.toCode())); + + searchComponent.addExtension(EmpiConstants.FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE, new CodeType(matchGrade.toCode())); return searchComponent; } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java index 22dd53992dc..d0fc3c2a277 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.empi.provider; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiConstants; import ca.uhn.fhir.empi.api.EmpiLinkJson; import ca.uhn.fhir.empi.api.IEmpiControllerSvc; import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; @@ -54,6 +55,7 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.codesystems.MatchGrade; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -85,6 +87,7 @@ public class EmpiProviderR4 extends BaseEmpiProvider { } List matches = myEmpiMatchFinderSvc.getMatchedTargets("Patient", thePatient); + matches.sort(Comparator.comparing((MatchedTarget m) -> m.getMatchResult().getNormalizedScore()).reversed()); Bundle retVal = new Bundle(); retVal.setType(Bundle.BundleType.SEARCHSET); @@ -107,19 +110,19 @@ public class EmpiProviderR4 extends BaseEmpiProvider { return retVal; } - private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget next) { + private Bundle.BundleEntrySearchComponent toBundleEntrySearchComponent(MatchedTarget theMatchedTarget) { Bundle.BundleEntrySearchComponent searchComponent = new Bundle.BundleEntrySearchComponent(); searchComponent.setMode(Bundle.SearchEntryMode.MATCH); - searchComponent.setScore(next.getMatchResult().getNormalizedScore()); + searchComponent.setScore(theMatchedTarget.getMatchResult().getNormalizedScore()); MatchGrade matchGrade = MatchGrade.PROBABLE; - if (next.isMatch()) { + if (theMatchedTarget.isMatch()) { matchGrade = MatchGrade.CERTAIN; - } else if (next.isPossibleMatch()) { + } else if (theMatchedTarget.isPossibleMatch()) { matchGrade = MatchGrade.POSSIBLE; } - searchComponent.addExtension("http://hl7.org/fhir/StructureDefinition/match-grade", - new CodeType(matchGrade.toCode())); + + searchComponent.addExtension(EmpiConstants.FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE, new CodeType(matchGrade.toCode())); return searchComponent; } diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/api/EmpiMatchOutcomeTest.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/api/EmpiMatchOutcomeTest.java new file mode 100644 index 00000000000..ecd5d8f52f5 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/api/EmpiMatchOutcomeTest.java @@ -0,0 +1,27 @@ +package ca.uhn.fhir.empi.api; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EmpiMatchOutcomeTest { + + @Test + void testNormalizedScore() { + EmpiMatchOutcome outcome = new EmpiMatchOutcome(0l, 0.0); + assertEquals(0.0, outcome.getNormalizedScore()); + + outcome = new EmpiMatchOutcome(10l, 10.0); + assertEquals(1.0, outcome.getNormalizedScore()); + + outcome = new EmpiMatchOutcome(10l, -10.0); + assertEquals(0.0, outcome.getNormalizedScore()); + + outcome = new EmpiMatchOutcome(3l, 2.0); + assertEquals(2.0 / 3.0, outcome.getNormalizedScore(), 0.0001); + + outcome = new EmpiMatchOutcome(5l, 19.0); + assertEquals(1.0, outcome.getNormalizedScore()); + } + +}