From 605fa7c9eb9482fc4e4e2e7a5504a77621168d6f Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 14:46:41 +0100 Subject: [PATCH 1/7] feat: DoseQuantity --- .../utils/config/DefaultImplementations.java | 13 +- .../r4/dosage/utils/config/FDUConfig.java | 15 ++- .../utils/functions/QuantityToString.java | 47 +++++++ .../utils/miscellaneous/Translators.java | 3 +- .../utils/translators/DoseQuantity.java | 35 +++++ .../r4/dosage/utils/types/DoseAndRateKey.java | 42 ++++++ src/main/resources/common_de.properties | 3 - src/main/resources/common_en.properties | 3 - src/main/resources/common_fr.properties | 3 - src/main/resources/common_nl.properties | 3 - .../utils/translators/DoseQuantityTest.java | 120 ++++++++++++++++++ 11 files changed, 268 insertions(+), 19 deletions(-) create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/types/DoseAndRateKey.java create mode 100644 src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/config/DefaultImplementations.java b/src/main/java/jy95/fhir/r4/dosage/utils/config/DefaultImplementations.java index 165af2532..428dfa50f 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/config/DefaultImplementations.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/config/DefaultImplementations.java @@ -5,9 +5,8 @@ import java.util.List; import java.util.stream.Collectors; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Quantity; -import org.hl7.fhir.r4.model.Extension; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.*; public class DefaultImplementations { @@ -85,4 +84,12 @@ public static CompletableFuture fromExtensionsToString(List e .collect(Collectors.joining(", ", "[", "]")); }); } + + public static Type selectDosageAndRateField( + List doseAndRates, + DoseAndRateKey extractor + ) { + // Keep it simple, use the first entry as most of the time, it is enough for everyone + return extractor.extract(doseAndRates.getFirst()); + } } diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/config/FDUConfig.java b/src/main/java/jy95/fhir/r4/dosage/utils/config/FDUConfig.java index e63b92c0b..3bc77dbe7 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/config/FDUConfig.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/config/FDUConfig.java @@ -1,5 +1,6 @@ package jy95.fhir.r4.dosage.utils.config; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; import lombok.Builder; import lombok.Getter; @@ -9,10 +10,10 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.function.BiFunction; -import org.hl7.fhir.r4.model.Quantity; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Dosage.DosageDoseAndRateComponent; // To provide a configuration with the @Getter @@ -71,4 +72,12 @@ public class FDUConfig { * The choice to handle national extensions, ... is thus under the hands of people ;) */ @Builder.Default private Function, CompletableFuture> fromExtensionsToString = DefaultImplementations::fromExtensionsToString; + /** + * Function to select the proper "doseAndRate" field of a given type from a list of "dosageAndRate" + * Because of optional type element DoseAndRateType, it is possible to have "Calculated" and "Ordered" fields + * Most of the time, what matter is only the first element, but in case of, this function give control + * on the selection strategy + * @see DoseAndRateType + */ + @Builder.Default private BiFunction, DoseAndRateKey, Type> selectDosageAndRateField = DefaultImplementations::selectDosageAndRateField; } diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java new file mode 100644 index 000000000..0bbe2fcc6 --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java @@ -0,0 +1,47 @@ +package jy95.fhir.r4.dosage.utils.functions; + +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import org.hl7.fhir.r4.model.Quantity; + +import java.text.MessageFormat; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class QuantityToString { + public static CompletableFuture convert(ResourceBundle bundle, FDUConfig config, Quantity quantity) { + var comparator = comparatorToString(bundle, config, quantity); + var unit = unitToString(config, quantity); + var amount = quantity.getValue().toString(); + + return comparator.thenCombineAsync(unit, (comparatorText, unitText) -> + Stream + .of(comparatorText, amount, unitText) + .filter(part -> !part.isEmpty()) + .collect(Collectors.joining(" ")) + ); + } + + // See if unit (code or text) could be found in quantity + private static boolean hasUnit(Quantity quantity) { + return quantity.hasUnit() || quantity.hasCode(); + } + + private static CompletableFuture comparatorToString(ResourceBundle bundle, FDUConfig config, Quantity quantity) { + if (quantity.hasComparator()) { + var code = quantity.getComparator().toCode(); + var comparatorMsg = bundle.getString(code); + var text = new MessageFormat(comparatorMsg, config.getLocale()).format(new Object[]{}); + return CompletableFuture.completedFuture(text); + } + return CompletableFuture.completedFuture(""); + } + + private static CompletableFuture unitToString(FDUConfig config, Quantity quantity) { + if (hasUnit(quantity)) { + return config.getFromFHIRQuantityUnitToString().apply(quantity); + } + return CompletableFuture.completedFuture(""); + } +} diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java index 6ddb586a0..00d68ddb7 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java @@ -44,7 +44,8 @@ public Translators(FDUConfig config) { DisplayOrder.FREQUENCY_FREQUENCY_MAX_PERIOD_PERIOD_MAX, new FrequencyFrequencyMaxPeriodPeriodMax(config) ), - Map.entry(DisplayOrder.COUNT_COUNT_MAX, new CountCountMax(config)) + Map.entry(DisplayOrder.COUNT_COUNT_MAX, new CountCountMax(config)), + Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)) ) ); this.bundleControl = new MultiResourceBundleControl( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java new file mode 100644 index 000000000..df5f92df9 --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java @@ -0,0 +1,35 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.functions.QuantityToString; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Quantity; + +import java.util.concurrent.CompletableFuture; + +public class DoseQuantity extends AbstractTranslator { + + public DoseQuantity(FDUConfig config) { + super(config); + } + + @Override + public CompletableFuture convert(Dosage dosage) { + var bundle = getResources(); + var doseAndRate = dosage.getDoseAndRate(); + var doseQuantity = getConfig() + .getSelectDosageAndRateField() + .apply(doseAndRate, DoseAndRateKey.DOSE_QUANTITY); + return QuantityToString.convert(bundle, getConfig(), (Quantity) doseQuantity); + } + + @Override + public boolean isPresent(Dosage dosage) { + return dosage.hasDoseAndRate() && dosage + .getDoseAndRate() + .stream() + .anyMatch(Dosage.DosageDoseAndRateComponent::hasDoseQuantity); + } +} diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/types/DoseAndRateKey.java b/src/main/java/jy95/fhir/r4/dosage/utils/types/DoseAndRateKey.java new file mode 100644 index 000000000..fe622da12 --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/types/DoseAndRateKey.java @@ -0,0 +1,42 @@ +package jy95.fhir.r4.dosage.utils.types; + +import org.hl7.fhir.r4.model.Type; +import org.hl7.fhir.r4.model.Dosage.DosageDoseAndRateComponent; + +/** + * Represents the available fields in "doseAndRate" + */ +public enum DoseAndRateKey { + DOSE_RANGE { + @Override + public Type extract(DosageDoseAndRateComponent doseAndRate) { + return doseAndRate.getDoseRange(); + } + }, + DOSE_QUANTITY { + @Override + public Type extract(DosageDoseAndRateComponent doseAndRate) { + return doseAndRate.getDoseQuantity(); + } + }, + RATE_RATIO { + @Override + public Type extract(DosageDoseAndRateComponent doseAndRate) { + return doseAndRate.getRateRatio(); + } + }, + RATE_RANGE { + @Override + public Type extract(DosageDoseAndRateComponent doseAndRate) { + return doseAndRate.getRateRange(); + } + }, + RATE_QUANTITY { + @Override + public Type extract(DosageDoseAndRateComponent doseAndRate) { + return doseAndRate.getRateQuantity(); + } + }; + + public abstract Type extract(DosageDoseAndRateComponent doseAndRate); +} diff --git a/src/main/resources/common_de.properties b/src/main/resources/common_de.properties index c70b9f9f2..3befd976c 100644 --- a/src/main/resources/common_de.properties +++ b/src/main/resources/common_de.properties @@ -6,9 +6,6 @@ amount.range.withoutUnit = {condition, select, 0{zwischen {minValue} und {maxVal amount.ratio.denominatorLinkword = {condition, select, 1{pro} other{jeder}} -amount.quantity.withUnit = {value} {unit} -amount.quantity.withoutUnit = {value} - fields.doseQuantity = {dose} fields.doseRange = {doseRange} fields.rateQuantity = mit einem Verhältnis von {rate} diff --git a/src/main/resources/common_en.properties b/src/main/resources/common_en.properties index 19e31fba8..06cb1d8c1 100644 --- a/src/main/resources/common_en.properties +++ b/src/main/resources/common_en.properties @@ -6,9 +6,6 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} to {maxValue}} 1{up amount.ratio.denominatorLinkword = {condition, select, 1{per} other{every}} -amount.quantity.withUnit = {value} {unit} -amount.quantity.withoutUnit = {value} - fields.doseQuantity = {dose} fields.doseRange = {doseRange} fields.rateQuantity = at a rate of {rate} diff --git a/src/main/resources/common_fr.properties b/src/main/resources/common_fr.properties index a0bdcea3f..6c116ce09 100644 --- a/src/main/resources/common_fr.properties +++ b/src/main/resources/common_fr.properties @@ -6,9 +6,6 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} amount.ratio.denominatorLinkword = {condition, select, 1{par} other{chaque}} -amount.quantity.withUnit = {value} {unit} -amount.quantity.withoutUnit = {value} - fields.doseQuantity = {dose} fields.doseRange = {doseRange} fields.rateQuantity = au taux de {rate} diff --git a/src/main/resources/common_nl.properties b/src/main/resources/common_nl.properties index b9cd43d85..01f866c6d 100644 --- a/src/main/resources/common_nl.properties +++ b/src/main/resources/common_nl.properties @@ -6,9 +6,6 @@ amount.range.withoutUnit = {condition, select, 0{tussen {minValue} en {maxValue} amount.ratio.denominatorLinkword = {condition, select, 1{per} other{elke}} -amount.quantity.withUnit = {value} {unit} -amount.quantity.withoutUnit = {value} - fields.doseQuantity = {dose} fields.doseRange = {doseRange} fields.rateQuantity = met een verhouding van {rate} diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java new file mode 100644 index 000000000..5d0e697d9 --- /dev/null +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java @@ -0,0 +1,120 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import jy95.fhir.r4.dosage.utils.AbstractFhirTest; +import jy95.fhir.r4.dosage.utils.classes.FhirDosageUtils; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.types.DisplayOrder; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Dosage.DosageDoseAndRateComponent; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DoseQuantityTest extends AbstractFhirTest { + + @ParameterizedTest + @MethodSource("localeProvider") + void testNoDoseQuantity(Locale locale) throws ExecutionException, InterruptedException { + Dosage dosage = new Dosage(); + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_QUANTITY); + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testSimpleDoseQuantity(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_QUANTITY); + Dosage dosage = new Dosage(); + Quantity quantity1 = new Quantity(5); + quantity1.setUnit("ml"); + DosageDoseAndRateComponent doseAndRateComponent1 = new DosageDoseAndRateComponent(); + doseAndRateComponent1.setDose(quantity1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("5 ml", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testDoseQuantityWithComparator(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_QUANTITY); + Dosage dosage = new Dosage(); + Quantity quantity1 = new Quantity(5); + quantity1.setUnit("ml"); + quantity1.setComparator(Quantity.QuantityComparator.LESS_THAN); + DosageDoseAndRateComponent doseAndRateComponent1 = new DosageDoseAndRateComponent(); + doseAndRateComponent1.setDose(quantity1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("< 5 ml", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testDoseQuantityWithoutUnit(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_QUANTITY); + Dosage dosage = new Dosage(); + Quantity quantity1 = new Quantity(5); + DosageDoseAndRateComponent doseAndRateComponent1 = new DosageDoseAndRateComponent(); + doseAndRateComponent1.setDose(quantity1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("5", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testDoseQuantityCustom(Locale locale) throws ExecutionException, InterruptedException { + FDUConfig config = FDUConfig + .builder() + .selectDosageAndRateField( + (doseAndRateComponentList, doseAndRateKey) + -> doseAndRateKey.extract(doseAndRateComponentList.get(1))) + .build(); + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(config); + Dosage dosage = new Dosage(); + Quantity quantity1 = new Quantity(5); + quantity1.setUnit("ml"); + Quantity quantity2 = new Quantity(8); + quantity2.setUnit("ml"); + DosageDoseAndRateComponent doseAndRateComponent1 = new DosageDoseAndRateComponent(); + doseAndRateComponent1.setDose(quantity1); + doseAndRateComponent1.setType( + new CodeableConcept( + new Coding( + "http://terminology.hl7.org/ValueSet/dose-rate-type", + "calculated", + "Calculated" + ) + ) + ); + DosageDoseAndRateComponent doseAndRateComponent2 = new DosageDoseAndRateComponent(); + doseAndRateComponent2.setDose(quantity2); + doseAndRateComponent2.setType( + new CodeableConcept( + new Coding( + "http://terminology.hl7.org/ValueSet/dose-rate-type", + "ordered", + "Ordered" + ) + ) + ); + dosage.setDoseAndRate(List.of(doseAndRateComponent1, doseAndRateComponent2)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("8 ml", result); + } + +} From 44d7aa4f84980aed9e1124ffe7c4fd59c4cfd374 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 14:55:13 +0100 Subject: [PATCH 2/7] test: fix typo --- .../fhir/r4/dosage/utils/translators/DoseQuantityTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java index 5d0e697d9..1e9b9485d 100644 --- a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantityTest.java @@ -79,6 +79,7 @@ void testDoseQuantityWithoutUnit(Locale locale) throws ExecutionException, Inter void testDoseQuantityCustom(Locale locale) throws ExecutionException, InterruptedException { FDUConfig config = FDUConfig .builder() + .displayOrder(List.of(DisplayOrder.DOSE_QUANTITY)) .selectDosageAndRateField( (doseAndRateComponentList, doseAndRateKey) -> doseAndRateKey.extract(doseAndRateComponentList.get(1))) @@ -111,8 +112,8 @@ void testDoseQuantityCustom(Locale locale) throws ExecutionException, Interrupte ) ) ); - dosage.setDoseAndRate(List.of(doseAndRateComponent1, doseAndRateComponent2)); - + dosage.addDoseAndRate(doseAndRateComponent1); + dosage.addDoseAndRate(doseAndRateComponent2); String result = dosageUtils.asHumanReadableText(dosage).get(); assertEquals("8 ml", result); } From 50c5a0ef0bb83d63c86653cc8d07e888a3a910f6 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 15:02:40 +0100 Subject: [PATCH 3/7] perf: improve DoseQuantity --- .../fhir/r4/dosage/utils/translators/DoseQuantity.java | 8 +++++++- src/main/resources/common_de.properties | 2 +- src/main/resources/common_en.properties | 2 +- src/main/resources/common_fr.properties | 2 +- src/main/resources/common_nl.properties | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java index df5f92df9..74cf2904c 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseQuantity.java @@ -1,5 +1,6 @@ package jy95.fhir.r4.dosage.utils.translators; +import com.ibm.icu.text.MessageFormat; import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; import jy95.fhir.r4.dosage.utils.config.FDUConfig; import jy95.fhir.r4.dosage.utils.functions.QuantityToString; @@ -22,7 +23,12 @@ public CompletableFuture convert(Dosage dosage) { var doseQuantity = getConfig() .getSelectDosageAndRateField() .apply(doseAndRate, DoseAndRateKey.DOSE_QUANTITY); - return QuantityToString.convert(bundle, getConfig(), (Quantity) doseQuantity); + return QuantityToString + .convert(bundle, getConfig(), (Quantity) doseQuantity) + .thenApplyAsync(quantityText -> { + var doseMsg = bundle.getString("fields.doseQuantity"); + return new MessageFormat(doseMsg, getConfig().getLocale()).format(new Object[]{quantityText}); + }); } @Override diff --git a/src/main/resources/common_de.properties b/src/main/resources/common_de.properties index 3befd976c..b5174536a 100644 --- a/src/main/resources/common_de.properties +++ b/src/main/resources/common_de.properties @@ -6,7 +6,7 @@ amount.range.withoutUnit = {condition, select, 0{zwischen {minValue} und {maxVal amount.ratio.denominatorLinkword = {condition, select, 1{pro} other{jeder}} -fields.doseQuantity = {dose} +fields.doseQuantity = {0} fields.doseRange = {doseRange} fields.rateQuantity = mit einem Verhältnis von {rate} fields.rateRange = mit einem Verhältnis von {rateRange} diff --git a/src/main/resources/common_en.properties b/src/main/resources/common_en.properties index 06cb1d8c1..508f980f0 100644 --- a/src/main/resources/common_en.properties +++ b/src/main/resources/common_en.properties @@ -6,7 +6,7 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} to {maxValue}} 1{up amount.ratio.denominatorLinkword = {condition, select, 1{per} other{every}} -fields.doseQuantity = {dose} +fields.doseQuantity = {0} fields.doseRange = {doseRange} fields.rateQuantity = at a rate of {rate} fields.rateRange = at a rate of {rateRange} diff --git a/src/main/resources/common_fr.properties b/src/main/resources/common_fr.properties index 6c116ce09..20d82b933 100644 --- a/src/main/resources/common_fr.properties +++ b/src/main/resources/common_fr.properties @@ -6,7 +6,7 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} amount.ratio.denominatorLinkword = {condition, select, 1{par} other{chaque}} -fields.doseQuantity = {dose} +fields.doseQuantity = {0} fields.doseRange = {doseRange} fields.rateQuantity = au taux de {rate} fields.rateRange = au taux de {rateRange} diff --git a/src/main/resources/common_nl.properties b/src/main/resources/common_nl.properties index 01f866c6d..dd3e8833b 100644 --- a/src/main/resources/common_nl.properties +++ b/src/main/resources/common_nl.properties @@ -6,7 +6,7 @@ amount.range.withoutUnit = {condition, select, 0{tussen {minValue} en {maxValue} amount.ratio.denominatorLinkword = {condition, select, 1{per} other{elke}} -fields.doseQuantity = {dose} +fields.doseQuantity = {0} fields.doseRange = {doseRange} fields.rateQuantity = met een verhouding van {rate} fields.rateRange = met een verhouding van {rateRange} From 23ef3f51d91f2d99887e72895ac4e0e3c8767491 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 15:22:20 +0100 Subject: [PATCH 4/7] feat: DoseRange --- .../utils/miscellaneous/Translators.java | 3 +- .../dosage/utils/translators/DoseRange.java | 42 ++++++++++++++ src/main/resources/common_de.properties | 8 +-- src/main/resources/common_en.properties | 8 +-- src/main/resources/common_fr.properties | 8 +-- src/main/resources/common_nl.properties | 8 +-- .../utils/translators/DoseRangeTest.java | 57 +++++++++++++++++++ 7 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseRange.java create mode 100644 src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java index 00d68ddb7..cfc5dae3a 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java @@ -45,7 +45,8 @@ public Translators(FDUConfig config) { new FrequencyFrequencyMaxPeriodPeriodMax(config) ), Map.entry(DisplayOrder.COUNT_COUNT_MAX, new CountCountMax(config)), - Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)) + Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)), + Map.entry(DisplayOrder.DOSE_RANGE, new DoseRange(config)) ) ); this.bundleControl = new MultiResourceBundleControl( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseRange.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseRange.java new file mode 100644 index 000000000..4f4587be9 --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseRange.java @@ -0,0 +1,42 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import com.ibm.icu.text.MessageFormat; +import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.functions.RangeToString; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Range; + +import java.util.concurrent.CompletableFuture; + +public class DoseRange extends AbstractTranslator { + + public DoseRange(FDUConfig config) { + super(config); + } + + @Override + public CompletableFuture convert(Dosage dosage) { + var bundle = getResources(); + var doseAndRate = dosage.getDoseAndRate(); + var doseRange = getConfig() + .getSelectDosageAndRateField() + .apply(doseAndRate, DoseAndRateKey.DOSE_RANGE); + + return RangeToString + .convert(bundle, getConfig(), (Range) doseRange) + .thenApplyAsync(rangeText -> { + var rangeMsg = bundle.getString("fields.doseRange"); + return new MessageFormat(rangeMsg, getConfig().getLocale()).format(new Object[]{rangeText}); + }); + } + + @Override + public boolean isPresent(Dosage dosage) { + return dosage.hasDoseAndRate() && dosage + .getDoseAndRate() + .stream() + .anyMatch(Dosage.DosageDoseAndRateComponent::hasDoseRange); + } +} diff --git a/src/main/resources/common_de.properties b/src/main/resources/common_de.properties index b5174536a..6942c3839 100644 --- a/src/main/resources/common_de.properties +++ b/src/main/resources/common_de.properties @@ -7,10 +7,10 @@ amount.range.withoutUnit = {condition, select, 0{zwischen {minValue} und {maxVal amount.ratio.denominatorLinkword = {condition, select, 1{pro} other{jeder}} fields.doseQuantity = {0} -fields.doseRange = {doseRange} -fields.rateQuantity = mit einem Verhältnis von {rate} -fields.rateRange = mit einem Verhältnis von {rateRange} -fields.rateRatio = mit einem Verhältnis von {rateRatio} +fields.doseRange = {0} +fields.rateQuantity = mit einem Verhältnis von {0} +fields.rateRange = mit einem Verhältnis von {0} +fields.rateRatio = mit einem Verhältnis von {0} fields.duration = für {duration} fields.durationMax = maximal {duration} diff --git a/src/main/resources/common_en.properties b/src/main/resources/common_en.properties index 508f980f0..bc1374672 100644 --- a/src/main/resources/common_en.properties +++ b/src/main/resources/common_en.properties @@ -7,10 +7,10 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} to {maxValue}} 1{up amount.ratio.denominatorLinkword = {condition, select, 1{per} other{every}} fields.doseQuantity = {0} -fields.doseRange = {doseRange} -fields.rateQuantity = at a rate of {rate} -fields.rateRange = at a rate of {rateRange} -fields.rateRatio = at a rate of {rateRatio} +fields.doseRange = {0} +fields.rateQuantity = at a rate of {0} +fields.rateRange = at a rate of {0} +fields.rateRatio = at a rate of {0} fields.duration = over {duration} fields.durationMax = maximum {duration} diff --git a/src/main/resources/common_fr.properties b/src/main/resources/common_fr.properties index 20d82b933..f5d73f85f 100644 --- a/src/main/resources/common_fr.properties +++ b/src/main/resources/common_fr.properties @@ -7,10 +7,10 @@ amount.range.withoutUnit = {condition, select, 0{{minValue} amount.ratio.denominatorLinkword = {condition, select, 1{par} other{chaque}} fields.doseQuantity = {0} -fields.doseRange = {doseRange} -fields.rateQuantity = au taux de {rate} -fields.rateRange = au taux de {rateRange} -fields.rateRatio = au taux de {rateRatio} +fields.doseRange = {0} +fields.rateQuantity = au taux de {0} +fields.rateRange = au taux de {0} +fields.rateRatio = au taux de {0} fields.duration = durant {duration} fields.durationMax = maximum {duration} diff --git a/src/main/resources/common_nl.properties b/src/main/resources/common_nl.properties index dd3e8833b..7b4a4f309 100644 --- a/src/main/resources/common_nl.properties +++ b/src/main/resources/common_nl.properties @@ -7,10 +7,10 @@ amount.range.withoutUnit = {condition, select, 0{tussen {minValue} en {maxValue} amount.ratio.denominatorLinkword = {condition, select, 1{per} other{elke}} fields.doseQuantity = {0} -fields.doseRange = {doseRange} -fields.rateQuantity = met een verhouding van {rate} -fields.rateRange = met een verhouding van {rateRange} -fields.rateRatio = met een verhouding van {rateRatio} +fields.doseRange = {0} +fields.rateQuantity = met een verhouding van {0} +fields.rateRange = met een verhouding van {0} +fields.rateRatio = met een verhouding van {0} fields.duration = gedurende {duration} fields.durationMax = maximaal {duration} diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java new file mode 100644 index 000000000..5858172b6 --- /dev/null +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java @@ -0,0 +1,57 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import jy95.fhir.r4.dosage.utils.AbstractFhirTest; +import jy95.fhir.r4.dosage.utils.classes.FhirDosageUtils; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.types.DisplayOrder; +import org.hl7.fhir.r4.model.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DoseRangeTest extends AbstractFhirTest { + + @ParameterizedTest + @MethodSource("localeProvider") + void testNoDoseRange(Locale locale) throws ExecutionException, InterruptedException { + Dosage dosage = new Dosage(); + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_RANGE); + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testSimpleDoseRange(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.DOSE_RANGE); + Dosage dosage = new Dosage(); + Range range1 = new Range(); + range1.setLow(new Quantity(1)); + range1.setHigh(new Quantity(3)); + Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent(); + doseAndRateComponent1.setDose(range1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + String expected = getExpectedText(locale); + assertEquals(expected, result); + } + + private String getExpectedText(Locale locale) { + if (locale.equals(Locale.ENGLISH)) { + return "1 to 3"; + } else if (locale.equals(Locale.FRENCH)) { + return "1 à 3"; + } else if (locale.equals(Locale.GERMAN)) { + return "zwischen 1 und 3"; + } else { + return "tussen 1 en 3"; + } + } + +} From 711124a505e8e178b0c2d161ec35c6b04c273116 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 15:35:52 +0100 Subject: [PATCH 5/7] feat: RateQuantity --- .../utils/miscellaneous/Translators.java | 3 +- .../utils/translators/RateQuantity.java | 42 ++++++++++++++ .../utils/translators/DoseRangeTest.java | 1 - .../utils/translators/RateQuantityTest.java | 56 +++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/translators/RateQuantity.java create mode 100644 src/test/java/jy95/fhir/r4/dosage/utils/translators/RateQuantityTest.java diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java index cfc5dae3a..5822478cf 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java @@ -46,7 +46,8 @@ public Translators(FDUConfig config) { ), Map.entry(DisplayOrder.COUNT_COUNT_MAX, new CountCountMax(config)), Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)), - Map.entry(DisplayOrder.DOSE_RANGE, new DoseRange(config)) + Map.entry(DisplayOrder.DOSE_RANGE, new DoseRange(config)), + Map.entry(DisplayOrder.RATE_QUANTITY, new RateQuantity(config)) ) ); this.bundleControl = new MultiResourceBundleControl( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateQuantity.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateQuantity.java new file mode 100644 index 000000000..c6b0ab37f --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateQuantity.java @@ -0,0 +1,42 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import com.ibm.icu.text.MessageFormat; +import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.functions.QuantityToString; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Quantity; + +import java.util.concurrent.CompletableFuture; + +public class RateQuantity extends AbstractTranslator { + + public RateQuantity(FDUConfig config) { + super(config); + } + + @Override + public CompletableFuture convert(Dosage dosage) { + var bundle = getResources(); + var doseAndRate = dosage.getDoseAndRate(); + var rateQuantity = getConfig() + .getSelectDosageAndRateField() + .apply(doseAndRate, DoseAndRateKey.RATE_QUANTITY); + return QuantityToString + .convert(bundle, getConfig(), (Quantity) rateQuantity) + .thenApplyAsync(rateQuantityText -> { + var doseMsg = bundle.getString("fields.rateQuantity"); + return new MessageFormat(doseMsg, getConfig().getLocale()).format(new Object[]{rateQuantityText}); + }); + } + + @Override + public boolean isPresent(Dosage dosage) { + return dosage.hasDoseAndRate() && dosage + .getDoseAndRate() + .stream() + .anyMatch(Dosage.DosageDoseAndRateComponent::hasRateQuantity); + } + +} diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java index 5858172b6..e677a5c27 100644 --- a/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/DoseRangeTest.java @@ -2,7 +2,6 @@ import jy95.fhir.r4.dosage.utils.AbstractFhirTest; import jy95.fhir.r4.dosage.utils.classes.FhirDosageUtils; -import jy95.fhir.r4.dosage.utils.config.FDUConfig; import jy95.fhir.r4.dosage.utils.types.DisplayOrder; import org.hl7.fhir.r4.model.*; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateQuantityTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateQuantityTest.java new file mode 100644 index 000000000..86c971d2a --- /dev/null +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateQuantityTest.java @@ -0,0 +1,56 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import jy95.fhir.r4.dosage.utils.AbstractFhirTest; +import jy95.fhir.r4.dosage.utils.classes.FhirDosageUtils; +import jy95.fhir.r4.dosage.utils.types.DisplayOrder; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Quantity; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RateQuantityTest extends AbstractFhirTest { + + @ParameterizedTest + @MethodSource("localeProvider") + void testNoRateQuantity(Locale locale) throws ExecutionException, InterruptedException { + Dosage dosage = new Dosage(); + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_QUANTITY); + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testSimpleRateQuantity(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_QUANTITY); + Dosage dosage = new Dosage(); + Quantity quantity1 = new Quantity(5); + quantity1.setUnit("ml"); + Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent(); + doseAndRateComponent1.setRate(quantity1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + String expected = getExpectedText(locale); + assertEquals(expected, result); + } + + private String getExpectedText(Locale locale) { + if (locale.equals(Locale.ENGLISH)) { + return "at a rate of 5 ml"; + } else if (locale.equals(Locale.FRENCH)) { + return "au taux de 5 ml"; + } else if (locale.equals(Locale.GERMAN)) { + return "mit einem Verhältnis von 5 ml"; + } else { + return "met een verhouding van 5 ml"; + } + } + +} From 43af5da508327cbf3e4cbeab70dcd85416b0bc29 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sat, 4 Jan 2025 16:01:56 +0100 Subject: [PATCH 6/7] feat: RateRange --- .../utils/miscellaneous/Translators.java | 3 +- .../dosage/utils/translators/RateRange.java | 42 ++++++++++++++ .../utils/translators/RateRangeTest.java | 57 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRange.java create mode 100644 src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRangeTest.java diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java index 5822478cf..c46845d5c 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java @@ -47,7 +47,8 @@ public Translators(FDUConfig config) { Map.entry(DisplayOrder.COUNT_COUNT_MAX, new CountCountMax(config)), Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)), Map.entry(DisplayOrder.DOSE_RANGE, new DoseRange(config)), - Map.entry(DisplayOrder.RATE_QUANTITY, new RateQuantity(config)) + Map.entry(DisplayOrder.RATE_QUANTITY, new RateQuantity(config)), + Map.entry(DisplayOrder.RATE_RANGE, new RateRange(config)) ) ); this.bundleControl = new MultiResourceBundleControl( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRange.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRange.java new file mode 100644 index 000000000..30cd4122c --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRange.java @@ -0,0 +1,42 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import com.ibm.icu.text.MessageFormat; +import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.functions.RangeToString; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Range; + +import java.util.concurrent.CompletableFuture; + +public class RateRange extends AbstractTranslator { + + public RateRange(FDUConfig config) { + super(config); + } + + @Override + public CompletableFuture convert(Dosage dosage) { + var bundle = getResources(); + var doseAndRate = dosage.getDoseAndRate(); + var rateRange = getConfig() + .getSelectDosageAndRateField() + .apply(doseAndRate, DoseAndRateKey.RATE_RANGE); + + return RangeToString + .convert(bundle, getConfig(), (Range) rateRange) + .thenApplyAsync(rateRatioText -> { + var doseRateMsg = bundle.getString("fields.rateRange"); + return new MessageFormat(doseRateMsg, getConfig().getLocale()).format(new Object[]{rateRatioText}); + }); + } + + @Override + public boolean isPresent(Dosage dosage) { + return dosage.hasDoseAndRate() && dosage + .getDoseAndRate() + .stream() + .anyMatch(Dosage.DosageDoseAndRateComponent::hasRateRange); + } +} diff --git a/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRangeTest.java b/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRangeTest.java new file mode 100644 index 000000000..3ad96f29f --- /dev/null +++ b/src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRangeTest.java @@ -0,0 +1,57 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import jy95.fhir.r4.dosage.utils.AbstractFhirTest; +import jy95.fhir.r4.dosage.utils.classes.FhirDosageUtils; +import jy95.fhir.r4.dosage.utils.types.DisplayOrder; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Range; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RateRangeTest extends AbstractFhirTest { + + @ParameterizedTest + @MethodSource("localeProvider") + void testNoRateRange(Locale locale) throws ExecutionException, InterruptedException { + Dosage dosage = new Dosage(); + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RANGE); + String result = dosageUtils.asHumanReadableText(dosage).get(); + assertEquals("", result); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testSimpleRateRange(Locale locale) throws ExecutionException, InterruptedException { + FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RANGE); + Dosage dosage = new Dosage(); + Range range1 = new Range(); + range1.setLow(new Quantity(1)); + range1.setHigh(new Quantity(3)); + Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent(); + doseAndRateComponent1.setRate(range1); + dosage.setDoseAndRate(List.of(doseAndRateComponent1)); + + String result = dosageUtils.asHumanReadableText(dosage).get(); + String expected = getExpectedText(locale); + assertEquals(expected, result); + } + + private String getExpectedText(Locale locale) { + if (locale.equals(Locale.ENGLISH)) { + return "at a rate of 1 to 3"; + } else if (locale.equals(Locale.FRENCH)) { + return "au taux de 1 à 3"; + } else if (locale.equals(Locale.GERMAN)) { + return "mit einem Verhältnis von zwischen 1 und 3"; + } else { + return "met een verhouding van tussen 1 en 3"; + } + } +} From 50c55d509473805bd50f941b265517d2aadaa671 Mon Sep 17 00:00:00 2001 From: jy95 Date: Sun, 5 Jan 2025 00:17:00 +0100 Subject: [PATCH 7/7] feat: RateRatio --- .../utils/functions/FormatDateTimes.java | 2 +- .../dosage/utils/functions/ListToString.java | 2 +- .../utils/functions/QuantityToString.java | 2 +- .../dosage/utils/functions/RangeToString.java | 2 +- .../dosage/utils/functions/RatioToString.java | 77 +++++++ .../utils/miscellaneous/Translators.java | 3 +- .../dosage/utils/translators/RateRatio.java | 42 ++++ src/main/resources/common_de.properties | 2 +- src/main/resources/common_en.properties | 2 +- src/main/resources/common_fr.properties | 2 +- src/main/resources/common_nl.properties | 2 +- .../utils/translators/RateRatioTest.java | 194 ++++++++++++++++++ 12 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/functions/RatioToString.java create mode 100644 src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRatio.java create mode 100644 src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRatioTest.java diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/FormatDateTimes.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/FormatDateTimes.java index 975484719..930d0c7ce 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/functions/FormatDateTimes.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/FormatDateTimes.java @@ -7,7 +7,7 @@ import java.util.Locale; import java.util.List; -public class FormatDateTimes { +public final class FormatDateTimes { public static String convert(Locale locale, DateTimeType date){ return DateTimeUtil.toHumanDisplay( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/ListToString.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/ListToString.java index b5c657649..646ec1deb 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/functions/ListToString.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/ListToString.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.ResourceBundle; -public class ListToString { +public final class ListToString { public enum LinkWord { AND("and"), diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java index 0bbe2fcc6..5f0c7b2d1 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/QuantityToString.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class QuantityToString { +public final class QuantityToString { public static CompletableFuture convert(ResourceBundle bundle, FDUConfig config, Quantity quantity) { var comparator = comparatorToString(bundle, config, quantity); var unit = unitToString(config, quantity); diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/RangeToString.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/RangeToString.java index c0f5e3670..b9afe8c53 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/functions/RangeToString.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/RangeToString.java @@ -11,7 +11,7 @@ import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; -public class RangeToString { +public final class RangeToString { final static String DURATION_SYSTEM = "http://hl7.org/fhir/ValueSet/duration-units"; diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/functions/RatioToString.java b/src/main/java/jy95/fhir/r4/dosage/utils/functions/RatioToString.java new file mode 100644 index 000000000..997a8411e --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/functions/RatioToString.java @@ -0,0 +1,77 @@ +package jy95.fhir.r4.dosage.utils.functions; + +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Ratio; + +import java.math.BigDecimal; +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class RatioToString { + public static CompletableFuture convert(ResourceBundle bundle, FDUConfig config, Ratio ratio) { + var linkword = retrieveRatioLinkWord(bundle, config, ratio); + + var numeratorText = ratio.hasNumerator() + ? QuantityToString.convert(bundle, config, ratio.getNumerator()) + : CompletableFuture.completedFuture(""); + + var denominatorText = ratio.hasDenominator() + ? turnDenominatorToText(bundle, config, ratio) + : CompletableFuture.completedFuture(""); + + return numeratorText.thenCombineAsync(denominatorText, (num, dem) -> Stream + .of(num, linkword, dem) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(" ")) + ); + } + + private static String retrieveRatioLinkWord(ResourceBundle bundle, FDUConfig config, Ratio ratio) { + var hasNumerator = ratio.hasNumerator(); + var hasDenominator = ratio.hasDenominator(); + var hasNumeratorUnit = hasNumerator && hasUnit(ratio.getNumerator()); + var hasBothElements = hasNumerator && hasDenominator; + var hasDenominatorUnit = hasDenominator && hasUnit(ratio.getDenominator()); + var hasUnitRatio = hasNumeratorUnit || hasDenominatorUnit; + var denominatorValue = hasDenominator ? ratio.getDenominator().getValue() : BigDecimal.ONE; + + if (hasUnitRatio && hasBothElements) { + var linkWordMsg = bundle.getString("amount.ratio.denominatorLinkword"); + return new MessageFormat(linkWordMsg, config.getLocale()).format(new Object[]{denominatorValue}); + } + + return hasBothElements ? ":" : ""; + } + + private static CompletableFuture turnDenominatorToText( + ResourceBundle bundle, + FDUConfig config, + Ratio ratio + ) { + var denominator = ratio.getDenominator(); + // Where the denominator value is known to be fixed to "1", Quantity should be used instead of Ratio + var denominatorValue = denominator.getValue(); + + // For titers cases (e.g. 1:128) + if (!hasUnit(denominator)) { + return CompletableFuture.completedFuture(denominatorValue.toString()); + } + + // For the per case + if (BigDecimal.ONE.equals(denominatorValue)) { + return config.getFromFHIRQuantityUnitToString().apply(denominator); + } + + return QuantityToString.convert(bundle, config, denominator); + } + + // See if unit (code or text) could be found in quantity + private static boolean hasUnit(Quantity quantity) { + return quantity.hasUnit() || quantity.hasCode(); + } +} diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java index c46845d5c..e58ba848d 100644 --- a/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java +++ b/src/main/java/jy95/fhir/r4/dosage/utils/miscellaneous/Translators.java @@ -48,7 +48,8 @@ public Translators(FDUConfig config) { Map.entry(DisplayOrder.DOSE_QUANTITY, new DoseQuantity(config)), Map.entry(DisplayOrder.DOSE_RANGE, new DoseRange(config)), Map.entry(DisplayOrder.RATE_QUANTITY, new RateQuantity(config)), - Map.entry(DisplayOrder.RATE_RANGE, new RateRange(config)) + Map.entry(DisplayOrder.RATE_RANGE, new RateRange(config)), + Map.entry(DisplayOrder.RATE_RATIO, new RateRatio(config)) ) ); this.bundleControl = new MultiResourceBundleControl( diff --git a/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRatio.java b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRatio.java new file mode 100644 index 000000000..ce7ab0571 --- /dev/null +++ b/src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRatio.java @@ -0,0 +1,42 @@ +package jy95.fhir.r4.dosage.utils.translators; + +import com.ibm.icu.text.MessageFormat; +import jy95.fhir.r4.dosage.utils.classes.AbstractTranslator; +import jy95.fhir.r4.dosage.utils.config.FDUConfig; +import jy95.fhir.r4.dosage.utils.functions.RatioToString; +import jy95.fhir.r4.dosage.utils.types.DoseAndRateKey; +import org.hl7.fhir.r4.model.Dosage; +import org.hl7.fhir.r4.model.Ratio; + +import java.util.concurrent.CompletableFuture; + +public class RateRatio extends AbstractTranslator { + + public RateRatio(FDUConfig config) { + super(config); + } + + @Override + public CompletableFuture convert(Dosage dosage) { + var bundle = getResources(); + var doseAndRate = dosage.getDoseAndRate(); + var rateRatio = getConfig() + .getSelectDosageAndRateField() + .apply(doseAndRate, DoseAndRateKey.RATE_RATIO); + + return RatioToString + .convert(bundle, getConfig(), (Ratio) rateRatio) + .thenApplyAsync(rateRatioText -> { + var doseRateMsg = bundle.getString("fields.rateRatio"); + return new MessageFormat(doseRateMsg, getConfig().getLocale()).format(new Object[]{rateRatioText}); + }); + } + + @Override + public boolean isPresent(Dosage dosage) { + return dosage.hasDoseAndRate() && dosage + .getDoseAndRate() + .stream() + .anyMatch(Dosage.DosageDoseAndRateComponent::hasRateRatio); + } +} diff --git a/src/main/resources/common_de.properties b/src/main/resources/common_de.properties index 6942c3839..7bc08de7d 100644 --- a/src/main/resources/common_de.properties +++ b/src/main/resources/common_de.properties @@ -4,7 +4,7 @@ linkwords.then = dann amount.range.withUnit = {condition, select, 0{zwischen {minValue} und {maxValue} {unit}} 1{bis {maxValue} {unit}} other{mindestens {minValue} {unit}}} amount.range.withoutUnit = {condition, select, 0{zwischen {minValue} und {maxValue}} 1{bis {maxValue}} other{mindestens {minValue}}} -amount.ratio.denominatorLinkword = {condition, select, 1{pro} other{jeder}} +amount.ratio.denominatorLinkword = {0, choice, 1#pro|1.0