Skip to content

Commit

Permalink
High dosage alert (IHTSDO#11)
Browse files Browse the repository at this point in the history
* WIP high dosage alert

* fixed calculation for daily dosage to use BigDecimals datatypes

* Vijay, David | fix for warning >=4

* Vijay, David | remove from app.props

---------

Co-authored-by: daviemukungi <mukungidavid@gmail.com>
  • Loading branch information
vijayanandtwks and daviemukungi committed Nov 9, 2023
1 parent d17459a commit 68ace49
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 26 deletions.
17 changes: 10 additions & 7 deletions src/main/java/org/snomed/cdsservice/model/PrescribedDailyDose.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package org.snomed.cdsservice.model;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class PrescribedDailyDose {
Double quantity;
BigDecimal quantity;
String unit;

public PrescribedDailyDose(Double quantity, String unit) {
this.quantity = quantity;
public PrescribedDailyDose(BigDecimal quantity, String unit) {
this.quantity = quantity.setScale(5, RoundingMode.HALF_UP);
this.unit = unit;
}

public Double getQuantity() {
public BigDecimal getQuantity() {
return quantity;
}

public void setQuantity(Double quantity) {
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}

Expand All @@ -25,8 +28,8 @@ public void setUnit(String unit) {
this.unit = unit;
}

public void addQuantity(Double newQuantity) {
this.quantity += newQuantity;
public void addQuantity(BigDecimal newQuantity) {
this.quantity = this.quantity.add(newQuantity);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
Expand Down Expand Up @@ -197,7 +198,7 @@ private PrescribedDailyDose getMedicationQuantityPerDay(Quantity doseQuantity, F
int frequencyCount = doseFrequency.getFrequencyCount();
int periodCount = doseFrequency.getPeriodCount();
String periodUnit = doseFrequency.getPeriodUnit();
Double dailyDoseQuantity = calculateTotalDosePerDay(doseQuantityValue, frequencyCount, periodCount, periodUnit);
BigDecimal dailyDoseQuantity = calculateTotalDosePerDay(doseQuantityValue, frequencyCount, periodCount, periodUnit);
return new PrescribedDailyDose(dailyDoseQuantity, doseQuantity.getUnit());
}

Expand All @@ -214,7 +215,7 @@ private Dosage getDosagefromMedicationRequest(MedicationRequest medicationReques
return dosage;
}

private Double calculateTotalDosePerDay(BigDecimal doseQuantityValue, int frequencyCount, int periodCount, String periodUnit) {
private BigDecimal calculateTotalDosePerDay(BigDecimal doseQuantityValue, int frequencyCount, int periodCount, String periodUnit) {
//calculate number of times per day
BigDecimal timesPerDay = new BigDecimal(0);
if (periodUnit.equals(Timing.UnitsOfTime.H.getDisplay())) {
Expand All @@ -226,7 +227,7 @@ private Double calculateTotalDosePerDay(BigDecimal doseQuantityValue, int freque
}
//calculate total dose per day
BigDecimal totalDosePerDay = timesPerDay.multiply(doseQuantityValue);
return totalDosePerDay.doubleValue();
return totalDosePerDay;
}

private void updateTotalPrescribedDailyDose(AggregatedMedicationsBySubstance aggregatedMedicationsBySubstance, PrescribedDailyDose prescribedDailyDoseInUnitOfDDD, String routeOfAdministrationCode, SubstanceDefinedDailyDose substanceDefinedDailyDose, String routeOfAdministration) {
Expand Down Expand Up @@ -307,11 +308,11 @@ private void aggregateMedicationsBySubstance(Map<String, AggregatedMedicationsBy
void composeDosageAlerts(Map<String, AggregatedMedicationsBySubstance> aggregatedMedicationsBySubstanceMap, List<CDSCard> cards) {
for (var aggregatedMedicationsBySubstanceEntry : aggregatedMedicationsBySubstanceMap.entrySet()) {
String substanceName = aggregatedMedicationsBySubstanceEntry.getValue().getSubstanceShortName();
Double prescribedDosageFactor = getPrescribedDosageFactor(aggregatedMedicationsBySubstanceEntry.getValue().getDosageComparisonByRouteMap());
BigDecimal prescribedDosageFactor = (getPrescribedDosageFactor(aggregatedMedicationsBySubstanceEntry.getValue().getDosageComparisonByRouteMap()));
String alertLevelIndicator = getAlertIndicator(prescribedDosageFactor);
if (StringUtils.isNotBlank(alertLevelIndicator)) {
UUID randomUuid = UUID.fromString(UUID.nameUUIDFromBytes(substanceName.getBytes()).toString());
String cardSummaryMsg = String.format(getCardSummaryTemplate(), substanceName, getDynamicDecimalPlace(prescribedDosageFactor));
String cardSummaryMsg = String.format(getCardSummaryTemplate(), substanceName, prescribedDosageFactor.stripTrailingZeros().toPlainString());
String cardDetailMsg = getCardDetailsInMarkDown(aggregatedMedicationsBySubstanceEntry.getValue(), prescribedDosageFactor);
String atcUrl = getAtcUrl(aggregatedMedicationsBySubstanceEntry);
CDSCard cdsCard = new CDSCard(randomUuid.toString(), cardSummaryMsg, cardDetailMsg, CDSIndicator.valueOf(alertLevelIndicator), new CDSSource("WHO ATC DDD", atcUrl), aggregatedMedicationsBySubstanceEntry.getValue().getReferenceList(), null, HIGH_DOSAGE_ALERT_TYPE);
Expand All @@ -329,20 +330,21 @@ private String getAtcUrl(Map.Entry<String, AggregatedMedicationsBySubstance> sub
return MessageFormat.format(atcUrlTemplate, dosageComparisonByRoute.getSubstanceDefinedDailyDose().atcCode());
}

private String getAlertIndicator(double prescribedDosageFactor) {
private String getAlertIndicator(BigDecimal prescribedDosageFactor) {
String alertLevel = null;
if (prescribedDosageFactor >= Double.parseDouble(maximumDailyDoseThresholdFactor)) {
if (prescribedDosageFactor.compareTo(new BigDecimal(maximumDailyDoseThresholdFactor)) >= 0) {
alertLevel = WARNING;
} else if (prescribedDosageFactor >= Double.parseDouble(acceptableDailyDoseThresholdFactor)) {
} else if (prescribedDosageFactor.compareTo(new BigDecimal(acceptableDailyDoseThresholdFactor)) >=0) {
alertLevel = INFO;
}
return alertLevel;
}

private Double getPrescribedDosageFactor(Map<String, DosageComparisonByRoute> dosageComparisonByRouteMap) {
Double prescribedDosageFactor = Double.valueOf(0);
private BigDecimal getPrescribedDosageFactor(Map<String, DosageComparisonByRoute> dosageComparisonByRouteMap) {
BigDecimal prescribedDosageFactor = BigDecimal.ZERO;
for (var eachRoute : dosageComparisonByRouteMap.entrySet()) {
prescribedDosageFactor = prescribedDosageFactor + (eachRoute.getValue().getTotalPrescribedDailyDose().getQuantity() / eachRoute.getValue().getSubstanceDefinedDailyDose().dose());
BigDecimal eachRoutePrescribedDosageFactor = (eachRoute.getValue().getTotalPrescribedDailyDose().getQuantity()).divide(new BigDecimal(eachRoute.getValue().getSubstanceDefinedDailyDose().dose()), 2, RoundingMode.HALF_UP);
prescribedDosageFactor = prescribedDosageFactor.add(eachRoutePrescribedDosageFactor);
}
return prescribedDosageFactor;
}
Expand All @@ -359,12 +361,12 @@ private void addMedications(AggregatedMedicationsBySubstance aggregatedMedicatio
}

private PrescribedDailyDose getPrescribedDailyDoseInUnitOfSubstanceStrength(PrescribedDailyDose prescribedDailyDose, String strengthValue, String strengthUnit, String denominatorValue, String denominatorUnit) {
Double prescribedDoseQuantity = prescribedDailyDose.getQuantity();
BigDecimal prescribedDoseQuantity = prescribedDailyDose.getQuantity();
String prescribedDisplayUnit = prescribedDailyDose.getUnit();

String strengthDisplayUnit = getSnomedParameterValue(strengthUnit, "display");
String denominatorDisplayUnit = getSnomedParameterValue(denominatorUnit, "display");
double prescribedDoseQuantityInUnitOfSubstanceStrength = prescribedDoseQuantity * Double.parseDouble(strengthValue) / Double.parseDouble(denominatorValue) * UnitConversion.factorOfConversion(prescribedDisplayUnit, denominatorDisplayUnit);
BigDecimal prescribedDoseQuantityInUnitOfSubstanceStrength = prescribedDoseQuantity.multiply((new BigDecimal(strengthValue)).divide(new BigDecimal(denominatorValue))).multiply(new BigDecimal(UnitConversion.factorOfConversion(prescribedDisplayUnit, denominatorDisplayUnit)));
return new PrescribedDailyDose(prescribedDoseQuantityInUnitOfSubstanceStrength, strengthDisplayUnit);
}

Expand All @@ -373,24 +375,24 @@ private String getSnomedParameterValue(String snomedCode, String parameterName)
return conceptParameters.getParameter(parameterName).getValue().toString();
}

private PrescribedDailyDose getPrescribedDailyDoseInUnitOfDDD(Double inputStrengthValue, String inputStrengthUnit, String targetStrengthUnit) {
Double prescribedDailyDoseQuantity = inputStrengthValue * UnitConversion.factorOfConversion(inputStrengthUnit, targetStrengthUnit);
private PrescribedDailyDose getPrescribedDailyDoseInUnitOfDDD(BigDecimal inputStrengthValue, String inputStrengthUnit, String targetStrengthUnit) {
BigDecimal prescribedDailyDoseQuantity = inputStrengthValue.multiply(new BigDecimal(UnitConversion.factorOfConversion(inputStrengthUnit, targetStrengthUnit)));
return new PrescribedDailyDose(prescribedDailyDoseQuantity, targetStrengthUnit);
}

private String getCardSummaryTemplate() {
return cardSummaryTemplate;
}

private String getCardDetailsInMarkDown(AggregatedMedicationsBySubstance aggregatedMedicationsBySubstance, Double dosageWeight) {
private String getCardDetailsInMarkDown(AggregatedMedicationsBySubstance aggregatedMedicationsBySubstance, BigDecimal dosageWeight) {
StringBuilder sb = new StringBuilder();
sb.append(new Text("Substance : " + aggregatedMedicationsBySubstance.getSubstanceShortName())).append(NEW_LINE).append(NEW_LINE);
sb.append(new Text("Present in this patient’s medication :")).append(NEW_LINE);
sb.append(new UnorderedList<>(aggregatedMedicationsBySubstance.getMedicationsList())).append(NEW_LINE);
sb.append(NEW_LINE).append(new Text("Route of administration :")).append(NEW_LINE);
Map<String, DosageComparisonByRoute> dosageMapByRoute = aggregatedMedicationsBySubstance.getDosageComparisonByRouteMap();
composeDosageByRoute(dosageMapByRoute, sb);
sb.append(new Text("Conclusion : Combined prescribed amount is " + getDecimalPlace(dosageWeight) + " times the average daily dose."));
sb.append(new Text("Conclusion : Combined prescribed amount is " + dosageWeight + " times the average daily dose."));
return sb.toString();
}

Expand All @@ -399,7 +401,7 @@ private void composeDosageByRoute(Map<String, DosageComparisonByRoute> dosageMap
DosageComparisonByRoute dosageComparisonByRouteValue = dosageInfo.getValue();
PrescribedDailyDose aggregatedDailyDosage = dosageComparisonByRouteValue.getTotalPrescribedDailyDose();
SubstanceDefinedDailyDose substanceDefinedDailyDose = dosageComparisonByRouteValue.getSubstanceDefinedDailyDose();
sb.append(new UnorderedList<>(List.of(dosageComparisonByRouteValue.getRouteOfAdministration(), new UnorderedList<>(List.of("Prescribed daily dose : " + getDecimalPlace(aggregatedDailyDosage.getQuantity()) + aggregatedDailyDosage.getUnit(), "Recommended average daily dose : " + substanceDefinedDailyDose.dose() + substanceDefinedDailyDose.unit(), "Prescribed amount is " + getDecimalPlace(aggregatedDailyDosage.getQuantity() / substanceDefinedDailyDose.dose()) + " times over the average daily dose"))))).append(NEW_LINE).append(NEW_LINE);
sb.append(new UnorderedList<>(List.of(dosageComparisonByRouteValue.getRouteOfAdministration(), new UnorderedList<>(List.of("Prescribed daily dose : " + formatToTwoDecimalPlaces(aggregatedDailyDosage.getQuantity()) + aggregatedDailyDosage.getUnit(), "Recommended average daily dose : " + substanceDefinedDailyDose.dose() + substanceDefinedDailyDose.unit(), "Prescribed amount is " + (aggregatedDailyDosage.getQuantity()).divide(new BigDecimal(substanceDefinedDailyDose.dose()),0, RoundingMode.HALF_UP) + " times over the average daily dose"))))).append(NEW_LINE).append(NEW_LINE);
}
}

Expand All @@ -416,6 +418,10 @@ private String getDynamicDecimalPlace(Double value) {
return decimalFormat.format(value);
}

private BigDecimal formatToTwoDecimalPlaces(BigDecimal value){
return value.setScale(2, RoundingMode.HALF_UP);
}

public void setDoseFormsManySnomedToOneAtcCodeMap(List<ManyToOneMapEntry> mapEntries) {
this.doseFormsManySnomedToOneAtcCodeMap = mapEntries;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.snomed.cdsservice.service.medication.dose;

import java.math.BigDecimal;

public record SubstanceDefinedDailyDose(String atcRouteOfAdministration, float dose, String unit, String atcCode) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public void shouldReturnOverDoseWarningAlert_WhenPrescribedDailyDoseExceedsMaxim
cdsRequest.setPrefetchStrings(Map.of(
"patient", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/PatientResource.json"), StandardCharsets.UTF_8),
"conditions", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/ConditionBundle.json"), StandardCharsets.UTF_8),
"draftMedicationRequests", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/MedicationRequestBundleWithWarningOverDose.json"), StandardCharsets.UTF_8)
"draftMedicationRequests", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/MedicationRequestBundleWithWarningExceedsOverDose.json"), StandardCharsets.UTF_8)
));

List<CDSCard> cards = service.call(cdsRequest);
Expand All @@ -143,6 +143,27 @@ public void shouldReturnOverDoseWarningAlert_WhenPrescribedDailyDoseExceedsMaxim
assertEquals(HIGH_DOSAGE_ALERT_TYPE, cdsCard.getAlertType());
}

@Test
public void shouldReturnOverDoseWarningAlert_WhenPrescribedDailyDoseEqualsMaximumThresholdFactor() throws IOException {
CDSRequest cdsRequest = new CDSRequest();
cdsRequest.setPrefetchStrings(Map.of(
"patient", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/PatientResource.json"), StandardCharsets.UTF_8),
"conditions", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/ConditionBundle.json"), StandardCharsets.UTF_8),
"draftMedicationRequests", StreamUtils.copyToString(getClass().getResourceAsStream("/medication-order-select/MedicationRequestBundleWithWarningEqualsOverDose.json"), StandardCharsets.UTF_8)
));

List<CDSCard> cards = service.call(cdsRequest);
assertEquals(1, cards.size());

CDSCard cdsCard = cards.get(0);
assertEquals("The amount of Ranitidine prescribed is 4 times the average daily dose.", cdsCard.getSummary());
assertEquals(CDSIndicator.warning, cdsCard.getIndicator());
assertTrue( cdsCard.getDetail().contains("Conclusion : Combined prescribed amount is 4.00 times the average daily dose."));
assertEquals("317249006", cdsCard.getReferenceMedications().get(0).getCoding().get(0).getCode());
assertTrue(cdsCard.getSource().getUrl().contains("https://www.whocc.no/atc_ddd_index/?code=A02BA02"));
assertEquals(HIGH_DOSAGE_ALERT_TYPE, cdsCard.getAlertType());
}

@Test
public void shouldReturnOverDoseInfoAlert_WhenPrescribedDailyDoseExceedsAcceptableThresholdFactor() throws IOException {
CDSRequest cdsRequest = new CDSRequest();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"resourceType": "Bundle",
"id": "be826c54-b905-40fb-bd90-c147a63ae4e8",
"entry": [
{
"fullUrl": "https://r4.smarthealthit.org/MedicationRequest/5180e54d-0187-494a-9ca9-9dbfcab90ab6",
"resource": {
"resourceType": "MedicationRequest",
"id": "5180e54d-0187-494a-9ca9-9dbfcab90ab6",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "317249006",
"display": "Ranitidine (as ranitidine hydrochloride) 150 mg oral tablet"
},
{
"system": "http://bahmni.org/cds",
"code": "36939d4e-d325-4819-9c81-91e1a0434f9a",
"display": "Ranitidine (as ranitidine hydrochloride) 150 mg oral tablet"
}
],
"text": "Ranitidine (as ranitidine hydrochloride) 150 mg oral tablet"
},
"subject": {
"reference": "Patient/618b2992-eec7-45c9-8544-12c9f586b78c"
},
"encounter": {
"reference": "Encounter/8f1950d4-13cc-4ea5-a9d0-855a4f4b9180"
},
"authoredOn": "2021-01-15T04:59:42+00:00",
"requester": {
"reference": "Practitioner/96333652-ed28-41d3-bb60-d435f478c8ed"
},
"reasonReference": [
{
"reference": "Condition/e21522b1-8006-4723-8b8d-bda8a575b87e"
}
],
"dosageInstruction": [
{
"sequence": 1,
"timing": {
"repeat": {
"frequency": 1,
"period": 3,
"periodUnit": "h"
}
},
"asNeededBoolean": false,
"doseAndRate": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/dose-rate-type",
"code": "ordered",
"display": "Ordered"
}
]
},
"doseQuantity": {
"value": 1,
"unit": "Tablet"
}
}
]
}
]
},
"search": {
"mode": "match"
},
"response": {
"status": "200 OK",
"etag": "W/\"4\""
}
}
]
}

0 comments on commit 68ace49

Please sign in to comment.