Skip to content

Commit

Permalink
feat: RateRatio
Browse files Browse the repository at this point in the history
  • Loading branch information
jy95 committed Jan 4, 2025
1 parent 43af5da commit 50c55d5
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.List;
import java.util.ResourceBundle;

public class ListToString {
public final class ListToString {

public enum LinkWord {
AND("and"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class QuantityToString {
public final class QuantityToString {
public static CompletableFuture<String> convert(ResourceBundle bundle, FDUConfig config, Quantity quantity) {
var comparator = comparatorToString(bundle, config, quantity);
var unit = unitToString(config, quantity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRatio.java
Original file line number Diff line number Diff line change
@@ -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<String> 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);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/common_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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<jeder}

fields.doseQuantity = {0}
fields.doseRange = {0}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/common_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ linkwords.then = then
amount.range.withUnit = {condition, select, 0{{minValue} to {maxValue} {unit}} 1{up to {maxValue} {unit}} other{at least {minValue} {unit}}}
amount.range.withoutUnit = {condition, select, 0{{minValue} to {maxValue}} 1{up to {maxValue}} other{at least {minValue}}}

amount.ratio.denominatorLinkword = {condition, select, 1{per} other{every}}
amount.ratio.denominatorLinkword = {0, choice, 1#per|1.0<every}

fields.doseQuantity = {0}
fields.doseRange = {0}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/common_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ linkwords.then = puis
amount.range.withUnit = {condition, select, 0{{minValue} à {maxValue} {unit}} 1{{maxValue} {unit} maximum} other{au moins {minValue} {unit}}}
amount.range.withoutUnit = {condition, select, 0{{minValue} à {maxValue}} 1{{maxValue} maximum} other{au moins {minValue}}}

amount.ratio.denominatorLinkword = {condition, select, 1{par} other{chaque}}
amount.ratio.denominatorLinkword = {0, choice, 1#par|1.0<chaque}

fields.doseQuantity = {0}
fields.doseRange = {0}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/common_nl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ linkwords.then = vervolgens
amount.range.withUnit = {condition, select, 0{tussen {minValue} en {maxValue} {unit}} 1{tot {maxValue} {unit}} other{minstens {minValue} {unit}}}
amount.range.withoutUnit = {condition, select, 0{tussen {minValue} en {maxValue}} 1{tot {maxValue}} other{minstens {minValue}}}

amount.ratio.denominatorLinkword = {condition, select, 1{per} other{elke}}
amount.ratio.denominatorLinkword = {0, choice, 1#per|1.0<elke}

fields.doseQuantity = {0}
fields.doseRange = {0}
Expand Down
194 changes: 194 additions & 0 deletions src/test/java/jy95/fhir/r4/dosage/utils/translators/RateRatioTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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.Ratio;
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 RateRatioTest extends AbstractFhirTest {

@ParameterizedTest
@MethodSource("localeProvider")
void testNoRateRatio(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
assertEquals("", result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testEmptyRateRatio(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
assertEquals("", result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testOnlyNumerator(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Quantity numerator = new Quantity(10);
numerator.setUnit("ml");
ratio.setNumerator(numerator);
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
String expected = getExpectedText1(locale);
assertEquals(expected, result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testOnlyDenominator(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Quantity denominator = new Quantity(10);
denominator.setUnit("ml");
ratio.setDenominator(denominator);
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
String expected = getExpectedText2(locale);
assertEquals(expected, result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testNumeratorAndDenominator(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Quantity numerator = new Quantity(10);
numerator.setUnit("mg");
ratio.setNumerator(numerator);
Quantity denominator = new Quantity(1);
denominator.setUnit("ml");
ratio.setDenominator(denominator);
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
String expected = getExpectedText3(locale);
assertEquals(expected, result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testTiterCase(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Quantity numerator = new Quantity(1);
ratio.setNumerator(numerator);
Quantity denominator = new Quantity(128);
ratio.setDenominator(denominator);
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
String expected = getExpectedText4(locale);
assertEquals(expected, result);
}

@ParameterizedTest
@MethodSource("localeProvider")
void testCommon(Locale locale) throws ExecutionException, InterruptedException {
Dosage dosage = new Dosage();
Ratio ratio = new Ratio();
Quantity numerator = new Quantity(10);
numerator.setUnit("mg");
ratio.setNumerator(numerator);
Quantity denominator = new Quantity(2);
denominator.setUnit("ml");
ratio.setDenominator(denominator);
Dosage.DosageDoseAndRateComponent doseAndRateComponent1 = new Dosage.DosageDoseAndRateComponent();
doseAndRateComponent1.setRate(ratio);
dosage.setDoseAndRate(List.of(doseAndRateComponent1));
FhirDosageUtils dosageUtils = DayOfWeekTest.getDosageUtilsInstance(locale, DisplayOrder.RATE_RATIO);
String result = dosageUtils.asHumanReadableText(dosage).get();
String expected = getExpectedText5(locale);
assertEquals(expected, result);
}

private String getExpectedText1(Locale locale) {
if (locale.equals(Locale.ENGLISH)) {
return "at a rate of 10 ml";
} else if (locale.equals(Locale.FRENCH)) {
return "au taux de 10 ml";
} else if (locale.equals(Locale.GERMAN)) {
return "mit einem Verhältnis von 10 ml";
} else {
return "met een verhouding van 10 ml";
}
}

private String getExpectedText2(Locale locale) {
if (locale.equals(Locale.ENGLISH)) {
return "at a rate of 10 ml";
} else if (locale.equals(Locale.FRENCH)) {
return "au taux de 10 ml";
} else if (locale.equals(Locale.GERMAN)) {
return "mit einem Verhältnis von 10 ml";
} else {
return "met een verhouding van 10 ml";
}
}

private String getExpectedText3(Locale locale) {
if (locale.equals(Locale.ENGLISH)) {
return "at a rate of 10 mg per ml";
} else if (locale.equals(Locale.FRENCH)) {
return "au taux de 10 mg par ml";
} else if (locale.equals(Locale.GERMAN)) {
return "mit einem Verhältnis von 10 mg pro ml";
} else {
return "met een verhouding van 10 mg per ml";
}
}

private String getExpectedText4(Locale locale) {
if (locale.equals(Locale.ENGLISH)) {
return "at a rate of 1 : 128";
} else if (locale.equals(Locale.FRENCH)) {
return "au taux de 1 : 128";
} else if (locale.equals(Locale.GERMAN)) {
return "mit einem Verhältnis von 1 : 128";
} else {
return "met een verhouding van 1 : 128";
}
}

private String getExpectedText5(Locale locale) {
if (locale.equals(Locale.ENGLISH)) {
return "at a rate of 10 mg every 2 ml";
} else if (locale.equals(Locale.FRENCH)) {
return "au taux de 10 mg chaque 2 ml";
} else if (locale.equals(Locale.GERMAN)) {
return "mit einem Verhältnis von 10 mg jeder 2 ml";
} else {
return "met een verhouding van 10 mg elke 2 ml";
}
}

}

0 comments on commit 50c55d5

Please sign in to comment.