Skip to content

Commit

Permalink
Merge pull request #815 from MeasureAuthoringTool/MAT-8277_updateGrou…
Browse files Browse the repository at this point in the history
…pAndPopulationIds

MAT-8277 add display id for groups and populations
  • Loading branch information
sb-cecilialiu authored Mar 4, 2025
2 parents 05ee78c + 511b48b commit f30ac16
Show file tree
Hide file tree
Showing 17 changed files with 804 additions and 729 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.6.81-SNAPSHOT</version>
<version>0.6.82-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>gov.cms.madie</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import cms.gov.madie.measure.exceptions.ResourceNotFoundException;
import cms.gov.madie.measure.factories.ModelValidatorFactory;
import cms.gov.madie.measure.repositories.MeasureRepository;
import cms.gov.madie.measure.utils.GroupPopulationUtil;
import cms.gov.madie.measure.utils.MeasureUtil;
import cms.gov.madie.measure.validations.CqlDefinitionReturnTypeService;
import cms.gov.madie.measure.validations.CqlObservationFunctionService;
Expand Down Expand Up @@ -98,6 +99,10 @@ public Group createOrUpdateGroup(Group group, String measureId, String username)
}
updateGroupForTestCases(group, measure.getTestCases(), measure.getModel());

if (!ModelType.QDM_5_6.getValue().equalsIgnoreCase(measure.getModel())) {
GroupPopulationUtil.setGroupAndPopulationsDisplayIds(measure, group);
}

Measure errors = measureUtil.validateAllMeasureDependencies(measure);
measure.setErrors(errors.getErrors());
measure.setCqlErrors(errors.isCqlErrors());
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/cms/gov/madie/measure/services/MeasureService.java
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ public List<AclSpecification> updateAccessControlList(
public Map<String, List<String>> getSharedWithUserIds(List<String> measureIds) {
Map<String, List<String>> userIdsByMeasureId = new HashMap<>();

for (String measureId: measureIds) {
for (String measureId : measureIds) {
Measure measure = findMeasureById(measureId);

if (measure == null) {
Expand All @@ -429,11 +429,14 @@ public Map<String, List<String>> getSharedWithUserIds(List<String> measureIds) {
if (measure.getMeasureSet().getAcls() == null) {
userIdsByMeasureId.put(measureId, Collections.emptyList());
} else {
userIdsByMeasureId.put(measureId, measure.getMeasureSet().getAcls().stream()
.filter(aclSpecification -> aclSpecification.getRoles().contains(RoleEnum.SHARED_WITH))
.map(AclSpecification::getUserId)
.sorted()
.toList());
userIdsByMeasureId.put(
measureId,
measure.getMeasureSet().getAcls().stream()
.filter(
aclSpecification -> aclSpecification.getRoles().contains(RoleEnum.SHARED_WITH))
.map(AclSpecification::getUserId)
.sorted()
.toList());
}
}

Expand Down
238 changes: 35 additions & 203 deletions src/main/java/cms/gov/madie/measure/utils/GroupPopulationUtil.java
Original file line number Diff line number Diff line change
@@ -1,227 +1,59 @@
package cms.gov.madie.measure.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.bson.types.ObjectId;

import org.apache.commons.lang3.StringUtils;

import gov.cms.madie.models.measure.Group;
import gov.cms.madie.models.measure.MeasureObservation;
import gov.cms.madie.models.measure.MeasureScoring;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.Population;
import gov.cms.madie.models.measure.PopulationType;
import gov.cms.madie.models.measure.Stratification;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GroupPopulationUtil {
private GroupPopulationUtil() {}

public static boolean areGroupsAndPopulationsMatching(
List<Group> originalGroups, List<Group> newGroups) {
boolean match = false;
if (!CollectionUtils.isEmpty(originalGroups) && !CollectionUtils.isEmpty(newGroups)) {
List<Group> matchedGroups =
originalGroups.stream()
.filter(
originalGroup ->
newGroups.stream()
.allMatch(
newGroup -> isGroupPopulationsMatching(originalGroup, newGroup)))
.toList();
match = CollectionUtils.isNotEmpty(matchedGroups);
}
return match;
}

public static boolean isGroupPopulationsMatching(Group originalGroup, Group newGroup) {
if (!StringUtils.equals(originalGroup.getScoring(), newGroup.getScoring())) {
return false;
}

if (!StringUtils.equals(originalGroup.getPopulationBasis(), newGroup.getPopulationBasis())) {
return false;
}

List<Population> originalValidPops = getValidPopulations(originalGroup);
List<Population> newValidPops = getValidPopulations(newGroup);
boolean populationsMatch = isPopulationMatch(originalValidPops, newValidPops);
if (!populationsMatch) {
return false;
}

List<MeasureObservation> originalValidObs = getValidObservations(originalGroup);
List<MeasureObservation> newValidObs = getValidObservations(newGroup);
boolean observationsMatch = isMeasureObservationMatch(originalValidObs, newValidObs);
if (!observationsMatch) {
return false;
}

List<Stratification> originalValidStras = getValidStratifications(originalGroup);
List<Stratification> newValidStras = getValidStratifications(newGroup);
boolean stratificationMatch = isStratificationMatch(originalValidStras, newValidStras);

return stratificationMatch;
}

static List<Population> getValidPopulations(Group group) {
if (group != null && !CollectionUtils.isEmpty(group.getPopulations())) {
return group.getPopulations().stream()
.filter(population -> !StringUtils.isBlank(population.getDefinition()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}

static boolean isPopulationMatch(
List<Population> originalValidPops, List<Population> newValidPops) {
if (CollectionUtils.isEmpty(originalValidPops) && CollectionUtils.isEmpty(newValidPops)) {
return true;
} else {
if (originalValidPops.size() != newValidPops.size()) {
return false;
}
List<Population> nonMatch =
originalValidPops.stream()
.filter(
originalPop ->
newValidPops.stream()
.noneMatch(
newPop ->
newPop
.getDefinition()
.equalsIgnoreCase(originalPop.getDefinition())))
.toList();
if (!CollectionUtils.isEmpty(nonMatch)) {
return false;
public static void setGroupAndPopulationsDisplayIds(Measure measure, Group group) {
String groupNumber = String.valueOf(getGroupNumber(group, measure.getGroups()));

// set group display id
group.setDisplayId("Group_" + groupNumber);

// set population display id
if (!CollectionUtils.isEmpty(group.getPopulations())) {
boolean hasMultipleIps =
group.getPopulations().stream()
.filter(pop -> pop.getName().equals(PopulationType.INITIAL_POPULATION))
.count()
> 1;

for (int index = 0; index < group.getPopulations().size(); index++) {
Population population = group.getPopulations().get(index);
String popDisplayId =
getPopulationDisplayId(population, groupNumber, hasMultipleIps, index);
population.setDisplayId(popDisplayId);
}
}
return true;
}

static List<MeasureObservation> getValidObservations(Group group) {
if (group != null && !CollectionUtils.isEmpty(group.getMeasureObservations())) {
return group.getMeasureObservations().stream()
.filter(observation -> !StringUtils.isBlank(observation.getDefinition()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}

static boolean isMeasureObservationMatch(
List<MeasureObservation> originalValidObs, List<MeasureObservation> newValidObs) {
if (CollectionUtils.isEmpty(originalValidObs) && CollectionUtils.isEmpty(newValidObs)) {
return true;
} else {
if (originalValidObs.size() != newValidObs.size()) {
return false;
}
List<MeasureObservation> nonMatch =
originalValidObs.stream()
.filter(
originalObs ->
newValidObs.stream()
.noneMatch(
newObs ->
newObs
.getDefinition()
.equalsIgnoreCase(originalObs.getDefinition())))
.toList();
if (!CollectionUtils.isEmpty(nonMatch)) {
return false;
static int getGroupNumber(Group group, List<Group> groups) {
int groupNumber = 0;
for (int i = 0; i < groups.size(); i++) {
Group currentGroup = groups.get(i);
if (currentGroup.getId().equals(group.getId())) {
groupNumber = i + 1;
}
}
return true;
return groupNumber;
}

static List<Stratification> getValidStratifications(Group group) {
if (group != null && !CollectionUtils.isEmpty(group.getStratifications())) {
return group.getStratifications().stream()
.filter(stratification -> !StringUtils.isBlank(stratification.getCqlDefinition()))
.collect(Collectors.toList());
static String getPopulationDisplayId(
Population population, String groupNumber, boolean multipleIps, int index) {
String newPopDisplayId = population.getName().getDisplay().replace(" ", "") + "_" + groupNumber;
if (multipleIps && (index == 0 || index == 1)) {
newPopDisplayId = newPopDisplayId + "_" + (index + 1);
}
return Collections.emptyList();
}

static boolean isStratificationMatch(
List<Stratification> originalValidStrats, List<Stratification> newValidStrats) {
if (CollectionUtils.isEmpty(originalValidStrats) && CollectionUtils.isEmpty(newValidStrats)) {
return true;
} else {
if (originalValidStrats.size() != newValidStrats.size()) {
return false;
}
List<Stratification> nonMatch =
originalValidStrats.stream()
.filter(
originalStrats ->
newValidStrats.stream()
.noneMatch(
newStrats ->
newStrats
.getCqlDefinition()
.equalsIgnoreCase(originalStrats.getCqlDefinition())))
.toList();
if (!CollectionUtils.isEmpty(nonMatch)) {
return false;
}
}
return true;
}

/**
* This method is to order the group populations in the order defined as in:
* cms.gov.madie.measure.utils.TestCaseServiceUtil. When the test cases are imported, the group
* populations are matched one by one in the order. Also, test case execution also needs the group
* populations in the right order.
*
* @param the list of the groups
* @return none
*/
public static void reorderGroupPopulations(List<Group> groups) {
List<Population> newPopulations;
if (!CollectionUtils.isEmpty(groups)) {
for (Group group : groups) {
newPopulations = new ArrayList<>();
List<Population> populations = group.getPopulations();
if (!CollectionUtils.isEmpty(populations)) {
newPopulations.add(findPopulation(populations, PopulationType.INITIAL_POPULATION));
if (StringUtils.equals(
group.getScoring(), MeasureScoring.CONTINUOUS_VARIABLE.toString())) {
newPopulations.add(findPopulation(populations, PopulationType.MEASURE_POPULATION));
newPopulations.add(
findPopulation(populations, PopulationType.MEASURE_POPULATION_EXCLUSION));
} else {
if (!StringUtils.equals(group.getScoring(), MeasureScoring.COHORT.toString())) {
newPopulations.add(findPopulation(populations, PopulationType.DENOMINATOR));
newPopulations.add(findPopulation(populations, PopulationType.DENOMINATOR_EXCLUSION));
newPopulations.add(findPopulation(populations, PopulationType.NUMERATOR));
newPopulations.add(findPopulation(populations, PopulationType.NUMERATOR_EXCLUSION));
if (!StringUtils.equals(group.getScoring(), MeasureScoring.RATIO.toString())) {
newPopulations.add(
findPopulation(populations, PopulationType.DENOMINATOR_EXCEPTION));
}
}
}
group.setPopulations(newPopulations);
}
}
}
}

private static Population findPopulation(
List<Population> populations, PopulationType populationType) {
return populations.stream()
.filter(population -> population.getName() == populationType)
.findFirst()
.orElse(
Population.builder()
.id(ObjectId.get().toString())
.name(populationType)
.definition("")
.build());
return newPopDisplayId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,8 @@ public void testCreateGroup() throws Exception {
PopulationType.INITIAL_POPULATION,
"Initial Population",
null,
null)))
null,
"IntialPopulation_1")))
.measureGroupTypes(List.of(MeasureGroupTypes.PROCESS))
.build();
final String groupJson =
Expand Down Expand Up @@ -1638,7 +1639,8 @@ public void testUpdateGroup() throws Exception {
PopulationType.INITIAL_POPULATION,
updateIppDefinition,
null,
null)))
null,
"IntialPopulation_1")))
.measureGroupTypes(List.of(MeasureGroupTypes.PROCESS))
.build();

Expand Down Expand Up @@ -2040,7 +2042,10 @@ public void testGetSharedWithUserIds() throws Exception {
.with(csrf())
.header("Authorization", "test-okta"))
.andExpect(status().isOk())
.andExpect(content().string("{\"measureId1\":[\"userId1\",\"userId2\"],\"measureId2\":[\"userId1\",\"userId2\"]}"));
.andExpect(
content()
.string(
"{\"measureId1\":[\"userId1\",\"userId2\"],\"measureId2\":[\"userId1\",\"userId2\"]}"));

verify(measureService, times(1)).getSharedWithUserIds(eq(measureIds));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,8 @@ void createGroup() {
PopulationType.INITIAL_POPULATION,
"Initial Population",
null,
null)))
null,
"IntialPopulation_1")))
.build();
Principal principal = mock(Principal.class);
when(principal.getName()).thenReturn("test.user");
Expand Down Expand Up @@ -640,7 +641,8 @@ void updateGroup() {
PopulationType.INITIAL_POPULATION,
"Initial Population",
null,
null)))
null,
"IntialPopulation_1")))
.build();
Principal principal = mock(Principal.class);
when(principal.getName()).thenReturn("test.user");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ public void setUp() {
.populations(
List.of(
new Population(
"id-1", PopulationType.INITIAL_POPULATION, "FactorialOfFive", null, null)))
"id-1",
PopulationType.INITIAL_POPULATION,
"FactorialOfFive",
null,
null,
null)))
.groupDescription("Description")
.scoringUnit("test-scoring-unit")
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ public void setUp() {
.populations(
List.of(
new Population(
"id-1", PopulationType.INITIAL_POPULATION, "FactorialOfFive", null, null)))
"id-1",
PopulationType.INITIAL_POPULATION,
"FactorialOfFive",
null,
null,
null)))
.groupDescription("Description")
.scoringUnit("test-scoring-unit")
.build();
Expand Down
Loading

0 comments on commit f30ac16

Please sign in to comment.