Skip to content

Commit

Permalink
Merge pull request #79 from MeasureAuthoringTool/MAT-6393
Browse files Browse the repository at this point in the history
MAT-6393: Add search functionality, MAT-6993: Implement Filter capability
  • Loading branch information
mcmcphillips authored May 21, 2024
2 parents faac06a + eea21b3 commit ef7d1fc
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,6 +70,16 @@ public ResponseEntity<List<CodeSystem>> getAllCodeSystems(Principal principal) {
return ResponseEntity.ok().body(fhirTerminologyService.getAllCodeSystems());
}

@GetMapping(path = "/search-value-sets", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<List<ValueSetForSearch>> searchValueSets(
Principal principal, @RequestParam Map<String, String> 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<Code> getCode(
@RequestParam() String code,
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/gov/cms/madie/terminology/dto/ValueSetForSearch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 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;
private String version;
private String codeSystem;
private Enumerations.PublicationStatus status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,16 +13,17 @@
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;

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
Expand Down Expand Up @@ -120,6 +118,49 @@ private List<QdmValueSet.Concept> getValueSetConcepts(
return List.of();
}

public List<ValueSetForSearch> searchValueSets(String apiKey, Map<String, String> queryParams) {
IParser parser = fhirContext.newJsonParser();
String responseString = fhirTerminologyServiceWebClient.searchValueSets(apiKey, queryParams);
Bundle bundle = parser.parseResource(Bundle.class, responseString);
List<ValueSetForSearch> valueSetList = new ArrayList<>();
bundle
.getEntry()
.forEach(
entry -> {
Resource resource = entry.getResource();
ValueSet vs = (ValueSet) resource;
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(vs.getTitle())
.author(String.valueOf(vs.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/valueset-author").getValue()))
.name(vs.getName())
.composedOf(vs.getCompose().getInclude().stream().map(x -> 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);
}
log.info("valueSetList {}", valueSetList);
});

return valueSetList;
}

public List<CodeSystem> getAllCodeSystems() {
// remove items that are marked as not present in vsac to cut expense
List<CodeSystemEntry> codeSystemMappingEntries =
Expand Down Expand Up @@ -211,7 +252,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -46,6 +50,7 @@ public FhirTerminologyServiceWebClient(
this.codeSystemPath = codeSystemUrn;
this.codeLookupsUrl = codeLookupsUrl;
this.defaultProfile = defaultProfile;
this.searchValueSetEndpoint = searchValueSetEndpoint;
}

public String getManifestBundle(String apiKey) {
Expand All @@ -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<String, String> 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<String, String> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -257,4 +254,25 @@ void testGetCodesList() {
assertEquals(response.getStatusCode(), HttpStatus.OK);
assertEquals(response.getBody().get(0), code);
}

@Test
void testSearchValueSets() {
List<ValueSetForSearch> 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<String, String> queryParams = new HashMap<>();
queryParams.put("param1", "value1");
queryParams.put("param2", "value2");
ResponseEntity<List<ValueSetForSearch>> response =
vsacFhirTerminologyController.searchValueSets(principal, queryParams);
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down

0 comments on commit ef7d1fc

Please sign in to comment.