Add simple local implementation of UCUM temperature canonicalization … (#3872)
* Add simple local implementation of UCUM temperature canonicalization for Celsius and Fahrenheit * Adjust conversion to not increase precision, as it matter when finding ranges * Simplify test variables Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
2e1f4c25f5
commit
ebac65cb31
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 3865
|
||||
title: "Previously, Celsius and Fahrenheit temperature quantities were not normalized. This is now fixed.
|
||||
This change requires reindexing of resources containing Celsius or Fahrenheit temperature quantities."
|
|
@ -1548,25 +1548,24 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
|
|||
}
|
||||
|
||||
@Nested
|
||||
@Disabled // These conversions are not supported by the library we use
|
||||
public class TemperatureUnitConversions {
|
||||
|
||||
@Test
|
||||
public void celsiusToFahrenheit() {
|
||||
public void storeCelsiusSearchFahrenheit() {
|
||||
withObservationWithQuantity(37.5, UCUM_CODESYSTEM_URL, "Cel" );
|
||||
|
||||
assertFind( "when eq UCUM 99.5 degF", "/Observation?value-quantity=99.5|" + UCUM_CODESYSTEM_URL + "|degF");
|
||||
assertNotFind( "when eq UCUM 101.1 degF", "/Observation?value-quantity=101.1|" + UCUM_CODESYSTEM_URL + "|degF");
|
||||
assertNotFind( "when eq UCUM 97.8 degF", "/Observation?value-quantity=97.8|" + UCUM_CODESYSTEM_URL + "|degF");
|
||||
assertFind( "when eq UCUM 99.5 degF", "/Observation?value-quantity=99.5|" + UCUM_CODESYSTEM_URL + "|[degF]");
|
||||
assertNotFind( "when eq UCUM 101.1 degF", "/Observation?value-quantity=101.1|" + UCUM_CODESYSTEM_URL + "|[degF]");
|
||||
assertNotFind( "when eq UCUM 97.8 degF", "/Observation?value-quantity=97.8|" + UCUM_CODESYSTEM_URL + "|[degF]");
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void fahrenheitToCelsius() {
|
||||
withObservationWithQuantity(99.5, UCUM_CODESYSTEM_URL, "degF" );
|
||||
@Test
|
||||
public void storeFahrenheitSearchCelsius() {
|
||||
withObservationWithQuantity(99.5, UCUM_CODESYSTEM_URL, "[degF]" );
|
||||
|
||||
assertFind( "when eq UCUM 37.5 Cel", "/Observation?value-quantity=99.5|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
assertNotFind( "when eq UCUM 38.1 Cel", "/Observation?value-quantity=101.1|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
assertNotFind( "when eq UCUM 36.9 Cel", "/Observation?value-quantity=97.8|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
assertFind( "when eq UCUM 37.5 Cel", "/Observation?value-quantity=37.5|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
assertNotFind( "when eq UCUM 37.3 Cel", "/Observation?value-quantity=37.3|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
assertNotFind( "when eq UCUM 37.7 Cel", "/Observation?value-quantity=37.7|" + UCUM_CODESYSTEM_URL + "|Cel");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import org.fhir.ucum.Decimal;
|
||||
|
@ -43,6 +45,10 @@ public class UcumServiceUtil {
|
|||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(UcumServiceUtil.class);
|
||||
|
||||
public static final String CELSIUS_CODE = "Cel";
|
||||
public static final String FAHRENHEIT_CODE = "[degF]";
|
||||
public static final float CELSIUS_KELVIN_DIFF = 273.15f;
|
||||
|
||||
public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org";
|
||||
private static final String UCUM_SOURCE = "/ucum-essence.xml";
|
||||
|
||||
|
@ -63,7 +69,7 @@ public class UcumServiceUtil {
|
|||
myUcumEssenceService = new UcumEssenceService(input);
|
||||
|
||||
} catch (UcumException e) {
|
||||
ourLog.warn("Failed to load ucum code from ", UCUM_SOURCE, e);
|
||||
ourLog.warn("Failed to load ucum code from {}: {}", UCUM_SOURCE, e);
|
||||
} finally {
|
||||
ClasspathUtil.close(input);
|
||||
}
|
||||
|
@ -87,6 +93,15 @@ public class UcumServiceUtil {
|
|||
if (!UCUM_CODESYSTEM_URL.equals(theSystem) || theValue == null || theCode == null)
|
||||
return null;
|
||||
|
||||
if ( isCelsiusOrFahrenheit(theCode) ) {
|
||||
try {
|
||||
return getCanonicalFormForCelsiusOrFahrenheit(theValue, theCode);
|
||||
} catch (UcumException theE) {
|
||||
ourLog.error("Exception when trying to obtain canonical form for value {} and code {}: {}", theValue, theCode, theE.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
Pair theCanonicalPair;
|
||||
|
||||
|
@ -103,6 +118,46 @@ public class UcumServiceUtil {
|
|||
return theCanonicalPair;
|
||||
}
|
||||
|
||||
|
||||
private static Pair getCanonicalFormForCelsiusOrFahrenheit(BigDecimal theValue, String theCode) throws UcumException {
|
||||
return theCode.equals(CELSIUS_CODE)
|
||||
? canonicalizeCelsius(theValue)
|
||||
: canonicalizeFahrenheit(theValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the received Fahrenheit value converted to Kelvin units and code
|
||||
* Formula is K = (x°F − 32) × 5/9 + 273.15
|
||||
*/
|
||||
private static Pair canonicalizeFahrenheit(BigDecimal theValue) throws UcumException {
|
||||
BigDecimal converted = theValue
|
||||
.subtract( BigDecimal.valueOf(32) )
|
||||
.multiply( BigDecimal.valueOf(5f / 9f) )
|
||||
.add( BigDecimal.valueOf(CELSIUS_KELVIN_DIFF) );
|
||||
// disallow precision larger than input, as it matters when defining ranges
|
||||
BigDecimal adjusted = converted.setScale(theValue.precision(), RoundingMode.HALF_UP);
|
||||
|
||||
Decimal newValue = new Decimal(adjusted.toPlainString());
|
||||
return new Pair(newValue, "K");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the received Celsius value converted to Kelvin units and code
|
||||
*/
|
||||
private static Pair canonicalizeCelsius(BigDecimal theValue) throws UcumException {
|
||||
Decimal valueDec = new Decimal(theValue.toPlainString(), theValue.precision());
|
||||
Decimal converted = valueDec
|
||||
.add(new Decimal(Float.toString(CELSIUS_KELVIN_DIFF)));
|
||||
|
||||
return new Pair(converted, "K");
|
||||
}
|
||||
|
||||
|
||||
private static boolean isCelsiusOrFahrenheit(String theCode) {
|
||||
return theCode.equals(CELSIUS_CODE) || theCode.equals(FAHRENHEIT_CODE);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static QuantityParam toCanonicalQuantityOrNull(QuantityParam theQuantityParam) {
|
||||
Pair canonicalForm = getCanonicalForm(theQuantityParam.getSystem(), theQuantityParam.getValue(), theQuantityParam.getUnits());
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package ca.uhn.fhir.jpa.model.util;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.util.UcumServiceUtil.CELSIUS_KELVIN_DIFF;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.fhir.ucum.Decimal;
|
||||
import org.fhir.ucum.Pair;
|
||||
import org.fhir.ucum.UcumException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class UcumServiceUtilTest {
|
||||
|
@ -54,9 +60,31 @@ public class UcumServiceUtilTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testUcumDegreeFahrenheit() {
|
||||
public void testUcumDegreeFahrenheit() throws UcumException {
|
||||
Pair canonicalPair = UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal("99.82"), "[degF]");
|
||||
Decimal converted = canonicalPair.getValue();
|
||||
Decimal expected = new Decimal("310.8278");
|
||||
// System.out.println("expected: " + expected);
|
||||
// System.out.println("converted: " + converted);
|
||||
assertTrue( converted.equals(expected));
|
||||
assertEquals("K", canonicalPair.getCode());
|
||||
|
||||
assertEquals(null, UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal(99.82), "[degF]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUcumDegreeCelsius() throws UcumException {
|
||||
// round it up to set expected decimal precision
|
||||
BigDecimal roundedCelsiusKelvinDiff = new BigDecimal(CELSIUS_KELVIN_DIFF)
|
||||
.round(new MathContext(5, RoundingMode.HALF_UP));
|
||||
|
||||
Pair canonicalPair = UcumServiceUtil.getCanonicalForm(UcumServiceUtil.UCUM_CODESYSTEM_URL, new BigDecimal("73.54"), "Cel");
|
||||
Decimal converted = canonicalPair.getValue();
|
||||
Decimal expected = new Decimal("73.54").add(new Decimal(roundedCelsiusKelvinDiff.toString()));
|
||||
// System.out.println("expected: " + expected);
|
||||
// System.out.println("converted: " + converted);
|
||||
// System.out.println("diff: " + converted.subtract(expectedApprox));
|
||||
assertTrue( converted.equals(expected));
|
||||
assertEquals("K", canonicalPair.getCode());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4839,10 +4839,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
|
||||
assertEquals(1, returnedBundle.getEntry().size());
|
||||
|
||||
//-- check only use original quantity table to search
|
||||
//-- check use normalized quantity table to search
|
||||
String searchSql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, true);
|
||||
assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
|
||||
assertThat(searchSql, not(containsString("HFJ_SPIDX_QUANTITY_NRML")));
|
||||
assertThat(searchSql, not (containsString("HFJ_SPIDX_QUANTITY t0")));
|
||||
assertThat(searchSql, (containsString("HFJ_SPIDX_QUANTITY_NRML")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue