• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 17991776982

24 Sep 2025 10:53PM UTC coverage: 64.928% (-0.05%) from 64.977%
17991776982

push

github

dkayiwa
TRUNK-6433 add isObsValueCodedAnswer method

7 of 10 new or added lines in 1 file covered. (70.0%)

21 existing lines in 9 files now uncovered.

23421 of 36072 relevant lines covered (64.93%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.91
/api/src/main/java/org/openmrs/util/ConceptReferenceRangeUtility.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.util;
11

12
import java.io.StringWriter;
13
import java.time.LocalDate;
14
import java.time.ZoneId;
15
import java.time.temporal.ChronoUnit;
16
import java.util.Collections;
17
import java.util.Date;
18
import java.util.List;
19
import java.util.Locale;
20
import java.util.Properties;
21

22
import org.apache.commons.lang.StringUtils;
23
import org.apache.velocity.VelocityContext;
24
import org.apache.velocity.app.VelocityEngine;
25
import org.apache.velocity.exception.ParseErrorException;
26
import org.apache.velocity.runtime.log.Log4JLogChute;
27
import org.joda.time.LocalTime;
28
import org.openmrs.Concept;
29
import org.openmrs.Obs;
30
import org.openmrs.Person;
31
import org.openmrs.api.APIException;
32
import org.openmrs.api.context.Context;
33

34
/**
35
 * A utility class that evaluates the concept ranges 
36
 * 
37
 * @since 2.7.0
38
 */
39
public class ConceptReferenceRangeUtility {
40
        
41
        private final long NULL_DATE_RETURN_VALUE = -1;
1✔
42
        
43
        public ConceptReferenceRangeUtility() {
1✔
44
        }
1✔
45
        
46
        /**
47
         * This method evaluates the given criteria against the provided {@link Obs}.
48
         *
49
         * @param criteria the criteria string to evaluate e.g. "$patient.getAge() > 1"
50
         * @param obs The observation (Obs) object containing the values to be used in the criteria evaluation.
51
         *                  
52
         * @return true if the criteria evaluates to true, false otherwise
53
         */
54
        public boolean evaluateCriteria(String criteria, Obs obs) {
55
                if (obs == null) {
1✔
56
                        throw new IllegalArgumentException("Failed to evaluate criteria with reason: Obs is null");
1✔
57
                }
58
                
59
                if (obs.getPerson() == null) {
1✔
60
                        throw new IllegalArgumentException("Failed to evaluate criteria with reason: patient is null");
×
61
                }
62
                
63
                if (StringUtils.isBlank(criteria)) {
1✔
64
                        throw new IllegalArgumentException("Failed to evaluate criteria with reason: criteria is empty");
1✔
65
                }
66
                
67
                VelocityContext velocityContext = new VelocityContext();
1✔
68
                velocityContext.put("fn", this);
1✔
69
                velocityContext.put("obs", obs);
1✔
70
                
71
                velocityContext.put("patient", obs.getPerson());
1✔
72
                
73
                VelocityEngine velocityEngine = new VelocityEngine();
1✔
74
                try {
75
                        Properties props = new Properties();
1✔
76
                        props.put("runtime.log.logsystem.class", Log4JLogChute.class.getName());
1✔
77
                        props.put("runtime.log.logsystem.log4j.category", "velocity");
1✔
78
                        props.put("runtime.log.logsystem.log4j.logger", "velocity");
1✔
79
                        velocityEngine.init(props);
1✔
80
                }
81
                catch (Exception e) {
×
82
                        throw new APIException("Failed to create the velocity engine: " + e.getMessage(), e);
×
83
                }
1✔
84
                
85
                StringWriter writer = new StringWriter();
1✔
86
                String wrappedCriteria = "#set( $criteria = " + criteria + " )$criteria";
1✔
87
                
88
                try {
89
                        velocityEngine.evaluate(velocityContext, writer, ConceptReferenceRangeUtility.class.getName(), wrappedCriteria);
1✔
90
                        return Boolean.parseBoolean(writer.toString());
1✔
91
                }
92
                catch (ParseErrorException e) {
1✔
93
                        throw new APIException("An error occurred while evaluating criteria. Invalid criteria: " + criteria, e);
1✔
94
                }
95
                catch (Exception e) {
×
96
                        throw new APIException("An error occurred while evaluating criteria: ", e);
×
97
                }
98
        }
99
        
100
        /**
101
         * Gets the latest Obs by concept.
102
         *
103
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
104
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
105
         * @param person person to get obs for
106
         *                   
107
         * @return Obs latest Obs
108
         */
109
        public Obs getLatestObs(String conceptRef, Person person) {
110
                Concept concept = Context.getConceptService().getConceptByReference(conceptRef);
1✔
111

112
                if (concept != null) {
1✔
113
                        List<Obs> observations = Context.getObsService().getObservations(
1✔
114
                                Collections.singletonList(person), 
1✔
115
                                null, 
116
                                Collections.singletonList(concept), 
1✔
117
                                null, 
118
                                null, 
119
                                null,
120
                                Collections.singletonList("dateCreated"), 
1✔
121
                                1, 
1✔
122
                                null,
123
                                null, 
124
                                null, 
125
                                false
126
                        );
127

128
                        return observations.isEmpty() ? null : observations.get(0);
1✔
129
                }
130

131
                return null;
1✔
132
        }
133
        
134
        /**
135
         * Gets the time of the day in hours.
136
         *
137
         * @return the hour of the day in 24hr format (e.g. 14 to mean 2pm)
138
         */
139
        public int getCurrentHour() {
140
                return LocalTime.now().getHourOfDay();
1✔
141
        }
142
        
143
        /**
144
         * Retrieves the most relevant Obs for the given current Obs and conceptRef. If the current Obs contains a valid value 
145
         * (coded, numeric, date, text e.t.c) and the concept in Obs is the same as the supplied concept,
146
         * the method returns the current Obs. Otherwise, it fetches the latest Obs for the supplied concept and patient.
147
         *
148
         * @param currentObs the current Obs being evaluated
149
         * @return the most relevant Obs based on the current Obs, or the latest Obs if the current one has no valid value
150
         */
151
        public Obs getCurrentObs(String conceptRef, Obs currentObs) {
152
                Concept concept = Context.getConceptService().getConceptByReference(conceptRef);
1✔
153
                
154
                if (currentObs.getValueAsString(Locale.ENGLISH).isEmpty() && (concept != null && concept == currentObs.getConcept())) {
1✔
155
                        return currentObs;
×
156
                } else {
157
                        return getLatestObs(conceptRef, currentObs.getPerson());
1✔
158
                }
159
        }
160
        
161
        /**
162
         * Gets the person's latest observation date for a given concept
163
         * 
164
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
165
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
166
         * @param person the person
167
         * 
168
         * @return the observation date
169
         * 
170
         * @since 2.7.8
171
         */
172
        public Date getLatestObsDate(String conceptRef, Person person) {
173
                Obs obs = getLatestObs(conceptRef, person);
1✔
174
                if (obs == null) {
1✔
175
                        return null;
×
176
                }
177
                
178
                Date date = obs.getValueDate();
1✔
179
                if (date == null) {
1✔
180
                        date = obs.getValueDatetime();
1✔
181
                }
182
                
183
                return date;
1✔
184
        }
185
        
186
        /**
187
         * Checks if an observation's value coded answer is equal to a given concept
188
         * 
189
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
190
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's question
191
         *                   
192
         * @param person the person
193
         * 
194
         * @param answerConceptRef can be either concept uuid or conceptMap's code and sourceName 
195
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's coded answer
196
         *                   
197
         * @return true if the given concept is equal to the observation's value coded answer
198
         * 
199
         * @since 2.7.8
200
         */
201
        public boolean isObsValueCodedAnswer(String conceptRef, Person person, String answerConceptRef) {
202
                Obs obs = getLatestObs(conceptRef, person);
1✔
203
                if (obs == null) {
1✔
NEW
204
                        return false;
×
205
                }
206
                
207
                Concept valudeCoded = obs.getValueCoded();
1✔
208
                if (valudeCoded == null) {
1✔
NEW
209
                        return false;
×
210
                }
211
                
212
                Concept answerConcept = Context.getConceptService().getConceptByReference(answerConceptRef);
1✔
213
                if (answerConcept == null) {
1✔
NEW
214
                        return false;
×
215
                }
216
                
217
                return valudeCoded.equals(answerConcept);
1✔
218
        }
219
        
220
        /**
221
         * Gets the number of days from the person's latest observation date value for a given concept to the current date
222
         * 
223
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
224
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
225
         * @param person the person
226
         * 
227
         * @return the number of days
228
         * 
229
         * @since 2.7.8
230
         */
231
        public long getObsDays(String conceptRef, Person person) {
232
                Date date = getLatestObsDate(conceptRef, person);
1✔
233
                if (date == null) {
1✔
234
                        return NULL_DATE_RETURN_VALUE;
×
235
                }
236
                return this.getDays(date);
1✔
237
        }
238
        
239
        /**
240
         * Gets the number of weeks from the person's latest observation date value for a given concept to the current date
241
         * 
242
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
243
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
244
         * @param person the person
245
         * 
246
         * @return the number of weeks
247
         * 
248
         * @since 2.7.8
249
         */
250
        public long getObsWeeks(String conceptRef, Person person) {
251
                Date date = getLatestObsDate(conceptRef, person);
1✔
252
                if (date == null) {
1✔
253
                        return NULL_DATE_RETURN_VALUE;
1✔
254
                }
255
                return this.getWeeks(date);
1✔
256
        }
257
        
258
        /**
259
         * Gets the number of months from the person's latest observation date value for a given concept to the current date
260
         * 
261
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
262
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
263
         * @param person the person
264
         * 
265
         * @return the number of months
266
         * 
267
         * @since 2.7.8
268
         */
269
        public long getObsMonths(String conceptRef, Person person) {
270
                Date date = getLatestObsDate(conceptRef, person);
1✔
271
                if (date == null) {
1✔
272
                        return NULL_DATE_RETURN_VALUE;
×
273
                }
274
                return this.getMonths(date);
1✔
275
        }
276
        
277
        /**
278
         * Gets the number of years from the person's latest observation date value for a given concept to the current date
279
         * 
280
         * @param conceptRef can be either concept uuid or conceptMap's code and sourceName 
281
         *                   e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434"
282
         * @param person the person
283
         * 
284
         * @return the number of years
285
         * 
286
         * @since 2.7.8
287
         */
288
        public long getObsYears(String conceptRef, Person person) {
289
                Date date = getLatestObsDate(conceptRef, person);
1✔
290
                if (date == null) {
1✔
291
                        return NULL_DATE_RETURN_VALUE;
×
292
                }
293
                return this.getYears(date);
1✔
294
        }
295
        
296
        /**
297
         * Gets the number of days between two given dates
298
         * 
299
         * @param fromDate the date from which to start counting
300
         * @param dateDate the date up to which to stop counting
301
         * 
302
         * @return the number of days between
303
         * 
304
         * @since 2.7.8
305
         */
306
        public long getDaysBetween(Date fromDate, Date toDate) {
307
                if (fromDate == null || toDate == null) {
1✔
308
                        return NULL_DATE_RETURN_VALUE;
×
309
                }
310
                return ChronoUnit.DAYS.between(toLocalDate(fromDate), toLocalDate(toDate));
1✔
311
        }
312
        
313
        /**
314
         * Gets the number of weeks between two given dates
315
         * 
316
         * @param fromDate the date from which to start counting
317
         * @param dateDate the date up to which to stop counting
318
         * 
319
         * @return the number of weeks between
320
         * 
321
         * @since 2.7.8
322
         */
323
        public long getWeeksBetween(Date fromDate, Date toDate) {
324
                if (fromDate == null || toDate == null) {
1✔
325
                        return NULL_DATE_RETURN_VALUE;
×
326
                }
327
                return ChronoUnit.WEEKS.between(toLocalDate(fromDate), toLocalDate(toDate));
1✔
328
        }
329
        
330
        /**
331
         * Gets the number of months between two given dates
332
         * 
333
         * @param fromDate the date from which to start counting
334
         * @param dateDate the date up to which to stop counting
335
         * 
336
         * @return the number of months between
337
         * 
338
         * @since 2.7.8
339
         */
340
        public long getMonthsBetween(Date fromDate, Date toDate) {
341
                if (fromDate == null || toDate == null) {
1✔
342
                        return NULL_DATE_RETURN_VALUE;
×
343
                }
344
                return ChronoUnit.MONTHS.between(toLocalDate(fromDate), toLocalDate(toDate));
1✔
345
        }
346
        
347
        /**
348
         * Gets the number of years between two given dates
349
         * 
350
         * @param fromDate the date from which to start counting
351
         * @param dateDate the date up to which to stop counting
352
         * 
353
         * @return the number of years between
354
         * 
355
         * @since 2.7.8
356
         */
357
        public long getYearsBetween(Date fromDate, Date toDate) {
358
                if (fromDate == null || toDate == null) {
1✔
359
                        return NULL_DATE_RETURN_VALUE;
×
360
                }
361
                return ChronoUnit.YEARS.between(toLocalDate(fromDate), toLocalDate(toDate));
1✔
362
        }
363
        
364
        /**
365
         * Gets the number of days from a given date up to the current date.
366
         * 
367
         * @param fromDate the date from which to start counting
368
         * @return the number of days
369
         * 
370
         * @since 2.7.8
371
         */
372
        public long getDays(Date fromDate) {
373
                return getDaysBetween(fromDate, new Date());
1✔
374
        }
375
        
376
        /**
377
         * Gets the number of weeks from a given date up to the current date.
378
         * 
379
         * @param fromDate the date from which to start counting
380
         * @return the number of weeks
381
         * 
382
         * @since 2.7.8
383
         */
384
        public long getWeeks(Date fromDate) {
385
                return getWeeksBetween(fromDate, new Date());
1✔
386
        }
387
        
388
        /**
389
         * Gets the number of months from a given date up to the current date.
390
         * 
391
         * @param fromDate the date from which to start counting
392
         * @return the number of months
393
         * 
394
         * @since 2.7.8
395
         */
396
        public long getMonths(Date fromDate) {
397
                return getMonthsBetween(fromDate, new Date());
1✔
398
        }
399
        
400
        /**
401
         * Gets the number of years from a given date up to the current date.
402
         * 
403
         * @param fromDate the date from which to start counting
404
         * @return the number of years
405
         * 
406
         * @since 2.7.8
407
         */
408
        public long getYears(Date fromDate) {
409
                return getYearsBetween(fromDate, new Date());
1✔
410
        }
411
        
412
        /**
413
         * Converts a java.util.Date to java.time.LocalDate
414
         * 
415
         * @param date the java.util.Date
416
         * @return the java.time.LocalDate
417
         */
418
        private LocalDate toLocalDate(Date date) {
419
                return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
1✔
420
        }
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc