Skip to content

Commit 04a022d

Browse files
authored
Merge pull request #4 from openmrs/concept-utils
(feat) Add CommonUtils to support rules referencing concepts
2 parents 7ffd7af + 68a82f9 commit 04a022d

File tree

5 files changed

+93
-4
lines changed

5 files changed

+93
-4
lines changed

api/src/main/java/org/openmrs/module/drools/api/DroolsEngineService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public interface DroolsEngineService extends OpenmrsService {
4848
* @param <T> the type of objects to retrieve
4949
* @param session the Drools session to query
5050
* @param tClass the class object representing the type to retrieve
51-
* @return List<T> a list of objects of the specified type from the session
51+
* @return a list of objects of the specified type from the session
5252
* @throws DroolsSessionException if the session does not exist or cannot be
5353
* accessed
5454
*/
@@ -62,7 +62,7 @@ public interface DroolsEngineService extends OpenmrsService {
6262
* @param session the Drools session to query
6363
* @param tClass the class object representing the type to retrieve
6464
* @param tPredicate the predicate that returned objects must satisfy
65-
* @return List<T> a list of matching objects from the session
65+
* @return a list of matching objects from the session
6666
* @throws DroolsSessionException if the session does not exist or cannot be
6767
* accessed
6868
*/

api/src/main/java/org/openmrs/module/drools/calculation/DroolsCalculationService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public interface DroolsCalculationService {
2424
*
2525
* // Check if most recent diagnosis equals a specific concept
2626
* checkMostRecentObs(patient, "CIEL:1284", Operator.EQUALS, "CIEL:5622");
27+
* }</pre>
2728
*
2829
* @param patient the patient whose observations will be evaluated; must not be null
2930
* @param conceptRef the concept reference (UUID or mapping)

api/src/main/java/org/openmrs/module/drools/utils/CommonUtils.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44
import org.drools.decisiontable.SpreadsheetCompiler;
55
import org.kie.api.runtime.KieContainer;
66
import org.kie.api.runtime.KieSession;
7+
import org.openmrs.Concept;
8+
import org.openmrs.ConceptMap;
9+
import org.openmrs.ConceptMapType;
10+
import org.openmrs.api.context.Context;
711
import org.openmrs.module.drools.session.ExternalEvaluatorManager;
812
import org.openmrs.module.drools.session.DroolsSessionConfig;
913
import org.slf4j.Logger;
1014
import org.slf4j.LoggerFactory;
1115

1216
import java.io.*;
17+
import java.util.HashSet;
18+
import java.util.List;
1319
import java.util.Map;
1420
import java.util.Objects;
21+
import java.util.Set;
1522

1623
public class CommonUtils {
1724

@@ -85,4 +92,83 @@ public static void convertExcelRulesToDrl(String excelFilePath, String outputDrl
8592
}
8693

8794
}
95+
96+
/**
97+
* Gets a concept by its SAME-AS mapping to an external source.
98+
* Unlike ConceptService.getConceptByMapping(), this method only considers
99+
* SAME-AS mappings, avoiding the "Multiple non-retired concepts found" error
100+
* that occurs when a concept has both SAME-AS and NARROWER-THAN mappings.
101+
*
102+
* @param code The concept code in the external source
103+
* @param sourceName The name of the concept source (e.g., "CIEL")
104+
* @return The concept with a SAME-AS mapping to the given code, or null if not found
105+
*/
106+
public static Concept getConceptBySameAsMapping(String code, String sourceName) {
107+
if (code == null || sourceName == null) {
108+
return null;
109+
}
110+
111+
List<Concept> concepts = Context.getConceptService().getConceptsByMapping(code, sourceName, false);
112+
if (concepts == null || concepts.isEmpty()) {
113+
return null;
114+
}
115+
116+
// Filter for SAME-AS mapping type only
117+
for (Concept concept : concepts) {
118+
for (ConceptMap mapping : concept.getConceptMappings()) {
119+
if (mapping.getConceptReferenceTerm() != null
120+
&& mapping.getConceptReferenceTerm().getConceptSource() != null
121+
&& sourceName.equals(mapping.getConceptReferenceTerm().getConceptSource().getName())
122+
&& code.equals(mapping.getConceptReferenceTerm().getCode())) {
123+
ConceptMapType mapType = mapping.getConceptMapType();
124+
if (mapType != null && "SAME-AS".equalsIgnoreCase(mapType.getName())) {
125+
return concept;
126+
}
127+
}
128+
}
129+
}
130+
131+
// Fall back to first result if no SAME-AS found (backwards compatibility)
132+
log.warn("No SAME-AS mapping found for code {} from source {}, returning first match", code, sourceName);
133+
return concepts.get(0);
134+
}
135+
136+
/**
137+
* Gets all concepts that have a SAME-AS or NARROWER-THAN mapping to the given code.
138+
* This is useful for finding concepts that represent the same or more specific
139+
* versions of a given terminology code (e.g., SNOMED CT).
140+
*
141+
* @param code The concept code in the external source
142+
* @param sourceName The name of the concept source (e.g., "SNOMED CT")
143+
* @return A set of concepts with SAME-AS or NARROWER-THAN mappings, or empty set if none found
144+
*/
145+
public static Set<Concept> getConceptsBySameAsOrNarrowerThanMapping(String code, String sourceName) {
146+
Set<Concept> result = new HashSet<>();
147+
if (code == null || sourceName == null) {
148+
return result;
149+
}
150+
151+
List<Concept> concepts = Context.getConceptService().getConceptsByMapping(code, sourceName, false);
152+
if (concepts == null || concepts.isEmpty()) {
153+
return result;
154+
}
155+
156+
for (Concept concept : concepts) {
157+
for (ConceptMap mapping : concept.getConceptMappings()) {
158+
if (mapping.getConceptReferenceTerm() != null
159+
&& mapping.getConceptReferenceTerm().getConceptSource() != null
160+
&& sourceName.equals(mapping.getConceptReferenceTerm().getConceptSource().getName())
161+
&& code.equals(mapping.getConceptReferenceTerm().getCode())
162+
&& mapping.getConceptMapType() != null) {
163+
String mapType = mapping.getConceptMapType().getName();
164+
if ("SAME-AS".equalsIgnoreCase(mapType) || "NARROWER-THAN".equalsIgnoreCase(mapType)) {
165+
result.add(concept);
166+
break;
167+
}
168+
}
169+
}
170+
}
171+
172+
return result;
173+
}
88174
}

api/src/main/java/org/openmrs/module/drools/utils/DroolsDateUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public static Date daysFromNow(int days) {
119119
}
120120

121121
/**
122-
* Checks if the difference between start and end is >= threshold in given granularity.
122+
* Checks if the difference between start and end is at least the threshold in given granularity.
123123
*
124124
* @param start the start date
125125
* @param end the end date
@@ -158,7 +158,7 @@ public static Boolean isAtLeast(Date start, Date end, long threshold, Granularit
158158
* @param start the start date
159159
* @param end the end date
160160
* @param granularity the granularity
161-
* @return the difference (end - start) in given units, negative if end < start
161+
* @return the difference (end - start) in given units, negative if end is before start
162162
*/
163163
public static long diff(Date start, Date end, Granularity granularity) {
164164
if (start == null || end == null || granularity == null) {

omod/src/main/resources/webModuleApplicationContext.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
2222

2323
<context:component-scan base-package="org.openmrs.module.drools.web.controller" />
24+
<context:component-scan base-package="org.openmrs.module.drools.loader" />
25+
<context:component-scan base-package="org.openmrs.module.drools.provider" />
2426
</beans>

0 commit comments

Comments
 (0)