From b854cc604e5ffba2c9ef4d239f7035aadbb45fa4 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Mon, 13 May 2024 11:14:42 -0700 Subject: [PATCH 1/6] MAT-6393: Add search functionality --- .../VsacFhirTerminologyController.java | 11 ++++ .../terminology/dto/ValueSetForSearch.java | 18 +++++++ .../service/FhirTerminologyService.java | 52 ++++++++++++++++--- .../FhirTerminologyServiceWebClient.java | 28 +++++++++- .../TerminologyServiceWebClient.java | 1 - .../VsacFhirTerminologyControllerTest.java | 3 ++ .../FhirTerminologyServiceWebClientTest.java | 8 ++- 7 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java diff --git a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java index 974ede9..e7412a2 100644 --- a/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java +++ b/src/main/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyController.java @@ -3,6 +3,7 @@ import gov.cms.madie.models.measure.ManifestExpansion; import gov.cms.madie.terminology.dto.Code; import gov.cms.madie.terminology.dto.QdmValueSet; +import gov.cms.madie.terminology.dto.ValueSetForSearch; import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; @@ -69,6 +70,16 @@ public ResponseEntity> getAllCodeSystems(Principal principal) { return ResponseEntity.ok().body(fhirTerminologyService.getAllCodeSystems()); } + @GetMapping(path = "/search-value-sets", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity> searchValueSets( + Principal principal, @RequestParam Map queryParams) { + final String username = principal.getName(); + UmlsUser umlsUser = vsacService.verifyUmlsAccess(username); + var result = fhirTerminologyService.searchValueSets(umlsUser.getApiKey(), queryParams); + return ResponseEntity.ok().body(result); + } + @GetMapping(path = "/code", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getCode( @RequestParam() String code, diff --git a/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java b/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java new file mode 100644 index 0000000..f0adff7 --- /dev/null +++ b/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java @@ -0,0 +1,18 @@ +package gov.cms.madie.terminology.dto; + +import lombok.Builder; +import lombok.Data; +import org.hl7.fhir.r4.model.Enumerations; + +@Data +@Builder +public class ValueSetForSearch { + private String title; + private String name; + private String url; + private String oid; + private String steward; + private String version; + private String codeSystem; + private Enumerations.PublicationStatus status; +} diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 520e7f3..1c83b8d 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -4,10 +4,7 @@ import ca.uhn.fhir.parser.IParser; import gov.cms.madie.models.mapping.CodeSystemEntry; import gov.cms.madie.models.measure.ManifestExpansion; -import gov.cms.madie.terminology.dto.Code; -import gov.cms.madie.terminology.dto.CodeStatus; -import gov.cms.madie.terminology.dto.QdmValueSet; -import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; +import gov.cms.madie.terminology.dto.*; import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; import gov.cms.madie.terminology.repositories.CodeSystemRepository; @@ -16,9 +13,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.*; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; @@ -26,6 +21,10 @@ import java.time.Instant; import java.util.*; import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; @Service @Slf4j @@ -120,6 +119,43 @@ private List getValueSetConcepts( return List.of(); } + public List searchValueSets(String apiKey, Map queryParams) { + IParser parser = fhirContext.newJsonParser(); + String responseString = fhirTerminologyServiceWebClient.searchValueSets(apiKey, queryParams); + Bundle bundle = parser.parseResource(Bundle.class, responseString); + List valueSetList = new ArrayList<>(); + bundle + .getEntry() + .forEach( + entry -> { + Resource resource = entry.getResource(); + if (resource instanceof ValueSet) { + String oid = ""; + for (Identifier identifier : ((ValueSet) resource).getIdentifier()) { + if (identifier.getValue() != null && !identifier.getValue().isEmpty()) { + oid = identifier.getValue(); + } + } + ValueSetForSearch valueSet = + ValueSetForSearch.builder() + .title(((ValueSet) resource).getTitle()) + .name(((ValueSet) resource).getName()) + .url(((ValueSet) resource).getUrl()) + .version(((ValueSet) resource).getVersion()) + // .codeSystem(codeSystem) // + // Theres a way to get this but its very complicated + .status(((ValueSet) resource).getStatus()) + .steward(((ValueSet) resource).getPublisher()) + .oid(oid) + .build(); + valueSetList.add(valueSet); + } + log.info("valueSetList {}", valueSetList); + }); + + return valueSetList; + } + public List getAllCodeSystems() { // remove items that are marked as not present in vsac to cut expense List codeSystemMappingEntries = @@ -211,7 +247,7 @@ private void recursiveRetrieveCodeSystems( entry -> { var codeSystem = (org.hl7.fhir.r4.model.CodeSystem) entry.getResource(); String codeSystemValue = ""; - for (org.hl7.fhir.r4.model.Identifier identifier : codeSystem.getIdentifier()) { + for (Identifier identifier : codeSystem.getIdentifier()) { if (identifier.getValue() != null && !identifier.getValue().isEmpty()) { codeSystemValue = identifier.getValue(); break; diff --git a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java index dba948a..8d6d29b 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClient.java @@ -11,6 +11,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; @@ -28,13 +30,15 @@ public class FhirTerminologyServiceWebClient { private final String codeSystemPath; private final String codeLookupsUrl; private final String defaultProfile; + private final String searchValueSetEndpoint; public FhirTerminologyServiceWebClient( @Value("${client.fhir-terminology-service.base-url}") String fhirTerminologyServiceBaseUrl, @Value("${client.fhir-terminology-service.manifests-urn}") String manifestUrn, @Value("${client.fhir-terminology-service.code-system-urn}") String codeSystemUrn, @Value("${client.fhir-terminology-service.code-lookups}") String codeLookupsUrl, - @Value("${client.default_profile}") String defaultProfile) { + @Value("${client.default_profile}") String defaultProfile, + @Value("${client.search_value_set_endpoint}") String searchValueSetEndpoint) { fhirTerminologyWebClient = WebClient.builder() .baseUrl(fhirTerminologyServiceBaseUrl) @@ -46,6 +50,7 @@ public FhirTerminologyServiceWebClient( this.codeSystemPath = codeSystemUrn; this.codeLookupsUrl = codeLookupsUrl; this.defaultProfile = defaultProfile; + this.searchValueSetEndpoint = searchValueSetEndpoint; } public String getManifestBundle(String apiKey) { @@ -59,6 +64,27 @@ public String getCodeSystemsPage(Integer offset, Integer count, String apiKey) { return fetchResourceFromVsac(codeUri.toString(), apiKey, "CodeSystem"); } + public String searchValueSets(String apiKey, Map queryParams) { + if (queryParams.containsKey("url")) { + String urlValue = queryParams.get("url"); + // if user didnt add htpp:// we do + if (!urlValue.startsWith("http://")) { + urlValue = "http://" + urlValue; + } + queryParams.put("url", urlValue); + } + MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); + multiValueMap.setAll(queryParams); + URI uri = + UriComponentsBuilder.fromUriString(searchValueSetEndpoint) + .queryParams(multiValueMap) + .encode() + .build() + .toUri(); + log.info("value set search url is: {}", uri.toString()); + return fetchResourceFromVsac(uri.toString(), apiKey, "bundle"); + } + public String getValueSetResource( String apiKey, ValueSetsSearchCriteria.ValueSetParams valueSetParams, diff --git a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java index 5a8dd1a..352b4c9 100644 --- a/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java +++ b/src/main/java/gov/cms/madie/terminology/webclient/TerminologyServiceWebClient.java @@ -29,7 +29,6 @@ public TerminologyServiceWebClient( @Value("${client.vsac_base_url}") String baseUrl, @Value("${client.valueset_endpoint}") String valueSetEndpoint, @Value("${client.default_profile}") String defaultProfile) { - this.terminologyClient = webClientBuilder.baseUrl(baseUrl).build(); this.baseUrl = baseUrl; this.valueSetEndpoint = valueSetEndpoint; diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 2ae6f6f..4e5a275 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -24,6 +24,9 @@ import java.security.Principal; import java.time.Instant; import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; diff --git a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java index 8fb177d..14b6606 100644 --- a/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java +++ b/src/test/java/gov/cms/madie/terminology/webclient/FhirTerminologyServiceWebClientTest.java @@ -27,6 +27,7 @@ class FhirTerminologyServiceWebClientTest { private static final String MOCK_CODE_LOOKUP = "/CodeSystem/$lookup"; private static final String DEFAULT_PROFILE = "Most Recent Code System Versions in VSAC"; public static MockWebServer mockBackEnd; + private static final String SEARCH_VALUE_SET_ENDPOINT = "https://cts.nlm.nih.gov/fhir/ValueSet"; private ValueSetsSearchCriteria.ValueSetParams testValueSetParams; @@ -44,7 +45,12 @@ void initialize() { String baseUrl = String.format("http://localhost:%s", mockBackEnd.getPort()); fhirTerminologyServiceWebClient = new FhirTerminologyServiceWebClient( - baseUrl, MOCK_MANIFEST_URN, MOCK_CODE_SYSTEM_URN, MOCK_CODE_LOOKUP, DEFAULT_PROFILE); + baseUrl, + MOCK_MANIFEST_URN, + MOCK_CODE_SYSTEM_URN, + MOCK_CODE_LOOKUP, + DEFAULT_PROFILE, + SEARCH_VALUE_SET_ENDPOINT); } @AfterAll From ef793811c964cca73fbda0e95be772801f252416 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Mon, 13 May 2024 11:48:55 -0700 Subject: [PATCH 2/6] MAT-6363: remove unused --- .../cms/madie/terminology/service/FhirTerminologyService.java | 1 - .../controller/VsacFhirTerminologyControllerTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 1c83b8d..5d05119 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -19,7 +19,6 @@ import org.springframework.web.util.UriComponentsBuilder; import java.time.Instant; -import java.util.*; import java.util.stream.Collectors; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 4e5a275..45af6c2 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -23,7 +23,6 @@ import java.security.Principal; import java.time.Instant; -import java.util.*; import java.util.ArrayList; import java.util.Date; import java.util.List; From a591900eda860136962e0b778a7b5b4a9318e35a Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Mon, 13 May 2024 11:51:30 -0700 Subject: [PATCH 3/6] MAT-6393: add back used import --- .../controller/VsacFhirTerminologyControllerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 45af6c2..7e1e727 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; From 75962d5d924de8309a01a641a8ca4c5d44f8d374 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Mon, 13 May 2024 12:01:11 -0700 Subject: [PATCH 4/6] MAT-6393: add config var --- src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c5a15ff..6767adb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,6 +24,7 @@ code-system-refresh-task: code-system-cron-date-time: ${CS_REFRESH_TIME:@midnight} client: + search_value_set_endpoint: ${SEARCH_VALUE_SET_ENDPOINT:https://cts.nlm.nih.gov/fhir/ValueSet} vsac_base_url: https://vsac.nlm.nih.gov/vsac valueset_endpoint: /svs/RetrieveMultipleValueSets?id={oid}&profile={profile}&includeDraft={includeDraft} default_profile: ${DEFAULT_VSAC_PROFILE:Most Recent Code System Versions in VSAC} From 12d04561677e958b774d402b24b39ffa3711b771 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Mon, 13 May 2024 12:16:37 -0700 Subject: [PATCH 5/6] MAT-6393: Add test --- .../VsacFhirTerminologyControllerTest.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java index 7e1e727..a576d46 100644 --- a/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java +++ b/src/test/java/gov/cms/madie/terminology/controller/VsacFhirTerminologyControllerTest.java @@ -1,10 +1,7 @@ package gov.cms.madie.terminology.controller; import gov.cms.madie.models.measure.ManifestExpansion; -import gov.cms.madie.terminology.dto.Code; -import gov.cms.madie.terminology.dto.CodeStatus; -import gov.cms.madie.terminology.dto.QdmValueSet; -import gov.cms.madie.terminology.dto.ValueSetsSearchCriteria; +import gov.cms.madie.terminology.dto.*; import gov.cms.madie.terminology.exceptions.VsacUnauthorizedException; import gov.cms.madie.terminology.models.CodeSystem; import gov.cms.madie.terminology.models.UmlsUser; @@ -23,10 +20,7 @@ import java.security.Principal; import java.time.Instant; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -260,4 +254,25 @@ void testGetCodesList() { assertEquals(response.getStatusCode(), HttpStatus.OK); assertEquals(response.getBody().get(0), code); } + + @Test + void testSearchValueSets() { + List mockValueSets = new ArrayList<>(); + ValueSetForSearch v1 = ValueSetForSearch.builder().title("title 1").name("title1").url("url").oid("oid") + .steward("steward").version("version").codeSystem("cs").build(); + ValueSetForSearch v2 = ValueSetForSearch.builder().title("title 2").name("title2").url("url").oid("oid") + .steward("steward").version("version").codeSystem("cs").build(); + mockValueSets.add(v1); + mockValueSets.add(v2); + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(TEST_USER); + when(vsacService.verifyUmlsAccess(anyString())).thenReturn(umlsUser); + when(fhirTerminologyService.searchValueSets(any(), any())).thenReturn(mockValueSets); + Map queryParams = new HashMap<>(); + queryParams.put("param1", "value1"); + queryParams.put("param2", "value2"); + ResponseEntity> response = + vsacFhirTerminologyController.searchValueSets(principal, queryParams); + assertEquals(response.getStatusCode(), HttpStatus.OK); + } } From eea21b3afa51b9494fa6f9bb45d67a54efdb870a Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 14 May 2024 15:10:26 -0700 Subject: [PATCH 6/6] MAT-6393: Add missing properties to dto needed for search --- .../terminology/dto/ValueSetForSearch.java | 7 ++++++ .../service/FhirTerminologyService.java | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java b/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java index f0adff7..b62f5cb 100644 --- a/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java +++ b/src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java @@ -9,6 +9,13 @@ public class ValueSetForSearch { private String title; private String name; + private String author; + private String composedOf; + private String effectiveDate; + private String lastReviewDate; + private String lastUpdated; + private String publisher; + private String purpose; private String url; private String oid; private String steward; diff --git a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java index 5d05119..9064b04 100644 --- a/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java +++ b/src/main/java/gov/cms/madie/terminology/service/FhirTerminologyService.java @@ -128,7 +128,8 @@ public List searchValueSets(String apiKey, Map { Resource resource = entry.getResource(); - if (resource instanceof ValueSet) { + ValueSet vs = (ValueSet) resource; + if (resource instanceof ValueSet) { String oid = ""; for (Identifier identifier : ((ValueSet) resource).getIdentifier()) { if (identifier.getValue() != null && !identifier.getValue().isEmpty()) { @@ -137,14 +138,19 @@ public List searchValueSets(String apiKey, Map x.getSystem()).collect(Collectors.joining(","))) + .effectiveDate(String.valueOf(vs.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/valueset-effectiveDate").getValue())) + .lastReviewDate(String.valueOf(vs.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/resource-lastReviewDate").getValue())) + .lastUpdated(vs.getMeta().getLastUpdated().toString()) + .url(vs.getUrl()) + .version(vs.getVersion()) + .status(vs.getStatus()) + .publisher(vs.getPublisher()) + .purpose(vs.getPurpose()) + .steward(vs.getPublisher()) .oid(oid) .build(); valueSetList.add(valueSet);