Skip to content

Commit

Permalink
Merge pull request #768 from MeasureAuthoringTool/MAT-7964_FixingVers…
Browse files Browse the repository at this point in the history
…ioningTranslatorVersionProblem

MAT-7964: Fixing issue where ELMJson translator wasn't updated on Ver…
  • Loading branch information
gregory-akins authored Jan 2, 2025
2 parents b676baa + a2c2752 commit a17c8c2
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 97 deletions.
36 changes: 3 additions & 33 deletions src/main/java/cms/gov/madie/measure/services/BundleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
import java.lang.reflect.InvocationTargetException;

import cms.gov.madie.measure.dto.PackageDto;
import cms.gov.madie.measure.exceptions.CqlElmTranslationErrorException;
import cms.gov.madie.measure.exceptions.InvalidResourceStateException;
import gov.cms.madie.models.measure.ElmJson;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestClientException;

import cms.gov.madie.measure.exceptions.BundleOperationException;
Expand All @@ -27,8 +22,8 @@
public class BundleService {

private final FhirServicesClient fhirServicesClient;
private final ElmTranslatorClient elmTranslatorClient;
private final ExportRepository exportRepository;
private final ElmToJsonService elmToJsonService;

/**
* Get the bundle for measure. For draft measure- generate bundle because for draft measure,
Expand All @@ -42,7 +37,7 @@ public String bundleMeasure(Measure measure, String accessToken, String bundleTy
// for draft measures
if (measure.getMeasureMetaData().isDraft()) {
try {
retrieveElmJson(measure, accessToken);
elmToJsonService.retrieveElmJson(measure, accessToken);
return fhirServicesClient.getMeasureBundle(measure, accessToken, bundleType);
} catch (RestClientException | IllegalArgumentException ex) {
log.error("An error occurred while bundling measure {}", measure.getId(), ex);
Expand All @@ -66,7 +61,7 @@ public PackageDto getMeasureExport(Measure measure, String accessToken) {
// for draft measures
if (measure.getMeasureMetaData().isDraft()) {
try {
retrieveElmJson(measure, accessToken);
elmToJsonService.retrieveElmJson(measure, accessToken);
return PackageDto.builder()
.fromStorage(false)
.exportPackage(fhirServicesClient.getMeasureBundleExport(measure, accessToken))
Expand Down Expand Up @@ -105,29 +100,4 @@ public PackageDto getMeasureExport(Measure measure, String accessToken) {
throw new BundleOperationException("Measure", measure.getId(), ex);
}
}

protected void retrieveElmJson(Measure measure, String accessToken) {
if (StringUtils.isBlank(measure.getCql())) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since there is no associated CQL.");
}

if (measure.isCqlErrors()) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since CQL errors exist.");
}

if (CollectionUtils.isEmpty(measure.getGroups())) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since there are no associated population criteria.");
}

final ElmJson elmJson =
elmTranslatorClient.getElmJson(measure.getCql(), measure.getModel(), accessToken);
if (elmTranslatorClient.hasErrors(elmJson)) {
throw new CqlElmTranslationErrorException(measure.getMeasureName());
}
measure.setElmJson(elmJson.getJson());
measure.setElmXml(elmJson.getXml());
}
}
44 changes: 44 additions & 0 deletions src/main/java/cms/gov/madie/measure/services/ElmToJsonService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cms.gov.madie.measure.services;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import cms.gov.madie.measure.exceptions.CqlElmTranslationErrorException;
import cms.gov.madie.measure.exceptions.InvalidResourceStateException;
import gov.cms.madie.models.measure.ElmJson;
import gov.cms.madie.models.measure.Measure;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@AllArgsConstructor
@Service
public class ElmToJsonService {
private final ElmTranslatorClient elmTranslatorClient;

protected void retrieveElmJson(Measure measure, String accessToken) {
if (StringUtils.isBlank(measure.getCql())) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since there is no associated CQL.");
}

if (measure.isCqlErrors()) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since CQL errors exist.");
}

if (CollectionUtils.isEmpty(measure.getGroups())) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since there are no associated population criteria.");
}

final ElmJson elmJson =
elmTranslatorClient.getElmJson(measure.getCql(), measure.getModel(), accessToken);
if (elmTranslatorClient.hasErrors(elmJson)) {
throw new CqlElmTranslationErrorException(measure.getMeasureName());
}
measure.setElmJson(elmJson.getJson());
measure.setElmXml(elmJson.getXml());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,18 @@ public void validateGroups(Measure measure) {
}

if (measure.getMeasureMetaData() != null && measure.getMeasureMetaData().isDraft()) {
measure.getGroups().forEach(
group -> {
if (StringUtils.isBlank(group.getImprovementNotation())) {
throw new InvalidResourceStateException(
"Measure", measure.getId(), "since there is at least one Population Criteria " +
"with no improvement notation.");
}
}
);
measure
.getGroups()
.forEach(
group -> {
if (StringUtils.isBlank(group.getImprovementNotation())) {
throw new InvalidResourceStateException(
"Measure",
measure.getId(),
"since there is at least one Population Criteria "
+ "with no improvement notation.");
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class VersionService {
private final QdmPackageService qdmPackageService;
private final ExportService exportService;
private final TestCaseSequenceService sequenceService;
private final AppConfigService appConfigService;
private final ElmToJsonService elmToJsonService;

public enum VersionValidationResult {
VALID,
Expand Down Expand Up @@ -111,10 +111,12 @@ private Measure versionQdmMeasure(
*/
private Measure versionFhirMeasure(
String versionType, String username, String accessToken, Measure measure) throws Exception {
elmToJsonService.retrieveElmJson(measure, accessToken);
Measure upversionedMeasure = version(versionType, username, measure);
var measureBundle =
fhirServicesClient.getMeasureBundle(upversionedMeasure, accessToken, "export");
saveMeasureBundle(upversionedMeasure, measureBundle, username);

return applyMeasureVersion(versionType, username, upversionedMeasure);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

import cms.gov.madie.measure.dto.PackageDto;
import cms.gov.madie.measure.exceptions.BundleOperationException;
import cms.gov.madie.measure.exceptions.CqlElmTranslationErrorException;
import cms.gov.madie.measure.exceptions.CqlElmTranslationServiceException;
import cms.gov.madie.measure.exceptions.InvalidResourceStateException;
import cms.gov.madie.measure.repositories.ExportRepository;
import cms.gov.madie.measure.utils.ResourceUtil;
import gov.cms.madie.models.common.ModelType;
import gov.cms.madie.models.measure.ElmJson;
import gov.cms.madie.models.measure.Export;
import gov.cms.madie.models.measure.Group;
import gov.cms.madie.models.measure.Measure;
Expand Down Expand Up @@ -54,6 +50,7 @@ class BundleServiceTest implements ResourceUtil {
@Mock private FhirServicesClient fhirServicesClient;
@Mock private ElmTranslatorClient elmTranslatorClient;
@Mock private ExportRepository exportRepository;
@Mock private ElmToJsonService elmToJsonService;

@InjectMocks private BundleService bundleService;

Expand Down Expand Up @@ -104,61 +101,22 @@ void testBundleMeasureReturnsNullForNullMeasure() {
assertThat(output, is(nullValue()));
}

@Test
void testBundleMeasureWhenThereIsNoCql() {
measure.setCql(null);
assertThrows(
InvalidResourceStateException.class,
() -> bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation"));
}

@Test
void testBundleMeasureWhenThereAreCqlErrors() {
measure.setCqlErrors(true);
assertThrows(
InvalidResourceStateException.class,
() -> bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation"));
}

@Test
void testBundleMeasureThrowsOperationException() {
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenReturn(ElmJson.builder().json("{}").xml("<></>").build());

when(fhirServicesClient.getMeasureBundle(any(Measure.class), anyString(), anyString()))
.thenThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN));
assertThrows(
BundleOperationException.class,
() -> bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation"));
}

@Test
void testBundleMeasureThrowsCqlElmTranslationServiceException() {
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenThrow(
new CqlElmTranslationServiceException(
"There was an error calling CQL-ELM translation service", new Exception()));
assertThrows(
CqlElmTranslationServiceException.class,
() -> bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation"));
}

@Test
void testBundleMeasureThrowsCqlElmTranslatorExceptionWithErrors() {
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenReturn(ElmJson.builder().json("{}").xml("<></>").build());
when(elmTranslatorClient.hasErrors(any(ElmJson.class))).thenReturn(true);
assertThrows(
CqlElmTranslationErrorException.class,
() -> bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation"));
}

@Test
void testBundleMeasureReturnsBundleStringForDraftMeasure() {
final String json = "{\"message\": \"GOOD JSON\"}";
when(fhirServicesClient.getMeasureBundle(any(Measure.class), anyString(), anyString()))
.thenReturn(json);
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenReturn(ElmJson.builder().json("{}").xml("<></>").build());

assertThat(measure.getMeasureMetaData().isDraft(), is(equalTo(true)));
String output = bundleService.bundleMeasure(measure, "Bearer TOKEN", "calculation");
assertThat(output, is(equalTo(json)));
Expand Down Expand Up @@ -256,8 +214,7 @@ void testExportBundleMeasureForDraftMeasure() throws IOException {
.developers(List.of(Organization.builder().name("ICF").build()))
.build());
measure.setModel("QI-Core v4.1.1");
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenReturn(ElmJson.builder().json("{}").xml("<></>").build());

// doThrow(new
// HttpClientErrorException(HttpStatus.FORBIDDEN)).when(fhirServicesClient).getMeasureBundleExport(any(Measure.class), eq("")))
byte[] exportBytes = "TEST".getBytes();
Expand All @@ -283,8 +240,7 @@ void testExportBundleMeasureForDraftMeasureThrowsException() throws IOException
.developers(List.of(Organization.builder().name("ICF").build()))
.build());
measure.setModel("QI-Core v4.1.1");
when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenReturn(ElmJson.builder().json("{}").xml("<></>").build());

doThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN))
.when(fhirServicesClient)
.getMeasureBundleExport(any(Measure.class), eq("Bearer TOKEN"));
Expand Down
105 changes: 105 additions & 0 deletions src/test/java/cms/gov/madie/measure/services/ElmToJsonServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package cms.gov.madie.measure.services;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import cms.gov.madie.measure.exceptions.CqlElmTranslationServiceException;
import cms.gov.madie.measure.exceptions.InvalidResourceStateException;
import cms.gov.madie.measure.utils.ResourceUtil;
import gov.cms.madie.models.common.ModelType;
import gov.cms.madie.models.common.Version;
import gov.cms.madie.models.measure.Group;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.MeasureGroupTypes;
import gov.cms.madie.models.measure.MeasureMetaData;
import gov.cms.madie.models.measure.Population;
import gov.cms.madie.models.measure.PopulationType;

@ExtendWith(MockitoExtension.class)
class ElmToJsonServiceTest implements ResourceUtil {

@Mock private ElmTranslatorClient elmTranslatorClient;

@InjectMocks private ElmToJsonService elmToJsonService;

private Measure measure;

@BeforeEach
public void setUp() {
Group group =
Group.builder()
.id("xyz-p12r-12ert")
.populationBasis("Encounter")
.measureGroupTypes(List.of(MeasureGroupTypes.PROCESS))
.populations(
List.of(
new Population(
"id-1", PopulationType.INITIAL_POPULATION, "FactorialOfFive", null, null)))
.groupDescription("Description")
.scoringUnit("test-scoring-unit")
.build();

List<Group> groups = new ArrayList<>();
groups.add(group);
String elmJson = getData("/test_elm.json");
MeasureMetaData metaData = MeasureMetaData.builder().draft(true).build();
measure =
Measure.builder()
.active(true)
.id("xyz-p13r-13ert")
.cql("test cql")
.model(ModelType.QDM_5_6.getValue())
.cqlErrors(false)
.elmJson(elmJson)
.measureSetId("IDIDID")
.measureName("MSR01")
.version(new Version(0, 0, 1))
.groups(groups)
.measureMetaData(metaData)
.createdAt(Instant.now())
.createdBy("test user")
.lastModifiedAt(Instant.now())
.lastModifiedBy("test user")
.build();
}

@Test
void testBundleMeasureThrowsCqlElmTranslationServiceException() {

when(elmTranslatorClient.getElmJson(anyString(), anyString(), anyString()))
.thenThrow(
new CqlElmTranslationServiceException(
"There was an error calling CQL-ELM translation service", new Exception()));
assertThrows(
CqlElmTranslationServiceException.class,
() -> elmToJsonService.retrieveElmJson(measure, "calculation"));
}

@Test
void testBundleMeasureWhenThereAreCqlErrors() {
measure.setCqlErrors(true);
assertThrows(
InvalidResourceStateException.class,
() -> elmToJsonService.retrieveElmJson(measure, "calculation"));
}

@Test
void testBundleMeasureWhenThereIsNoCql() {
measure.setCql(null);
assertThrows(
InvalidResourceStateException.class,
() -> elmToJsonService.retrieveElmJson(measure, "calculation"));
}
}
Loading

0 comments on commit a17c8c2

Please sign in to comment.