diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index d6f65b91794..17dbf26162e 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 3d3a0683523..150ce2c1295 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 8f8e138ac7a..98e10ec3c10 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index fb52d568b9f..346a929a547 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 7b4c1afe91d..4f4df91ba18 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index cbd07617e0f..c378ea02796 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 982c6c46480..04a840db292 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index cd84edbca81..a98004d7d99 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 08d53f813c0..b91cee3d080 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 38cac3e4603..ab4e11fa4c1 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index c067f7e19c9..f736e14d80d 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index bef8a3b2810..a08d329cbee 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index e6042b475df..2d137c54951 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5274-adding-metric-svc-to-mdm.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5274-adding-metric-svc-to-mdm.yaml new file mode 100644 index 00000000000..6d9b3be278f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5274-adding-metric-svc-to-mdm.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 5274 +title: "Added a service for generating metrics on mdm links and resources. + This includes JPA queries and updated indices. + " diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 5e82fe2a42b..89cc744fcc2 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 9ed9b7cc147..17ae3e225c7 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index cd0d62fd93a..32ffc32391c 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 223eb3c1ffa..954eac61b5f 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IMdmLinkJpaMetricsRepository.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IMdmLinkJpaMetricsRepository.java new file mode 100644 index 00000000000..03566ca238a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IMdmLinkJpaMetricsRepository.java @@ -0,0 +1,27 @@ +package ca.uhn.fhir.jpa.dao.data; + +import ca.uhn.fhir.jpa.entity.MdmLink; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository("metricsRepository") +public interface IMdmLinkJpaMetricsRepository extends JpaRepository, IHapiFhirJpaRepository { + + @Query("SELECT ml.myMatchResult AS match_result, ml.myLinkSource AS link_source, count(*) AS c " + + "FROM MdmLink ml " + + "WHERE ml.myMdmSourceType = :resourceName " + + "AND ml.myLinkSource in (:linkSource) " + + "AND ml.myMatchResult in (:matchResult) " + + "GROUP BY match_result, link_source " + + "ORDER BY match_result") + Object[][] generateMetrics( + @Param("resourceName") String theResourceType, + @Param("linkSource") List theLinkSources, + @Param("matchResult") List theMatchTypes); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmMetricSvcJpaImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmMetricSvcJpaImpl.java new file mode 100644 index 00000000000..6f1b4a57ff4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmMetricSvcJpaImpl.java @@ -0,0 +1,136 @@ +package ca.uhn.fhir.jpa.dao.mdm; + +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.data.IMdmLinkJpaMetricsRepository; +import ca.uhn.fhir.mdm.api.BaseMdmMetricSvc; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.api.params.GenerateMdmMetricsParameters; +import ca.uhn.fhir.mdm.model.MdmLinkMetrics; +import ca.uhn.fhir.mdm.model.MdmLinkScoreMetrics; +import ca.uhn.fhir.mdm.model.MdmMetrics; +import ca.uhn.fhir.mdm.model.MdmResourceMetrics; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Query; + +public class MdmMetricSvcJpaImpl extends BaseMdmMetricSvc { + + private final IMdmLinkJpaMetricsRepository myJpaRepository; + + private final EntityManagerFactory myEntityManagerFactory; + + public MdmMetricSvcJpaImpl( + IMdmLinkJpaMetricsRepository theRepository, + DaoRegistry theDaoRegistry, + EntityManagerFactory theEntityManagerFactory) { + super(theDaoRegistry); + myJpaRepository = theRepository; + myEntityManagerFactory = theEntityManagerFactory; + } + + protected MdmLinkMetrics generateLinkMetrics(GenerateMdmMetricsParameters theParameters) { + List linkSources = theParameters.getLinkSourceFilters(); + List matchResults = theParameters.getMatchResultFilters(); + + if (linkSources.isEmpty()) { + linkSources = Arrays.asList(MdmLinkSourceEnum.values()); + } + if (matchResults.isEmpty()) { + matchResults = Arrays.asList(MdmMatchResultEnum.values()); + } + + Object[][] data = myJpaRepository.generateMetrics(theParameters.getResourceType(), linkSources, matchResults); + MdmLinkMetrics metrics = new MdmLinkMetrics(); + metrics.setResourceType(theParameters.getResourceType()); + for (Object[] row : data) { + MdmMatchResultEnum matchResult = (MdmMatchResultEnum) row[0]; + MdmLinkSourceEnum source = (MdmLinkSourceEnum) row[1]; + long count = (Long) row[2]; + metrics.addMetric(matchResult, source, count); + } + return metrics; + } + + protected MdmLinkScoreMetrics generateLinkScoreMetrics(GenerateMdmMetricsParameters theParameters) { + String resourceType = theParameters.getResourceType(); + + List matchResultTypes = theParameters.getMatchResultFilters(); + + // if no result type filter, add all result types + if (matchResultTypes.isEmpty()) { + matchResultTypes = Arrays.asList(MdmMatchResultEnum.values()); + } + + String sql = "SELECT %s FROM MPI_LINK ml WHERE ml.TARGET_TYPE = :resourceType " + + "AND ml.MATCH_RESULT in (:matchResult)"; + + StringBuilder sb = new StringBuilder(); + sb.append("sum(case when ml.SCORE is null then 1 else 0 end) as B_" + NULL_VALUE); + + for (int i = 0; i < BUCKETS; i++) { + double bucket = getBucket(i + 1); + sb.append(",\n"); + if (i == 0) { + // score <= .01 + sb.append(String.format("sum(case when ml.SCORE <= %.2f then 1 else 0 end) as B%d", bucket, i)); + } else { + // score > i/100 && score <= i/100 + sb.append(String.format( + "sum(case when ml.score > %.2f and ml.SCORE <= %.2f then 1 else 0 end) as B%d", + getBucket(i), bucket, i)); + } + } + + EntityManager em = myEntityManagerFactory.createEntityManager(); + + Query nativeQuery = em.createNativeQuery(String.format(sql, sb.toString())); + + org.hibernate.query.Query hibernateQuery = (org.hibernate.query.Query) nativeQuery; + + hibernateQuery.setParameter("resourceType", resourceType); + hibernateQuery.setParameter( + "matchResult", matchResultTypes.stream().map(Enum::ordinal).collect(Collectors.toList())); + + List results = hibernateQuery.getResultList(); + + em.close(); + + MdmLinkScoreMetrics metrics = new MdmLinkScoreMetrics(); + + // we only get one row back + Object[] row = (Object[]) results.get(0); + int length = row.length; + for (int i = 0; i < length; i++) { + // if there's nothing in the db, these values will all be null + BigInteger bi = row[i] != null ? (BigInteger) row[i] : BigInteger.valueOf(0); + double bucket = getBucket(i); + if (i == 0) { + metrics.addScore(NULL_VALUE, bi.longValue()); + } else if (i == 1) { + metrics.addScore(String.format(FIRST_BUCKET, bucket), bi.longValue()); + } else { + metrics.addScore(String.format(NTH_BUCKET, getBucket(i - 1), bucket), bi.longValue()); + } + } + + return metrics; + } + + @Transactional + @Override + public MdmMetrics generateMdmMetrics(GenerateMdmMetricsParameters theParameters) { + MdmResourceMetrics resourceMetrics = generateResourceMetrics(theParameters); + MdmLinkMetrics linkMetrics = generateLinkMetrics(theParameters); + MdmLinkScoreMetrics scoreMetrics = generateLinkScoreMetrics(theParameters); + + MdmMetrics metrics = MdmMetrics.fromSeperableMetrics(resourceMetrics, linkMetrics, scoreMetrics); + return metrics; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/MdmLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/MdmLink.java index 24e5063ab0c..4363bde74c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/MdmLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/MdmLink.java @@ -64,7 +64,10 @@ import javax.persistence.UniqueConstraint; @Index(name = "IDX_EMPI_MATCH_TGT_VER", columnList = "MATCH_RESULT, TARGET_PID, VERSION"), // v---- this one @Index(name = "IDX_EMPI_GR_TGT", columnList = "GOLDEN_RESOURCE_PID, TARGET_PID"), - @Index(name = "FK_EMPI_LINK_TARGET", columnList = "TARGET_PID") + @Index(name = "FK_EMPI_LINK_TARGET", columnList = "TARGET_PID"), + // indexes for metrics + @Index(name = "IDX_EMPI_TGT_MR_LS", columnList = "TARGET_TYPE, MATCH_RESULT, LINK_SOURCE"), + @Index(name = "IDX_EMPI_TGT_MR_SCORE", columnList = "TARGET_TYPE, MATCH_RESULT, SCORE") }) @Audited // This is the table name generated by default by envers, but we set it explicitly for clarity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index b68af9bc768..c17de307498 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -93,6 +93,23 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { init640_after_20230126(); init660(); init680(); + init700(); + } + + protected void init700() { + Builder version = forVersion(VersionEnum.V7_0_0); + + // new indices on MdmLink + Builder.BuilderWithTableName mdmLinkTable = version.onTable("MPI_LINK"); + + mdmLinkTable + .addIndex("20230911.1", "IDX_EMPI_TGT_MR_LS") + .unique(false) + .withColumns("TARGET_TYPE", "MATCH_RESULT", "LINK_SOURCE"); + mdmLinkTable + .addIndex("20230911.2", "IDX_EMPi_TGT_MR_SCore") + .unique(false) + .withColumns("TARGET_TYPE", "MATCH_RESULT", "SCORE"); } protected void init680() { diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 6f7859823db..bee277633ec 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index 452671378ab..a2ac4d063a4 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index c48e50f0fc4..db9e2677280 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 3ee942fbcd1..b7e925ea214 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java index 1d82d9e49a1..2c80e680249 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java @@ -39,7 +39,7 @@ import ca.uhn.fhir.jpa.mdm.svc.MdmLinkUpdaterSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmMatchFinderSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc; import ca.uhn.fhir.jpa.mdm.svc.MdmModelConverterSvcImpl; -import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc; +import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc; import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateSearcher; import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByEidSvc; @@ -57,6 +57,7 @@ import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc; import ca.uhn.fhir.mdm.api.IMdmLinkSvc; import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc; import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmSettings; import ca.uhn.fhir.mdm.batch2.MdmBatch2Config; import ca.uhn.fhir.mdm.blocklist.svc.IBlockListRuleProvider; @@ -123,8 +124,8 @@ public class MdmConsumerConfig { } @Bean - MdmResourceDaoSvc mdmResourceDaoSvc() { - return new MdmResourceDaoSvc(); + IMdmResourceDaoSvc mdmResourceDaoSvc() { + return new MdmResourceDaoSvcImpl(); } @Bean diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/GoldenResourceMergerSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/GoldenResourceMergerSvcImpl.java index b1e15c91a40..fb60e425f57 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/GoldenResourceMergerSvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/GoldenResourceMergerSvcImpl.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc; import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc; import ca.uhn.fhir.mdm.api.IMdmLink; import ca.uhn.fhir.mdm.api.IMdmLinkSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; import ca.uhn.fhir.mdm.api.MdmMatchOutcome; @@ -74,7 +75,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc { IIdHelperService myIdHelperService; @Autowired - MdmResourceDaoSvc myMdmResourceDaoSvc; + IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired MdmPartitionHelper myMdmPartitionHelper; diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmEidUpdateService.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmEidUpdateService.java index 95e445012d3..52677a69b51 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmEidUpdateService.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmEidUpdateService.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate; import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc; import ca.uhn.fhir.mdm.api.IMdmLink; import ca.uhn.fhir.mdm.api.IMdmLinkSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmSettings; import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; @@ -49,7 +50,7 @@ public class MdmEidUpdateService { private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); @Autowired - private MdmResourceDaoSvc myMdmResourceDaoSvc; + private IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired private IMdmLinkSvc myMdmLinkSvc; diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkSvcImpl.java index 3870b5d3393..027f69e3b7f 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkSvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkSvcImpl.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.api.svc.IIdHelperService; import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc; import ca.uhn.fhir.mdm.api.IMdmLink; import ca.uhn.fhir.mdm.api.IMdmLinkSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; import ca.uhn.fhir.mdm.api.MdmMatchOutcome; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; @@ -51,7 +52,7 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc { private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); @Autowired - private MdmResourceDaoSvc myMdmResourceDaoSvc; + private IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired private MdmLinkDaoSvc myMdmLinkDaoSvc; diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImpl.java index 96892d37965..c37ff21be1d 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImpl.java @@ -30,6 +30,7 @@ import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc; import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; import ca.uhn.fhir.mdm.api.IMdmLink; import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmSettings; import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; @@ -72,7 +73,7 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc { MdmLinkDaoSvc myMdmLinkDaoSvc; @Autowired - MdmResourceDaoSvc myMdmResourceDaoSvc; + IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired MdmMatchLinkSvc myMdmMatchLinkSvc; diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmMatchLinkSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmMatchLinkSvc.java index b7fba3ab877..f3512ea535a 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmMatchLinkSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmMatchLinkSvc.java @@ -19,6 +19,7 @@ */ package ca.uhn.fhir.jpa.mdm.svc; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.mdm.models.FindGoldenResourceCandidatesParams; import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateList; import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateStrategyEnum; @@ -70,6 +71,9 @@ public class MdmMatchLinkSvc { @Autowired private IBlockRuleEvaluationSvc myBlockRuleEvaluationSvc; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired private IMdmSurvivorshipService myMdmSurvivorshipService; @@ -106,6 +110,8 @@ public class MdmMatchLinkSvc { * (so that future resources may match to it). */ boolean isResourceBlocked = myBlockRuleEvaluationSvc.isMdmMatchingBlocked(theResource); + // we will mark the golden resource special for this + theMdmTransactionContext.setIsBlocked(isResourceBlocked); if (!isResourceBlocked) { FindGoldenResourceCandidatesParams params = diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcImpl.java similarity index 83% rename from hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvc.java rename to hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcImpl.java index 90d581ca649..2cf9489ee13 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcImpl.java @@ -26,14 +26,15 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmSettings; import ca.uhn.fhir.mdm.api.MdmConstants; +import ca.uhn.fhir.mdm.util.MdmSearchParamBuildingUtils; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.SystemRequestDetails; import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; -import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -42,10 +43,9 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; -import javax.annotation.Nonnull; @Service -public class MdmResourceDaoSvc { +public class MdmResourceDaoSvcImpl implements IMdmResourceDaoSvc { private static final int MAX_MATCHING_GOLDEN_RESOURCES = 1000; @@ -55,6 +55,7 @@ public class MdmResourceDaoSvc { @Autowired IMdmSettings myMdmSettings; + @Override public DaoMethodOutcome upsertGoldenResource(IAnyResource theGoldenResource, String theResourceType) { IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType); RequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId((RequestPartitionId) @@ -66,12 +67,7 @@ public class MdmResourceDaoSvc { } } - /** - * Given a resource, remove its Golden Resource tag. - * - * @param theGoldenResource the {@link IAnyResource} to remove the tag from. - * @param theResourcetype the type of that resource - */ + @Override public void removeGoldenResourceTag(IAnyResource theGoldenResource, String theResourcetype) { IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourcetype); RequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId((RequestPartitionId) @@ -84,18 +80,22 @@ public class MdmResourceDaoSvc { requestDetails); } + @Override public IAnyResource readGoldenResourceByPid(IResourcePersistentId theGoldenResourcePid, String theResourceType) { IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType); return (IAnyResource) resourceDao.readByPid(theGoldenResourcePid); } + @Override public Optional searchGoldenResourceByEID(String theEid, String theResourceType) { return this.searchGoldenResourceByEID(theEid, theResourceType, null); } + @Override public Optional searchGoldenResourceByEID( String theEid, String theResourceType, RequestPartitionId thePartitionId) { - SearchParameterMap map = buildEidSearchParameterMap(theEid, theResourceType); + SearchParameterMap map = MdmSearchParamBuildingUtils.buildEidSearchParameterMap( + theEid, theResourceType, myMdmSettings.getMdmRules()); IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType); SystemRequestDetails systemRequestDetails = new SystemRequestDetails(); @@ -118,16 +118,4 @@ public class MdmResourceDaoSvc { return Optional.of((IAnyResource) resources.get(0)); } } - - @Nonnull - private SearchParameterMap buildEidSearchParameterMap(String theEid, String theResourceType) { - SearchParameterMap map = new SearchParameterMap(); - map.setLoadSynchronous(true); - map.add( - "identifier", - new TokenParam( - myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType(theResourceType), theEid)); - map.add("_tag", new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD)); - return map; - } } diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/FindCandidateByEidSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/FindCandidateByEidSvc.java index 2ab9e19ace0..e887cb47b39 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/FindCandidateByEidSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/FindCandidateByEidSvc.java @@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc; -import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.IMdmLink; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.api.MdmMatchOutcome; import ca.uhn.fhir.mdm.log.Logs; import ca.uhn.fhir.mdm.model.CanonicalEID; @@ -47,7 +47,7 @@ public class FindCandidateByEidSvc extends BaseCandidateFinder { private EIDHelper myEIDHelper; @Autowired - private MdmResourceDaoSvc myMdmResourceDaoSvc; + private IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired private MdmLinkDaoSvc myMdmLinkDaoSvc; diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/MdmGoldenResourceFindingSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/MdmGoldenResourceFindingSvc.java index 073ca1ced2b..c6f235eda67 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/MdmGoldenResourceFindingSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/candidate/MdmGoldenResourceFindingSvc.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate; import ca.uhn.fhir.jpa.mdm.models.FindGoldenResourceCandidatesParams; -import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.log.Logs; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; @@ -36,7 +36,7 @@ public class MdmGoldenResourceFindingSvc { private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); @Autowired - private MdmResourceDaoSvc myMdmResourceDaoSvc; + private IMdmResourceDaoSvc myMdmResourceDaoSvc; @Autowired private FindCandidateByEidSvc myFindCandidateByEidSvc; diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/BaseMdmR4Test.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/BaseMdmR4Test.java index e1474eada1a..3e9d31edb6b 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/BaseMdmR4Test.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/BaseMdmR4Test.java @@ -98,8 +98,6 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test { .setValue("555-555-5555"); private static final String NAME_GIVEN_FRANK = "Frank"; - - @Autowired protected IFhirResourceDao myPatientDao; @Autowired diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/dao/MdmMetricSvcJpaIT.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/dao/MdmMetricSvcJpaIT.java new file mode 100644 index 00000000000..77a186cc549 --- /dev/null +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/dao/MdmMetricSvcJpaIT.java @@ -0,0 +1,172 @@ +package ca.uhn.fhir.jpa.mdm.dao; + +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean; +import ca.uhn.fhir.jpa.dao.data.IMdmLinkJpaMetricsRepository; +import ca.uhn.fhir.jpa.dao.mdm.MdmMetricSvcJpaImpl; +import ca.uhn.fhir.jpa.entity.MdmLink; +import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test; +import ca.uhn.fhir.jpa.mdm.IMdmMetricSvcTest; +import ca.uhn.fhir.jpa.mdm.helper.MdmLinkHelper; +import ca.uhn.fhir.jpa.mdm.helper.testmodels.MDMState; +import ca.uhn.fhir.jpa.mdm.models.GenerateMetricsTestParameters; +import ca.uhn.fhir.jpa.mdm.models.LinkMetricTestParameters; +import ca.uhn.fhir.jpa.mdm.models.LinkScoreMetricTestParams; +import ca.uhn.fhir.jpa.mdm.models.ResourceMetricTestParams; +import ca.uhn.fhir.jpa.model.dao.JpaPid; +import ca.uhn.fhir.mdm.api.IMdmMetricSvc; +import ca.uhn.fhir.mdm.model.MdmMetrics; +import ca.uhn.fhir.mdm.util.MdmResourceUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; + +import javax.persistence.EntityManagerFactory; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.fail; + +@ContextConfiguration(classes = { + MdmMetricSvcJpaIT.TestConfig.class +}) +public class MdmMetricSvcJpaIT extends BaseMdmR4Test implements IMdmMetricSvcTest { + + private static final Logger ourLog = LoggerFactory.getLogger(MdmMetricSvcJpaIT.class); + + @Configuration + public static class TestConfig { + + @Autowired + @Qualifier("metricsRepository") + private IMdmLinkJpaMetricsRepository myJpaRepository; + + @Autowired + private DaoRegistry myDaoRegistry; + + @Autowired + private EntityManagerFactory myEntityManagerFactory; + + @Autowired + private HapiFhirLocalContainerEntityManagerFactoryBean myEntityFactory; + + // this has to be provided via spring, or the + // @Transactional barrier is never invoked + @Bean + IMdmMetricSvc mdmMetricSvc() { + return new MdmMetricSvcJpaImpl( + myJpaRepository, + myDaoRegistry, + myEntityManagerFactory + ); + } + } + + private final ObjectMapper myObjectMapper = new ObjectMapper(); + + @Autowired + private MdmLinkHelper myLinkHelper; + + @Autowired + private IMdmMetricSvc mySvc; + + @BeforeEach + public void before() throws Exception { + super.before(); + } + + @Override + public IMdmMetricSvc getMetricsSvc() { + return mySvc; + } + + @Override + public void generateMdmMetricsSetup(GenerateMetricsTestParameters theParameters) { + if (StringUtils.isNotBlank(theParameters.getInitialState())) { + MDMState state = new MDMState<>(); + state.setInputState(theParameters.getInitialState()); + myLinkHelper.setup(state); + + // update scores if needed + setupScores(theParameters.getScores()); + } + } + + @Override + public void generateLinkMetricsSetup(LinkMetricTestParameters theParameters) { + ourLog.info(theParameters.getInitialState()); + if (StringUtils.isNotBlank(theParameters.getInitialState())) { + // we can only initialize the state if there is a state to initialize + MDMState state = new MDMState<>(); + state.setInputState(theParameters.getInitialState()); + myLinkHelper.setup(state); + } + } + + @Override + public void generateResourceMetricsSetup(ResourceMetricTestParams theParams) { + MDMState state = new MDMState<>(); + String initialState = theParams.getInitialState(); + if (StringUtils.isNotBlank(initialState)) { + state.setInputState(initialState); + + for (String forcedBlockedGRId : theParams.getBlockedResourceGoldenResourceIds()) { + Patient gr = new Patient(); + gr.setActive(true); + gr.setId("Patient/" + forcedBlockedGRId); + MdmResourceUtil.setMdmManaged(gr); + MdmResourceUtil.setGoldenResource(gr); + MdmResourceUtil.setGoldenResourceAsBlockedResourceGoldenResource(gr); + + Patient p = createPatient(gr, true, false); + state.addParameter(forcedBlockedGRId, p); + } + + myLinkHelper.setup(state); + } + } + + @Override + public void generateLinkScoreMetricsSetup(LinkScoreMetricTestParams theParams) { + MDMState state = new MDMState<>(); + String initialState = theParams.getInitialState(); + + if (StringUtils.isNotBlank(initialState)) { + state.setInputState(initialState); + + myLinkHelper.setup(state); + + // update scores if needed + setupScores(theParams.getScores()); + } + } + + private void setupScores(List theParams) { + List links = myMdmLinkDao.findAll(); + for (int i = 0; i < theParams.size() && i < links.size(); i++) { + Double score = theParams.get(i); + MdmLink link = links.get(i); + link.setScore(score); + myMdmLinkDao.save(link); + } + } + + @Override + public String getStringMetrics(MdmMetrics theMetrics) { + try { + return myObjectMapper.writeValueAsString(theMetrics); + } catch (JsonProcessingException ex) { + // we've failed anyway - we might as well display the exception + fail(ex); + return "NOT PARSEABLE!"; + } + } +} diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImplIT.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImplIT.java index 6433348881e..549f818f399 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImplIT.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkUpdaterSvcImplIT.java @@ -41,7 +41,7 @@ class MdmLinkUpdaterSvcImplIT extends BaseMdmR4Test { private IMdmLinkUpdaterSvc myMdmLinkUpdaterSvc; @Autowired - private MdmResourceDaoSvc myMdmResourceDaoSvc; + private MdmResourceDaoSvcImpl myMdmResourceDaoSvc; @Autowired private MessageHelper myMessageHelper; diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcTest.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcTest.java index 1bd33081e2f..e51949689c4 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcTest.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmResourceDaoSvcTest.java @@ -7,13 +7,13 @@ import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.mdm.api.IMdmResourceDaoSvc; import ca.uhn.fhir.mdm.util.MdmResourceUtil; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.SystemRequestDetails; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.AfterEach; @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -35,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class MdmResourceDaoSvcTest extends BaseMdmR4Test { private static final String TEST_EID = "TEST_EID"; @Autowired - MdmResourceDaoSvc myResourceDaoSvc; + IMdmResourceDaoSvc myResourceDaoSvc; @Autowired private ISearchParamExtractor mySearchParamExtractor; diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 27c8d0a0379..0eb9cabc642 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 3fe94ad2c5f..a2728494bf4 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 972d0aca525..ed3cab96c80 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index 6fe814dfd00..2e00cf23d76 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index de5da25656a..f03dae46f3b 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 74e8f394246..9253e66d122 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index ffe2b31be48..22ad4ef0907 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 2e85a2a2bd4..1a9b4c1527a 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index 0c810ee1268..c5a65a61dd0 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/IMdmMetricSvcTest.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/IMdmMetricSvcTest.java new file mode 100644 index 00000000000..18b51d0bc28 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/IMdmMetricSvcTest.java @@ -0,0 +1,177 @@ +package ca.uhn.fhir.jpa.mdm; + +import ca.uhn.fhir.jpa.mdm.models.GenerateMetricsTestParameters; +import ca.uhn.fhir.jpa.mdm.models.LinkMetricTestParameters; +import ca.uhn.fhir.jpa.mdm.models.LinkScoreMetricTestParams; +import ca.uhn.fhir.jpa.mdm.models.ResourceMetricTestParams; +import ca.uhn.fhir.jpa.mdm.util.MdmMetricSvcTestUtil; +import ca.uhn.fhir.mdm.api.IMdmMetricSvc; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.api.params.GenerateMdmMetricsParameters; +import ca.uhn.fhir.mdm.model.MdmMetrics; +import ca.uhn.fhir.mdm.model.MdmResourceMetrics; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests the various metrics returned by IMdmMetricSvc + * Because of the way these metrics are broken down in 3 different ways, + * these results are tested separately, even though there is a single + * entry point. + */ +public interface IMdmMetricSvcTest { + + IMdmMetricSvc getMetricsSvc(); + + void generateMdmMetricsSetup(GenerateMetricsTestParameters theParameters); + + @Test + default void generateMdmMetrics_generalTest_happyPath() { + // setup + GenerateMetricsTestParameters testParameters = new GenerateMetricsTestParameters(); + testParameters.setInitialState(MdmMetricSvcTestUtil.OUR_BASIC_STATE); + testParameters.setScores(Arrays.asList(0.1, 0.2, 0.3, 0.4)); + + generateMdmMetricsSetup(testParameters); + + // test + GenerateMdmMetricsParameters parameters = new GenerateMdmMetricsParameters("Patient"); + MdmMetrics results = getMetricsSvc().generateMdmMetrics(parameters); + + // verify + assertNotNull(results); + assertEquals("Patient", results.getResourceType()); + assertEquals(4, results.getGoldenResourcesCount()); + assertEquals(4, results.getSourceResourcesCount()); + assertEquals(0, results.getExcludedResources()); + + Map> map = results.getMatchTypeToLinkToCountMap(); + // See OUR_BASIC_STATE + assertEquals(3, map.size()); + for (MdmMatchResultEnum matchResult : new MdmMatchResultEnum[] { + MdmMatchResultEnum.MATCH, MdmMatchResultEnum.NO_MATCH, MdmMatchResultEnum.POSSIBLE_MATCH + }) { + assertTrue(map.containsKey(matchResult)); + Map source2Count = map.get(matchResult); + assertNotNull(source2Count); + for (MdmLinkSourceEnum ls : MdmLinkSourceEnum.values()) { + assertNotNull(source2Count.get(ls)); + } + } + } + + void generateLinkMetricsSetup(LinkMetricTestParameters theParameters); + + @ParameterizedTest + @MethodSource("ca.uhn.fhir.jpa.mdm.util.MdmMetricSvcTestUtil#linkMetricsParameters") + default void test_generateLinkMetrics_multipleInputs(LinkMetricTestParameters theParameters) { + // setup + generateLinkMetricsSetup(theParameters); + + // all tests use Patient resource type + GenerateMdmMetricsParameters parameters = new GenerateMdmMetricsParameters("Patient"); + for (MdmLinkSourceEnum linkSource : theParameters.getLinkSourceFilters()) { + parameters.addLinkSource(linkSource); + } + for (MdmMatchResultEnum matchResultEnum : theParameters.getMatchFilters()) { + parameters.addMatchResult(matchResultEnum); + } + + // test + MdmMetrics metrics = getMetricsSvc().generateMdmMetrics(parameters); + + // verify + assertNotNull(metrics); + assertEquals(metrics.getResourceType(), "Patient"); + + MdmMetrics expectedMetrics = theParameters.getExpectedMetrics(); + + Supplier err = () -> getComparingMetrics(metrics, expectedMetrics); + + Map> actual = metrics.getMatchTypeToLinkToCountMap(); + Map> expected = expectedMetrics.getMatchTypeToLinkToCountMap(); + assertEquals(expected, actual, err.get()); + + for (MdmMatchResultEnum matchResult : MdmMatchResultEnum.values()) { + assertEquals(expected.containsKey(matchResult), actual.containsKey(matchResult), err.get()); + if (actual.containsKey(matchResult)) { + Map actualMatch = actual.get(matchResult); + Map expectedMatch = expected.get(matchResult); + assertEquals(expectedMatch, actualMatch, err.get()); + for (MdmLinkSourceEnum linkSource : MdmLinkSourceEnum.values()) { + assertEquals(expectedMatch.get(linkSource), actualMatch.get(linkSource), err.get()); + } + } + } + } + + void generateResourceMetricsSetup(ResourceMetricTestParams theParams); + + @ParameterizedTest + @MethodSource("ca.uhn.fhir.jpa.mdm.util.MdmMetricSvcTestUtil#resourceMetricParameters") + default void test_generateResourceMetrics_multipleInputs(ResourceMetricTestParams theParams) { + // setup + generateResourceMetricsSetup(theParams); + + // test + GenerateMdmMetricsParameters parameters = new GenerateMdmMetricsParameters("Patient"); + MdmResourceMetrics results = getMetricsSvc().generateMdmMetrics(parameters); + + // verify + assertNotNull(results); + assertEquals("Patient", results.getResourceType()); + assertEquals( + theParams.getExpectedResourceCount(), + results.getSourceResourcesCount() + results.getGoldenResourcesCount()); + assertEquals(theParams.getExpectedBlockedResourceCount(), results.getExcludedResources()); + assertEquals(theParams.getExpectedGoldenResourceCount(), results.getGoldenResourcesCount()); + } + + void generateLinkScoreMetricsSetup(LinkScoreMetricTestParams theParams); + + @ParameterizedTest + @MethodSource("ca.uhn.fhir.jpa.mdm.util.MdmMetricSvcTestUtil#linkScoreParameters") + default void test_generateLinkScoreMetrics_multipleInputs(LinkScoreMetricTestParams theParams) { + // setup + generateLinkScoreMetricsSetup(theParams); + + GenerateMdmMetricsParameters scoreMetricsParameters = new GenerateMdmMetricsParameters("Patient"); + for (MdmMatchResultEnum matchType : theParams.getMatchFilter()) { + scoreMetricsParameters.addMatchResult(matchType); + } + + // test + MdmMetrics actualMetrics = getMetricsSvc().generateMdmMetrics(scoreMetricsParameters); + + // verify + assertNotNull(actualMetrics); + assertEquals("Patient", actualMetrics.getResourceType()); + + MdmMetrics expectedMetrics = theParams.getExpectedMetrics(); + + Map actual = actualMetrics.getScoreCounts(); + Map expected = expectedMetrics.getScoreCounts(); + assertEquals(expected.size(), actual.size()); + for (String score : expected.keySet()) { + assertTrue(actual.containsKey(score), String.format("Score of %s is not in results", score)); + assertEquals(expected.get(score), actual.get(score), score); + } + } + + private String getComparingMetrics(MdmMetrics theActual, MdmMetrics theExpected) { + return String.format( + "\nExpected: \n%s - \nActual: \n%s", getStringMetrics(theExpected), getStringMetrics(theActual)); + } + + String getStringMetrics(MdmMetrics theMetrics); +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/GenerateMetricsTestParameters.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/GenerateMetricsTestParameters.java new file mode 100644 index 00000000000..8afeb438902 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/GenerateMetricsTestParameters.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.jpa.mdm.models; + +import java.util.List; + +public class GenerateMetricsTestParameters { + + private String myInitialState; + + /** + * The scores for each link. + * The order should match the order of the + * links listed in initial state. + */ + private List myScores; + + public String getInitialState() { + return myInitialState; + } + + public void setInitialState(String theInitialState) { + myInitialState = theInitialState; + } + + public List getScores() { + return myScores; + } + + public void setScores(List theScores) { + myScores = theScores; + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkMetricTestParameters.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkMetricTestParameters.java new file mode 100644 index 00000000000..95ba075b645 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkMetricTestParameters.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jpa.mdm.models; + +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.model.MdmMetrics; + +import java.util.ArrayList; +import java.util.List; + +public class LinkMetricTestParameters { + /** + * The initial state (as to be fed into MdmLinkHelper) + */ + private String myInitialState; + + /** + * The filters for MatchResult + */ + private List myMatchFilters; + + /** + * The filters for LinkSource + */ + private List myLinkSourceEnums; + + /** + * The expected metrics to be returned + */ + private MdmMetrics myExpectedMetrics; + + public String getInitialState() { + return myInitialState; + } + + public void setInitialState(String theInitialState) { + myInitialState = theInitialState; + } + + public List getMatchFilters() { + if (myMatchFilters == null) { + myMatchFilters = new ArrayList<>(); + } + return myMatchFilters; + } + + public void setMatchFilters(List theMatchFilters) { + myMatchFilters = theMatchFilters; + } + + public List getLinkSourceFilters() { + if (myLinkSourceEnums == null) { + myLinkSourceEnums = new ArrayList<>(); + } + return myLinkSourceEnums; + } + + public void setLinkSourceFilters(List theLinkSourceEnums) { + myLinkSourceEnums = theLinkSourceEnums; + } + + public MdmMetrics getExpectedMetrics() { + return myExpectedMetrics; + } + + public void setExpectedMetrics(MdmMetrics theExpectedMetrics) { + myExpectedMetrics = theExpectedMetrics; + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkScoreMetricTestParams.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkScoreMetricTestParams.java new file mode 100644 index 00000000000..0434ccb69fb --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/LinkScoreMetricTestParams.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.jpa.mdm.models; + +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.model.MdmMetrics; + +import java.util.ArrayList; +import java.util.List; + +public class LinkScoreMetricTestParams { + private String myInitialState; + + private List myMatchFilter; + + private MdmMetrics myExpectedMetrics; + + /** + * The scores for each link. + * The order should match the order of the + * links listed in initial state. + */ + private List myScores; + + public String getInitialState() { + return myInitialState; + } + + public void setInitialState(String theInitialState) { + myInitialState = theInitialState; + } + + public MdmMetrics getExpectedMetrics() { + return myExpectedMetrics; + } + + public void setExpectedMetrics(MdmMetrics theExpectedMetrics) { + myExpectedMetrics = theExpectedMetrics; + } + + public List getMatchFilter() { + if (myMatchFilter == null) { + myMatchFilter = new ArrayList<>(); + } + return myMatchFilter; + } + + public void addMatchType(MdmMatchResultEnum theResultEnum) { + getMatchFilter().add(theResultEnum); + } + + public List getScores() { + if (myScores == null) { + myScores = new ArrayList<>(); + } + return myScores; + } + + public void setScores(List theScores) { + myScores = theScores; + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/ResourceMetricTestParams.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/ResourceMetricTestParams.java new file mode 100644 index 00000000000..4ed1b52972c --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/models/ResourceMetricTestParams.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.mdm.models; + +import java.util.ArrayList; +import java.util.List; + +public class ResourceMetricTestParams { + /** + * The initial state, as consumable by + * MdmLinkHelper. + */ + private String myInitialState; + + /** + * The list of Golden Resource Ids (in initial state) that should be + * saved as BlockedResources + */ + private List myBlockedResourceGoldenResourceIds; + + private long myExpectedResourceCount; + + private long myExpectedGoldenResourceCount; + + public String getInitialState() { + return myInitialState; + } + + public void setInitialState(String theInitialState) { + myInitialState = theInitialState; + } + + public List getBlockedResourceGoldenResourceIds() { + if (myBlockedResourceGoldenResourceIds == null) { + myBlockedResourceGoldenResourceIds = new ArrayList<>(); + } + return myBlockedResourceGoldenResourceIds; + } + + public void addBlockedResourceGoldenResources(String theBlockedResourceId) { + getBlockedResourceGoldenResourceIds().add(theBlockedResourceId); + } + + public long getExpectedResourceCount() { + return myExpectedResourceCount; + } + + public void setExpectedResourceCount(long theExpectedResourceCount) { + myExpectedResourceCount = theExpectedResourceCount; + } + + public long getExpectedGoldenResourceCount() { + return myExpectedGoldenResourceCount; + } + + public void setExpectedGoldenResourceCount(long theExpectedGoldenResourceCount) { + myExpectedGoldenResourceCount = theExpectedGoldenResourceCount; + } + + public long getExpectedBlockedResourceCount() { + return getBlockedResourceGoldenResourceIds().size(); + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/package-info.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/package-info.java new file mode 100644 index 00000000000..13da8304544 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/package-info.java @@ -0,0 +1,6 @@ +/** + * This package is for persistence-agnostic mdm tests. + * Even though the package is "jpaserver-test-utils", these + * classes are not dependent on jpa backed persistence. + */ +package ca.uhn.fhir.jpa.mdm; diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/util/MdmMetricSvcTestUtil.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/util/MdmMetricSvcTestUtil.java new file mode 100644 index 00000000000..9e5a7503dcd --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/mdm/util/MdmMetricSvcTestUtil.java @@ -0,0 +1,338 @@ +package ca.uhn.fhir.jpa.mdm.util; + +import ca.uhn.fhir.jpa.mdm.models.LinkMetricTestParameters; +import ca.uhn.fhir.jpa.mdm.models.LinkScoreMetricTestParams; +import ca.uhn.fhir.jpa.mdm.models.ResourceMetricTestParams; +import ca.uhn.fhir.mdm.api.BaseMdmMetricSvc; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.model.MdmMetrics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * This provides parameter methods for the {@link ca.uhn.fhir.jpa.mdm.IMdmMetricSvcTest}. + */ +public class MdmMetricSvcTestUtil { + + public static final String OUR_BASIC_STATE = + """ + G1, AUTO, MATCH, P1 + G2, AUTO, MATCH, P2, + G3, AUTO, POSSIBLE_MATCH, P3, + G4, MANUAL, MATCH, P4 + G2, AUTO, NO_MATCH, P1 + G1, MANUAL, NO_MATCH, P2 + G1, MANUAL, POSSIBLE_MATCH, P3 + """; + + /** + * Parameters supplied to {@link ca.uhn.fhir.jpa.mdm.IMdmMetricSvcTest#test_generateLinkMetrics_multipleInputs(LinkMetricTestParameters)} + */ + public static List linkMetricsParameters() { + List params = new ArrayList<>(); + + // 1 + { + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(OUR_BASIC_STATE); + MdmMetrics metrics = new MdmMetrics(); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.AUTO, 2); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.MANUAL, 1); + metrics.addMetric(MdmMatchResultEnum.NO_MATCH, MdmLinkSourceEnum.AUTO, 1); + metrics.addMetric(MdmMatchResultEnum.NO_MATCH, MdmLinkSourceEnum.MANUAL, 1); + metrics.addMetric(MdmMatchResultEnum.POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, 1); + metrics.addMetric(MdmMatchResultEnum.POSSIBLE_MATCH, MdmLinkSourceEnum.MANUAL, 1); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + // 2 + { + // link source filter + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(OUR_BASIC_STATE); + testParameters.setLinkSourceFilters(Arrays.asList(MdmLinkSourceEnum.AUTO)); + MdmMetrics metrics = new MdmMetrics(); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.AUTO, 2); + metrics.addMetric(MdmMatchResultEnum.NO_MATCH, MdmLinkSourceEnum.AUTO, 1); + metrics.addMetric(MdmMatchResultEnum.POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, 1); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + // 3 + { + // match result filter + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(OUR_BASIC_STATE); + testParameters.setMatchFilters(Arrays.asList(MdmMatchResultEnum.MATCH, MdmMatchResultEnum.POSSIBLE_MATCH)); + MdmMetrics metrics = new MdmMetrics(); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.AUTO, 2); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.MANUAL, 1); + metrics.addMetric(MdmMatchResultEnum.POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, 1); + metrics.addMetric(MdmMatchResultEnum.POSSIBLE_MATCH, MdmLinkSourceEnum.MANUAL, 1); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + // 4 + { + // match result and link source filters + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(OUR_BASIC_STATE); + testParameters.setMatchFilters(Arrays.asList(MdmMatchResultEnum.MATCH)); + testParameters.setLinkSourceFilters(Arrays.asList(MdmLinkSourceEnum.MANUAL)); + MdmMetrics metrics = new MdmMetrics(); + metrics.addMetric(MdmMatchResultEnum.MATCH, MdmLinkSourceEnum.MANUAL, 1); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + // 5 + { + // no initial state + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(""); + MdmMetrics metrics = new MdmMetrics(); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + // 6 + { + // initial state with filters to omit all values + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(""" + G1, AUTO, NO_MATCH, P1 + G2, MANUAL, MATCH, P2 + """); + testParameters.setMatchFilters(Arrays.asList(MdmMatchResultEnum.MATCH)); + testParameters.setLinkSourceFilters(Arrays.asList(MdmLinkSourceEnum.AUTO)); + testParameters.setExpectedMetrics(new MdmMetrics()); + params.add(testParameters); + } + + // 7 + { + // initial state with filters to omit some values + LinkMetricTestParameters testParameters = new LinkMetricTestParameters(); + testParameters.setInitialState(""" + G1, AUTO, NO_MATCH, P1 + G2, MANUAL, MATCH, P2 + """); + testParameters.setMatchFilters(Arrays.asList(MdmMatchResultEnum.NO_MATCH)); + testParameters.setLinkSourceFilters(Arrays.asList(MdmLinkSourceEnum.AUTO)); + MdmMetrics metrics = new MdmMetrics(); + metrics.addMetric(MdmMatchResultEnum.NO_MATCH, MdmLinkSourceEnum.AUTO, 1); + testParameters.setExpectedMetrics(metrics); + params.add(testParameters); + } + + return params; + } + + /** + * Parameters supplied to {@link ca.uhn.fhir.jpa.mdm.IMdmMetricSvcTest#test_generateResourceMetrics_multipleInputs(ResourceMetricTestParams)} + */ + public static List resourceMetricParameters() { + List params = new ArrayList<>(); + + // 1 + { + // a mix of golden, regular, and blocked resources + ResourceMetricTestParams p = new ResourceMetricTestParams(); + p.setInitialState( + """ + G1, AUTO, MATCH, P1 + G2, AUTO, MATCH, P2 + G2, AUTO, MATCH, P1, + G3, AUTO, MATCH, P3 + """); + p.addBlockedResourceGoldenResources("G2"); + p.addBlockedResourceGoldenResources("G3"); + p.setExpectedResourceCount(6); + p.setExpectedGoldenResourceCount(3); + params.add(p); + } + + // 2 + { + // 2 non-golden, 1 golden + ResourceMetricTestParams p = new ResourceMetricTestParams(); + p.setInitialState(""" + G1, AUTO, MATCH, P1, + G1, MANUAL, MATCH, P2 + """); + p.setExpectedResourceCount(3); + p.setExpectedGoldenResourceCount(1); + params.add(p); + } + + // 3 + { + // 2 golden, 1 non-golden + ResourceMetricTestParams p = new ResourceMetricTestParams(); + p.setInitialState(""" + G1, AUTO, MATCH, P1 + G2, AUTO, POSSIBLE_DUPLICATE, G1 + """); + p.setExpectedGoldenResourceCount(2); + p.setExpectedResourceCount(3); + params.add(p); + } + + // 4 + { + // 2 golden, 1 blocked, 0 non-golden + ResourceMetricTestParams p = new ResourceMetricTestParams(); + p.setInitialState(""" + G1, AUTO, POSSIBLE_DUPLICATE, G2 + """); + p.addBlockedResourceGoldenResources("G1"); + p.setExpectedResourceCount(2); + p.setExpectedGoldenResourceCount(2); + params.add(p); + } + + // 5 + { + // no resources + ResourceMetricTestParams p = new ResourceMetricTestParams(); + p.setInitialState(""); + params.add(p); + } + + return params; + } + + /** + * Parameters supplied to {@link ca.uhn.fhir.jpa.mdm.IMdmMetricSvcTest#generateLinkScoreMetricsSetup(LinkScoreMetricTestParams)} + */ + public static List linkScoreParameters() { + List parameters = new ArrayList<>(); + + // 1 + { + // score counts + LinkScoreMetricTestParams p = new LinkScoreMetricTestParams(); + p.setInitialState( + """ + G1, AUTO, MATCH, P1 + G2, AUTO, POSSIBLE_MATCH, P2, + G3, AUTO, POSSIBLE_MATCH, P1 + """); + p.setScores(Arrays.asList(.2D, .2D, .1D)); + MdmMetrics metrics = new MdmMetrics(); + metrics.setResourceType("Patient"); + populateScoreIntoMetrics(p, metrics); + p.setExpectedMetrics(metrics); + parameters.add(p); + } + + // 2 + { + // a null score + LinkScoreMetricTestParams p = new LinkScoreMetricTestParams(); + p.setInitialState(""" + G1, AUTO, POSSIBLE_MATCH, P1, + G2, AUTO, POSSIBLE_MATCH, P2 + """); + p.setScores(Arrays.asList(null, 0.02D)); + MdmMetrics metrics = new MdmMetrics(); + metrics.setResourceType("Patient"); + populateScoreIntoMetrics(p, metrics); + p.setExpectedMetrics(metrics); + parameters.add(p); + } + + // 3 + { + // match type filtering + LinkScoreMetricTestParams p = new LinkScoreMetricTestParams(); + p.setInitialState( + """ + G1, AUTO, POSSIBLE_MATCH, P1 + G2, AUTO, MATCH, P2 + G3, AUTO, POSSIBLE_MATCH, P3 + G4, AUTO, MATCH, P4 + """); + p.setScores(Arrays.asList(0.4D, 0.4D, 0.1D, 0.3D)); + p.addMatchType(MdmMatchResultEnum.POSSIBLE_MATCH); + MdmMetrics metrics = new MdmMetrics(); + metrics.setResourceType("Patient"); + populateScoreIntoMetrics(p, metrics); + p.setExpectedMetrics(metrics); + parameters.add(p); + } + + // 4 + { + // no links + LinkScoreMetricTestParams p = new LinkScoreMetricTestParams(); + p.setInitialState(""); + MdmMetrics metrics = new MdmMetrics(); + metrics.setResourceType("Patient"); + p.setExpectedMetrics(metrics); + populateScoreIntoMetrics(p, metrics); + parameters.add(p); + } + + return parameters; + } + + private static void populateScoreIntoMetrics(LinkScoreMetricTestParams p, MdmMetrics metrics) { + String initialState = p.getInitialState(); + Map indexToMatchResult = new HashMap<>(); + if (isNotBlank(initialState)) { + String[] states = initialState.split("\n"); + int len = states.length; + for (int i = 0; i < len; i++) { + String state = states[i]; + String[] values = state.split(","); + indexToMatchResult.put(i, MdmMatchResultEnum.valueOf(values[2].trim())); + } + } + + Map score2Count = new HashMap<>(); + long nullCount = 0; + for (int i = 0; i < p.getScores().size(); i++) { + MdmMatchResultEnum matchResult = indexToMatchResult.get(i); + // if it's not a filtered value, add it to the expected metrics + if (p.getMatchFilter().isEmpty() || p.getMatchFilter().contains(matchResult)) { + Double d = p.getScores().get(i); + if (d == null) { + nullCount++; + } else { + if (!score2Count.containsKey(d)) { + score2Count.put(d, 0L); + } + score2Count.put(d, score2Count.get(d) + 1); + } + } + } + metrics.addScore(BaseMdmMetricSvc.NULL_VALUE, nullCount); + for (int i = 0; i < BaseMdmMetricSvc.BUCKETS; i++) { + double bucket = (double) Math.round((float) (100 * (i + 1)) / BaseMdmMetricSvc.BUCKETS) / 100; + long count = 0; + // TODO - do not add it if the corresponding link does not have + // the correct MATCH_RESULT value + if (score2Count.containsKey(bucket)) { + count = score2Count.get(bucket); + } + if (i == 0) { + metrics.addScore(String.format(BaseMdmMetricSvc.FIRST_BUCKET, bucket), count); + } else { + metrics.addScore( + String.format(BaseMdmMetricSvc.NTH_BUCKET, (float) i / BaseMdmMetricSvc.BUCKETS, bucket), + count); + } + } + } +} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 883d501d672..dbb6efb3f9c 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index feac29e26be..266e0577cf8 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 03faeaa9b70..f01dc291872 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/BaseMdmMetricSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/BaseMdmMetricSvc.java new file mode 100644 index 00000000000..a3a9f75f56d --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/BaseMdmMetricSvc.java @@ -0,0 +1,94 @@ +package ca.uhn.fhir.mdm.api; + +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.mdm.api.params.GenerateMdmMetricsParameters; +import ca.uhn.fhir.mdm.model.MdmResourceMetrics; +import ca.uhn.fhir.mdm.util.MdmSearchParamBuildingUtils; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; + +public abstract class BaseMdmMetricSvc implements IMdmMetricSvc { + + /** + * Count of numbered buckets. + * There will also be a NULL bucket, so there will be a total + * of BUCKETS + 1 buckets. + */ + public static final int BUCKETS = 100; + + /** + * The NULL label + */ + public static final String NULL_VALUE = "NULL"; + + /** + * The label for the first bucket + */ + public static final String FIRST_BUCKET = "x_<_%.2f"; + + /** + * The label for the nth bucket (2... buckets) + */ + public static final String NTH_BUCKET = "%.2f_<_x_<=_%.2f"; + + protected final DaoRegistry myDaoRegistry; + + public BaseMdmMetricSvc(DaoRegistry theDaoRegistry) { + myDaoRegistry = theDaoRegistry; + } + + protected double getBucket(int theBucketId) { + return (double) Math.round((float) (100 * theBucketId) / BUCKETS) / 100; + } + + protected MdmResourceMetrics generateResourceMetrics(GenerateMdmMetricsParameters theParameters) { + String resourceType = theParameters.getResourceType(); + @SuppressWarnings("rawtypes") + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); + + // TODO + /* + * We are using 3 different queries to count: + * * all resources + * * all golden resources + * * all blocked resources. + * + * This is inefficient and if we want, we can speed it up with + * a custom query in the future. + */ + IBundleProvider outcome = null; + SearchParameterMap map = null; + + MdmResourceMetrics metrics = new MdmResourceMetrics(); + metrics.setResourceType(resourceType); + + // find golden resources + map = MdmSearchParamBuildingUtils.buildBasicGoldenResourceSearchParameterMap(resourceType); + setCountOnly(map); + outcome = dao.search(map, new SystemRequestDetails()); + metrics.setGoldenResourcesCount(outcome.size()); + + // find blocked resources + map = MdmSearchParamBuildingUtils.buildSearchParameterForBlockedResourceCount(resourceType); + setCountOnly(map); + outcome = dao.search(map, new SystemRequestDetails()); + metrics.setExcludedResources(outcome.size()); + + // find all resources + map = new SearchParameterMap(); + setCountOnly(map); + outcome = dao.search(map, new SystemRequestDetails()); + metrics.setSourceResourcesCount(outcome.size() - metrics.getGoldenResourcesCount()); + + return metrics; + } + + private void setCountOnly(SearchParameterMap theMap) { + theMap.setCount(0); + theMap.setLoadSynchronous(true); + theMap.setSearchTotalMode(SearchTotalModeEnum.ACCURATE); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmMetricSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmMetricSvc.java new file mode 100644 index 00000000000..451239bd243 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmMetricSvc.java @@ -0,0 +1,19 @@ +package ca.uhn.fhir.mdm.api; + +import ca.uhn.fhir.mdm.api.params.GenerateMdmMetricsParameters; +import ca.uhn.fhir.mdm.model.MdmMetrics; + +public interface IMdmMetricSvc { + + /** + * Generates metrics on MDM Links. + * Metrics include: + * * breakdowns of counts of MATCH_RESULT types by LINK_SOURCE types. + * * counts of resources of each type + * * a histogram of score 'buckets' with the appropriate counts. + * @param theParameters - Parameters defining resource type of interest, + * as well as MatchResult and LinkSource filters. + * @return The metrics in a JSON format. + */ + MdmMetrics generateMdmMetrics(GenerateMdmMetricsParameters theParameters); +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmResourceDaoSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmResourceDaoSvc.java new file mode 100644 index 00000000000..7afe2aa388e --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmResourceDaoSvc.java @@ -0,0 +1,27 @@ +package ca.uhn.fhir.mdm.api; + +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.Optional; + +public interface IMdmResourceDaoSvc { + DaoMethodOutcome upsertGoldenResource(IAnyResource theGoldenResource, String theResourceType); + + /** + * Given a resource, remove its Golden Resource tag. + * + * @param theGoldenResource the {@link IAnyResource} to remove the tag from. + * @param theResourcetype the type of that resource + */ + void removeGoldenResourceTag(IAnyResource theGoldenResource, String theResourcetype); + + IAnyResource readGoldenResourceByPid(IResourcePersistentId theGoldenResourcePid, String theResourceType); + + Optional searchGoldenResourceByEID(String theEid, String theResourceType); + + Optional searchGoldenResourceByEID( + String theEid, String theResourceType, RequestPartitionId thePartitionId); +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmConstants.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmConstants.java index 7d35442c202..0207577abd7 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmConstants.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmConstants.java @@ -34,6 +34,13 @@ public class MdmConstants { "http://hapifhir.io/fhir/NamingSystem/mdm-golden-resource-enterprise-id"; public static final String ALL_RESOURCE_SEARCH_PARAM_TYPE = "*"; + /** + * Blocked resource tag info + */ + public static final String CODE_BLOCKED = "BLOCKED_RESOURCE"; + + public static final String CODE_BLOCKED_DISPLAY = "Source Resource is omitted from MDM matching."; + public static final String FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE = "http://hl7.org/fhir/StructureDefinition/match-grade"; diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmLinkMetricParameters.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmLinkMetricParameters.java new file mode 100644 index 00000000000..b8f0c2bfcbc --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmLinkMetricParameters.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.mdm.api.params; + +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateMdmLinkMetricParameters { + + /** + * The resource type of interest. + * Must be provided! + */ + private final String myResourceType; + + /** + * The MDM MatchResult types of interest. + * Specified MatchResults will be included. + * If none are specified, all will be included. + */ + private List myMatchResultFilters; + + /** + * The MDM Link values of interest. + * Specified LinkSources will be included. + * If none are specified, all are included. + */ + private List myLinkSourceFilters; + + public GenerateMdmLinkMetricParameters(String theResourceType) { + myResourceType = theResourceType; + } + + public String getResourceType() { + return myResourceType; + } + + public List getMatchResultFilters() { + if (myMatchResultFilters == null) { + myMatchResultFilters = new ArrayList<>(); + } + return myMatchResultFilters; + } + + public void addMatchResultFilter(MdmMatchResultEnum theMdmMatchResultEnum) { + getMatchResultFilters().add(theMdmMatchResultEnum); + } + + public List getLinkSourceFilters() { + if (myLinkSourceFilters == null) { + myLinkSourceFilters = new ArrayList<>(); + } + return myLinkSourceFilters; + } + + public void addLinkSourceFilter(MdmLinkSourceEnum theLinkSource) { + getLinkSourceFilters().add(theLinkSource); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmMetricsParameters.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmMetricsParameters.java new file mode 100644 index 00000000000..98dd96b8366 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmMetricsParameters.java @@ -0,0 +1,69 @@ +package ca.uhn.fhir.mdm.api.params; + +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateMdmMetricsParameters { + + /** + * We only allow finding metrics by resource type + */ + private final String myResourceType; + + /** + * The MDM MatchResult types of interest. + * Specified MatchResults will be included. + * If none are specified, all will be included. + */ + private List myMatchResultFilters; + + /** + * The MDM Link values of interest. + * Specified LinkSources will be included. + * If none are specified, all are included. + */ + private List myLinkSourceFilters; + + public GenerateMdmMetricsParameters(String theResourceType) { + myResourceType = theResourceType; + } + + public String getResourceType() { + return myResourceType; + } + + public List getMatchResultFilters() { + if (myMatchResultFilters == null) { + myMatchResultFilters = new ArrayList<>(); + } + return myMatchResultFilters; + } + + public void addMatchResult(MdmMatchResultEnum theMdmMatchResultEnum) { + getMatchResultFilters().add(theMdmMatchResultEnum); + } + + public List getLinkSourceFilters() { + if (myLinkSourceFilters == null) { + myLinkSourceFilters = new ArrayList<>(); + } + return myLinkSourceFilters; + } + + public void addLinkSource(MdmLinkSourceEnum theLinkSource) { + getLinkSourceFilters().add(theLinkSource); + } + + // public GenerateMdmLinkMetricParameters toLinkMetricParams() { + // + // } + // + // public GenerateMdmResourceMetricsParameters toResourceMetricParams() { + // + // } + // + +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmResourceMetricsParameters.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmResourceMetricsParameters.java new file mode 100644 index 00000000000..504f169604e --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateMdmResourceMetricsParameters.java @@ -0,0 +1,17 @@ +package ca.uhn.fhir.mdm.api.params; + +public class GenerateMdmResourceMetricsParameters { + + /** + * We only allow finding metrics by resource type + */ + private final String myResourceType; + + public GenerateMdmResourceMetricsParameters(String theResourceType) { + myResourceType = theResourceType; + } + + public String getResourceType() { + return myResourceType; + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateScoreMetricsParameters.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateScoreMetricsParameters.java new file mode 100644 index 00000000000..412703585de --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/params/GenerateScoreMetricsParameters.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.mdm.api.params; + +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateScoreMetricsParameters { + /** + * The resource type of interest. + */ + private final String myResourceType; + + /** + * MatchResult types to filter for. + * Specified MatchResults will be included. + * If none specified, all will be included. + */ + private List myMatchTypeFilters; + + public GenerateScoreMetricsParameters(String theResourceType) { + myResourceType = theResourceType; + } + + public String getResourceType() { + return myResourceType; + } + + public List getMatchTypes() { + if (myMatchTypeFilters == null) { + myMatchTypeFilters = new ArrayList<>(); + } + return myMatchTypeFilters; + } + + public void addMatchType(MdmMatchResultEnum theMatchType) { + getMatchTypes().add(theMatchType); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkMetrics.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkMetrics.java new file mode 100644 index 00000000000..9d30b033030 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkMetrics.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.mdm.model; + +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; + +import java.util.HashMap; +import java.util.Map; + +public class MdmLinkMetrics { + /** + * The resource type to which these metrics apply. + */ + private String myResourceType; + + /** + * A mapping of MatchType -> LinkSource -> count. + * Eg: + * MATCH + * AUTO - 2 + * MANUAL - 1 + * NO_MATCH + * AUTO - 1 + * MANUAL - 3 + */ + private Map> myMatchTypeToLinkToCountMap; + + public String getResourceType() { + return myResourceType; + } + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + + public Map> getMatchTypeToLinkToCountMap() { + if (myMatchTypeToLinkToCountMap == null) { + myMatchTypeToLinkToCountMap = new HashMap<>(); + } + return myMatchTypeToLinkToCountMap; + } + + public void addMetric( + MdmMatchResultEnum theMdmMatchResultEnum, MdmLinkSourceEnum theLinkSourceEnum, long theCount) { + Map> map = getMatchTypeToLinkToCountMap(); + + if (!map.containsKey(theMdmMatchResultEnum)) { + map.put(theMdmMatchResultEnum, new HashMap<>()); + } + Map lsToCountMap = map.get(theMdmMatchResultEnum); + lsToCountMap.put(theLinkSourceEnum, theCount); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkScoreMetrics.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkScoreMetrics.java new file mode 100644 index 00000000000..d5ea731ddea --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmLinkScoreMetrics.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.mdm.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class MdmLinkScoreMetrics { + + private String myResourceType; + + /** + * Map of Score:Count + * Scores are typically Doubles. But we cast to string because + * Score is not a non-null field, and so "NULL" is a value. + */ + private Map myScoreCounts; + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + + public String getResourceType() { + return myResourceType; + } + + public Map getScoreCounts() { + if (myScoreCounts == null) { + myScoreCounts = new LinkedHashMap<>(); + } + return myScoreCounts; + } + + public void addScore(String theScore, Long theCount) { + getScoreCounts().put(theScore, theCount); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmMetrics.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmMetrics.java new file mode 100644 index 00000000000..aed9fbe9f76 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmMetrics.java @@ -0,0 +1,132 @@ +package ca.uhn.fhir.mdm.model; + +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.model.api.IModelJson; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class MdmMetrics extends MdmResourceMetrics implements IModelJson { + + @JsonProperty("resourceType") + private String myResourceType; + + /** + * A mapping of MatchType -> LinkSource -> count. + * Eg: + * MATCH + * AUTO - 2 + * MANUAL - 1 + * NO_MATCH + * AUTO - 1 + * MANUAL - 3 + */ + @JsonProperty("matchResult2linkSource2count") + private Map> myMatchTypeToLinkToCountMap; + + /** + * Score buckets (in brackets of 0.01 size, and null) to counts. + */ + @JsonProperty("scoreCounts") + private Map myScoreCounts; + + /** + * The number of golden resources. + */ + @JsonProperty("goldenResources") + private long myGoldenResourcesCount; + + /** + * The number of source resources. + */ + @JsonProperty("sourceResources") + private long mySourceResourcesCount; + + /** + * The number of excluded resources. + * These are necessarily a subset of both + * GoldenResources and SourceResources + * (as each Blocked resource will still generate + * a GoldenResource) + */ + @JsonProperty("excludedResources") + private long myExcludedResources; + + public String getResourceType() { + return myResourceType; + } + + public Map> getMatchTypeToLinkToCountMap() { + if (myMatchTypeToLinkToCountMap == null) { + myMatchTypeToLinkToCountMap = new HashMap<>(); + } + return myMatchTypeToLinkToCountMap; + } + + public void addMetric( + MdmMatchResultEnum theMdmMatchResultEnum, MdmLinkSourceEnum theLinkSourceEnum, long theCount) { + Map> map = getMatchTypeToLinkToCountMap(); + + if (!map.containsKey(theMdmMatchResultEnum)) { + map.put(theMdmMatchResultEnum, new HashMap<>()); + } + Map lsToCountMap = map.get(theMdmMatchResultEnum); + lsToCountMap.put(theLinkSourceEnum, theCount); + } + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + + public long getGoldenResourcesCount() { + return myGoldenResourcesCount; + } + + public void setGoldenResourcesCount(long theGoldenResourcesCount) { + myGoldenResourcesCount = theGoldenResourcesCount; + } + + public long getSourceResourcesCount() { + return mySourceResourcesCount; + } + + public void setSourceResourcesCount(long theSourceResourcesCount) { + mySourceResourcesCount = theSourceResourcesCount; + } + + public long getExcludedResources() { + return myExcludedResources; + } + + public void setExcludedResources(long theExcludedResources) { + myExcludedResources = theExcludedResources; + } + + public Map getScoreCounts() { + if (myScoreCounts == null) { + myScoreCounts = new LinkedHashMap<>(); + } + return myScoreCounts; + } + + public void addScore(String theScore, Long theCount) { + getScoreCounts().put(theScore, theCount); + } + + public static MdmMetrics fromSeperableMetrics( + MdmResourceMetrics theMdmResourceMetrics, + MdmLinkMetrics theLinkMetrics, + MdmLinkScoreMetrics theLinkScoreMetrics) { + MdmMetrics metrics = new MdmMetrics(); + metrics.setResourceType(theMdmResourceMetrics.getResourceType()); + metrics.setExcludedResources(theMdmResourceMetrics.getExcludedResources()); + metrics.setGoldenResourcesCount(theMdmResourceMetrics.getGoldenResourcesCount()); + metrics.setSourceResourcesCount(theMdmResourceMetrics.getSourceResourcesCount()); + metrics.myMatchTypeToLinkToCountMap = theLinkMetrics.getMatchTypeToLinkToCountMap(); + metrics.myScoreCounts = theLinkScoreMetrics.getScoreCounts(); + return metrics; + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmResourceMetrics.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmResourceMetrics.java new file mode 100644 index 00000000000..49939a5fd27 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmResourceMetrics.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.mdm.model; + +public class MdmResourceMetrics { + + /** + * The resource type to which these metrics apply. + */ + private String myResourceType; + + /** + * The number of golden resources. + */ + private long myGoldenResourcesCount; + + /** + * The number of source resources. + */ + private long mySourceResourcesCount; + + /** + * The number of excluded resources. + * These are necessarily a subset of both + * GoldenResources and SourceResources + * (as each Blocked resource will still generate + * a GoldenResource) + */ + private long myExcludedResources; + + public String getResourceType() { + return myResourceType; + } + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + + public long getGoldenResourcesCount() { + return myGoldenResourcesCount; + } + + public void setGoldenResourcesCount(long theGoldenResourcesCount) { + myGoldenResourcesCount = theGoldenResourcesCount; + } + + public long getSourceResourcesCount() { + return mySourceResourcesCount; + } + + public void setSourceResourcesCount(long theSourceResourcesCount) { + mySourceResourcesCount = theSourceResourcesCount; + } + + public long getExcludedResources() { + return myExcludedResources; + } + + public void setExcludedResources(long theExcludedResources) { + myExcludedResources = theExcludedResources; + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java index a2ca1b94930..17613f59be8 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java @@ -49,6 +49,12 @@ public class MdmTransactionContext { private String myResourceType; + /** + * Whether or not the currently processed resource is a 'blocked resource'. + * This will only be set on matching. + */ + private boolean myIsBlockedResource; + private List myMdmLinkEvents = new ArrayList<>(); public TransactionLogMessages getTransactionLogMessages() { @@ -111,4 +117,12 @@ public class MdmTransactionContext { public void setMdmLinks(List theMdmLinkEvents) { myMdmLinkEvents = theMdmLinkEvents; } + + public void setIsBlocked(boolean theIsBlocked) { + myIsBlockedResource = theIsBlocked; + } + + public boolean getIsBlocked() { + return myIsBlockedResource; + } } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/GoldenResourceHelper.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/GoldenResourceHelper.java index d133a7a9605..c7b46f4cd38 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/GoldenResourceHelper.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/GoldenResourceHelper.java @@ -116,6 +116,14 @@ public class GoldenResourceHelper { MdmResourceUtil.setMdmManaged(newGoldenResource); MdmResourceUtil.setGoldenResource(newGoldenResource); + // TODO - on updating links, if resolving a link, this should go away? + // blocked resource's golden resource will be marked special + // they are not part of MDM matching algorithm (will not link to other resources) + // but other resources can link to them + if (theMdmTransactionContext.getIsBlocked()) { + MdmResourceUtil.setGoldenResourceAsBlockedResourceGoldenResource(newGoldenResource); + } + // add the partition id to the new resource newGoldenResource.setUserData( Constants.RESOURCE_PARTITION_ID, diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmResourceUtil.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmResourceUtil.java index 44f34ea094f..4e7faf9d523 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmResourceUtil.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmResourceUtil.java @@ -132,6 +132,24 @@ public final class MdmResourceUtil { MdmConstants.DISPLAY_GOLDEN_REDIRECT); } + /** + * Adds the BLOCKED tag to the golden resource. + * Because this is called *before* a resource is saved, + * we must add a new system/code combo to it + * @param theBaseResource + * @return + */ + public static IBaseResource setGoldenResourceAsBlockedResourceGoldenResource(IBaseResource theBaseResource) { + IBaseCoding tag = theBaseResource.getMeta().addTag(); + tag.setSystem(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS); + tag.setCode(MdmConstants.CODE_BLOCKED); + tag.setDisplay(MdmConstants.CODE_BLOCKED_DISPLAY); + tag.setUserSelected(false); + tag.setVersion("1"); + + return theBaseResource; + } + /** * WARNING: This code may _look_ like it replaces in place a code of a tag, but this DOES NOT ACTUALLY WORK!. In reality what will * happen is a secondary tag will be created with the same system. the only way to actually remove a tag from a resource diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmSearchParamBuildingUtils.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmSearchParamBuildingUtils.java new file mode 100644 index 00000000000..d2085329296 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MdmSearchParamBuildingUtils.java @@ -0,0 +1,51 @@ +package ca.uhn.fhir.mdm.util; + +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.mdm.api.MdmConstants; +import ca.uhn.fhir.mdm.rules.json.MdmRulesJson; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenParam; + +public class MdmSearchParamBuildingUtils { + + private static final String IDENTIFIER = "identifier"; + + private static final String TAG = "_tag"; + + /** + * Builds a search parameter map that can be used to find the + * golden resources associated with MDM blocked resources (ie, those + * resources that were omitted from MDM matching). + */ + public static SearchParameterMap buildSearchParameterForBlockedResourceCount(String theResourceType) { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + TokenAndListParam tagsToSearch = new TokenAndListParam(); + tagsToSearch.addAnd(new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD)); + tagsToSearch.addAnd(new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_BLOCKED)); + + map.add(TAG, tagsToSearch); + return map; + } + + /** + * Creates a SearchParameterMap used for searching for golden resources + * by EID specifically. + */ + public static SearchParameterMap buildEidSearchParameterMap( + String theEid, String theResourceType, MdmRulesJson theMdmRules) { + SearchParameterMap map = buildBasicGoldenResourceSearchParameterMap(theEid); + map.add(IDENTIFIER, new TokenParam(theMdmRules.getEnterpriseEIDSystemForResourceType(theResourceType), theEid)); + return map; + } + + /** + * Creates a SearchParameterMap that can be used to find golden resources. + */ + public static SearchParameterMap buildBasicGoldenResourceSearchParameterMap(String theResourceType) { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(TAG, new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD)); + return map; + } +} diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/util/MdmResourceUtilTest.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/util/MdmResourceUtilTest.java index 8b128896f1d..85671682cc0 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/util/MdmResourceUtilTest.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/util/MdmResourceUtilTest.java @@ -1,11 +1,21 @@ package ca.uhn.fhir.mdm.util; +import ca.uhn.fhir.mdm.api.MdmConstants; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.Test; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; class MdmResourceUtilTest { @@ -20,4 +30,28 @@ class MdmResourceUtilTest { assertThat(hasGoldenRecordTag, is(equalTo(false))); } + + @Test + public void testSetGoldenAndBlockedResource() { + // setup + Patient patient = new Patient(); + patient.setActive(true); + + // test + Patient changed = (Patient) MdmResourceUtil.setGoldenResourceAsBlockedResourceGoldenResource( + MdmResourceUtil.setGoldenResource(patient) + ); + + // verify + assertNotNull(changed); + List tags = changed.getMeta().getTag(); + Set codes = new HashSet<>(); + codes.add(MdmConstants.CODE_BLOCKED); + codes.add(MdmConstants.CODE_GOLDEN_RECORD); + assertEquals(2, tags.size()); + for (Coding code : tags) { + assertEquals(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, code.getSystem()); + assertTrue(codes.contains(code.getCode())); + } + } } diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 97a9d442082..c57d846a2d1 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 05f1e323a7c..a52010ad2d0 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index 3d61960b0b0..7a91609e596 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index b3446ed9a2a..a3b872bcbb6 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index ab639b80f69..31a240fdbb3 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index bd046a5d4a5..119a3ced85c 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index c465731f954..4d50f71fa86 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 3cd4f85ea30..148c5717fb5 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index 36d820541cf..debccca5b4b 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index cdf05f46650..9b6ba0cdf17 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 27dc8f1ee62..05465b1fd32 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 7f01939e11a..8d89550158d 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 59b94d03c74..6a845cc9ffc 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 465d0b784d3..16232047a4f 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 50f71fb0834..f7ee91e88a1 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index da9f668f778..53aaee618a2 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index 4b55a6d0612..259a89b3dbb 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 1e2ba7f7e8c..791bfc09bbf 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index bceff64f591..6adae32a21c 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index b815a275b9f..0ba86977969 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index e665b690315..b12e654faf8 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index bb9a01ab472..8677b904bbe 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 6f9ae35ffb8..23a3f4ccfd9 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index a9643757fc7..e3d347cc12c 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index cda18bff14a..1fb57ba2778 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index e0567ffd108..98b3407c903 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index e070a8fb9e7..db74917e705 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 5b9471961cb..b31d930227f 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index c97208fe2e2..eab80fc01a3 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java index c8cdbf8062f..239efa1efea 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java @@ -16,7 +16,7 @@ import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; +import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses; import java.util.List; import java.util.Optional; @@ -108,7 +108,7 @@ public class FhirPathR5 implements IFhirPath { } @Override - public FunctionDetails resolveFunction(String functionName) { + public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index f208de1b2f7..3c9b6e6c687 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index fb872aa9377..d1336006d52 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 77f87de8553..82f33474782 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 0c33abe7f82..7ed3464e804 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 1d399034981..8ec6bcb529e 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index fb0365de8a7..961ea491248 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index 8d78cb03be9..1b4da7b170b 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 5018a5c0a0c..f44873e9e6a 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index f7c7a9fcfa1..d11066607d5 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 2880a0397e4..0ec080fa1e6 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 133b6269e46..01786c9d945 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index a9bf58d73ad..4c37982117a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 3d54ad5b0c4..95bb4d038b7 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index 11f932aa409..bd679c2e152 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index 2bac2e98877..b5570d3c637 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.9.8-SNAPSHOT + 6.9.9-SNAPSHOT ../../pom.xml