Skip to content

Commit 11b055c

Browse files
authored
Deduplicate logicDefinition extensions on the moduleDefinitionLibrary (#594)
* Deduplicate logicDefinition extensions on the moduleDefinitionLibrary * Created util for common code and added focused unit testing for new utility
1 parent 1524d69 commit 11b055c

5 files changed

Lines changed: 147 additions & 0 deletions

File tree

tooling/src/main/java/org/opencds/cqf/tooling/measure/MeasureRefreshProcessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.hl7.fhir.r5.model.RelatedArtifact;
2121
import org.hl7.fhir.r5.model.Resource;
2222
import org.hl7.fhir.r5.model.StringType;
23+
import org.opencds.cqf.tooling.utilities.LogicDefinitionUtils;
2324
import org.opencds.cqf.tooling.utilities.constants.CrmiConstants;
2425

2526
public class MeasureRefreshProcessor {
@@ -39,6 +40,7 @@ public Measure refreshMeasure(Measure measureToUse, LibraryManager libraryManage
3940

4041
Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, compiledLibrary, options);
4142
removeModelInfoDependencies(moduleDefinitionLibrary);
43+
LogicDefinitionUtils.deduplicate(moduleDefinitionLibrary.getExtension());
4244
measureToUse.setDate(new Date());
4345
// http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/measure-cqfm
4446
setMeta(measureToUse, moduleDefinitionLibrary);

tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/Refresh.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hl7.fhir.r5.model.*;
1010
import org.opencds.cqf.tooling.parameter.RefreshIGParameters;
1111
import org.opencds.cqf.tooling.utilities.BundleUtils;
12+
import org.opencds.cqf.tooling.utilities.LogicDefinitionUtils;
1213
import org.opencds.cqf.tooling.utilities.constants.CqfmConstants;
1314
import org.opencds.cqf.tooling.utilities.constants.CrmiConstants;
1415
import org.opencds.cqf.tooling.utilities.converters.ResourceAndTypeConverter;
@@ -66,10 +67,17 @@ public void refreshCqfmExtensions(MetadataResource resource, Library moduleDefin
6667
resource.getExtension().removeAll(resource.getExtensionsByUrl(CqfmConstants.LOGIC_DEFINITION_EXT_URL));
6768
resource.getExtension().removeAll(resource.getExtensionsByUrl(CqfmConstants.EFFECTIVE_DATA_REQS_EXT_URL));
6869

70+
Set<String> logicDefinitionKeys = new HashSet<>();
6971
for (Extension extension : moduleDefinitionLibrary.getExtension()) {
7072
if (extension.hasUrl() && extension.getUrl().equals(CqfmConstants.DIRECT_REF_CODE_EXT_URL)) {
7173
continue;
7274
}
75+
if (LogicDefinitionUtils.isLogicDefinition(extension)) {
76+
String key = LogicDefinitionUtils.getLogicDefinitionKey(extension);
77+
if (key != null && !logicDefinitionKeys.add(key)) {
78+
continue;
79+
}
80+
}
7381
resource.addExtension(extension);
7482
}
7583
}

tooling/src/main/java/org/opencds/cqf/tooling/plandefinition/PlanDefinitionRefreshProcessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
66
import org.cqframework.cql.elm.requirements.fhir.DataRequirementsProcessor;
77
import org.hl7.fhir.r5.model.*;
8+
import org.opencds.cqf.tooling.utilities.LogicDefinitionUtils;
89
import org.opencds.cqf.tooling.utilities.constants.CqfConstants;
910
import org.opencds.cqf.tooling.utilities.constants.CqfmConstants;
1011
import org.opencds.cqf.tooling.utilities.constants.CrmiConstants;
@@ -23,6 +24,7 @@ public PlanDefinition refreshPlanDefinition(PlanDefinition planToUse, LibraryMan
2324
var dqReqTrans = new DataRequirementsProcessor();
2425
var moduleDefinitionLibrary = dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary,
2526
options, expressions, true);
27+
LogicDefinitionUtils.deduplicate(moduleDefinitionLibrary.getExtension());
2628

2729
// Clear all existing CQFM extensions
2830
// These extensions are now deprecated, but may be in use for older artifacts
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.opencds.cqf.tooling.utilities;
2+
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
7+
import org.hl7.fhir.r5.model.Extension;
8+
import org.opencds.cqf.tooling.utilities.constants.CqfConstants;
9+
import org.opencds.cqf.tooling.utilities.constants.CqfmConstants;
10+
11+
public class LogicDefinitionUtils {
12+
13+
private LogicDefinitionUtils() {
14+
}
15+
16+
public static String getLogicDefinitionKey(Extension logicDefinition) {
17+
String libraryName = null;
18+
String name = null;
19+
for (Extension sub : logicDefinition.getExtension()) {
20+
if ("libraryName".equals(sub.getUrl()) && sub.hasValue()) {
21+
libraryName = sub.getValue().primitiveValue();
22+
} else if ("name".equals(sub.getUrl()) && sub.hasValue()) {
23+
name = sub.getValue().primitiveValue();
24+
}
25+
}
26+
return (libraryName != null && name != null) ? libraryName + "|" + name : null;
27+
}
28+
29+
public static boolean isLogicDefinition(Extension extension) {
30+
return extension.hasUrl()
31+
&& (CqfmConstants.LOGIC_DEFINITION_EXT_URL.equals(extension.getUrl())
32+
|| CqfConstants.LOGIC_DEFINITION_EXT_URL.equals(extension.getUrl()));
33+
}
34+
35+
public static void deduplicate(List<Extension> extensions) {
36+
Set<String> seen = new HashSet<>();
37+
extensions.removeIf(ext -> {
38+
if (isLogicDefinition(ext)) {
39+
String key = getLogicDefinitionKey(ext);
40+
return key != null && !seen.add(key);
41+
}
42+
return false;
43+
});
44+
}
45+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.opencds.cqf.tooling.utilities;
2+
3+
import static org.testng.Assert.assertEquals;
4+
import static org.testng.Assert.assertNull;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
import org.hl7.fhir.r5.model.Extension;
10+
import org.hl7.fhir.r5.model.IntegerType;
11+
import org.hl7.fhir.r5.model.StringType;
12+
import org.opencds.cqf.tooling.utilities.constants.CqfConstants;
13+
import org.opencds.cqf.tooling.utilities.constants.CqfmConstants;
14+
import org.testng.annotations.Test;
15+
16+
public class LogicDefinitionUtilsTests {
17+
18+
private static Extension logicDefinition(String url, String libraryName, String name, int displaySequence) {
19+
Extension ext = new Extension().setUrl(url);
20+
ext.addExtension(new Extension().setUrl("libraryName").setValue(new StringType(libraryName)));
21+
ext.addExtension(new Extension().setUrl("name").setValue(new StringType(name)));
22+
ext.addExtension(new Extension().setUrl("displaySequence").setValue(new IntegerType(displaySequence)));
23+
return ext;
24+
}
25+
26+
@Test
27+
public void TestKeyUsesLibraryNameAndName() {
28+
Extension ext = logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "HRDMeasure", "Inpatient Beds Initial Population", 38);
29+
assertEquals(LogicDefinitionUtils.getLogicDefinitionKey(ext), "HRDMeasure|Inpatient Beds Initial Population");
30+
}
31+
32+
@Test
33+
public void TestKeyIsNullWhenLibraryNameMissing() {
34+
Extension ext = new Extension().setUrl(CqfConstants.LOGIC_DEFINITION_EXT_URL);
35+
ext.addExtension(new Extension().setUrl("name").setValue(new StringType("X")));
36+
assertNull(LogicDefinitionUtils.getLogicDefinitionKey(ext));
37+
}
38+
39+
@Test
40+
public void TestIsLogicDefinitionRecognizesBothUrls() {
41+
Extension cqf = new Extension().setUrl(CqfConstants.LOGIC_DEFINITION_EXT_URL);
42+
Extension cqfm = new Extension().setUrl(CqfmConstants.LOGIC_DEFINITION_EXT_URL);
43+
Extension other = new Extension().setUrl("http://example.org/other");
44+
assertEquals(LogicDefinitionUtils.isLogicDefinition(cqf), true);
45+
assertEquals(LogicDefinitionUtils.isLogicDefinition(cqfm), true);
46+
assertEquals(LogicDefinitionUtils.isLogicDefinition(other), false);
47+
}
48+
49+
@Test
50+
public void TestDeduplicateRemovesDuplicatesKeepingFirst() {
51+
List<Extension> extensions = new ArrayList<>();
52+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "HRDMeasure", "Inpatient Beds Initial Population", 38));
53+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "HRDMeasure", "Adult Inpatient Beds Initial Population", 40));
54+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "HRDMeasure", "Inpatient Beds Initial Population", 82));
55+
56+
LogicDefinitionUtils.deduplicate(extensions);
57+
58+
assertEquals(extensions.size(), 2);
59+
assertEquals(LogicDefinitionUtils.getLogicDefinitionKey(extensions.get(0)), "HRDMeasure|Inpatient Beds Initial Population");
60+
// The first-encountered entry is kept, so the displaySequence of the survivor is 38, not 82.
61+
Extension kept = extensions.get(0);
62+
int displaySequence = ((IntegerType) kept.getExtensionByUrl("displaySequence").getValue()).getValue();
63+
assertEquals(displaySequence, 38);
64+
}
65+
66+
@Test
67+
public void TestDeduplicatePreservesNonLogicDefinitionExtensions() {
68+
List<Extension> extensions = new ArrayList<>();
69+
extensions.add(new Extension().setUrl("http://example.org/other").setValue(new StringType("a")));
70+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "Lib", "Def", 1));
71+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "Lib", "Def", 2));
72+
extensions.add(new Extension().setUrl("http://example.org/other").setValue(new StringType("b")));
73+
74+
LogicDefinitionUtils.deduplicate(extensions);
75+
76+
// One logicDefinition removed; both unrelated extensions retained.
77+
assertEquals(extensions.size(), 3);
78+
}
79+
80+
@Test
81+
public void TestDeduplicateDedupesAcrossCqfmAndCqfUrls() {
82+
List<Extension> extensions = new ArrayList<>();
83+
extensions.add(logicDefinition(CqfmConstants.LOGIC_DEFINITION_EXT_URL, "Lib", "Def", 1));
84+
extensions.add(logicDefinition(CqfConstants.LOGIC_DEFINITION_EXT_URL, "Lib", "Def", 2));
85+
86+
LogicDefinitionUtils.deduplicate(extensions);
87+
88+
assertEquals(extensions.size(), 1);
89+
}
90+
}

0 commit comments

Comments
 (0)