Skip to content

Commit

Permalink
Merge pull request #2 from jy95/doseAndRate
Browse files Browse the repository at this point in the history
feat: doseAndRate translations
  • Loading branch information
jy95 authored Jan 4, 2025
2 parents 5bdf2f8 + 50c55d5 commit 964dd68
Show file tree
Hide file tree
Showing 23 changed files with 914 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -85,4 +84,12 @@ public static CompletableFuture<String> fromExtensionsToString(List<Extension> e
.collect(Collectors.joining(", ", "[", "]"));
});
}

public static Type selectDosageAndRateField(
List<Dosage.DosageDoseAndRateComponent> 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());
}
}
15 changes: 12 additions & 3 deletions src/main/java/jy95/fhir/r4/dosage/utils/config/FDUConfig.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -71,4 +72,12 @@ public class FDUConfig {
* The choice to handle national extensions, ... is thus under the hands of people ;)
*/
@Builder.Default private Function<List<Extension>, CompletableFuture<String>> 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 <a href="http://terminology.hl7.org/ValueSet/dose-rate-type">DoseAndRateType</a>
*/
@Builder.Default private BiFunction<List<DosageDoseAndRateComponent>, DoseAndRateKey, Type> selectDosageAndRateField = DefaultImplementations::selectDosageAndRateField;
}
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
@@ -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 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);
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<String> 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<String> unitToString(FDUConfig config, Quantity quantity) {
if (hasUnit(quantity)) {
return config.getFromFHIRQuantityUnitToString().apply(quantity);
}
return CompletableFuture.completedFuture("");
}
}
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 @@ -44,7 +44,12 @@ 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)),
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_RATIO, new RateRatio(config))
)
);
this.bundleControl = new MultiResourceBundleControl(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 DoseQuantity extends AbstractTranslator {

public DoseQuantity(FDUConfig config) {
super(config);
}

@Override
public CompletableFuture<String> 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)
.thenApplyAsync(quantityText -> {
var doseMsg = bundle.getString("fields.doseQuantity");
return new MessageFormat(doseMsg, getConfig().getLocale()).format(new Object[]{quantityText});
});
}

@Override
public boolean isPresent(Dosage dosage) {
return dosage.hasDoseAndRate() && dosage
.getDoseAndRate()
.stream()
.anyMatch(Dosage.DosageDoseAndRateComponent::hasDoseQuantity);
}
}
42 changes: 42 additions & 0 deletions src/main/java/jy95/fhir/r4/dosage/utils/translators/DoseRange.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.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<String> 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);
}
}
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.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<String> 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);
}

}
42 changes: 42 additions & 0 deletions src/main/java/jy95/fhir/r4/dosage/utils/translators/RateRange.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.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<String> 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);
}
}
Loading

0 comments on commit 964dd68

Please sign in to comment.