org.openmrs.module.casereport.CaseReportUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.casereport.CaseReportUtil.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 * 
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs.module.casereport;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.openmrs.Cohort;
import org.openmrs.Concept;
import org.openmrs.DrugOrder;
import org.openmrs.Obs;
import org.openmrs.Order;
import org.openmrs.OrderType;
import org.openmrs.Patient;
import org.openmrs.Person;
import org.openmrs.Visit;
import org.openmrs.api.APIException;
import org.openmrs.api.OrderService;
import org.openmrs.api.PatientService;
import org.openmrs.api.context.Context;
import org.openmrs.module.casereport.api.CaseReportService;
import org.openmrs.module.reporting.cohort.definition.SqlCohortDefinition;
import org.openmrs.module.reporting.definition.DefinitionContext;
import org.openmrs.module.reporting.evaluation.EvaluationContext;
import org.openmrs.module.reporting.evaluation.EvaluationException;
import org.openmrs.module.reporting.evaluation.parameter.Parameter;
import org.openmrs.scheduler.TaskDefinition;
import org.openmrs.util.OpenmrsUtil;

public class CaseReportUtil {

    protected static final Log log = LogFactory.getLog(CaseReportUtil.class);

    private static Concept getCeilConceptByCode(String code) {
        Concept concept = Context.getConceptService().getConceptByMapping(code,
                CaseReportConstants.SOURCE_CIEL_HL7_CODE);
        if (concept == null) {
            throw new APIException(
                    "Failed to find concept with mapping " + CaseReportConstants.SOURCE_CIEL_HL7_CODE + ":" + code);
        }
        return concept;
    }

    private static List<Obs> getMostRecentObsByPatientAndConceptMapping(Patient patient, String code,
            Integer limit) {
        if (patient == null) {
            throw new APIException("Patient cannot be null");
        }

        List<Person> patients = Collections.singletonList((Person) patient);
        List<Concept> concepts = Collections.singletonList(getCeilConceptByCode(code));

        return Context.getObsService().getObservations(patients, null, concepts, null, null, null,
                Collections.singletonList("obsDatetime"), limit, null, null, null, false);
    }

    /**
     * Gets the 3 most recent viral load observations for the specified patient, they are ordered in a
     * way such that the most recent comes first and the earliest is last. Note that the method can
     * return less than 3 items in case the patient has had less than 3 of them in total in the past.
     *
     * @param patient the patient to match against
     * @return a list of the most recent viral load observations
     * @should return the 3 most recent Viral load observations
     */
    public static List<Obs> getMostRecentViralLoads(Patient patient) {
        return getMostRecentObsByPatientAndConceptMapping(patient, CaseReportConstants.CIEL_CODE_VIRAL_LOAD, 3);
    }

    /**
     * Gets the 3 most recent cd4 count observations for the specified patient, they are ordered in a
     * way such that the most recent comes first and the earliest is last. Note that the method can
     * return less than 3 items in case the patient has had less than 3 of them in total in the past.
     *
     * @param patient the patient to match against
     * @return a list of the most recent cd4 count observations
     * @should return the 3 most recent cd4 count observations
     */
    public static List<Obs> getMostRecentCD4counts(Patient patient) {
        return getMostRecentObsByPatientAndConceptMapping(patient, CaseReportConstants.CIEL_CODE_CD4_COUNT, 3);
    }

    /**
     * Gets the 3 most HIV test result observations for the specified patient, they are ordered in a way
     * such that the most recent comes first and the earliest is last. Note that the method can return
     * less than 3 items in case the patient has had less than 3 of them in total in the past.
     *
     * @param patient the patient to match against
     * @return a list of the most recent HIV test observations
     * @should return the 3 most recent HIV test observations
     */
    public static List<Obs> getMostRecentHIVTests(Patient patient) {
        return getMostRecentObsByPatientAndConceptMapping(patient, CaseReportConstants.CIEL_CODE_HIV_TEST, 3);
    }

    /**
     * Gets the most recent WHO stage observation for the specified patient.
     *
     * @param patient the patient to match against
     * @return the most recent WHO stage observation
     * @should return the most recent WHO stage observation
     */
    public static Obs getMostRecentWHOStage(Patient patient) {
        List<Obs> whoStages = getMostRecentObsByPatientAndConceptMapping(patient,
                CaseReportConstants.CIEL_CODE_WHO_STAGE, 1);
        if (whoStages.isEmpty()) {
            return null;
        }
        return whoStages.get(0);
    }

    /**
     * Gets the active ARV drug orders for the specified patient
     *
     * @param patient the patient to match against
     * @param asOfDate reference date
     * @return a list of active ARV drug orders
     * @should get the active ARV drug orders for the specified patient
     */
    public static List<DrugOrder> getActiveArvDrugOrders(Patient patient, Date asOfDate) {
        Concept arvMedset = getCeilConceptByCode(CaseReportConstants.CIEL_CODE_ARV_MED_SET);
        OrderService os = Context.getOrderService();
        OrderType orderType = os.getOrderTypeByUuid(OrderType.DRUG_ORDER_TYPE_UUID);
        List<Order> orders = os.getActiveOrders(patient, orderType, null, asOfDate);
        List<DrugOrder> arvDrugOrders = new ArrayList<>();
        for (Order order : orders) {
            DrugOrder drugOrder = (DrugOrder) order;
            if (arvMedset.getSetMembers().contains(order.getConcept())) {
                arvDrugOrders.add(drugOrder);
            }
        }
        return arvDrugOrders;
    }

    /**
     * Gets the most recent observation for the reason why the specified patient stopped taking ARVs.
     *
     * @param patient the patient to match against
     * @return the most recent observation for the reason why the patient stopped taking ARVs
     * @should return the most recent obs for the reason why the patient stopped taking ARVs
     */
    public static Obs getMostRecentReasonARVsStopped(Patient patient) {
        List<Obs> reasons = getMostRecentObsByPatientAndConceptMapping(patient,
                CaseReportConstants.CIEL_CODE_REASON_FOR_STOPPING_ARVS, 1);
        if (reasons.isEmpty()) {
            return null;
        }
        return reasons.get(0);
    }

    /**
     * Gets the last visit made by the specified patient
     *
     * @param patient the patient to match against
     * @return the last visit for the specified patient
     * @should return the last visit for the specified patient
     */
    public static Visit getLastVisit(Patient patient) {
        final List<Visit> visits = Context.getVisitService().getVisitsByPatient(patient, true, false);
        Collections.sort(visits, Collections.reverseOrder(new Comparator<Visit>() {

            @Override
            public int compare(Visit v1, Visit v2) {
                return OpenmrsUtil.compare(v1.getStartDatetime(), v2.getStartDatetime());
            }
        }));

        if (visits.isEmpty()) {
            return null;
        }
        return visits.get(0);
    }

    public static boolean collContainsItemWithValue(Collection<? extends UuidAndValue> coll, String value) {
        for (UuidAndValue uv : coll) {
            if (value.equals(uv.getValue())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the concept that matches the specified mapping string, the mapping is expected to be of the
     * form SOURCE_IDENTIFIER e.g CIEL_856
     *
     * @param mappingString the string to match against
     * @param failIfNotFound specifies if an exception should be thrown if no match is found
     * @return the matched concept
     */
    public static Concept getConceptByMappingString(String mappingString, boolean failIfNotFound) {
        String[] sourceAndCode = StringUtils.split(mappingString, CaseReportConstants.CONCEPT_MAPPING_SEPARATOR);
        if (sourceAndCode.length == 1 && failIfNotFound) {
            throw new APIException("Invalid concept mapping: " + mappingString);
        }
        String source = sourceAndCode[0];
        String code = sourceAndCode[1];
        Concept concept = Context.getConceptService().getConceptByMapping(code, source);
        if (concept == null && failIfNotFound) {
            throw new APIException("Failed to find concept with mapping: " + mappingString);
        }

        return concept;
    }

    /**
     * Looks up a concept with a concept mapping to the specified source and code
     *
     * @param code the code to match
     * @param source the name of the concept source to match
     * @return the concept
     */
    public static Concept getConceptByMapping(String code, String source) {
        Concept concept = Context.getConceptService().getConceptByMapping(code, source);
        if (concept == null) {
            throw new APIException("No concept found with a mapping to source: " + source + " and code: " + code);
        }

        return concept;
    }

    /**
     * Gets the SqlCohortDefinition that matches the specified trigger name, will throw an APIException
     * if multiple cohort queries are found that match the trigger name
     *
     * @param triggerName the name to match against
     * @return the sql cohort query that matches the name
     * @throws APIException
     * @should return null if no cohort query is found that matches the trigger name
     * @should fail if multiple cohort queries are found that match the trigger name
     * @should not return a retired cohort query
     * @should return the matched cohort query
     */
    public static SqlCohortDefinition getSqlCohortDefinition(String triggerName) throws APIException {
        SqlCohortDefinition ret = null;
        List<SqlCohortDefinition> matches = DefinitionContext.getDefinitionService(SqlCohortDefinition.class)
                .getDefinitions(triggerName, true);
        if (matches.size() > 1) {
            throw new APIException("Found multiple Sql Cohort Queries with name:" + triggerName);
        } else if (matches.size() == 0 || matches.get(0).isRetired()) {
            String msg;
            if (matches.size() == 0) {
                msg = "Cannot find a Sql Cohort Query with name:" + triggerName;
            } else {
                msg = triggerName + " is a retired Sql Cohort Query";
            }
            log.warn(msg);
        } else {
            ret = matches.get(0);
        }

        return ret;
    }

    /**
     * Runs the SQL cohort query with the specified name and creates a case report for each matched
     * patient of none exists
     *
     * @param taskDefinition the scheduler taskDefinition inside which the trigger is being run
     * @throws APIException
     * @throws EvaluationException
     * @should fail if no sql cohort query matches the specified trigger name
     * @should create case reports for the matched patients
     * @should set the last execution time in the evaluation context
     * @should add a new trigger to an existing queue item for the patient
     * @should not create a duplicate trigger for the same patient
     * @should set the concept mappings in the evaluation context
     * @should fail for a task where the last execution time cannot be resolved
     */
    public synchronized static void executeTask(TaskDefinition taskDefinition)
            throws APIException, EvaluationException {
        if (taskDefinition == null) {
            throw new APIException("TaskDefinition can't be null");
        }

        String triggerName = taskDefinition.getProperty(CaseReportConstants.TRIGGER_NAME_TASK_PROPERTY);
        if (StringUtils.isBlank(triggerName)) {
            throw new APIException(taskDefinition.getName() + " task doesn't have a "
                    + CaseReportConstants.TRIGGER_NAME_TASK_PROPERTY + " property");
        }
        SqlCohortDefinition definition = getSqlCohortDefinition(triggerName);
        if (definition == null) {
            throw new APIException("No sql cohort query was found that matches the name: " + triggerName);
        }
        EvaluationContext evaluationContext = new EvaluationContext();
        Map<String, Object> params = new HashMap<>();
        if (definition.getParameter(CaseReportConstants.LAST_EXECUTION_TIME) != null) {
            Date lastExecutionTime = taskDefinition.getLastExecutionTime();
            if (lastExecutionTime == null && taskDefinition.getRepeatInterval() != null
                    && taskDefinition.getRepeatInterval() > 0) {
                //TODO add a unit test for this
                //default to now minus repeat interval
                lastExecutionTime = DateUtils.addSeconds(new Date(),
                        -taskDefinition.getRepeatInterval().intValue());
            }
            if (lastExecutionTime == null) {
                throw new APIException("Failed to resolve the value for the last execution time");
            }
            params.put(CaseReportConstants.LAST_EXECUTION_TIME, lastExecutionTime);
        }

        if (definition.getParameters() != null) {
            for (Parameter p : definition.getParameters()) {
                if (p.getName().startsWith(CaseReportConstants.CIEL_MAPPING_PREFIX)) {
                    Concept concept = CaseReportUtil.getConceptByMappingString(p.getName(), true);
                    params.put(p.getName(), concept.getConceptId());
                }
            }
        }

        evaluationContext.setParameterValues(params);
        Cohort cohort = (Cohort) DefinitionContext.evaluate(definition, evaluationContext);

        PatientService ps = Context.getPatientService();
        CaseReportService caseReportService = Context.getService(CaseReportService.class);
        List<CaseReport> autoSubmitReports = new ArrayList<>(cohort.getMemberIds().size());
        for (Integer patientId : cohort.getMemberIds()) {
            Patient patient = ps.getPatient(patientId);
            if (patient == null) {
                throw new APIException("No patient found with patientId: " + patientId);
            }

            boolean autoSubmit = false;
            if ("true".equals(taskDefinition.getProperty(CaseReportConstants.AUTO_SUBMIT_TASK_PROPERTY))) {
                autoSubmit = true;
            }
            CaseReport caseReport = createReportIfNecessary(patient, autoSubmit, triggerName);
            if (caseReport != null) {
                //We can't auto submit an existing report because the surveillance officer needs
                //to take a look at the other triggers to be included in the existing report
                if (caseReport.getId() == null && autoSubmit) {
                    caseReport.setAutoSubmitted(true);
                    autoSubmitReports.add(caseReport);
                }
                caseReportService.saveCaseReport(caseReport);
            } else {
                log.debug(patient + " already has an item in the queue with the trigger " + triggerName);
            }
        }

        for (CaseReport caseReport : autoSubmitReports) {
            //TODO reports should be auto submitted in parallel
            try {
                CaseReportForm form = new CaseReportForm(caseReport);
                caseReport.setReportForm(new ObjectMapper().writeValueAsString(form));
                caseReportService.submitCaseReport(caseReport);
            } catch (Throwable t) {
                log.warn("Failed to auto submit " + caseReport, t);
            }
        }
    }

    /**
     * Creates a case report the specified patient with the specified triggers, if the parent already
     * has an existing case report then no new one is created instead the unique triggers are added to
     * the existing case report. If the patient already has a case report with all the specified
     * triggers then nothing happens.
     *
     * @param patient the patient to create a case report for
     * @param createNew Specifies if a new case report MUST be created
     * @param triggerNames the triggers to add
     * @return the created trigger or none was created or if all the triggers are duplicates
     */
    public static CaseReport createReportIfNecessary(Patient patient, boolean createNew, String... triggerNames) {
        CaseReport caseReport;
        CaseReport existingCR = Context.getService(CaseReportService.class).getCaseReportByPatient(patient);
        if (createNew || existingCR == null) {
            caseReport = new CaseReport();
            caseReport.setPatient(patient);
        } else {
            caseReport = existingCR;
        }

        int addedTriggerCount = 0;
        for (String t : triggerNames) {
            if (StringUtils.isNotBlank(t)) {
                if (existingCR == null || existingCR.getCaseReportTriggerByName(t) == null) {
                    caseReport.addTrigger(new CaseReportTrigger(t));
                    addedTriggerCount++;
                }
            } else {
                throw new APIException("Trigger name cannot be blank");
            }
        }

        if (existingCR != null && addedTriggerCount == 0) {
            //This patient had a queue item and all the 'new' triggers were duplicates
            //Therefore don't create duplicates for the same patient
            return null;
        }

        return caseReport;
    }
}