Skip to content

Commit dec445a

Browse files
committed
Introduce sepsis rules and patient flag
1 parent 766fc52 commit dec445a

File tree

47 files changed

+1000
-463
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1000
-463
lines changed

api/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,5 @@
6969
<groupId>org.openmrs</groupId>
7070
<artifactId>event-api</artifactId>
7171
</dependency>
72-
<dependency>
73-
<groupId>org.openmrs.module</groupId>
74-
<artifactId>calculation-api</artifactId>
75-
</dependency>
7672
</dependencies>
7773
</project>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
*/
1010
package org.openmrs.module.drools;
1111

12-
import org.apache.commons.logging.Log;
13-
import org.apache.commons.logging.LogFactory;
1412
import org.kie.api.KieServices;
1513
import org.kie.api.builder.KieFileSystem;
1614
import org.kie.api.io.ResourceType;
1715
import org.openmrs.api.context.Context;
1816
import org.openmrs.module.drools.api.RuleProvider;
1917
import org.openmrs.module.drools.session.ExternalEvaluator;
2018
import org.openmrs.module.drools.session.ExternalEvaluatorManager;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
2121
import org.springframework.beans.factory.annotation.Autowired;
2222
import org.springframework.context.annotation.Bean;
2323
import org.springframework.context.annotation.Configuration;
@@ -32,7 +32,7 @@
3232
@Configuration
3333
public class DroolsConfig {
3434

35-
Log log = LogFactory.getLog(DroolsConfig.class);
35+
private Logger log = LoggerFactory.getLogger(this.getClass());
3636

3737
private List<RuleProvider> ruleProviders;
3838

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package org.openmrs.module.drools;
22

3-
import java.util.HashSet;
4-
import java.util.List;
5-
import java.util.Set;
6-
7-
import org.apache.commons.logging.Log;
8-
import org.apache.commons.logging.LogFactory;
93
import org.kie.api.KieServices;
104
import org.kie.api.builder.KieFileSystem;
115
import org.kie.api.runtime.KieContainer;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Set;
1212

1313
public class KieContainerBuilder {
1414

15-
Log log = LogFactory.getLog(KieContainerBuilder.class);
15+
private Logger log = LoggerFactory.getLogger(this.getClass());
1616

1717
private KieServices kieServices;
1818

api/src/main/java/org/openmrs/module/drools/api/impl/DroolsEngineServiceImpl.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package org.openmrs.module.drools.api.impl;
22

3+
import org.apache.commons.lang3.StringUtils;
34
import org.kie.api.runtime.KieContainer;
45
import org.kie.api.runtime.KieSession;
56
import org.kie.api.runtime.ObjectFilter;
7+
import org.kie.api.runtime.rule.AgendaFilter;
68
import org.openmrs.OpenmrsObject;
79
import org.openmrs.api.impl.BaseOpenmrsService;
810
import org.openmrs.module.drools.DroolsConfig;
911
import org.openmrs.module.drools.KieContainerBuilder;
1012
import org.openmrs.module.drools.api.DroolsEngineService;
1113
import org.openmrs.module.drools.api.RuleProvider;
1214
import org.openmrs.module.drools.event.DroolsEventsManager;
15+
import org.openmrs.module.drools.session.AgendaFilterByNameOrGroup;
1316
import org.openmrs.module.drools.session.DroolsSessionException;
1417
import org.openmrs.module.drools.session.RuleSessionConfig;
15-
import org.openmrs.module.drools.session.SessionPool;
1618
import org.openmrs.module.drools.session.StatefulSessionRegistry;
1719
import org.springframework.beans.factory.annotation.Autowired;
18-
import java.util.ArrayList;
19-
import java.util.Collection;
20-
import java.util.List;
21-
import java.util.Map;
20+
21+
import java.util.*;
2222
import java.util.function.Predicate;
2323
import java.util.stream.Collectors;
2424

@@ -37,10 +37,10 @@ public class DroolsEngineServiceImpl extends BaseOpenmrsService implements Drool
3737

3838
private Map<String, RuleSessionConfig> ruleConfigs;
3939

40-
private SessionPool sessionPool = new SessionPool();
41-
4240
private DroolsEventsManager eventsManager = new DroolsEventsManager();
4341

42+
private final Map<String, Map <String, Object>> globalBindings = new HashMap<>();
43+
4444
@Override
4545
public KieSession requestSession(String sessionId) {
4646
KieSession session;
@@ -52,11 +52,7 @@ public KieSession requestSession(String sessionId) {
5252
}
5353
if (ruleConfigs.get(sessionId) != null) {
5454
RuleSessionConfig requestedSessionConfig = ruleConfigs.get(sessionId);
55-
if (requestedSessionConfig.getStateful()) {
56-
session = sessionRegistry.requestSession(requestedSessionConfig, kieContainer);
57-
} else {
58-
session = sessionPool.borrowSession(sessionId, kieContainer);
59-
}
55+
session = sessionRegistry.requestSession(requestedSessionConfig, kieContainer, globalBindings);
6056
if (session != null) {
6157
eventsManager.subscribeSessionEventListenersIfNecessary(sessionId, session, ruleConfigs);
6258
return session;
@@ -72,7 +68,15 @@ public KieSession evaluate(String sessionId, Collection<? extends OpenmrsObject>
7268
KieSession currentSession = requestSession(sessionId);
7369
if (currentSession != null) {
7470
facts.forEach(currentSession::insert);
75-
currentSession.fireAllRules();
71+
String allowedAgendaGroup = ruleConfigs.get(sessionId).getAgendaGroup();
72+
AgendaFilter agendaFilter = ruleConfigs.get(sessionId).getAgendaFilter();
73+
if (StringUtils.isNotBlank(allowedAgendaGroup)) {
74+
currentSession.getAgenda().getAgendaGroup(allowedAgendaGroup).setFocus();
75+
if (agendaFilter == null) {
76+
agendaFilter = new AgendaFilterByNameOrGroup(null, allowedAgendaGroup);
77+
}
78+
}
79+
currentSession.fireAllRules(agendaFilter);
7680
} else {
7781
throw new DroolsSessionException("Could not establish a KIE session of ID: " + sessionId);
7882
}
@@ -133,6 +137,9 @@ public void registerRuleProvider(RuleProvider ruleProvider) {
133137
if (!ruleConfigs.containsKey(ruleSessionConfig.getSessionId())) {
134138
ruleConfigs.put(ruleSessionConfig.getSessionId(), ruleSessionConfig);
135139
}
140+
if (!globalBindings.containsKey(ruleSessionConfig.getSessionId())) {
141+
globalBindings.put(ruleSessionConfig.getSessionId(), ruleSessionConfig.getGlobals());
142+
}
136143
});
137144
}
138145

@@ -150,8 +157,12 @@ public List<RuleSessionConfig> getSessionsForAutoStart() {
150157

151158
private Map<String, RuleSessionConfig> initializeSessionConfigs() {
152159
List<RuleProvider> ruleProviders = droolsConfig.getRuleProviders();
153-
return ruleProviders.stream().map(RuleProvider::getSessionConfigs).flatMap(List::stream)
160+
Map<String, RuleSessionConfig> sessionConfigMap = ruleProviders.stream().map(RuleProvider::getSessionConfigs).flatMap(List::stream)
154161
.collect(Collectors.toMap(RuleSessionConfig::getSessionId, ruleSessionConfig -> ruleSessionConfig));
162+
sessionConfigMap.forEach((sessionId, config) -> {
163+
globalBindings.put(sessionId, config.getGlobals());
164+
});
165+
return sessionConfigMap;
155166
}
156167

157168
public StatefulSessionRegistry getSessionRegistry() {
@@ -162,14 +173,6 @@ public void setSessionRegistry(StatefulSessionRegistry sessionRegistry) {
162173
this.sessionRegistry = sessionRegistry;
163174
}
164175

165-
public SessionPool getSessionPool() {
166-
return sessionPool;
167-
}
168-
169-
public void setSessionPool(SessionPool sessionPool) {
170-
this.sessionPool = sessionPool;
171-
}
172-
173176
public KieContainer getKieContainer() {
174177
return kieContainer;
175178
}

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.openmrs.module.drools.calculation;
22

33
import org.apache.commons.lang3.StringUtils;
4+
import org.apache.commons.lang3.math.NumberUtils;
45
import org.openmrs.Concept;
56
import org.openmrs.ConceptDatatype;
67
import org.openmrs.Obs;
@@ -40,17 +41,26 @@ public static Object refineRhsOperand(Object operand, ConceptDatatype datatype)
4041
return operand;
4142
}
4243

43-
public static Concept getConcept(String conceptUuidOrMapping) {
44+
public static Concept getConcept(String conceptRef) {
4445
ConceptService conceptService = Context.getConceptService();
45-
if (StringUtils.isBlank(conceptUuidOrMapping)) {
46+
if (StringUtils.isBlank(conceptRef)) {
4647
throw new IllegalArgumentException("Concept ref can't be blank");
4748
}
48-
49-
if (conceptUuidOrMapping.indexOf(":") > 0) {
50-
String [] parts = conceptUuidOrMapping.split(":");
49+
// handle mapping
50+
if (conceptRef.indexOf(":") > 0) {
51+
String [] parts = conceptRef.split(":");
5152
return conceptService.getConceptByMapping(parts[1], parts[0]);
5253
}
53-
return conceptService.getConceptByUuid(conceptUuidOrMapping);
54+
// handle id
55+
int conceptId = NumberUtils.toInt(conceptRef, -1);
56+
if (conceptId >= 0) {
57+
Concept cpt = conceptService.getConcept(conceptId);
58+
if (cpt != null) {
59+
return cpt;
60+
}
61+
}
62+
// handle uuid
63+
return conceptService.getConceptByUuid(conceptRef);
5464
}
5565

5666
public static Object extractObsValue(Obs obs, ConceptDatatypeWrapper datatype){

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.openmrs.Obs;
44
import org.openmrs.Patient;
55

6+
import java.util.Date;
7+
68
public interface DroolsCalculationService {
79

810
/**
@@ -33,6 +35,36 @@ public interface DroolsCalculationService {
3335
*/
3436
public Boolean checkMostRecentObs(Patient patient, String conceptRef, Operator operator, Object value);
3537

38+
39+
/**
40+
* Retrieves the most recent observation for the given patient and concept that matches
41+
* the specified date constraint.
42+
* <p>
43+
* The method constructs a date range using the provided {@link Operator} (e.g., {@code LT}, {@code GTE}),
44+
* and filters observations by their {@code obsDatetime}. For example:
45+
* <ul>
46+
* <li>{@code LT} sets an exclusive upper bound (obsDatetime &lt; date)</li>
47+
* <li>{@code LTE} includes the end of the provided day</li>
48+
* <li>{@code GT} sets an exclusive lower bound (obsDatetime &gt; date)</li>
49+
* <li>{@code GTE} includes the start of the provided day</li>
50+
* <li>{@code EQUALS} includes observations occurring on the same calendar day</li>
51+
* </ul>
52+
* The most recent matching observation within that range is returned as a {@link MatchableObsResult},
53+
* allowing value-based assertions using methods like {@code matches(Operator.GT, 5.0)}.
54+
*
55+
* @param patient the patient whose observations are to be evaluated
56+
* @param conceptRef the concept UUID or reference to match observations against
57+
* @param dateOperator the temporal operator to constrain observation dates
58+
* @param date the reference date used with the operator
59+
* @return a {@link MatchableObsResult} wrapping the most recent matching observation, or a null result if none match
60+
*
61+
* @throws IllegalArgumentException if the provided operator is not supported for date-based filtering
62+
*
63+
* @see MatchableObsResult#matches(Operator, Object)
64+
* @see Operator
65+
*/
66+
public MatchableObsResult checkObs(Patient patient, String conceptRef, Operator dateOperator, Date date);
67+
3668
public Obs getLatestObs(Patient patient, String conceptUuid);
3769

3870
public Boolean isInProgram(Patient patient, String programUuid);

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

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
package org.openmrs.module.drools.calculation;
22

3-
import java.util.Collections;
4-
import java.util.HashMap;
5-
63
import org.openmrs.*;
74
import org.openmrs.api.EncounterService;
5+
import org.openmrs.api.ObsService;
86
import org.openmrs.api.ProgramWorkflowService;
97
import org.openmrs.api.context.Context;
10-
import org.openmrs.calculation.patient.PatientCalculationContext;
11-
import org.openmrs.calculation.patient.PatientCalculationService;
12-
import org.openmrs.calculation.result.CalculationResult;
138
import org.openmrs.parameter.EncounterSearchCriteria;
9+
import org.openmrs.util.OpenmrsUtil;
10+
import org.springframework.beans.factory.annotation.Autowired;
1411

15-
public class DroolsCalculationServiceImp implements DroolsCalculationService {
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.Date;
15+
import java.util.List;
1616

17-
private MostRecentObsCalculation obsCalculation = new MostRecentObsCalculation();
17+
public class DroolsCalculationServiceImp implements DroolsCalculationService {
1818

19-
private PatientCalculationService calculationService;
19+
@Autowired
20+
ObsService obsService;
2021

21-
private PatientCalculationContext context;
22+
@Autowired
23+
ProgramWorkflowService programWorkflowService;
2224

2325
@Override
2426
public Boolean checkMostRecentObs(Patient patient, String conceptRef, Operator operator, Object value) {
@@ -30,22 +32,73 @@ public Boolean checkMostRecentObs(Patient patient, String conceptRef, Operator o
3032
Concept concept = obsValue.getConcept();
3133
ConceptDatatypeWrapper datatype = new ConceptDatatypeWrapper(concept.getDatatype());
3234
if (!operator.getSupportedDatatypes().contains(datatype.getDatatypeCode())) {
33-
throw new IllegalArgumentException("Operator " + operator + " not supported for datatype " + datatype.getDatatype().getName());
35+
throw new IllegalArgumentException(
36+
"Operator " + operator + " not supported for datatype " + datatype.getDatatype().getName());
3437
}
3538
Object refinedValue = CalculationUtils.extractObsValue((Obs) obsValue, datatype);
3639
return operator.apply(refinedValue, value, datatype);
3740
}
3841

3942
@Override
40-
public Obs getLatestObs(Patient patient, String conceptUuid) {
41-
CalculationResult result = getCalculationService().evaluate(patient.getId(), obsCalculation,
42-
Collections.singletonMap("concept", CalculationUtils.getConcept(conceptUuid)), getContext());
43-
return (Obs) result.getValue();
43+
public MatchableObsResult checkObs(Patient patient, String conceptRef, Operator dateOperator, Date date) {
44+
Date fromDate = null;
45+
Date toDate = null;
46+
if (dateOperator == null) {
47+
dateOperator = Operator.EQUALS;
48+
}
49+
Concept concept = CalculationUtils.getConcept(conceptRef);
50+
51+
switch (dateOperator) {
52+
case LT:
53+
toDate = new Date(date.getTime() - 1);
54+
break;
55+
case LTE:
56+
toDate = OpenmrsUtil.getLastMomentOfDay(date);
57+
break;
58+
case GT:
59+
fromDate = new Date(date.getTime() + 1);
60+
break;
61+
case GTE:
62+
fromDate = OpenmrsUtil.firstSecondOfDay(date);
63+
break;
64+
case EQUALS:
65+
fromDate = OpenmrsUtil.firstSecondOfDay(date);
66+
toDate = OpenmrsUtil.getLastMomentOfDay(date);
67+
break;
68+
default:
69+
throw new IllegalArgumentException("Unsupported date operator: " + dateOperator);
70+
}
71+
72+
List<Obs> obsList = Context.getObsService().getObservations(
73+
List.of(patient),
74+
null,
75+
List.of(concept),
76+
null,
77+
null,
78+
null,
79+
List.of("obsDatetime"),
80+
1,
81+
null,
82+
fromDate,
83+
toDate,
84+
false);
85+
return new MatchableObsResult(obsList.isEmpty() ? null : obsList.get(0),
86+
new ConceptDatatypeWrapper(concept.getDatatype()));
87+
}
88+
89+
@Override
90+
public Obs getLatestObs(Patient patient, String conceptRef) {
91+
Concept concept = CalculationUtils.getConcept(conceptRef);
92+
List<Obs> obsList = obsService.getObservations(Arrays.asList(patient.getPerson()), null,
93+
Arrays.asList(concept), null, null, null, Arrays.asList("obsDatetime"), 1, null, null, null, false);
94+
if (obsList.isEmpty()) {
95+
return null;
96+
}
97+
return obsList.get(0);
4498
}
4599

46100
@Override
47101
public Boolean isInProgram(Patient patient, String programUuid) {
48-
ProgramWorkflowService programWorkflowService = Context.getProgramWorkflowService();
49102
Program program = programWorkflowService.getProgramByUuid(programUuid);
50103
if (program == null) {
51104
throw new IllegalArgumentException("Program not found for uuid: " + programUuid);
@@ -79,17 +132,4 @@ public Boolean hasEncounterRecord(Patient patient, String encounterTypeUuid) {
79132
return !encounterService.getEncounters(criteria).isEmpty();
80133
}
81134

82-
private PatientCalculationService getCalculationService() {
83-
if (calculationService == null) {
84-
calculationService = Context.getService(PatientCalculationService.class);
85-
}
86-
return calculationService;
87-
}
88-
89-
private PatientCalculationContext getContext() {
90-
if (context == null) {
91-
context = getCalculationService().createCalculationContext();
92-
}
93-
return context;
94-
}
95135
}

0 commit comments

Comments
 (0)