001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.dao.mdm;
021
022import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
023import ca.uhn.fhir.jpa.dao.data.IMdmLinkJpaMetricsRepository;
024import ca.uhn.fhir.mdm.api.BaseMdmMetricSvc;
025import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
026import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
027import ca.uhn.fhir.mdm.api.params.GenerateMdmMetricsParameters;
028import ca.uhn.fhir.mdm.model.MdmLinkMetrics;
029import ca.uhn.fhir.mdm.model.MdmLinkScoreMetrics;
030import ca.uhn.fhir.mdm.model.MdmMetrics;
031import ca.uhn.fhir.mdm.model.MdmResourceMetrics;
032import jakarta.persistence.EntityManager;
033import jakarta.persistence.EntityManagerFactory;
034import jakarta.persistence.Query;
035import org.springframework.transaction.annotation.Transactional;
036
037import java.math.BigInteger;
038import java.util.Arrays;
039import java.util.List;
040import java.util.stream.Collectors;
041
042public class MdmMetricSvcJpaImpl extends BaseMdmMetricSvc {
043
044        private final IMdmLinkJpaMetricsRepository myJpaRepository;
045
046        private final EntityManagerFactory myEntityManagerFactory;
047
048        public MdmMetricSvcJpaImpl(
049                        IMdmLinkJpaMetricsRepository theRepository,
050                        DaoRegistry theDaoRegistry,
051                        EntityManagerFactory theEntityManagerFactory) {
052                super(theDaoRegistry);
053                myJpaRepository = theRepository;
054                myEntityManagerFactory = theEntityManagerFactory;
055        }
056
057        protected MdmLinkMetrics generateLinkMetrics(GenerateMdmMetricsParameters theParameters) {
058                List<MdmLinkSourceEnum> linkSources = theParameters.getLinkSourceFilters();
059                List<MdmMatchResultEnum> matchResults = theParameters.getMatchResultFilters();
060
061                if (linkSources.isEmpty()) {
062                        linkSources = Arrays.asList(MdmLinkSourceEnum.values());
063                }
064                if (matchResults.isEmpty()) {
065                        matchResults = Arrays.asList(MdmMatchResultEnum.values());
066                }
067
068                Object[][] data = myJpaRepository.generateMetrics(theParameters.getResourceType(), linkSources, matchResults);
069                MdmLinkMetrics metrics = new MdmLinkMetrics();
070                metrics.setResourceType(theParameters.getResourceType());
071                for (Object[] row : data) {
072                        MdmMatchResultEnum matchResult = (MdmMatchResultEnum) row[0];
073                        MdmLinkSourceEnum source = (MdmLinkSourceEnum) row[1];
074                        long count = (Long) row[2];
075                        metrics.addMetric(matchResult, source, count);
076                }
077                return metrics;
078        }
079
080        protected MdmLinkScoreMetrics generateLinkScoreMetrics(GenerateMdmMetricsParameters theParameters) {
081                String resourceType = theParameters.getResourceType();
082
083                List<MdmMatchResultEnum> matchResultTypes = theParameters.getMatchResultFilters();
084
085                // if no result type filter, add all result types
086                if (matchResultTypes.isEmpty()) {
087                        matchResultTypes = Arrays.asList(MdmMatchResultEnum.values());
088                }
089
090                String sql = "SELECT %s FROM MPI_LINK ml WHERE ml.TARGET_TYPE = :resourceType "
091                                + "AND ml.MATCH_RESULT in (:matchResult)";
092
093                StringBuilder sb = new StringBuilder();
094                sb.append("sum(case when ml.SCORE is null then 1 else 0 end) as B_" + NULL_VALUE);
095
096                for (int i = 0; i < BUCKETS; i++) {
097                        double bucket = getBucket(i + 1);
098                        sb.append(",\n");
099                        if (i == 0) {
100                                // score <= .01
101                                sb.append(String.format("sum(case when ml.SCORE <= %.2f then 1 else 0 end) as B%d", bucket, i));
102                        } else {
103                                // score > i/100 && score <= i/100
104                                sb.append(String.format(
105                                                "sum(case when ml.score > %.2f and ml.SCORE <= %.2f then 1 else 0 end) as B%d",
106                                                getBucket(i), bucket, i));
107                        }
108                }
109
110                EntityManager em = myEntityManagerFactory.createEntityManager();
111
112                Query nativeQuery = em.createNativeQuery(String.format(sql, sb.toString()));
113
114                org.hibernate.query.Query<?> hibernateQuery = (org.hibernate.query.Query<?>) nativeQuery;
115
116                hibernateQuery.setParameter("resourceType", resourceType);
117                hibernateQuery.setParameter(
118                                "matchResult", matchResultTypes.stream().map(Enum::ordinal).collect(Collectors.toList()));
119
120                List<?> results = hibernateQuery.getResultList();
121
122                em.close();
123
124                MdmLinkScoreMetrics metrics = new MdmLinkScoreMetrics();
125
126                // we only get one row back
127                Object[] row = (Object[]) results.get(0);
128                int length = row.length;
129                for (int i = 0; i < length; i++) {
130                        // if there's nothing in the db, these values will all be null
131                        Number bi = row[i] != null ? (Number) row[i] : BigInteger.valueOf(0);
132                        double bucket = getBucket(i);
133                        if (i == 0) {
134                                metrics.addScore(NULL_VALUE, bi.longValue());
135                        } else if (i == 1) {
136                                metrics.addScore(String.format(FIRST_BUCKET, bucket), bi.longValue());
137                        } else {
138                                metrics.addScore(String.format(NTH_BUCKET, getBucket(i - 1), bucket), bi.longValue());
139                        }
140                }
141
142                return metrics;
143        }
144
145        @Transactional
146        @Override
147        public MdmMetrics generateMdmMetrics(GenerateMdmMetricsParameters theParameters) {
148                MdmResourceMetrics resourceMetrics = generateResourceMetrics(theParameters);
149                MdmLinkMetrics linkMetrics = generateLinkMetrics(theParameters);
150                MdmLinkScoreMetrics scoreMetrics = generateLinkScoreMetrics(theParameters);
151
152                MdmMetrics metrics = MdmMetrics.fromSeperableMetrics(resourceMetrics, linkMetrics, scoreMetrics);
153                return metrics;
154        }
155}