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

openmrs / openmrs-core / 17128381054

21 Aug 2025 01:28PM UTC coverage: 64.842% (-0.03%) from 64.868%
17128381054

push

github

dkayiwa
Do not just swallow attribute validation errors

(cherry picked from commit 23f1d4a87)

2 of 2 new or added lines in 1 file covered. (100.0%)

684 existing lines in 16 files now uncovered.

23362 of 36029 relevant lines covered (64.84%)

0.65 hits per line

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

93.75
/api/src/main/java/org/openmrs/validator/ObsValidator.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.validator;
11

12
import java.util.ArrayList;
13
import java.util.Collections;
14
import java.util.Comparator;
15
import java.util.List;
16
import java.util.Objects;
17

18
import org.apache.commons.text.StringEscapeUtils;
19
import org.openmrs.Concept;
20
import org.openmrs.ConceptDatatype;
21
import org.openmrs.ConceptNumeric;
22
import org.openmrs.ConceptReferenceRange;
23
import org.openmrs.Obs;
24
import org.openmrs.ObsReferenceRange;
25
import org.openmrs.annotation.Handler;
26
import org.openmrs.api.APIException;
27
import org.openmrs.api.context.Context;
28
import org.openmrs.api.db.hibernate.HibernateUtil;
29
import org.openmrs.util.ConceptReferenceRangeUtility;
30
import org.openmrs.util.OpenmrsUtil;
31
import org.springframework.validation.Errors;
32
import org.springframework.validation.Validator;
33

34
/**
35
 * Validator for the Obs class. This class checks for anything set on the Obs object that will cause
36
 * errors or is incorrect. Things checked are similar to:
37
 * <ul>
38
 * <li>all required properties are filled in on the Obs object.
39
 * <li>checks for no recursion in the obs grouping.
40
 * <li>Makes sure the obs has at least one value (if not an obs grouping)</li>
41
 * </ul>
42
 * 
43
 * @see org.openmrs.Obs
44
 */
45
@Handler(supports = { Obs.class }, order = 50)
46
public class ObsValidator implements Validator {
1✔
47
        
48
        public static final int VALUE_TEXT_MAX_LENGTH = 65535;
49
        
50
        /**
51
         * @see org.springframework.validation.Validator#supports(java.lang.Class)
52
         * <strong>Should</strong> support Obs class
53
         */
54
        @Override
55
        public boolean supports(Class<?> c) {
56
                return Obs.class.isAssignableFrom(c);
1✔
57
        }
58
        
59
        /**
60
         * @see org.springframework.validation.Validator#validate(java.lang.Object,
61
         *      org.springframework.validation.Errors)
62
         * <strong>Should</strong> fail validation if personId is null
63
         * <strong>Should</strong> fail validation if obsDatetime is null
64
         * <strong>Should</strong> fail validation if concept is null
65
         * <strong>Should</strong> fail validation if concept datatype is boolean and valueBoolean is null
66
         * <strong>Should</strong> fail validation if concept datatype is coded and valueCoded is null
67
         * <strong>Should</strong> fail validation if concept datatype is date and valueDatetime is null
68
         * <strong>Should</strong> fail validation if concept datatype is numeric and valueNumeric is null
69
         * <strong>Should</strong> fail validation if concept datatype is text and valueText is null
70
         * <strong>Should</strong> fail validation if obs ancestors contains obs
71
         * <strong>Should</strong> pass validation if all values present
72
         * <strong>Should</strong> fail validation if the parent obs has values
73
         * <strong>Should</strong> reject an invalid concept and drug combination
74
         * <strong>Should</strong> pass if answer concept and concept of value drug match
75
         * <strong>Should</strong> pass validation if field lengths are correct
76
         * <strong>Should</strong> fail validation if field lengths are not correct
77
         * <strong>Should</strong> not validate if obs is voided
78
         * <strong>Should</strong> not validate a voided child obs
79
         * <strong>Should</strong> fail for a null object
80
         */
81
        @Override
82
        public void validate(Object obj, Errors errors) {
83
                Obs obs = (Obs) obj;
1✔
84
                if (obs == null) {
1✔
85
                        throw new APIException("Obs can't be null");
1✔
86
                } else if (obs.getVoided()) {
1✔
87
                        return;
1✔
88
                }
89
                List<Obs> ancestors = new ArrayList<>();
1✔
90
                validateHelper(obs, errors, ancestors, true);
1✔
91
                ValidateUtil.validateFieldLengths(errors, obj.getClass(), "accessionNumber", "valueModifier", "valueComplex",
1✔
92
                    "comment", "voidReason");
93
        }
1✔
94
        
95
        /**
96
         * Checks whether obs has all required values, and also checks to make sure that no obs group
97
         * contains any of its ancestors
98
         *
99
         * @param obs
100
         * @param errors
101
         * @param ancestors
102
         * @param atRootNode whether or not this is the obs that validate() was originally called on. If
103
         *            not then we shouldn't reject fields by name.
104
         */
105
        private void validateHelper(Obs obs, Errors errors, List<Obs> ancestors, boolean atRootNode) {
106
                if (obs.getPersonId() == null) {
1✔
107
                        errors.rejectValue("person", "error.null");
1✔
108
                }
109
                if (obs.getObsDatetime() == null) {
1✔
110
                        errors.rejectValue("obsDatetime", "error.null");
1✔
111
                }
112
                
113
                boolean isObsGroup = obs.hasGroupMembers(true);
1✔
114
                // if this is an obs group (i.e., parent) make sure that it has no values (other than valueGroupId) set
115
                if (isObsGroup) {
1✔
116
                        if (obs.getValueCoded() != null) {
1✔
117
                                errors.rejectValue("valueCoded", "error.not.null");
1✔
118
                        }
119
                        
120
                        if (obs.getValueDrug() != null) {
1✔
121
                                errors.rejectValue("valueDrug", "error.not.null");
1✔
122
                        }
123
                        
124
                        if (obs.getValueDatetime() != null) {
1✔
125
                                errors.rejectValue("valueDatetime", "error.not.null");
1✔
126
                        }
127
                        
128
                        if (obs.getValueNumeric() != null) {
1✔
129
                                errors.rejectValue("valueNumeric", "error.not.null");
1✔
130
                        }
131
                        
132
                        if (obs.getValueModifier() != null) {
1✔
133
                                errors.rejectValue("valueModifier", "error.not.null");
1✔
134
                        }
135
                        
136
                        if (obs.getValueText() != null) {
1✔
137
                                errors.rejectValue("valueText", "error.not.null");
1✔
138
                        }
139
                        
140
                        if (obs.getValueBoolean() != null) {
1✔
141
                                errors.rejectValue("valueBoolean", "error.not.null");
1✔
142
                        }
143
                        
144
                        if (obs.getValueComplex() != null) {
1✔
145
                                errors.rejectValue("valueComplex", "error.not.null");
1✔
146
                        }
147
                        
148
                }
149
                // if this is NOT an obs group, make sure that it has at least one value set (not counting obsGroupId)
150
                else if (obs.getValueBoolean() == null && obs.getValueCoded() == null && obs.getValueCodedName() == null
1✔
151
                        && obs.getValueComplex() == null && obs.getValueDatetime() == null && obs.getValueDrug() == null
1✔
152
                        && obs.getValueModifier() == null && obs.getValueNumeric() == null && obs.getValueText() == null
1✔
153
                        && obs.getComplexData() == null) {
1✔
154
                        errors.reject("error.noValue");
1✔
155
                }
156
                
157
                // make sure there is a concept associated with the obs
158
                Concept c = obs.getConcept();
1✔
159
                if (c == null) {
1✔
160
                        errors.rejectValue("concept", "error.null");
1✔
161
                }
162
                // if there is a concept, and this isn't a group, perform validation tests specific to the concept datatype
163
                else if (!isObsGroup) {
1✔
164
                        ConceptDatatype dt = c.getDatatype();
1✔
165
                        if (dt != null) {
1✔
166
                                if (dt.isBoolean() && obs.getValueBoolean() == null) {
1✔
167
                                        if (atRootNode) {
1✔
168
                                                errors.rejectValue("valueBoolean", "error.null");
1✔
169
                                        } else {
UNCOV
170
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
171
                                        }
172
                                } else if (dt.isCoded() && obs.getValueCoded() == null) {
1✔
173
                                        if (atRootNode) {
1✔
174
                                                errors.rejectValue("valueCoded", "error.null");
1✔
175
                                        } else {
UNCOV
176
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
177
                                        }
178
                                } else if ((dt.isDateTime() || dt.isDate() || dt.isTime()) && obs.getValueDatetime() == null) {
1✔
179
                                        if (atRootNode) {
1✔
180
                                                errors.rejectValue("valueDatetime", "error.null");
1✔
181
                                        } else {
UNCOV
182
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
183
                                        }
184
                                } else if (dt.isNumeric() && obs.getValueNumeric() == null) {
1✔
185
                                        if (atRootNode) {
1✔
186
                                                errors.rejectValue("valueNumeric", "error.null");
1✔
187
                                        } else {
UNCOV
188
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
189
                                        }
190
                                } else if (dt.isNumeric()) {
1✔
191
                                        ConceptNumeric cn = Context.getConceptService().getConceptNumeric(c.getConceptId());
1✔
192
                                        // If the concept numeric is not precise, the value cannot be a float, so raise an error 
193
                                        if (!cn.getAllowDecimal() && Math.ceil(obs.getValueNumeric()) != obs.getValueNumeric()) {
1✔
UNCOV
194
                                                if (atRootNode) {
×
195
                                                        errors.rejectValue("valueNumeric", "Obs.error.precision");
×
196
                                                } else {
UNCOV
197
                                                        errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
198
                                                }
199
                                        }
200
                                        
201
                                        validateConceptReferenceRange(obs, errors, atRootNode);
1✔
202
                                } else if (dt.isText() && obs.getValueText() == null) {
1✔
203
                                        if (atRootNode) {
1✔
204
                                                errors.rejectValue("valueText", "error.null");
1✔
205
                                        } else {
UNCOV
206
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
207
                                        }
208
                                }
209
                                
210
                                //If valueText is longer than the maxlength, raise an error as well.
211
                                if (dt.isText() && obs.getValueText() != null && obs.getValueText().length() > VALUE_TEXT_MAX_LENGTH) {
1✔
212
                                        if (atRootNode) {
1✔
213
                                                errors.rejectValue("valueText", "error.exceededMaxLengthOfField");
1✔
214
                                        } else {
UNCOV
215
                                                errors.rejectValue("groupMembers", "Obs.error.inGroupMember");
×
216
                                        }
217
                                }
218
                        } else { // dt is null
UNCOV
219
                                errors.rejectValue("concept", "must have a datatype");
×
220
                        }
221
                }
222
                
223
                // If an obs fails validation, don't bother checking its children
224
                if (errors.hasErrors()) {
1✔
225
                        return;
1✔
226
                }
227
                
228
                if (ancestors.contains(obs)) {
1✔
229
                        errors.rejectValue("groupMembers", "Obs.error.groupContainsItself");
1✔
230
                }
231
                
232
                if (obs.isObsGrouping()) {
1✔
233
                        ancestors.add(obs);
1✔
234
                        for (Obs child : obs.getGroupMembers()) {
1✔
235
                                validateHelper(child, errors, ancestors, false);
1✔
236
                        }
1✔
237
                        ancestors.remove(ancestors.size() - 1);
1✔
238
                }
239
                
240
                if (obs.getValueCoded() != null && obs.getValueDrug() != null && obs.getValueDrug().getConcept() != null) {
1✔
241
                        Concept trueConcept = Context.getConceptService().getTrueConcept();
1✔
242
                        Concept falseConcept = Context.getConceptService().getFalseConcept();
1✔
243
                        //Ignore if this is not a true or false response since they are stored as coded too
244
                        if (!obs.getValueCoded().equals(trueConcept) && !obs.getValueCoded().equals(falseConcept)
1✔
245
                                && !obs.getValueDrug().getConcept().equals(obs.getValueCoded())) {
1✔
246
                                errors.rejectValue("valueDrug", "Obs.error.invalidDrug");
1✔
247
                        }
248
                }
249
        }
1✔
250

251
        /**
252
         * This method validates Obs' numeric values:
253
         * <ol>
254
         *     <li>Validates Obs in relation to criteria e.g. checks patient's age is within the valid range</li>
255
         *     <li>Validates if Obs' numeric value is within the valid range; i.e. >= low absolute && <= high absolute.</li>
256
         *     <li>Sets field errors if numeric value is outside the valid range</li>
257
         * <ol/>
258
         *
259
         * @param obs Observation to validate
260
         * @param errors Errors to record validation issues
261
         */
262
        private void validateConceptReferenceRange(Obs obs, Errors errors, boolean atRootNode) {
263
                ConceptReferenceRange conceptReferenceRange = getReferenceRange(obs);
1✔
264

265
                if (conceptReferenceRange != null) {
1✔
266
                        validateAbsoluteRanges(obs, conceptReferenceRange, errors, atRootNode);
1✔
267
                        
268
                        if (obs.getId() == null) {
1✔
269
                                setObsReferenceRange(obs, conceptReferenceRange);
1✔
270
                        }
271
                } else if (obs.getId() == null) {
1✔
272
                        setObsReferenceRange(obs);
1✔
273
                }
274
                setObsInterpretation(obs);
1✔
275
        }
1✔
276

277
        /**
278
         * Evaluates the criteria and return the most strict {@link ConceptReferenceRange} for a given concept
279
         * and patient contained in an observation.
280
         * It considers all valid ranges that match the criteria for the person.
281
         *
282
         * @param obs containing The concept and patient for whom the range is being evaluated
283
         * @return The strictest {@link ConceptReferenceRange}, or null if no valid range is found
284
         * 
285
         * @since 2.7.0
286
         */
287
        public ConceptReferenceRange getReferenceRange(Obs obs) {
288
                Concept concept = HibernateUtil.getRealObjectFromProxy(obs.getConcept());
1✔
289
                if (concept == null || concept.getDatatype() == null || !concept.getDatatype().isNumeric()) {
1✔
290
                        return null;
1✔
291
                }
292
                
293
                ConceptNumeric conceptNumeric = (ConceptNumeric) concept;
1✔
294

295
                List<ConceptReferenceRange> referenceRanges = Context.getConceptService()
1✔
296
                        .getConceptReferenceRangesByConceptId(concept.getConceptId());
1✔
297

298
                if (referenceRanges.isEmpty()) {
1✔
299
                        return getDefaultReferenceRange(conceptNumeric);
1✔
300
                }
301

302
                ConceptReferenceRangeUtility referenceRangeUtility = new ConceptReferenceRangeUtility();
1✔
303
                List<ConceptReferenceRange> validRanges = new ArrayList<>();
1✔
304

305
                for (ConceptReferenceRange referenceRange : referenceRanges) {
1✔
306
                        if (referenceRangeUtility.evaluateCriteria(StringEscapeUtils.unescapeHtml4(referenceRange.getCriteria()), obs)) {
1✔
307
                                validRanges.add(referenceRange);
1✔
308
                        }
309
                }
1✔
310

311
                if (validRanges.isEmpty()) {
1✔
312
                        ConceptReferenceRange defaultReferenceRange = getDefaultReferenceRange(conceptNumeric);
1✔
313
                        if (defaultReferenceRange != null) {
1✔
314
                                validRanges.add(defaultReferenceRange);
1✔
315
                        } else {
UNCOV
316
                                return null;
×
317
                        }
318
                }
319
                
320
                return findStrictestReferenceRange(validRanges);
1✔
321
        }
322

323
        /**
324
         * Loads the reference range from the ConceptNumeric if no reference ranges are associated with
325
         * this concept and person.
326
         * 
327
         * @param conceptNumeric A {@link ConceptNumeric} to extract the default values from
328
         * @return a {@link ConceptReferenceRange} containing the reference range from the concept
329
         */
330
        private static ConceptReferenceRange getDefaultReferenceRange(ConceptNumeric conceptNumeric) {
331
                if (conceptNumeric == null || (
1✔
332
                        conceptNumeric.getHiAbsolute() == null &&
1✔
333
                        conceptNumeric.getHiCritical() == null &&
1✔
334
                        conceptNumeric.getHiNormal() == null &&
1✔
335
                        conceptNumeric.getLowAbsolute() == null &&
1✔
336
                        conceptNumeric.getLowCritical() == null &&
1✔
337
                        conceptNumeric.getLowNormal() == null
1✔
338
                )) {
339
                        return null;
1✔
340
                }
341
                
342
                ConceptReferenceRange defaultReferenceRange = new ConceptReferenceRange();
1✔
343
                defaultReferenceRange.setConceptNumeric(conceptNumeric);
1✔
344
                defaultReferenceRange.setHiAbsolute(conceptNumeric.getHiAbsolute());
1✔
345
                defaultReferenceRange.setHiCritical(conceptNumeric.getHiCritical());
1✔
346
                defaultReferenceRange.setHiNormal(conceptNumeric.getHiNormal());
1✔
347
                defaultReferenceRange.setLowAbsolute(conceptNumeric.getLowAbsolute());
1✔
348
                defaultReferenceRange.setLowCritical(conceptNumeric.getLowCritical());
1✔
349
                defaultReferenceRange.setLowNormal(conceptNumeric.getLowNormal());
1✔
350
                return defaultReferenceRange;
1✔
351
        }
352

353
        /**
354
         * Finds the strictest {@link ConceptReferenceRange} from a list of valid ranges.
355
         * The strictest range is determined separately for each value, e.g., the lowAbsolute will
356
         * be the highest lowAbsolute of any matching range, the lowCritical value will be the
357
         * highest lowCritical value of any matching range.
358
         * e.g.
359
         * If ConceptReferenceRange-1 has a range of 80-150.
360
         * and ConceptReferenceRange-2 has a range of 60-140,
361
         * the "strictest" range will be 80-140. 
362
         *
363
         * @param conceptReferenceRanges A list of valid {@link ConceptReferenceRange} objects
364
         * @return The strictest {@link ConceptReferenceRange} constructed from the strictest bounds
365
         */
366
        private ConceptReferenceRange findStrictestReferenceRange(List<ConceptReferenceRange> conceptReferenceRanges) {
367
                if (conceptReferenceRanges.size() == 1) {
1✔
368
                        return conceptReferenceRanges.get(0);
1✔
369
                }
370

371
                ConceptReferenceRange strictestRange = new ConceptReferenceRange();
1✔
372
                strictestRange.setConceptNumeric(conceptReferenceRanges.get(0).getConceptNumeric());
1✔
373

374
                for (ConceptReferenceRange conceptReferenceRange : conceptReferenceRanges) {
1✔
375
                        if (conceptReferenceRange.getLowAbsolute() != null && 
1✔
376
                                        (strictestRange.getLowAbsolute() == null || strictestRange.getLowAbsolute() < conceptReferenceRange.getLowAbsolute())) {
1✔
377
                                strictestRange.setLowAbsolute(conceptReferenceRange.getLowAbsolute());
1✔
378
                        }
379
                        
380
                        if (conceptReferenceRange.getLowCritical() != null && 
1✔
381
                                        (strictestRange.getLowCritical() == null || strictestRange.getLowCritical() < conceptReferenceRange.getLowCritical())) {
1✔
382
                                strictestRange.setLowCritical(conceptReferenceRange.getLowCritical());
1✔
383
                        }
384
                        
385
                        if (conceptReferenceRange.getLowNormal() != null &&
1✔
386
                                        (strictestRange.getLowNormal() == null || strictestRange.getLowNormal() < conceptReferenceRange.getLowNormal())) {
1✔
387
                                strictestRange.setLowNormal(conceptReferenceRange.getLowNormal());
1✔
388
                        }
389
                        
390
                        if (conceptReferenceRange.getHiNormal() != null &&
1✔
391
                                        (strictestRange.getHiNormal() == null || strictestRange.getHiNormal() > conceptReferenceRange.getHiNormal())) {
1✔
392
                                strictestRange.setHiNormal(conceptReferenceRange.getHiNormal());
1✔
393
                        }
394
                        
395
                        if (conceptReferenceRange.getHiCritical() != null &&
1✔
396
                                        (strictestRange.getHiCritical() == null || strictestRange.getHiCritical() > conceptReferenceRange.getHiCritical())) {
1✔
397
                                strictestRange.setHiCritical(conceptReferenceRange.getHiCritical());
1✔
398
                        }
399
                        
400
                        if (conceptReferenceRange.getHiAbsolute() != null &&
1✔
401
                                        (strictestRange.getHiAbsolute() == null || strictestRange.getHiAbsolute() > conceptReferenceRange.getHiAbsolute())) {
1✔
402
                                strictestRange.setHiAbsolute(conceptReferenceRange.getHiAbsolute());
1✔
403
                        }
404
                }
1✔
405
                
406
                return strictestRange;
1✔
407
        }
408

409
        /**
410
         * Validates the high and low absolute values of the Obs.
411
         *
412
         * @param obs Observation to validate
413
         * @param conceptReferenceRange ConceptReferenceRange containing the range values
414
         * @param errors Errors to record validation issues
415
         */
416
        private void validateAbsoluteRanges(Obs obs, ConceptReferenceRange conceptReferenceRange, Errors errors, boolean atRootNode) {
417
                if (conceptReferenceRange.getHiAbsolute() != null && conceptReferenceRange.getHiAbsolute() < obs.getValueNumeric()) {
1✔
418
                        if (atRootNode) {
1✔
419
                                errors.rejectValue(
1✔
420
                                        "valueNumeric",
421
                                        "error.value.outOfRange.high",
422
                                        new Object[] { conceptReferenceRange.getHiAbsolute() },
1✔
423
                                        null
424
                                );
425
                        } else {
UNCOV
426
                                errors.rejectValue(
×
427
                                        "groupMember",
428
                                        "Obs.error.inGroupMember",
429
                                        new Object[] {},
430
                                        null
431
                                );
432
                        }
433
                }
434
                
435
                if (conceptReferenceRange.getLowAbsolute() != null && conceptReferenceRange.getLowAbsolute() > obs.getValueNumeric()) {
1✔
436
                        if (atRootNode) {
1✔
437
                                errors.rejectValue(
1✔
438
                                        "valueNumeric",
439
                                        "error.value.outOfRange.low",
440
                                        new Object[] { conceptReferenceRange.getLowAbsolute() },
1✔
441
                                        null
442
                                );
443
                        } else {
UNCOV
444
                                errors.rejectValue(
×
445
                                        "groupMember",
446
                                        "Obs.error.inGroupMember",
447
                                        new Object[] { },
448
                                        null
449
                                );
450
                        }
451
                }
452
        }
1✔
453

454
        /**
455
         * Builds and sets the ObsReferenceRange for the given Obs.
456
         *
457
         * @param obs Observation to set the reference range
458
         * @param conceptReferenceRange ConceptReferenceRange used to build the ObsReferenceRange
459
         */
460
        private void setObsReferenceRange(Obs obs, ConceptReferenceRange conceptReferenceRange) {
461
                ObsReferenceRange obsRefRange = new ObsReferenceRange();
1✔
462

463
                obsRefRange.setHiAbsolute(conceptReferenceRange.getHiAbsolute());
1✔
464
                obsRefRange.setHiCritical(conceptReferenceRange.getHiCritical());
1✔
465
                obsRefRange.setHiNormal(conceptReferenceRange.getHiNormal());
1✔
466
                obsRefRange.setLowAbsolute(conceptReferenceRange.getLowAbsolute());
1✔
467
                obsRefRange.setLowCritical(conceptReferenceRange.getLowCritical());
1✔
468
                obsRefRange.setLowNormal(conceptReferenceRange.getLowNormal());
1✔
469
                obsRefRange.setObs(obs);
1✔
470

471
                obs.setReferenceRange(obsRefRange);
1✔
472
        }
1✔
473

474
        /**
475
         * Builds and sets the ObsReferenceRange from concept numeric values.
476
         *
477
         * @param obs Observation to set the reference range
478
         */
479
        private void setObsReferenceRange(Obs obs) {
480
                if (obs.getConcept() == null) {
1✔
UNCOV
481
                        return;
×
482
                }
483
                
484
                ConceptNumeric conceptNumeric = Context.getConceptService().getConceptNumeric(obs.getConcept().getId());
1✔
485

486
                if (conceptNumeric != null) {
1✔
487
                        ObsReferenceRange obsRefRange = new ObsReferenceRange();
1✔
488

489
                        obsRefRange.setHiAbsolute(conceptNumeric.getHiAbsolute());
1✔
490
                        obsRefRange.setHiCritical(conceptNumeric.getHiCritical());
1✔
491
                        obsRefRange.setHiNormal(conceptNumeric.getHiNormal());
1✔
492
                        obsRefRange.setLowAbsolute(conceptNumeric.getLowAbsolute());
1✔
493
                        obsRefRange.setLowCritical(conceptNumeric.getLowCritical());
1✔
494
                        obsRefRange.setLowNormal(conceptNumeric.getLowNormal());
1✔
495
                        obsRefRange.setObs(obs);
1✔
496
                        
497
                        obs.setReferenceRange(obsRefRange);
1✔
498
                }
499
        }
1✔
500

501
        /**
502
         * This method sets Obs interpretation based on the current obs' numeric value.
503
         *
504
         * @param obs Observation to set the interpretation
505
         */
506
        private void setObsInterpretation(Obs obs) {
507
                ObsReferenceRange referenceRange = obs.getReferenceRange();
1✔
508
                if (referenceRange == null || obs.getValueNumeric() == null) {
1✔
509
                        return;
1✔
510
                }
511
                
512
                if (referenceRange.getHiNormal() != null 
1✔
513
                        && referenceRange.getHiCritical() != null
1✔
514
                        && obs.getValueNumeric() > referenceRange.getHiNormal()
1✔
515
                        && obs.getValueNumeric() < referenceRange.getHiCritical()) {
1✔
516
                        obs.setInterpretation(Obs.Interpretation.HIGH);
1✔
517
                } else if (referenceRange.getHiCritical() != null 
1✔
518
                        && obs.getValueNumeric() >= referenceRange.getHiCritical()) {
1✔
519
                        obs.setInterpretation(Obs.Interpretation.CRITICALLY_HIGH);
1✔
520
                } else if (referenceRange.getLowNormal() != null 
1✔
521
                        && referenceRange.getLowCritical() != null
1✔
522
                        && obs.getValueNumeric() < referenceRange.getLowNormal() 
1✔
523
                        && obs.getValueNumeric() > referenceRange.getLowCritical()) {
1✔
524
                        obs.setInterpretation(Obs.Interpretation.LOW);
1✔
525
                } else if (referenceRange.getLowNormal() != null 
1✔
526
                        && referenceRange.getHiNormal() != null
1✔
527
                        && obs.getValueNumeric() >= referenceRange.getLowNormal() 
1✔
528
                        && obs.getValueNumeric() <= referenceRange.getHiNormal()) {
1✔
529
                        obs.setInterpretation(Obs.Interpretation.NORMAL);
1✔
530
                } else if (referenceRange.getLowCritical() != null 
1✔
531
                        && obs.getValueNumeric() <= referenceRange.getLowCritical()) {
1✔
532
                        obs.setInterpretation(Obs.Interpretation.CRITICALLY_LOW);
1✔
533
                }
534
        }
1✔
535
        
536
}
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