adding metrics (#5301)

* hapi metrics

* step 1

* metrics service

* adding metrics service

* adding documentation

* refactor

* refactoring

* cleanup

* changing

* adding changelog

* some refactoring

* format

* review fixes part 1

* review points

* review fixes

* updates for code review

* updates for code review

* bumping versions

---------

Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-MacBook-Pro.local>
This commit is contained in:
TipzCM 2023-09-29 09:23:11 -04:00 committed by GitHub
parent c3ff3dae82
commit 7e0fa9833c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 1955 additions and 121 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -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.
"

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -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<MdmLink, Long>, 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<MdmLinkSourceEnum> theLinkSources,
@Param("matchResult") List<MdmMatchResultEnum> theMatchTypes);
}

View File

@ -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<MdmLinkSourceEnum> linkSources = theParameters.getLinkSourceFilters();
List<MdmMatchResultEnum> 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<MdmMatchResultEnum> 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;
}
}

View File

@ -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

View File

@ -93,6 +93,23 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
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() {

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 =

View File

@ -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<IAnyResource> searchGoldenResourceByEID(String theEid, String theResourceType) {
return this.searchGoldenResourceByEID(theEid, theResourceType, null);
}
@Override
public Optional<IAnyResource> 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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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<Patient> myPatientDao;
@Autowired

View File

@ -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<Patient, JpaPid> 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<Patient, JpaPid> state = new MDMState<>();
state.setInputState(theParameters.getInitialState());
myLinkHelper.setup(state);
}
}
@Override
public void generateResourceMetricsSetup(ResourceMetricTestParams theParams) {
MDMState<Patient, JpaPid> 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<Patient, JpaPid> 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<Double> theParams) {
List<MdmLink> 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!";
}
}
}

View File

@ -41,7 +41,7 @@ class MdmLinkUpdaterSvcImplIT extends BaseMdmR4Test {
private IMdmLinkUpdaterSvc myMdmLinkUpdaterSvc;
@Autowired
private MdmResourceDaoSvc myMdmResourceDaoSvc;
private MdmResourceDaoSvcImpl myMdmResourceDaoSvc;
@Autowired
private MessageHelper myMessageHelper;

View File

@ -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;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -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<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> 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<MdmLinkSourceEnum, Long> 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<String> err = () -> getComparingMetrics(metrics, expectedMetrics);
Map<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> actual = metrics.getMatchTypeToLinkToCountMap();
Map<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> 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<MdmLinkSourceEnum, Long> actualMatch = actual.get(matchResult);
Map<MdmLinkSourceEnum, Long> 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<String, Long> actual = actualMetrics.getScoreCounts();
Map<String, Long> 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);
}

View File

@ -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<Double> myScores;
public String getInitialState() {
return myInitialState;
}
public void setInitialState(String theInitialState) {
myInitialState = theInitialState;
}
public List<Double> getScores() {
return myScores;
}
public void setScores(List<Double> theScores) {
myScores = theScores;
}
}

View File

@ -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<MdmMatchResultEnum> myMatchFilters;
/**
* The filters for LinkSource
*/
private List<MdmLinkSourceEnum> myLinkSourceEnums;
/**
* The expected metrics to be returned
*/
private MdmMetrics myExpectedMetrics;
public String getInitialState() {
return myInitialState;
}
public void setInitialState(String theInitialState) {
myInitialState = theInitialState;
}
public List<MdmMatchResultEnum> getMatchFilters() {
if (myMatchFilters == null) {
myMatchFilters = new ArrayList<>();
}
return myMatchFilters;
}
public void setMatchFilters(List<MdmMatchResultEnum> theMatchFilters) {
myMatchFilters = theMatchFilters;
}
public List<MdmLinkSourceEnum> getLinkSourceFilters() {
if (myLinkSourceEnums == null) {
myLinkSourceEnums = new ArrayList<>();
}
return myLinkSourceEnums;
}
public void setLinkSourceFilters(List<MdmLinkSourceEnum> theLinkSourceEnums) {
myLinkSourceEnums = theLinkSourceEnums;
}
public MdmMetrics getExpectedMetrics() {
return myExpectedMetrics;
}
public void setExpectedMetrics(MdmMetrics theExpectedMetrics) {
myExpectedMetrics = theExpectedMetrics;
}
}

View File

@ -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<MdmMatchResultEnum> myMatchFilter;
private MdmMetrics myExpectedMetrics;
/**
* The scores for each link.
* The order should match the order of the
* links listed in initial state.
*/
private List<Double> 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<MdmMatchResultEnum> getMatchFilter() {
if (myMatchFilter == null) {
myMatchFilter = new ArrayList<>();
}
return myMatchFilter;
}
public void addMatchType(MdmMatchResultEnum theResultEnum) {
getMatchFilter().add(theResultEnum);
}
public List<Double> getScores() {
if (myScores == null) {
myScores = new ArrayList<>();
}
return myScores;
}
public void setScores(List<Double> theScores) {
myScores = theScores;
}
}

View File

@ -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<String> myBlockedResourceGoldenResourceIds;
private long myExpectedResourceCount;
private long myExpectedGoldenResourceCount;
public String getInitialState() {
return myInitialState;
}
public void setInitialState(String theInitialState) {
myInitialState = theInitialState;
}
public List<String> 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();
}
}

View File

@ -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;

View File

@ -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<LinkMetricTestParameters> linkMetricsParameters() {
List<LinkMetricTestParameters> 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<ResourceMetricTestParams> resourceMetricParameters() {
List<ResourceMetricTestParams> 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<LinkScoreMetricTestParams> linkScoreParameters() {
List<LinkScoreMetricTestParams> 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<Integer, MdmMatchResultEnum> 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<Double, Long> 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);
}
}
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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<IAnyResource> searchGoldenResourceByEID(String theEid, String theResourceType);
Optional<IAnyResource> searchGoldenResourceByEID(
String theEid, String theResourceType, RequestPartitionId thePartitionId);
}

View File

@ -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";

View File

@ -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<MdmMatchResultEnum> myMatchResultFilters;
/**
* The MDM Link values of interest.
* Specified LinkSources will be included.
* If none are specified, all are included.
*/
private List<MdmLinkSourceEnum> myLinkSourceFilters;
public GenerateMdmLinkMetricParameters(String theResourceType) {
myResourceType = theResourceType;
}
public String getResourceType() {
return myResourceType;
}
public List<MdmMatchResultEnum> getMatchResultFilters() {
if (myMatchResultFilters == null) {
myMatchResultFilters = new ArrayList<>();
}
return myMatchResultFilters;
}
public void addMatchResultFilter(MdmMatchResultEnum theMdmMatchResultEnum) {
getMatchResultFilters().add(theMdmMatchResultEnum);
}
public List<MdmLinkSourceEnum> getLinkSourceFilters() {
if (myLinkSourceFilters == null) {
myLinkSourceFilters = new ArrayList<>();
}
return myLinkSourceFilters;
}
public void addLinkSourceFilter(MdmLinkSourceEnum theLinkSource) {
getLinkSourceFilters().add(theLinkSource);
}
}

View File

@ -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<MdmMatchResultEnum> myMatchResultFilters;
/**
* The MDM Link values of interest.
* Specified LinkSources will be included.
* If none are specified, all are included.
*/
private List<MdmLinkSourceEnum> myLinkSourceFilters;
public GenerateMdmMetricsParameters(String theResourceType) {
myResourceType = theResourceType;
}
public String getResourceType() {
return myResourceType;
}
public List<MdmMatchResultEnum> getMatchResultFilters() {
if (myMatchResultFilters == null) {
myMatchResultFilters = new ArrayList<>();
}
return myMatchResultFilters;
}
public void addMatchResult(MdmMatchResultEnum theMdmMatchResultEnum) {
getMatchResultFilters().add(theMdmMatchResultEnum);
}
public List<MdmLinkSourceEnum> getLinkSourceFilters() {
if (myLinkSourceFilters == null) {
myLinkSourceFilters = new ArrayList<>();
}
return myLinkSourceFilters;
}
public void addLinkSource(MdmLinkSourceEnum theLinkSource) {
getLinkSourceFilters().add(theLinkSource);
}
// public GenerateMdmLinkMetricParameters toLinkMetricParams() {
//
// }
//
// public GenerateMdmResourceMetricsParameters toResourceMetricParams() {
//
// }
//
}

View File

@ -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;
}
}

View File

@ -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<MdmMatchResultEnum> myMatchTypeFilters;
public GenerateScoreMetricsParameters(String theResourceType) {
myResourceType = theResourceType;
}
public String getResourceType() {
return myResourceType;
}
public List<MdmMatchResultEnum> getMatchTypes() {
if (myMatchTypeFilters == null) {
myMatchTypeFilters = new ArrayList<>();
}
return myMatchTypeFilters;
}
public void addMatchType(MdmMatchResultEnum theMatchType) {
getMatchTypes().add(theMatchType);
}
}

View File

@ -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<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> myMatchTypeToLinkToCountMap;
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public Map<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> getMatchTypeToLinkToCountMap() {
if (myMatchTypeToLinkToCountMap == null) {
myMatchTypeToLinkToCountMap = new HashMap<>();
}
return myMatchTypeToLinkToCountMap;
}
public void addMetric(
MdmMatchResultEnum theMdmMatchResultEnum, MdmLinkSourceEnum theLinkSourceEnum, long theCount) {
Map<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> map = getMatchTypeToLinkToCountMap();
if (!map.containsKey(theMdmMatchResultEnum)) {
map.put(theMdmMatchResultEnum, new HashMap<>());
}
Map<MdmLinkSourceEnum, Long> lsToCountMap = map.get(theMdmMatchResultEnum);
lsToCountMap.put(theLinkSourceEnum, theCount);
}
}

View File

@ -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<String, Long> myScoreCounts;
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public String getResourceType() {
return myResourceType;
}
public Map<String, Long> getScoreCounts() {
if (myScoreCounts == null) {
myScoreCounts = new LinkedHashMap<>();
}
return myScoreCounts;
}
public void addScore(String theScore, Long theCount) {
getScoreCounts().put(theScore, theCount);
}
}

View File

@ -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<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> myMatchTypeToLinkToCountMap;
/**
* Score buckets (in brackets of 0.01 size, and null) to counts.
*/
@JsonProperty("scoreCounts")
private Map<String, Long> 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<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> getMatchTypeToLinkToCountMap() {
if (myMatchTypeToLinkToCountMap == null) {
myMatchTypeToLinkToCountMap = new HashMap<>();
}
return myMatchTypeToLinkToCountMap;
}
public void addMetric(
MdmMatchResultEnum theMdmMatchResultEnum, MdmLinkSourceEnum theLinkSourceEnum, long theCount) {
Map<MdmMatchResultEnum, Map<MdmLinkSourceEnum, Long>> map = getMatchTypeToLinkToCountMap();
if (!map.containsKey(theMdmMatchResultEnum)) {
map.put(theMdmMatchResultEnum, new HashMap<>());
}
Map<MdmLinkSourceEnum, Long> 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<String, Long> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<IMdmLink> myMdmLinkEvents = new ArrayList<>();
public TransactionLogMessages getTransactionLogMessages() {
@ -111,4 +117,12 @@ public class MdmTransactionContext {
public void setMdmLinks(List<IMdmLink> theMdmLinkEvents) {
myMdmLinkEvents = theMdmLinkEvents;
}
public void setIsBlocked(boolean theIsBlocked) {
myIsBlockedResource = theIsBlocked;
}
public boolean getIsBlocked() {
return myIsBlockedResource;
}
}

View File

@ -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,

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<Coding> tags = changed.getMeta().getTag();
Set<String> 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()));
}
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -21,7 +21,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.8-SNAPSHOT</version>
<version>6.9.9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

Some files were not shown because too many files have changed in this diff Show More