Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validate codes list for saved codes tab #78

Merged
merged 13 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.security.Principal;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping(path = "/terminology")
Expand Down Expand Up @@ -85,4 +86,13 @@ public ResponseEntity<Code> getCode(
return ResponseEntity.ok()
.body(fhirTerminologyService.retrieveCode(code, codeSystem, version, user.getApiKey()));
}

@PostMapping(path = "/codes", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Code>> getCodesAndCodeSystems(
@RequestBody() List<Map<String, String>> codeList, Principal principal) {
jkotanchik-SB marked this conversation as resolved.
Show resolved Hide resolved
final String username = principal.getName();
UmlsUser user = vsacService.verifyUmlsAccess(username);
return ResponseEntity.ok()
.body(fhirTerminologyService.retrieveCodesAndCodeSystems(codeList, user.getApiKey()));
}
}
3 changes: 2 additions & 1 deletion src/main/java/gov/cms/madie/terminology/dto/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
public class Code {
private String name;
private String display;
private String version;
private String version; // 'fhir' in the code-system-entry.json
private String svsVersion; // 'vsac' in the code-system-entry.json
private String codeSystem;
private String codeSystemOid;
private CodeStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public interface CodeSystemRepository extends MongoRepository<CodeSystem, String
Optional<CodeSystem> findById(String id);

Optional<CodeSystem> findByNameAndVersion(String name, String version);

Optional<CodeSystem> findByOidAndVersion(String oid, String version);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@
import org.springframework.web.util.UriComponentsBuilder;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
Expand Down Expand Up @@ -278,4 +277,94 @@ private void updateOrInsertAllCodeSystems(List<CodeSystem> codeSystemList) {
}
}
}

public List<Code> retrieveCodesAndCodeSystems(List<Map<String, String>> codeList, String apiKey) {
return codeList.stream()
.map(
code -> {
List<CodeSystemEntry> codeSystemEntries = mappingService.getCodeSystemEntries();
String codeName = code.get("code");
String codeSystemName = code.get("codeSystem");
String oid = code.get("oid") != null ? code.get("oid").replaceAll("'|'", "") : null;

Optional<Map.Entry<String, String>> mappedVersion =
mapToFhirVersion(code.get("version"), oid, codeSystemEntries);

if (mappedVersion.isPresent()) {
String vsacVersion = mappedVersion.get().getKey();
String fhirVersion = mappedVersion.get().getValue();

return retrieveCodes(
codeName, codeSystemName, vsacVersion, fhirVersion, oid, apiKey);
}
return null;
})
.collect(Collectors.toList());
}

private Optional<Map.Entry<String, String>> mapToFhirVersion(
String version, String oid, List<CodeSystemEntry> codeSystemEntries) {
if (oid == null) {
return Optional.empty();
}

Optional<Map.Entry<String, String>> result;
if (version == null) {
result =
codeSystemEntries.stream()
.filter(codeSystemEntry -> StringUtils.equals(codeSystemEntry.getOid(), oid))
.findFirst()
.map(
codeSystemVersion ->
Map.entry(
codeSystemVersion.getVersions().get(0).getVsac(),
codeSystemVersion.getVersions().get(0).getFhir()));
} else {
result =
codeSystemEntries.stream()
.filter(codeSystemEntry -> StringUtils.equals(codeSystemEntry.getOid(), oid))
.flatMap(codeSystemEntry -> codeSystemEntry.getVersions().stream())
.filter(codeSystemVersion -> StringUtils.equals(codeSystemVersion.getVsac(), version))
.map(
codeSystemVersion ->
Map.entry(codeSystemVersion.getVsac(), codeSystemVersion.getFhir()))
.findFirst();
}

return result;
}

private Code retrieveCodes(
String codeName,
String codeSystemName,
String vsacVersion,
String fhirVersion,
String oid,
String apiKey) {
if (StringUtils.isEmpty(codeName)
|| StringUtils.isEmpty(codeSystemName)
|| StringUtils.isEmpty(fhirVersion)) {
return null;
}
CodeSystem codeSystem = codeSystemRepository.findByOidAndVersion(oid, fhirVersion).orElse(null);
if (codeSystem == null) {
return null;
}
String codeJson = fhirTerminologyServiceWebClient.getCodeResource(codeName, codeSystem, apiKey);

Parameters parameters = fhirContext.newJsonParser().parseResource(Parameters.class, codeJson);
Code code =
Code.builder()
.name(codeName)
.codeSystem(codeSystemName)
.version(fhirVersion)
.svsVersion(vsacVersion)
.display(parameters.getParameter("display").getValue().toString())
.codeSystemOid(parameters.getParameter("Oid").getValue().toString())
.build();

CodeStatus status = vsacService.getCodeStatus(code, apiKey);
code.setStatus(status);
return code;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ public List<CodeSystemEntry> getCodeSystemEntries() {
return Collections.emptyList();
}

public CodeSystemEntry getCodeSystemEntry(String codeSystemName) {
public CodeSystemEntry getCodeSystemEntryByOid(String oid) {
List<CodeSystemEntry> codeSystemEntries = getCodeSystemEntries();
if (CollectionUtils.isEmpty(codeSystemEntries)) {
return null;
}
return codeSystemEntries.stream()
.filter(entry -> Objects.equals(entry.getName(), codeSystemName))
.filter(
entry ->
Objects.equals(entry.getOid(), oid.startsWith("urn:oid:") ? oid : "urn:oid:" + oid))
.findFirst()
.orElse(null);
}
Expand Down
42 changes: 26 additions & 16 deletions src/main/java/gov/cms/madie/terminology/service/VsacService.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,34 @@ public List<CqlCode> validateCodes(List<CqlCode> cqlCodes, UmlsUser umlsUser, St
}

public CodeStatus getCodeStatus(Code code, String apiKey) {
CodeSystemEntry systemEntry = mappingService.getCodeSystemEntry(code.getCodeSystem());
String svsVersion = getSvsCodeSystemVersion(code);
if (svsVersion == null) {
return CodeStatus.NA;
}
// prepare code path e.g. CODE:/CodeSystem/ActCode/Version/9.0.0/Code/AMB/Info
String codePath =
TerminologyServiceUtil.buildCodePath(code.getCodeSystem(), svsVersion, code.getName());
VsacCode svsCode = terminologyWebClient.getCode(codePath, apiKey);
if (svsCode.getStatus().equalsIgnoreCase("ok")) {
if ("Yes".equals(svsCode.getData().getResultSet().get(0).getActive())) {
return CodeStatus.ACTIVE;
} else {
return CodeStatus.INACTIVE;
}
}
return CodeStatus.NA;
}

private String getSvsCodeSystemVersion(Code code) {
if (StringUtils.isNotBlank(code.getSvsVersion())) {
return code.getSvsVersion();
}
CodeSystemEntry systemEntry = mappingService.getCodeSystemEntryByOid(code.getCodeSystemOid());
// do not call SVS API to get code status if the system is not in SVS API
if (systemEntry == null
|| systemEntry.getOid().contains("NOT.IN.VSAC")
|| CollectionUtils.isEmpty(systemEntry.getVersions())) {
return CodeStatus.NA;
return null;
}

// get corresponding SVS version for given FHIR version
Expand All @@ -176,21 +198,9 @@ public CodeStatus getCodeStatus(Code code, String apiKey) {
.findFirst()
.orElse(null);
if (version == null || version.getVsac() == null) {
return CodeStatus.NA;
}
// prepare code path e.g. CODE:/CodeSystem/ActCode/Version/9.0.0/Code/AMB/Info
String codePath =
TerminologyServiceUtil.buildCodePath(
code.getCodeSystem(), version.getVsac(), code.getName());
VsacCode svsCode = terminologyWebClient.getCode(codePath, apiKey);
if (svsCode.getStatus().equalsIgnoreCase("ok")) {
if ("Yes".equals(svsCode.getData().getResultSet().get(0).getActive())) {
return CodeStatus.ACTIVE;
} else {
return CodeStatus.INACTIVE;
}
return null;
}
return CodeStatus.NA;
return version.getVsac();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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.exceptions.VsacUnauthorizedException;
Expand All @@ -22,10 +23,7 @@

import java.security.Principal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -230,4 +228,33 @@ void testGetCodeIfNoUmlsUserFound() {
() -> vsacFhirTerminologyController.getCode(codeName, codeSystem, version, principal));
assertEquals(ex.getMessage(), "Please login to UMLS before proceeding");
}

@Test
void testGetCodesList() {
List<Map<String, String>> codeList =
List.of(
Map.of(
"code", "1963-8", "codeSystem", "LOINC", "oid", "'urn:oid:2.16.840.1.113883.6.1'"),
Map.of(
"code", "8462-4", "codeSystem", "LOINC", "oid", "'urn:oid:2.16.840.1.113883.6.1'"));
Code code =
Code.builder()
.name("1963-8")
.codeSystem("LOINC")
.version("2.72")
.display("Bicarbonate [Moles/volume] in Serum")
.codeSystemOid("2.16.840.1.113883.6.1")
.status(CodeStatus.valueOf("ACTIVE"))
.build();

Principal principal = mock(Principal.class);
when(principal.getName()).thenReturn(TEST_USER);
when(vsacService.verifyUmlsAccess(anyString())).thenReturn(umlsUser);
when(fhirTerminologyService.retrieveCodesAndCodeSystems(any(), anyString()))
.thenReturn(List.of(code));
ResponseEntity<List<Code>> response =
vsacFhirTerminologyController.getCodesAndCodeSystems(codeList, principal);
assertEquals(response.getStatusCode(), HttpStatus.OK);
assertEquals(response.getBody().get(0), code);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,70 @@ void testRetrieveCodeSuccessfully() {
assertThat(code.getVersion(), is(equalTo(version)));
assertThat(code.getStatus(), is(equalTo(CodeStatus.ACTIVE)));
}

@Test
void testRetrieveCodesListSuccessfully() {
List<Map<String, String>> codeList =
List.of(
Map.of(
"code", "1963-8", "codeSystem", "LOINC", "oid", "'urn:oid:2.16.840.1.113883.6.1'"));

String codeJson =
"{\n"
+ " \"resourceType\": \"Parameters\",\n"
+ " \"parameter\": [ {\n"
+ " \"name\": \"name\",\n"
+ " \"valueString\": \"LOINC\"\n"
+ " }, {\n"
+ " \"name\": \"version\",\n"
+ " \"valueString\": \"2.40\"\n"
+ " }, {\n"
+ " \"name\": \"display\",\n"
+ " \"valueString\": \"Bicarbonate [Moles/volume] in Serum\"\n"
+ " }, {\n"
+ " \"name\": \"Oid\",\n"
+ " \"valueString\": \"2.16.840.1.113883.6.1\"\n"
+ " } ]\n"
+ "}";

codeSystemEntries = new ArrayList<>();
CodeSystemEntry.Version version = new CodeSystemEntry.Version();
version.setVsac("2.40");
version.setFhir("2.40");
var codeSystemEntry =
CodeSystemEntry.builder()
.name("8462-4")
.oid("urn:oid:2.16.840.1.113883.6.1")
.url("http://loinc.org")
.versions(List.of(version))
.build();
codeSystemEntries.add(codeSystemEntry);

gov.cms.madie.terminology.models.CodeSystem codeSystem =
gov.cms.madie.terminology.models.CodeSystem.builder()
.id("LOINC2.40")
.fullUrl("http://loinc.org")
.title("LOINC")
.name("LOINC")
.version("2.40")
.versionId("404676818")
.oid("urn:oid:2.16.840.1.113883.6.1")
.lastUpdated(Instant.parse("2024-04-30T20:18:48.706Z"))
.lastUpdatedUpstream(new Date("Fri Apr 01 00:00:00 EDT 2022"))
.build();

when(mappingService.getCodeSystemEntries()).thenReturn(codeSystemEntries);
when(codeSystemRepository.findByOidAndVersion(anyString(), anyString()))
.thenReturn(Optional.ofNullable(codeSystem));
when(fhirTerminologyServiceWebClient.getCodeResource(anyString(), any(), any()))
.thenReturn(codeJson);
when(fhirContext.newJsonParser()).thenReturn(FhirContext.forR4().newJsonParser());
when(vsacService.getCodeStatus(any(), anyString())).thenReturn(CodeStatus.ACTIVE);
List<Code> code = fhirTerminologyService.retrieveCodesAndCodeSystems(codeList, TEST_API_KEY);
assertThat(code.get(0).getName(), is(equalTo("1963-8")));
assertThat(code.get(0).getDisplay(), is(equalTo("Bicarbonate [Moles/volume] in Serum")));
assertThat(code.get(0).getCodeSystem(), is(equalTo("LOINC")));
assertThat(code.get(0).getVersion(), is(equalTo("2.40")));
assertThat(code.get(0).getStatus(), is(equalTo(CodeStatus.ACTIVE)));
}
}
Loading
Loading