com.apothesource.pillfill.service.drug.impl.DefaultDrugAlertServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.apothesource.pillfill.service.drug.impl.DefaultDrugAlertServiceImpl.java

Source

/*
 *
 *  * The MIT License
 *  *
 *  * Copyright {$YEAR} Apothesource, Inc.
 *  *
 *  * Permission is hereby granted, free of charge, to any person obtaining a copy
 *  * of this software and associated documentation files (the "Software"), to deal
 *  * in the Software without restriction, including without limitation the rights
 *  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  * copies of the Software, and to permit persons to whom the Software is
 *  * furnished to do so, subject to the following conditions:
 *  *
 *  * The above copyright notice and this permission notice shall be included in
 *  * all copies or substantial portions of the Software.
 *  *
 *  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  * THE SOFTWARE.
 *
 */
package com.apothesource.pillfill.service.drug.impl;

/**
 * Created by Michael Ramirez on 6/1/15. Copyright 2015, Apothesource, Inc. All Rights Reserved.
 */

import com.apothesource.pillfill.datamodel.DrugAlertType;
import com.apothesource.pillfill.datamodel.MRTDCalculation;
import com.apothesource.pillfill.datamodel.PrescriptionType;
import com.apothesource.pillfill.datamodel.rxnorm.interaction.FullInteractionType;
import com.apothesource.pillfill.datamodel.rxnorm.interaction.FullInteractionTypeGroup;
import com.apothesource.pillfill.datamodel.rxnorm.interaction.InteractionConcept;
import com.apothesource.pillfill.datamodel.rxnorm.interaction.InteractionPair;
import com.apothesource.pillfill.datamodel.rxnorm.interaction.MinConceptItem;
import com.apothesource.pillfill.network.PFNetworkManager;
import com.apothesource.pillfill.service.cache.CacheService;
import com.apothesource.pillfill.service.cache.impl.DefaultNoOpCacheServiceImpl;
import com.apothesource.pillfill.service.drug.DrugAlertService;
import com.apothesource.pillfill.service.PFServiceEndpoints;
import static com.apothesource.pillfill.utilites.ReactiveUtils.subscribeIoObserveImmediate;
import com.google.common.base.Joiner;

import com.google.common.base.Optional;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import rx.Observable;

public class DefaultDrugAlertServiceImpl implements DrugAlertService {
    public static final Type ALERT_LIST_TYPE = new TypeToken<ArrayList<DrugAlertType>>() {
    }.getType();
    public static final Type MRTD_LIST_TYPE = new TypeToken<ArrayList<MRTDCalculation>>() {
    }.getType();
    private static final float MRTD_OVER_LOAD_THRESHOLD = 100;
    private static final float MRTD_HIGH_LOAD_THRESHOLD = 90;
    private static final Gson gson = new Gson();

    private static final Logger log = Logger.getLogger("DrugAlertSvcImpl");
    private final OkHttpClient externalHttpClient;
    private CacheService cache;

    public DefaultDrugAlertServiceImpl() {
        externalHttpClient = new OkHttpClient();
        cache = new DefaultNoOpCacheServiceImpl();
    }

    private List<DrugAlertType> getDrugAlertsFromMrtdCalculations(Collection<MRTDCalculation> mrtds,
            float weightInKgs) {
        ArrayList<DrugAlertType> retList = new ArrayList<>();

        for (MRTDCalculation mrtd : mrtds) {
            DrugAlertType dat = new DrugAlertType();
            dat.setType("MRTD");
            dat.setKey("MRTD:" + mrtd.unii + ":" + mrtd.currentLoad);
            if (mrtd.relatedRxs != null) {
                dat.setRelatedRxIds(mrtd.relatedRxs);
            }
            if (mrtd.currentLoad < MRTD_OVER_LOAD_THRESHOLD && mrtd.currentLoad > MRTD_HIGH_LOAD_THRESHOLD) {
                dat.setTitle(ResourceBundle.getBundle("mrtd-messages").getString("highDrugDoseTitle"));
                dat.setCritical(false);
                dat.setReason(Collections.singletonList(
                        String.format(ResourceBundle.getBundle("mrtd-messages").getString("highDrugDoseMessage"),
                                (int) weightInKgs)));
                retList.add(dat);
            } else if (mrtd.currentLoad > MRTD_OVER_LOAD_THRESHOLD) {
                dat.setKey("MRTD:" + mrtd.unii + ":" + mrtd.currentLoad);
                dat.setTitle(ResourceBundle.getBundle("mrtd-messages").getString("potentialOverdoseTitle"));
                dat.setCritical(true);
                dat.setReason(Collections.singletonList(String.format(
                        ResourceBundle.getBundle("mrtd-messages").getString("potentialOverdoseMessage"),
                        (int) weightInKgs)));
                retList.add(dat);
            }
            dat.setResolution(Collections
                    .singletonList(ResourceBundle.getBundle("mrtd-messages").getString("resolutionBody")));
        }
        return retList;
    }

    private String getMrtdUrlString(Collection<PrescriptionType> activeRxList, float weightInKgs) {
        return String.format(PFServiceEndpoints.MRTD_ALERT_URL,
                Joiner.on(",").join(
                        Observable.from(activeRxList).map(PrescriptionType::getUuid).toBlocking().toIterable()),
                ((int) weightInKgs));
    }

    @Override
    public Observable<DrugAlertType> checkForAllAlerts(Collection<PrescriptionType> activeRxList,
            float weightInKgs) {
        return subscribeIoObserveImmediate(
                Observable.merge(Arrays.asList(checkForDrugDoseWarnings(activeRxList, weightInKgs),
                        checkForDrugAlerts(activeRxList), checkForRxNormInteractions(activeRxList))));

    }

    @Override
    public Observable<DrugAlertType> checkForDrugDoseWarnings(Collection<PrescriptionType> activeRxList,
            final float weightInKgs) {
        return subscribeIoObserveImmediate(Observable.create(subscriber -> {
            if (!activeRxList.isEmpty()) {
                final String urlStr = getMrtdUrlString(activeRxList, weightInKgs);
                Optional<String> cachedResponse = cache.getCachedData(urlStr);
                if (cachedResponse.isPresent()) {
                    try {
                        List<MRTDCalculation> mrtds = gson.fromJson(cachedResponse.get(), MRTD_LIST_TYPE);
                        Observable.from(getDrugAlertsFromMrtdCalculations(mrtds, weightInKgs))
                                .forEach(subscriber::onNext);
                        subscriber.onCompleted();
                        return;
                    } catch (Exception e) {
                        log.warning("Error handling cached MRTD response. Failing back to server.");
                    }
                }

                log.fine("Checking for drug alerts to URL: " + urlStr);
                try {
                    OkHttpClient client = PFNetworkManager.getPinnedPFHttpClient();
                    Request req = new Request.Builder().url(urlStr).build();
                    log.fine("Requesting MRTD calculations from server: " + urlStr);

                    String response = client.newCall(req).execute().body().string();
                    cache.setCachedData(urlStr, response);
                    List<MRTDCalculation> mrtds = gson.fromJson(response, MRTD_LIST_TYPE);
                    Observable.from(getDrugAlertsFromMrtdCalculations(mrtds, weightInKgs))
                            .forEach(subscriber::onNext);
                    subscriber.onCompleted();
                } catch (IOException e) {
                    log.log(Level.WARNING, "Could not retrieve alerts.", e);
                    subscriber.onError(e);
                }
            } else {
                subscriber.onCompleted();
            }
        }));
    }

    @Override
    public Observable<DrugAlertType> checkForDrugAlerts(Collection<PrescriptionType> rxList) {
        ArrayList<String> ndcList = new ArrayList<>();
        for (PrescriptionType rx : rxList) {
            if (rx.getNdc() != null)
                ndcList.add(rx.getNdc());
        }

        final String urlParam = Joiner.on(",").join(ndcList);
        return subscribeIoObserveImmediate(Observable.create(subscriber -> {
            try {
                if (ndcList.isEmpty()) {
                    subscriber.onCompleted();
                } else {
                    String url = String.format(PFServiceEndpoints.DRUG_ALERT_URL, urlParam);
                    log.fine(String.format("Checking for drug alerts to URL: %s", url));
                    OkHttpClient client = PFNetworkManager.getPinnedPFHttpClient();
                    Request req = new Request.Builder().url(url).build();

                    String response = client.newCall(req).execute().body().string();
                    List<DrugAlertType> alerts = gson.fromJson(response, ALERT_LIST_TYPE);

                    Observable.from(alerts).map(alert -> {
                        alert.setKey(alert.getAdditionalInfoUrl());
                        return alert;
                    }).forEach(subscriber::onNext);

                    subscriber.onCompleted();
                }
            } catch (IOException e) {
                log.log(Level.WARNING, "Could not retrieve alerts.", e);
                subscriber.onError(e);
            }
        }));
    }

    @Override
    public Observable<DrugAlertType> checkForRxNormInteractions(Collection<PrescriptionType> rxList) {
        HashSet<String> cuiSet = new HashSet<>();
        Observable<String> rxNormIdList = Observable.from(rxList).filter(rx -> rx.getRxNormId() != null)
                .map(PrescriptionType::getRxNormId);
        cuiSet.addAll(rxNormIdList.toList().toBlocking().first());

        return subscribeIoObserveImmediate(Observable.create(subscriber -> {
            if (cuiSet.size() < 2) {
                log.fine("Not processing drug interaction set size of " + cuiSet.size());
                subscriber.onCompleted();
            } else {
                // Check to see if this set has been queried before
                ArrayList<String> cuiList = new ArrayList<>(cuiSet);
                Collections.sort(cuiList);
                String param = Joiner.on("+").join(cuiList);
                final String url = String.format(PFServiceEndpoints.EXT_RXNORM_INTERACTIONS_URL, param);

                Optional<String> cachedResult = cache.getCachedData(url);
                if (cachedResult.isPresent()) {
                    log.fine("Returning cached result for URL query: " + url);
                    List<DrugAlertType> alertList = deserializeDrugAlerts(rxList, cachedResult.get());
                    Observable.from(alertList).forEach(subscriber::onNext);
                    subscriber.onCompleted();
                } else {
                    log.fine("Requesting Interaction URL: " + url);
                    Request.Builder rxNormInteractionCheckRequest = new Request.Builder();
                    Request req = rxNormInteractionCheckRequest.url(url).build();
                    try {
                        Response responseObject = externalHttpClient.newCall(req).execute();
                        String responseMsg = responseObject.body().string();
                        cache.setCachedData(url, responseMsg);
                        List<DrugAlertType> drugAlerts = deserializeDrugAlerts(rxList, responseMsg);
                        Observable.from(drugAlerts).forEach(subscriber::onNext);
                        subscriber.onCompleted();
                    } catch (IOException e) {
                        log.log(Level.WARNING, "Error processing drug interaction response.", e);
                        subscriber.onError(new RuntimeException(e));
                    }
                }
            }
        }));
    }

    private List<DrugAlertType> deserializeDrugAlerts(Collection<PrescriptionType> rxs, String msg) {
        try {
            JsonObject returnValue = new JsonParser().parse(msg).getAsJsonObject();
            if (!returnValue.has("fullInteractionTypeGroup")) {
                return Collections.emptyList();
            } else {
                Gson gson = new Gson();
                TypeToken<List<FullInteractionTypeGroup>> interactionGroupListType = new TypeToken<List<FullInteractionTypeGroup>>() {
                };
                List<FullInteractionTypeGroup> group = gson.fromJson(returnValue.get("fullInteractionTypeGroup"),
                        interactionGroupListType.getType());
                ArrayList<DrugAlertType> alerts = processDrugInteractions(rxs, group);
                return alerts;

            }
        } catch (Exception e) {
            log.log(Level.SEVERE, "Invalid drug interaction alert response.", e);
            throw new RuntimeException(e);
        }
    }

    public CacheService getCache() {
        return cache;
    }

    public void setCache(CacheService cache) {
        this.cache = cache;
    }

    private ArrayList<DrugAlertType> processDrugInteractions(Collection<PrescriptionType> rxs,
            Collection<FullInteractionTypeGroup> interactions) {
        if (interactions == null || interactions.isEmpty())
            return new ArrayList<>();
        ArrayList<DrugAlertType> alerts = new ArrayList<DrugAlertType>(interactions.size());
        log.fine("Found " + interactions.size() + " interactions.");
        for (FullInteractionTypeGroup group : interactions) {
            for (FullInteractionType interaction : group.getFullInteractionType()) {
                DrugAlertType alert = new DrugAlertType();

                InteractionPair pair = interaction.getInteractionPair().get(0);
                alert.setType("Drug Interaction");
                InteractionConcept drug0 = pair.getInteractionConcept().get(0);
                InteractionConcept drug1 = pair.getInteractionConcept().get(1);
                alert.setTitle("Potential Drug Interaction");
                String severity = pair.getSeverity();
                alert.setResolution(Collections.singletonList(
                        "Please check with your doctor or pharmacist to make sure this is intentional."));
                if (severity.equalsIgnoreCase("CRITICAL"))
                    alert.setCritical(true);
                alert.setAdditionalInfoUrl(drug0.getSourceConceptItem().getUrl());
                alert.setReason(Collections.singletonList(pair.getDescription()));
                alert.setKey("DRUG_INTERACTION+" + drug0.getMinConceptItem().getName() + "+"
                        + drug1.getMinConceptItem().getName());

                for (PrescriptionType rx : rxs) {
                    String rxNormId = rx.getRxNormId();
                    if (rxNormId != null) {
                        for (MinConceptItem mc : interaction.getMinConcept()) {
                            if (rxNormId.equalsIgnoreCase(mc.getRxcui())) {
                                alert.getRelatedRxIds().add(rx.getUuid());
                            }
                        }
                    }
                }
                alerts.add(alert);
            }
        }

        return alerts;
    }

}