Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: doseAndRate translations #2

Merged
merged 7 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading