diff --git a/api/src/main/java/org/openmrs/Obs.java b/api/src/main/java/org/openmrs/Obs.java index 490c5e791089..962df310f7a7 100644 --- a/api/src/main/java/org/openmrs/Obs.java +++ b/api/src/main/java/org/openmrs/Obs.java @@ -1279,5 +1279,8 @@ public ObsReferenceRange getReferenceRange() { */ public void setReferenceRange(ObsReferenceRange referenceRange) { this.referenceRange = referenceRange; + if (referenceRange != null) { + referenceRange.setObs(this); + } } } diff --git a/api/src/main/java/org/openmrs/util/ConceptReferenceRangeUtility.java b/api/src/main/java/org/openmrs/util/ConceptReferenceRangeUtility.java index 39d08f70dc51..1d60666ce4b1 100644 --- a/api/src/main/java/org/openmrs/util/ConceptReferenceRangeUtility.java +++ b/api/src/main/java/org/openmrs/util/ConceptReferenceRangeUtility.java @@ -162,12 +162,10 @@ public Obs getCurrentObs(String conceptRef, Obs currentObs) { /** * Gets the person's latest observation date for a given concept * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" * @param person the person - * * @return the observation date - * * @since 2.7.8 */ public Date getLatestObsDate(String conceptRef, Person person) { @@ -187,16 +185,14 @@ public Date getLatestObsDate(String conceptRef, Person person) { /** * Checks if an observation's value coded answer is equal to a given concept * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's question - * + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's + * question * @param person the person - * - * @param answerConceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's coded answer - * + * @param answerConceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" for the observation's coded + * answer * @return true if the given concept is equal to the observation's value coded answer - * * @since 2.7.8 */ public boolean isObsValueCodedAnswer(String conceptRef, Person person, String answerConceptRef) { @@ -219,14 +215,13 @@ public boolean isObsValueCodedAnswer(String conceptRef, Person person, String an } /** - * Gets the number of days from the person's latest observation date value for a given concept to the current date + * Gets the number of days from the person's latest observation date value for a given concept + * to the current date * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" * @param person the person - * * @return the number of days - * * @since 2.7.8 */ public long getObsDays(String conceptRef, Person person) { @@ -238,14 +233,13 @@ public long getObsDays(String conceptRef, Person person) { } /** - * Gets the number of weeks from the person's latest observation date value for a given concept to the current date + * Gets the number of weeks from the person's latest observation date value for a given concept + * to the current date * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" * @param person the person - * * @return the number of weeks - * * @since 2.7.8 */ public long getObsWeeks(String conceptRef, Person person) { @@ -257,14 +251,13 @@ public long getObsWeeks(String conceptRef, Person person) { } /** - * Gets the number of months from the person's latest observation date value for a given concept to the current date + * Gets the number of months from the person's latest observation date value for a given concept + * to the current date * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" * @param person the person - * * @return the number of months - * * @since 2.7.8 */ public long getObsMonths(String conceptRef, Person person) { @@ -276,14 +269,13 @@ public long getObsMonths(String conceptRef, Person person) { } /** - * Gets the number of years from the person's latest observation date value for a given concept to the current date + * Gets the number of years from the person's latest observation date value for a given concept + * to the current date * - * @param conceptRef can be either concept uuid or conceptMap's code and sourceName - * e.g "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" + * @param conceptRef can be either concept uuid or conceptMap's code and sourceName e.g + * "bac25fd5-c143-4e43-bffe-4eb1e7efb6ce" or "CIEL:1434" * @param person the person - * * @return the number of years - * * @since 2.7.8 */ public long getObsYears(String conceptRef, Person person) { @@ -299,9 +291,7 @@ public long getObsYears(String conceptRef, Person person) { * * @param fromDate the date from which to start counting * @param dateDate the date up to which to stop counting - * * @return the number of days between - * * @since 2.7.8 */ public long getDaysBetween(Date fromDate, Date toDate) { @@ -316,9 +306,7 @@ public long getDaysBetween(Date fromDate, Date toDate) { * * @param fromDate the date from which to start counting * @param dateDate the date up to which to stop counting - * * @return the number of weeks between - * * @since 2.7.8 */ public long getWeeksBetween(Date fromDate, Date toDate) { @@ -333,9 +321,7 @@ public long getWeeksBetween(Date fromDate, Date toDate) { * * @param fromDate the date from which to start counting * @param dateDate the date up to which to stop counting - * * @return the number of months between - * * @since 2.7.8 */ public long getMonthsBetween(Date fromDate, Date toDate) { @@ -350,9 +336,7 @@ public long getMonthsBetween(Date fromDate, Date toDate) { * * @param fromDate the date from which to start counting * @param dateDate the date up to which to stop counting - * * @return the number of years between - * * @since 2.7.8 */ public long getYearsBetween(Date fromDate, Date toDate) { @@ -367,7 +351,6 @@ public long getYearsBetween(Date fromDate, Date toDate) { * * @param fromDate the date from which to start counting * @return the number of days - * * @since 2.7.8 */ public long getDays(Date fromDate) { @@ -379,7 +362,6 @@ public long getDays(Date fromDate) { * * @param fromDate the date from which to start counting * @return the number of weeks - * * @since 2.7.8 */ public long getWeeks(Date fromDate) { @@ -391,7 +373,6 @@ public long getWeeks(Date fromDate) { * * @param fromDate the date from which to start counting * @return the number of months - * * @since 2.7.8 */ public long getMonths(Date fromDate) { @@ -403,7 +384,6 @@ public long getMonths(Date fromDate) { * * @param fromDate the date from which to start counting * @return the number of years - * * @since 2.7.8 */ public long getYears(Date fromDate) { diff --git a/api/src/main/java/org/openmrs/validator/ObsValidator.java b/api/src/main/java/org/openmrs/validator/ObsValidator.java index 24d8cb4949dc..29feb30e133e 100644 --- a/api/src/main/java/org/openmrs/validator/ObsValidator.java +++ b/api/src/main/java/org/openmrs/validator/ObsValidator.java @@ -10,12 +10,10 @@ package org.openmrs.validator; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Objects; import org.apache.commons.text.StringEscapeUtils; +import org.openmrs.BaseReferenceRange; import org.openmrs.Concept; import org.openmrs.ConceptDatatype; import org.openmrs.ConceptNumeric; @@ -27,7 +25,6 @@ import org.openmrs.api.context.Context; import org.openmrs.api.db.hibernate.HibernateUtil; import org.openmrs.util.ConceptReferenceRangeUtility; -import org.openmrs.util.OpenmrsUtil; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -198,7 +195,7 @@ else if (!isObsGroup) { } } - validateConceptReferenceRange(obs, errors, atRootNode); + validateReferenceRange(obs, errors, atRootNode); } else if (dt.isText() && obs.getValueText() == null) { if (atRootNode) { errors.rejectValue("valueText", "error.null"); @@ -259,18 +256,22 @@ else if (!isObsGroup) { * @param obs Observation to validate * @param errors Errors to record validation issues */ - private void validateConceptReferenceRange(Obs obs, Errors errors, boolean atRootNode) { - ConceptReferenceRange conceptReferenceRange = getReferenceRange(obs); + private void validateReferenceRange(Obs obs, Errors errors, boolean atRootNode) { + BaseReferenceRange referenceRange = obs.getReferenceRange(); + if (referenceRange == null) { + referenceRange = getReferenceRange(obs); + } - if (conceptReferenceRange != null) { - validateAbsoluteRanges(obs, conceptReferenceRange, errors, atRootNode); + if (referenceRange != null) { + validateAbsoluteRanges(obs, referenceRange, errors, atRootNode); if (obs.getId() == null) { - setObsReferenceRange(obs, conceptReferenceRange); + setObsReferenceRange(obs, referenceRange); } } else if (obs.getId() == null) { setObsReferenceRange(obs); } + setObsInterpretation(obs); } @@ -410,16 +411,16 @@ private ConceptReferenceRange findStrictestReferenceRange(List obs.getValueNumeric()) { + if (referenceRange.getLowAbsolute() != null && referenceRange.getLowAbsolute() > obs.getValueNumeric()) { if (atRootNode) { errors.rejectValue( "valueNumeric", "error.value.outOfRange.low", - new Object[] { conceptReferenceRange.getLowAbsolute() }, + new Object[] { referenceRange.getLowAbsolute() }, null ); } else { @@ -455,20 +456,23 @@ private void validateAbsoluteRanges(Obs obs, ConceptReferenceRange conceptRefere * Builds and sets the ObsReferenceRange for the given Obs. * * @param obs Observation to set the reference range - * @param conceptReferenceRange ConceptReferenceRange used to build the ObsReferenceRange + * @param referenceRange ConceptReferenceRange used to build the ObsReferenceRange */ - private void setObsReferenceRange(Obs obs, ConceptReferenceRange conceptReferenceRange) { - ObsReferenceRange obsRefRange = new ObsReferenceRange(); - - obsRefRange.setHiAbsolute(conceptReferenceRange.getHiAbsolute()); - obsRefRange.setHiCritical(conceptReferenceRange.getHiCritical()); - obsRefRange.setHiNormal(conceptReferenceRange.getHiNormal()); - obsRefRange.setLowAbsolute(conceptReferenceRange.getLowAbsolute()); - obsRefRange.setLowCritical(conceptReferenceRange.getLowCritical()); - obsRefRange.setLowNormal(conceptReferenceRange.getLowNormal()); - obsRefRange.setObs(obs); - - obs.setReferenceRange(obsRefRange); + private void setObsReferenceRange(Obs obs, BaseReferenceRange referenceRange) { + if (referenceRange instanceof ObsReferenceRange) { + if (((ObsReferenceRange) referenceRange).getObs() == null) { + obs.setReferenceRange((ObsReferenceRange) referenceRange); + } + } else { + ObsReferenceRange obsRefRange = new ObsReferenceRange(); + obsRefRange.setHiAbsolute(referenceRange.getHiAbsolute()); + obsRefRange.setHiCritical(referenceRange.getHiCritical()); + obsRefRange.setHiNormal(referenceRange.getHiNormal()); + obsRefRange.setLowAbsolute(referenceRange.getLowAbsolute()); + obsRefRange.setLowCritical(referenceRange.getLowCritical()); + obsRefRange.setLowNormal(referenceRange.getLowNormal()); + obs.setReferenceRange(obsRefRange); + } } /** @@ -479,24 +483,24 @@ private void setObsReferenceRange(Obs obs, ConceptReferenceRange conceptReferenc private void setObsReferenceRange(Obs obs) { if (obs.getConcept() == null) { return; + } else if (obs.getReferenceRange() != null) { + return; } - + ConceptNumeric conceptNumeric = Context.getConceptService().getConceptNumeric(obs.getConcept().getId()); if (conceptNumeric != null) { ObsReferenceRange obsRefRange = new ObsReferenceRange(); - obsRefRange.setHiAbsolute(conceptNumeric.getHiAbsolute()); obsRefRange.setHiCritical(conceptNumeric.getHiCritical()); obsRefRange.setHiNormal(conceptNumeric.getHiNormal()); obsRefRange.setLowAbsolute(conceptNumeric.getLowAbsolute()); obsRefRange.setLowCritical(conceptNumeric.getLowCritical()); obsRefRange.setLowNormal(conceptNumeric.getLowNormal()); - obsRefRange.setObs(obs); - obs.setReferenceRange(obsRefRange); } } + /** * This method sets Obs interpretation based on the current obs' numeric value. @@ -504,8 +508,12 @@ private void setObsReferenceRange(Obs obs) { * @param obs Observation to set the interpretation */ private void setObsInterpretation(Obs obs) { + if (obs.getInterpretation() != null || obs.getValueNumeric() == null) { + return; + } + ObsReferenceRange referenceRange = obs.getReferenceRange(); - if (referenceRange == null || obs.getValueNumeric() == null) { + if (referenceRange == null) { return; } diff --git a/api/src/test/java/org/openmrs/validator/ObsValidatorTest.java b/api/src/test/java/org/openmrs/validator/ObsValidatorTest.java index fa9a159b5c2f..92650585e123 100644 --- a/api/src/test/java/org/openmrs/validator/ObsValidatorTest.java +++ b/api/src/test/java/org/openmrs/validator/ObsValidatorTest.java @@ -11,10 +11,10 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -25,12 +25,14 @@ import org.apache.commons.lang3.StringUtils; import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.openmrs.Concept; import org.openmrs.ConceptDatatype; import org.openmrs.ConceptReferenceRange; import org.openmrs.Drug; import org.openmrs.Obs; +import org.openmrs.ObsReferenceRange; import org.openmrs.Person; import org.openmrs.api.APIException; import org.openmrs.api.context.Context; @@ -851,6 +853,20 @@ public void shouldSetInterpretationToNormalIfObsValueIsWithinNormalRange() { assertEquals(Obs.Interpretation.NORMAL, obs.getInterpretation()); } + /** + * @see ObsValidator#validate(java.lang.Object, org.springframework.validation.Errors) + */ + @Test + public void shouldSetInterpretationToLowIfObsValueIsBelowLowButAboveCriticalLow() { + Obs obs = getObs(60, 4090, 78.0); + + Errors errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.LOW, obs.getInterpretation()); + } + /** * @see ObsValidator#validate(java.lang.Object, org.springframework.validation.Errors) */ @@ -865,6 +881,103 @@ public void shouldSetInterpretationToCriticalLowIfObsValueIsBelowLowCritical() { assertEquals(Obs.Interpretation.CRITICALLY_LOW, obs.getInterpretation()); } + /** + * @see ObsValidator#validate(java.lang.Object, org.springframework.validation.Errors) + */ + @Test + public void shouldNotChangeInterpretationOfObsIfOneExists() { + Obs obs = getObs(60, 4090, 131.0); + obs.setInterpretation(Obs.Interpretation.NORMAL); + + Errors errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.NORMAL, obs.getInterpretation()); + } + + /** + * @see ObsValidator#validate(java.lang.Object, org.springframework.validation.Errors) + */ + @Test + public void shouldNotChangeReferenceRangeOfObsIfOneExists() { + Obs obs = getObs(60, 4090, 131.0); + ObsReferenceRange obsReferenceRange = new ObsReferenceRange(); + obsReferenceRange.setLowAbsolute(50d); + obs.setReferenceRange(obsReferenceRange); + + Errors errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertNotNull(obs.getReferenceRange()); + assertEquals(50d, obs.getReferenceRange().getLowAbsolute()); + assertNull(obs.getReferenceRange().getLowCritical()); + assertNull(obs.getReferenceRange().getLowNormal()); + assertNull(obs.getReferenceRange().getHiNormal()); + assertNull(obs.getReferenceRange().getHiCritical()); + assertNull(obs.getReferenceRange().getHiAbsolute()); + } + + /** + * @see ObsValidator#validate(java.lang.Object, org.springframework.validation.Errors) + */ + @Test + public void shouldInterpretObsRelativeToObsReferenceRange() { + ObsReferenceRange obsReferenceRange = new ObsReferenceRange(); + obsReferenceRange.setLowAbsolute(50d); + obsReferenceRange.setLowCritical(60d); + obsReferenceRange.setLowNormal(70d); + obsReferenceRange.setHiNormal(80d); + obsReferenceRange.setHiCritical(90d); + obsReferenceRange.setHiAbsolute(100d); + + Obs obs = getObs(60, 4090, 51d); + obs.setReferenceRange(obsReferenceRange); + + Errors errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.CRITICALLY_LOW, obs.getInterpretation()); + + obs = getObs(60, 4090, 61d); + obs.setReferenceRange(obsReferenceRange); + + errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.LOW, obs.getInterpretation()); + + obs = getObs(60, 4090, 75d); + obs.setReferenceRange(obsReferenceRange); + + errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.NORMAL, obs.getInterpretation()); + + obs = getObs(60, 4090, 81d); + obs.setReferenceRange(obsReferenceRange); + + errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.HIGH, obs.getInterpretation()); + + obs = getObs(60, 4090, 91d); + obs.setReferenceRange(obsReferenceRange); + + errors = new BindException(obs, "obs"); + obsValidator.validate(obs, errors); + + assertFalse(errors.hasErrors()); + assertEquals(Obs.Interpretation.CRITICALLY_HIGH, obs.getInterpretation()); + } + private static Obs getObs(int numberOfYears, int conceptId, double valueNumeric) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.YEAR, -numberOfYears);