Skip to content

Commit

Permalink
MAT-7030: update cqmMapper for missing properties (#70)
Browse files Browse the repository at this point in the history
* MAT-7030: update cqm mapper to include missing properties

* MAT-7030: change ElmDependencyUtil to return for all libraries

* MAT-7030: update tests

* MAT-7030: overwrite population sets for qrda generation

* MAT-7030: remove unused mapper
  • Loading branch information
chubert-sb authored Jun 6, 2024
1 parent c34628a commit a9af259
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 53 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.6.34-SNAPSHOT</version>
<version>0.6.37-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>gov.cms.madie.packaging</groupId>
Expand Down
273 changes: 236 additions & 37 deletions src/main/java/gov/cms/madie/services/CqmMeasureMapper.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package gov.cms.madie.services;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import gov.cms.madie.Exceptions.CqmConversionException;
import gov.cms.madie.dto.SourceDataCriteria;
import gov.cms.madie.models.cqm.*;
import gov.cms.madie.models.cqm.datacriteria.basetypes.DataElement;
import gov.cms.madie.models.cqm.datacriteria.*;
import gov.cms.madie.models.measure.QdmMeasure;
import gov.cms.madie.models.measure.*;
import gov.cms.madie.util.ElmDependencyUtil;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
Expand All @@ -20,6 +24,7 @@

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface CqmMeasureMapper {
ObjectMapper mapper = new ObjectMapper();

@Mapping(target = "hqmf_set_id", source = "measure.measureSetId")
@Mapping(target = "hqmf_version_number", source = "measure.versionId")
Expand All @@ -35,6 +40,7 @@ public interface CqmMeasureMapper {
@Mapping(target = "calculation_method", expression = "java(getCalculationMethod(measure))")
@Mapping(target = "measure_period", expression = "java(getMeasurePeriod(measure))")
@Mapping(target = "cql_libraries", expression = "java(getCqlLibraries(measure, elms))")
@Mapping(target = "population_sets", expression = "java(getPopulationSets(measure))")
@Mapping(
target = "source_data_criteria",
expression = "java(getSourceDataCriteria(dataCriteria))")
Expand All @@ -46,42 +52,46 @@ default String getCalculationMethod(QdmMeasure measure) {
}

default MeasurePeriod getMeasurePeriod(QdmMeasure measure) {
JSONObject low = new JSONObject();
low.put(
"value",
measure
.getMeasurementPeriodStart()
.toInstant()
.atZone(ZoneId.of("UTC"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH")));

JSONObject high = new JSONObject();
high.put(
"value",
measure
.getMeasurementPeriodEnd()
.toInstant()
.atZone(ZoneId.of("UTC"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH")));
PeriodPoint low =
PeriodPoint.builder()
.value(
measure
.getMeasurementPeriodStart()
.toInstant()
.atZone(ZoneId.of("UTC"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH"))
.toString())
.build();
PeriodPoint high =
PeriodPoint.builder()
.value(
measure
.getMeasurementPeriodEnd()
.toInstant()
.atZone(ZoneId.of("UTC"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH"))
.toString())
.build();

return MeasurePeriod.builder().low(low.toString()).high(high.toString()).build();
return MeasurePeriod.builder().low(low).high(high).build();
}

default List<CQLLibrary> getCqlLibraries(QdmMeasure measure, List<String> elms) {

List<StatementDependency> statements =
Map<String, List<StatementDependency>> statements =
ElmDependencyUtil.findDependencies(elms, measure.getCqlLibraryName());

List<StatementDependency> finalStatements = statements;
return elms.stream()
.map(elm -> buildCQLLibrary(elm, measure.getCqlLibraryName(), finalStatements))
.map(elm -> buildCQLLibrary(elm, measure.getCqlLibraryName(), statements))
.collect(Collectors.toList());
}

default CQLLibrary buildCQLLibrary(
String elm, String measureLibraryName, List<StatementDependency> statementDependencies) {
String elm,
String measureLibraryName,
Map<String, List<StatementDependency>> statementDependencies) {

JSONObject elmJson = new JSONObject(elm);
if (elmJson.has("library") && elmJson.getJSONObject("library").has("valueSets")) {
Expand Down Expand Up @@ -115,27 +125,19 @@ default CQLLibrary buildCQLLibrary(
// true for all non-composite measures
.is_top_level(true)
.statement_dependencies(
statementDependencies.stream()
.filter(
statementDependency ->
statementDependency
.getStatement_name()
.equals(
elmJson
.getJSONObject("library")
.getJSONObject("identifier")
.getString("id")))
.collect(Collectors.toList()))
statementDependencies.get(
elmJson.getJSONObject("library").getJSONObject("identifier").getString("id")))
.build();

cqlLibrary.setElm(elmJson.toMap());
cqlLibrary.set_main_library(measureLibraryName.equals(cqlLibrary.getLibrary_name()));

return cqlLibrary;
}

default List<DataElement> getSourceDataCriteria(List<SourceDataCriteria> dataCriteria) {
if (CollectionUtils.isEmpty(dataCriteria)) {
return null;
return Collections.emptyList();
}

return dataCriteria.stream()
Expand Down Expand Up @@ -261,4 +263,201 @@ default DataElement instantiateModel(String type) {
throw new CqmConversionException("Unsupported data type: " + type);
}
}

default List<PopulationSet> getPopulationSets(QdmMeasure measure) {
if (CollectionUtils.isEmpty(measure.getGroups())) {
return null;
}

String measureScoring = null;
if (!StringUtils.isEmpty(measure.getScoring())) {
measureScoring = measure.getScoring().replaceAll(" +", "");
}
List<PopulationSet> populationSets = new ArrayList<>();
for (int i = 0; i < measure.getGroups().size(); i++) {
String groupId = measure.getGroups().get(i).getId();
PopulationSet populationSet =
PopulationSet.builder()
.id(groupId)
.title("Population Criteria Section")
.population_set_id(groupId)
.populations(
generateCqmPopulations(
measure.getGroups().get(i).getPopulations(),
measure.getCqlLibraryName(),
measureScoring))
.stratifications(
generateCqmStratifications(
measure.getGroups().get(i).getStratifications(),
measure.getCqlLibraryName(),
i))
.supplemental_data_elements(
generateCqmSupplementalDataElements(
measure.getSupplementalData(), measure.getCqlLibraryName()))
.build();
if (!StringUtils.isEmpty(measureScoring)
&& (measureScoring.equals("ContinuousVariable") || measureScoring.equals("Ratio"))) {
populationSet.setObservations(generateCqmObservations(measure));
}
populationSets.add(populationSet);
}
return populationSets;
}

default String mapPopulationName(PopulationType populationName) {
switch (populationName) {
case INITIAL_POPULATION:
return "IPP";
case DENOMINATOR:
return "DENOM";
case DENOMINATOR_EXCLUSION:
return "DENEX";
case DENOMINATOR_EXCEPTION:
return "DENEXCEP";
case NUMERATOR:
return "NUMER";
case NUMERATOR_EXCLUSION:
return "NUMEX";
case MEASURE_POPULATION:
return "MSRPOPL";
case MEASURE_POPULATION_EXCLUSION:
return "MSRPOPLEX";
default:
return populationName.name();
}
}

default PopulationMap determinePopulationType(Map<String, Object> acc, String measureScoring) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
try {
switch (measureScoring) {
case "Cohort":
acc.put("@class", CohortPopulationMap.class);
return mapper.readValue(mapper.writeValueAsString(acc), CohortPopulationMap.class);
case "ContinuousVariable":
acc.put("@class", ContinuousVariablePopulationMap.class);
return mapper.readValue(
mapper.writeValueAsString(acc), ContinuousVariablePopulationMap.class);
case "Proportion":
acc.put("@class", ProportionPopulationMap.class);
return mapper.readValue(mapper.writeValueAsString(acc), ProportionPopulationMap.class);
case "Ratio":
acc.put("@class", RatioPopulationMap.class);
return mapper.readValue(mapper.writeValueAsString(acc), RatioPopulationMap.class);
default:
return null;
}

} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

default PopulationMap generateCqmPopulations(
List<Population> populations, String cqlLibraryName, String measureScoring) {
if (StringUtils.isEmpty(measureScoring)) {
return null;
}
Map<String, Object> acc = new HashMap<>();
for (Population population : populations) {
String key = mapPopulationName(population.getName());
acc.put(
key,
StatementReference.builder()
.id(population.getId())
.library_name(cqlLibraryName)
.statement_name(population.getDefinition())
.hqmf_id(null)
.build());
}
return determinePopulationType(acc, measureScoring);
}

default List<CqmStratification> generateCqmStratifications(
List<Stratification> stratifications, String cqlLibraryName, int groupIndex) {
List<CqmStratification> cqmStratifications = new ArrayList<>();
if (stratifications != null) {
for (int i = 0; i < stratifications.size(); i++) {
Stratification stratification = stratifications.get(i);
CqmStratification cqmStratification =
CqmStratification.builder()
.id(UUID.randomUUID().toString())
.hqmf_id(null)
.stratification_id(
String.format("PopulationSet_%d_Stratification_%d", groupIndex + 1, i + 1))
.title(String.format("PopSet%d Stratification %d", groupIndex + 1, i + 1))
.statement(
StatementReference.builder()
.id(stratification.getId())
.library_name(cqlLibraryName)
.statement_name(stratification.getCqlDefinition())
.hqmf_id(null)
.build())
.build();
cqmStratifications.add(cqmStratification);
}
}
return cqmStratifications;
}

default List<StatementReference> generateCqmSupplementalDataElements(
List<DefDescPair> supplementalDataElements, String cqlLibraryName) {
List<StatementReference> statementReferences = new ArrayList<>();
for (DefDescPair element : supplementalDataElements) {
statementReferences.add(
StatementReference.builder()
.id(UUID.randomUUID().toString())
.library_name(cqlLibraryName)
.statement_name(element.getDefinition())
.hqmf_id(null)
.build());
}
return statementReferences;
}

default List<Observation> generateCqmObservations(QdmMeasure measure) {
List<Observation> cqmObservations = new ArrayList<>();
if (!CollectionUtils.isEmpty(measure.getGroups())) {
for (Group group : measure.getGroups()) {
if (!CollectionUtils.isEmpty(group.getMeasureObservations())) {
for (MeasureObservation observation : group.getMeasureObservations()) {
cqmObservations.add(
Observation.builder()
.id(observation.getId())
.hqmf_id(null)
.aggregation_type(observation.getAggregateMethod())
.observation_function(
StatementReference.builder()
.id(UUID.randomUUID().toString())
.library_name(measure.getCqlLibraryName())
.statement_name(observation.getDefinition())
.hqmf_id(null)
.build())
.observation_parameter(
StatementReference.builder()
.id(UUID.randomUUID().toString())
.library_name(measure.getCqlLibraryName())
.statement_name(
getAssociatedPopulationDefinition(
observation.getCriteriaReference(), group.getPopulations()))
.hqmf_id(null)
.build())
.build());
}
}
}
}
return cqmObservations;
}

default String getAssociatedPopulationDefinition(
String criteriaReference, List<Population> populations) {
for (Population population : populations) {
if (population.getId().equals(criteriaReference)) {
return population.getDefinition();
}
}
return null;
}
}
7 changes: 5 additions & 2 deletions src/main/java/gov/cms/madie/services/QrdaService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import gov.cms.madie.dto.qrda.QrdaExportResponseDto;
import gov.cms.madie.dto.qrda.QrdaRequestDTO;
import gov.cms.madie.dto.SourceDataCriteria;
import gov.cms.madie.models.cqm.CqmMeasure;
import gov.cms.madie.models.dto.TranslatedLibrary;
import gov.cms.madie.models.measure.QdmMeasure;
import lombok.AllArgsConstructor;
Expand All @@ -16,6 +17,7 @@

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -46,10 +48,11 @@ public QrdaExportResponseDto generateQrda(QrdaRequestDTO request, String accessT

QrdaDTO dto = null;
try {
CqmMeasure cqmMeasure = mapper.measureToCqmMeasure(measure, elms, null);
cqmMeasure.setPopulation_sets(new ArrayList<>());
dto =
QrdaDTO.builder()
.measure(
objectMapper.writeValueAsString(mapper.measureToCqmMeasure(measure, elms, null)))
.measure(objectMapper.writeValueAsString(cqmMeasure))
.testCases(measure.getTestCases())
.sourceDataCriteria(dataCriteria)
.options(buildOptions(measure))
Expand Down
Loading

0 comments on commit a9af259

Please sign in to comment.