org.openmrs.module.lancearmstrong.LafServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.lancearmstrong.LafServiceImpl.java

Source

/**
 * The contents of this file are subject to the Regenstrief Public License
 * Version 1.0 (the "License"); you may not use this file except in compliance with the License.
 * Please contact Regenstrief Institute if you would like to obtain a copy of the license.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) Regenstrief Institute.  All Rights Reserved.
 */
package org.openmrs.module.lancearmstrong;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.api.context.Context;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.module.lancearmstrong.db.*;

/**
 * Follow-up care alerting rule:
 * 
 * 1. Generate an alert for each recommended follow-up care type 30-day before corresponding next target date if and only if no schedule or snooze found for this target date and follow-up care type
 * 2. Next target date for each recommended follow-up care type is calculated as:
 *    1) Get the date of latest received care
 *    2) Match above date to one of the recommended dates
 *    3) Find next target date based on today's date, matched date above and recommended dates:
 *       (1) find two consecutive recommended dates between which today's date is
 *       (2) find the mid-date of the two consecutive recommended dates above
 *       (3) set the lower recommended date as the next target date if and only if today is before the mid-date above and the lower recommended date has not been matched by a received care  
 *       (4) set the upper recommended date as the next target date if and only if today is after the mid-date above and the upper recommended date has not been matched by a received care
 * 3. Mark recommended dates as yellow (missed date), green (matched date), red (alert date), and blue (scheduled or snoozed date) and display detail as hover texts
 * 4. Assume date format in ENG-US locale   
 * 
 * @author hxiao
 */
public class LafServiceImpl extends BaseOpenmrsService implements LafService {
    protected final Log log = LogFactory.getLog(getClass());

    private LafGuidelineDAO guidelineDao;
    private LafReminderDAO reminderDao;

    private final static Integer CANCER_ABNORMALITY_TOLD = 6102;
    private final static Integer CANCER_ABNORMALITY_TOLD_YES = 1065;
    private final static Integer CANCER_ABNORMALITY = 6147;
    private final static Integer CANCER_ABNORMALITY_FAP = 6104;
    private final static Integer CANCER_ABNORMALITY_HNPCC = 6103;
    private final static Integer CANCER_ABNORMALITY_INFLBD = 6105;

    private final static Integer CANCER_TYPE = 6145;
    private final static Integer CANCER_STAGE = 6146;
    private final static Integer SURGERY_TYPE = 6152;
    private final static Integer SURGERY_DATE = 6118;
    private final static Integer DIAGNOSIS_DATE = 6101;
    private final static Integer RADIATION_TYPE = 6154;
    private final static Integer RADIATION_START_DATE = 6132;
    private final static Integer CHEMOTHERAPY_MEDS = 6156;

    private final static String CANCER_TREATMENT_SUMMARY_ENCOUNTER = "CANCER TREATMENT SUMMARY";
    private final static String RADIATION_ENCOUNTER = "CANCER TREATMENT - RADIATION";
    private final static String CHEMOTHERAPY_ENCOUNTER = "CANCER TREATMENT - CHEMOTHERAPY";
    private final static String SURGERY_ENCOUNTER = "CANCER TREATMENT - SURGERY";

    private final static int ALERT_DAYS = 60;
    private final static int MATCH_DAYS = 7;

    //ANY TREATMENT TYPE=0, CHEMOTHERAPY=1, RADIOLOGY=2, SURGERY=3
    //6225-6248: Concept ID's of side effects
    //6115-6130: Sub types of each treatment type
    protected final static HashMap<MultiKey, Integer[]> sideEffectsMap = new HashMap<MultiKey, Integer[]>();
    static {
        sideEffectsMap.put(new MultiKey(new Integer[] { 0, 0 }), new Integer[] { 6250, 6226, 6254, 6255 }); //all
        sideEffectsMap.put(new MultiKey(new Integer[] { 0, 1 }), new Integer[] { 6235, 6252 }); //male
        sideEffectsMap.put(new MultiKey(new Integer[] { 0, 2 }), new Integer[] { 6233, 6251 }); //female
        sideEffectsMap.put(new MultiKey(new Integer[] { 1, 6123 }), new Integer[] { 6228 });//chemotherapy
        sideEffectsMap.put(new MultiKey(new Integer[] { 1, 6124 }), new Integer[] { 6229 });
        sideEffectsMap.put(new MultiKey(new Integer[] { 2, 0 }), new Integer[] { 6244, 6245 }); //radiation
        sideEffectsMap.put(new MultiKey(new Integer[] { 3, 6110 }), new Integer[] { 6256 }); //surgeries
        sideEffectsMap.put(new MultiKey(new Integer[] { 3, 6111 }), new Integer[] { 6253 });
    }

    @Override
    public LafGuidelineDAO getGuidelineDao() {
        return guidelineDao;
    }

    @Override
    public void setGuidelineDao(LafGuidelineDAO guidelineDao) {
        this.guidelineDao = guidelineDao;
    }

    @Override
    public LafReminderDAO getReminderDao() {
        return reminderDao;
    }

    @Override
    public void setReminderDao(LafReminderDAO reminderDao) {
        this.reminderDao = reminderDao;
    }

    /**
      * @see org.openmrs.module.lancearmstrong.LafService#getReminders(org.openmrs.Patient)
      */
    @Override
    public List<LafReminder> getReminders(Patient pat) {
        return findReminders(pat);
    }

    /**
      * @see org.openmrs.module.lancearmstrong.LafService#getReminders(org.openmrs.Patient)
      */
    @Override
    public List<LafReminder> getReminders(Patient pat, Date indexDate) {
        return findReminders(pat, indexDate);
    }

    /**
     * @see org.openmrs.module.lancearmstrong.LafService#getReminders(org.openmrs.Patient)
     */
    @Override
    public List<LafReminder> getRemindersCompleted(Patient pat) {
        List<LafReminder> reminderList = reminderDao.getLafRemindersCompleted(pat);
        return reminderList;
    }

    @Override
    public List<LafReminder> addRemindersCompleted(Patient pat) {
        List<LafReminder> origReminderList = reminderDao.getLafRemindersCompleted(pat);
        List<LafReminder> reminderList = null;

        //Look for completed tests from OpenMRS Obs records
        List<Obs> obsList = Context.getObsService().getObservationsByPerson(pat);
        Concept colonoscopy = Context.getConceptService().getConcept("Colonoscopy"); //6219
        String csString = colonoscopy.getName().getName().toLowerCase();
        for (Obs obs : obsList) {
            Encounter enc = obs.getEncounter();
            if (enc == null || enc.getEncounterType().getName().matches("(CANCER|SUMMARIZATION)"))
                continue;

            String value = obs.getValueAsString(Context.getLocale());
            if (value != null && value.toLowerCase().contains(csString)) {
                LafReminder rem = new LafReminder();
                rem.setFollowProcedure(colonoscopy);
                rem.setCompleteDate(enc.getEncounterDatetime());
                if (enc.getProvider() != null) {
                    rem.setDoctorName(enc.getProvider().getPersonName().getFullName().replace("Unknown", ""));
                }
                rem.setFlag("Completed");
                rem.setPatient(pat);
                rem.setResponseType("CCD");
                rem.setResponseDate(new Date());
                rem.setResponseComments("A follow up care is found from CCD imported");
                //rem.setTargetDate(enc.getEncounterDatetime());
                rem.setResponseUser(enc.getCreator());
                if (reminderList == null) {
                    reminderList = new ArrayList<LafReminder>();
                }
                reminderList.add(rem);
                if (findReminder(origReminderList, rem) == null) {
                    reminderDao.saveLafReminder(rem);
                }
            }
        }

        return reminderList;
    }

    private LafReminder findReminder(List<LafReminder> origReminderList, LafReminder rem) {
        if (origReminderList == null)
            return null;
        for (LafReminder reminder : origReminderList) {
            if (reminder.getFollowProcedure().getId().compareTo(rem.getFollowProcedure().getId()) == 0
                    && reminder.getCompleteDate().equals(rem.getCompleteDate())) {
                return reminder;
            }
        }
        return null;
    }

    private List<LafReminder> getRemindersByProvider(Patient pat) {
        return reminderDao.getLafRemindersByProvider(pat);
    }

    /**
      * @see org.openmrs.module.lancearmstrong.LafService#getReminders(org.openmrs.Patient)
      */
    @Override
    public List<Concept> getSideEffects(Patient pat) {
        log.debug("Calling LafServiceImpl:getSideEffects for patient " + pat);
        // TODO Auto-generated method stub
        List<Concept> sideEffects = new ArrayList<Concept>();

        //Key set
        List<MultiKey> keys = new ArrayList<MultiKey>();

        //for all patients
        keys.add(new MultiKey(new Integer[] { 0, 0 }));

        //for male patients
        if ("M".equalsIgnoreCase(pat.getGender())) {
            keys.add(new MultiKey(new Integer[] { 0, 1 }));
        } else if ("F".equalsIgnoreCase(pat.getGender())) {
            keys.add(new MultiKey(new Integer[] { 0, 2 }));
        } else {
            keys.add(new MultiKey(new Integer[] { 0, 1 }));
            keys.add(new MultiKey(new Integer[] { 0, 2 }));
        }

        //find  Chemotherapy meds used
        Encounter enc = findCancerTreatment(pat, CHEMOTHERAPY_ENCOUNTER);
        if (enc != null) {
            Concept chemoMedsConcept = Context.getConceptService().getConcept(CHEMOTHERAPY_MEDS);
            List<Obs> meds = Context.getObsService().getObservationsByPersonAndConcept(pat, chemoMedsConcept);
            if (meds != null) {
                for (Obs med : meds) {
                    if (med.getValueCoded() != null) {
                        keys.add(new MultiKey(new Integer[] { 1, med.getValueCoded().getId() }));
                        log.debug("Chemotherapy med added: " + med + ", id=" + med.getValueCoded().getId());
                    }
                }
            } else {
                log.debug("No chemotherapy meds are found.");
            }
        } else {
            log.debug("No chemotherapy is found.");
        }

        //find  Radiation types
        enc = findCancerTreatment(pat, RADIATION_ENCOUNTER);
        if (enc != null) {
            //for all radiation types
            keys.add(new MultiKey(new Integer[] { 2, 0 }));
        }

        //find  Surgery types
        enc = findCancerTreatment(pat, SURGERY_ENCOUNTER);
        if (enc != null) {
            //get the patient's cancer type
            Concept cancerType = getCancerType(pat);
            if (cancerType != null) {
                keys.add(new MultiKey(new Integer[] { 3, cancerType.getConceptId() }));
            }
        }

        //query side effect concepts' id's from a hash map and add corresponding Concept objects to the return list
        for (MultiKey key : keys) {
            Integer[] sideEffectIds = sideEffectsMap.get(key);
            if (sideEffectIds == null) {
                log.debug("Key skipped: " + key);
                continue;
            }
            for (Integer sideEffId : sideEffectIds) {
                if ("M".equals(pat.getGender()) && sideEffId.intValue() == 6248) {
                    continue;
                } else if ("F".equals(pat.getGender()) && sideEffId.intValue() == 6247) {
                    continue;
                } else if (isDuplicate(sideEffId, sideEffects)) {
                    continue;
                }
                Concept sideEffect = Context.getConceptService().getConcept(sideEffId);
                sideEffects.add(sideEffect);//sideEffect.getName().getName(); sideEffect.getDescription().getChangedBy();
                log.debug("side effect found: " + sideEffId + "|" + sideEffect.getName().getName() + " for key "
                        + key);
            }
        }

        log.debug("Number of side effects found: " + (sideEffects == null ? 0 : sideEffects.size()));
        return sideEffects;
    }

    /**
     * Auto generated method comment
     * 
     * @param sideEffId
     * @param sideEffects
     * @return
     */
    private boolean isDuplicate(Integer sideEffId, List<Concept> sideEffects) {
        // TODO Auto-generated method stub
        for (Concept sd : sideEffects) {
            if (sd.getId().intValue() == sideEffId.intValue()) {
                return true;
            }
        }
        return false;
    }

    private Encounter findCancerTreatment(Patient pat, String encounterType) {
        //find cancer treatment summary encounter   
        List<Encounter> encs = Context.getEncounterService().getEncountersByPatient(pat);
        Integer encId = null;
        Date encDate = null;
        Encounter latestEncounter = null;
        for (Encounter enc : encs) {
            if (!enc.isVoided() && encounterType.equals(enc.getEncounterType().getName())) {
                if ((encId == null || enc.getEncounterDatetime().after(encDate))) {
                    encId = enc.getId();
                    encDate = enc.getEncounterDatetime();
                    enc.getObs();
                    latestEncounter = enc;
                }
            }
        }

        return latestEncounter;

    }

    /*
     * Find specific reminders for alerting purpose for a given patient at a given date based on ALERT_DAYS (e.g. 60 days) alerting rule
     * 
     * Alert display logic: 
      * 
      * find reminders by guideline -> find and exclude reminders completed/matched, snoozed, scheduled, or not performed -> 
      * find reminders by providers -> exclude reminders completed/matched, snoozed, scheduled, or not performed for provider-recommended reminders ->
      * add provider recommended reminder alerts (overriding corresponding guidline reminder alerts to keep just one alert of the same kind)  
      *  
      * Note: 1. one completed test can match up to two reminders, one from guideline (middle-date rule), the other from providers (7-day-proximity rule)
      *       2. alert date is based on 60-day rule for both types of reminders (by guideline, and by providers)
      *       
     */
    private List<LafReminder> findReminders(Patient pat, Date indexDate) {
        //find all follow-up care guidelines
        List<LafGuideline> guidelines = findGuidelines(pat);

        //find all reminders sorted by target date
        List<LafReminder> reminders = findAllReminders(pat);

        //find all completed reminders
        List<LafReminder> remindersCompleted = getRemindersCompleted(pat);

        //mark completed reminders (will take prevalence over other marks)
        if (remindersCompleted != null) {
            for (int ii = 0; ii < reminders.size(); ii++) {
                LafReminder reminder = reminders.get(ii);
                LafReminder nextReminder = null;
                LafReminder previousReminder = null;

                for (int jj = ii + 1; jj < reminders.size(); jj++) {
                    nextReminder = reminders.get(jj);
                    if (nextReminder.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        break;
                    }
                }

                for (int jj = ii - 1; jj >= 0; jj--) {
                    previousReminder = reminders.get(jj);
                    if (previousReminder.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        break;
                    }
                }

                for (LafReminder reminderCompl : remindersCompleted) {
                    if (reminderCompl.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        if (reminderCompl.getCompleteDate().equals(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (previousReminder == null
                                && reminderCompl.getCompleteDate().before(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (nextReminder == null
                                && reminderCompl.getCompleteDate().after(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (previousReminder != null
                                && reminderCompl.getCompleteDate().before(reminder.getTargetDate())
                                && reminderCompl.getCompleteDate().after(
                                        findMidDate(previousReminder.getTargetDate(), reminder.getTargetDate()))) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (nextReminder != null
                                && reminderCompl.getCompleteDate().after(reminder.getTargetDate())
                                && reminderCompl.getCompleteDate().before(
                                        findMidDate(reminder.getTargetDate(), nextReminder.getTargetDate()))) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        }
                    }
                }
            }
        }

        //compare with indexDate to determine reminder/alert for each guideline
        List<LafReminder> newReminders = new ArrayList<LafReminder>();
        if (guidelines != null) {
            log.debug("Number of guidelines found = " + guidelines.size());
            for (LafGuideline guideline : guidelines) {
                Date targetDate = findNextTargetDate(reminders, guideline.getFollowProcedure()); //completed reminders have been excluded from the next target date calculation

                Date alertDate = findAlertDate(pat, guideline.getFollowProcedure(), targetDate); //based on default offset of 60 days and user response to earlier alert
                log.debug(
                        "guideline=" + guideline.getFollowProcedure() + ", years = " + guideline.getFollowYears());
                log.debug("targetDate=" + targetDate);
                log.debug("alertDate=" + alertDate);

                if (alertDate != null && indexDate.after(alertDate)) {
                    log.debug("Alert is found for patient: " + pat + ", guideline=" + guideline + ", targetDate="
                            + targetDate + ", alertStartDate=" + alertDate);

                    //find Not Performed flag for this reminder
                    String notPerformedFlag = findAttribute(pat, guideline.getFollowProcedure(), targetDate,
                            "notPerformed");
                    if (notPerformedFlag != null && "Yes".equals(notPerformedFlag)) {
                        continue; //no need to alert
                    }

                    //find snooze date or schedule date for this reminder
                    Date ssDate = findSnoozeOrScheduleDate(pat, guideline.getFollowProcedure(), targetDate);

                    if (ssDate == null || ssDate.before(new Date())) {
                        LafReminder reminder = new LafReminder();
                        reminder.setPatient(pat);
                        reminder.setFollowProcedure(guideline.getFollowProcedure());
                        reminder.setTargetDate(targetDate);
                        newReminders.add(reminder);
                    }
                }
            }
        } else {
            log.debug("No guidelines are found!");
        }

        log.debug("Number of alerts found = " + (newReminders == null ? 0 : newReminders.size()) + " on index date "
                + indexDate);

        return newReminders;
    }

    /**
      * Match a target date with a completed date based on 7-day-proximity rule
      * 
      * @param targetDate
      * @param lastCompleted
      * @return
      */
    private boolean matchCompletedDate(Date targetDate, Date lastCompleted) {
        // TODO Auto-generated method stub
        Calendar calTarget1 = Calendar.getInstance();
        calTarget1.setTime(targetDate);
        calTarget1.add(Calendar.DATE, MATCH_DAYS);
        Calendar calTarget2 = Calendar.getInstance();
        calTarget2.setTime(targetDate);
        calTarget2.add(Calendar.DATE, -MATCH_DAYS);

        Calendar calCompl = Calendar.getInstance();
        calCompl.setTime(lastCompleted);

        if (calCompl.before(calTarget1) && calCompl.after(calTarget2)) {
            return true;
        }
        return false;
    }

    /**
      * Auto generated method comment
      * 
      * @param followProcedure
      * @param targetDate
     * @param sb 
      * @return
      */
    private Date findSnoozeOrScheduleDate(Patient patient, Concept careType, Date targetDate) {
        LafReminder reminder = this.reminderDao.getLafReminder(patient, careType, targetDate);

        Date ssDate = null;
        String ssType = null;
        if (reminder != null) {
            String[] splits = reminder.getResponseAttributes().split("=");
            if (splits.length >= 2) {
                ssType = splits[0];
                if ("snoozeDate".equals(ssType) || "scheduleDate".equals(ssType)) {
                    try {
                        ssDate = Context.getDateFormat().parse(splits[1]);
                    } catch (ParseException e) {
                        log.error("Bad date format: ssType=" + ssType + ", ssDate=" + ssDate);
                    }
                    log.debug("This reminder is snoozed or scheduled: ssType=" + ssType + ", ssDate=" + ssDate);
                }
            }
        }

        //void this date if it is older than today's date
        //if(ssDate!=null && ssDate.before(new Date())) {
        //   ssDate = null;
        //}

        return ssDate;
    }

    private Date findSnoozeOrScheduleDate(Patient patient, Concept careType, Date targetDate, String type) {
        LafReminder reminder = this.reminderDao.getLafReminder(patient, careType, targetDate);

        Date ssDate = null;
        String ssType = null;
        if (reminder != null) {
            String[] splits = reminder.getResponseAttributes().split("=");
            if (splits.length >= 2) {
                ssType = splits[0];
                if (type.equals(ssType)) {
                    try {
                        ssDate = Context.getDateFormat().parse(splits[1]);
                    } catch (ParseException e) {
                        log.error("Bad date format: ssType=" + ssType + ", ssDate=" + ssDate);
                    }
                    log.debug("This reminder is snoozed or scheduled: ssType=" + ssType + ", ssDate=" + ssDate);
                }
            }
        }

        //void this date if it is older than today's date
        if (ssDate != null && ssDate.before(new Date())) {
            ssDate = null;
        }

        return ssDate;
    }

    private String findAttribute(Patient patient, Concept careType, Date targetDate, String type) {
        LafReminder reminder = this.reminderDao.getLafReminder(patient, careType, targetDate);

        if (reminder == null || reminder.getResponseAttributes() == null) {
            return null;
        }
        String[] segments = reminder.getResponseAttributes().split(";");

        String ssValue = null;
        for (String segment : segments) {
            String ssType = null;
            if (segment != null) {
                String[] splits = segment.split("=");
                if (splits.length >= 2) {
                    ssType = splits[0].trim();
                    if (type.equals(ssType)) {
                        ssValue = splits[1];
                    }
                }
            }
        }

        return ssValue;
    }

    /**
      * Auto generated method comment
     * @param careType 
     * @param pat 
      * 
      * @param targetDate
      * @return
      */
    private Date findAlertDate(Patient pat, Concept careType, Date targetDate) {
        // TODO Auto-generated method stub
        if (targetDate == null)
            return null;

        Calendar cal = Calendar.getInstance();
        cal.setTime(targetDate);
        cal.add(Calendar.DATE, -ALERT_DAYS);

        log.debug("alertDate=" + cal.getTime() + " based on offset of " + ALERT_DAYS + "days");

        return cal.getTime();
    }

    /**
      * Auto generated method comment
      * 
      * @param remindersCompleted
      * @param followProcedure
      * @return
      */
    private LafReminder findLastCompleted(List<LafReminder> remindersCompleted, Concept followProcedure) {
        // TODO Auto-generated method stub
        LafReminder lastCompletedReminder = null;
        if (remindersCompleted == null) {
            return null;
        }
        for (LafReminder reminder : remindersCompleted) {
            if (reminder.getFollowProcedure().getId() == followProcedure.getId()) {
                if (lastCompletedReminder == null
                        || lastCompletedReminder.getCompleteDate().before(reminder.getCompleteDate())) {
                    lastCompletedReminder = reminder;
                }
            }
        }
        return lastCompletedReminder;
    }

    /**
      * Find guidelines for a given patient
      * 
      * @param pat
      */
    private List<LafGuideline> findGuidelines(Patient pat) {
        //find cancer type
        Concept type = getCancerType(pat);
        //find cancer stage
        Concept cancerStageConcept = Context.getConceptService().getConcept(CANCER_STAGE);
        Obs cancerStage = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, cancerStageConcept));
        Concept stage = cancerStage == null ? null : cancerStage.getValueCoded();

        //find follow-up years guidelines
        List<LafGuideline> guidelines = guidelineDao.getLafGuideline(type, stage);

        return guidelines;
    }

    private Concept getCancerType(Patient pat) {
        Concept cancerTypeConcept = Context.getConceptService().getConcept(CANCER_TYPE);
        Obs cancerType = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, cancerTypeConcept));
        Concept type = cancerType == null ? null : cancerType.getValueCoded();
        return type;
    }

    /**
      * Find flagged reminders for a given patient for Calendar display
      * (flagged as Completed, Snoozed, Scheduled, Not Performed, or Skipped)
      * 
      * Calendar display logic: 
      * 
      * find reminders by guideline -> find and add reminders by providers -> sort reminders by date -> 
      * find and mark alerted reminders -> find and mark scheduled/snoozed reminders -> 
      * find and mark not performed reminders -> find, match and mark completed reminders (overriding) ->
      * mark skipped reminders (remaining)  
      * 
      * @param pat
      */
    private List<LafReminder> findReminders(Patient pat) {
        //find all reminders recommended by guideline and patient's providers
        List<LafReminder> reminders = findAllReminders(pat);

        if (reminders == null || reminders.isEmpty()) {
            return null;
        }

        //find completed reminders
        List<LafReminder> remindersCompleted = getRemindersCompleted(pat);

        //***********************************************
        //mark each reminder as either completed, missed, scheduled, snoozed, near-future or far-future
        //based on follow-up care received, today's date, schedule response, and snooze response.
        //***********************************************

        //find alerted reminders
        Date today = new Date();
        List<LafReminder> remindersAlerted = getReminders(pat, today);

        //mark alerted reminders
        if (remindersAlerted != null) {
            for (int ii = 0; ii < reminders.size(); ii++) {
                LafReminder reminder = reminders.get(ii);
                for (LafReminder alert : remindersAlerted) {
                    if (reminder.getFollowProcedure().equals(alert.getFollowProcedure())
                            && reminder.getTargetDate().equals(alert.getTargetDate())) {
                        reminder.setFlag(LafReminder.FLAG_ALERTED);
                    }
                }
            }
        }

        //mark snoozed or scheduled reminders
        for (int ii = 0; ii < reminders.size(); ii++) {
            LafReminder reminder = reminders.get(ii);
            Date scheduleDate = this.findSnoozeOrScheduleDate(pat, reminder.getFollowProcedure(),
                    reminder.getTargetDate(), "scheduleDate");
            Date snoozeDate = this.findSnoozeOrScheduleDate(pat, reminder.getFollowProcedure(),
                    reminder.getTargetDate(), "snoozeDate");
            if (scheduleDate != null) {
                reminder.setFlag(LafReminder.FLAG_SCHEDULED);
                reminder.setResponseDate(scheduleDate);
            } else if (snoozeDate != null) {
                reminder.setFlag(LafReminder.FLAG_SNOOZED);
                reminder.setResponseDate(snoozeDate);
            }
        }

        //mark Not-Performed reminders
        for (int ii = 0; ii < reminders.size(); ii++) {
            LafReminder reminder = reminders.get(ii);
            String yesOrNo = this.findAttribute(pat, reminder.getFollowProcedure(), reminder.getTargetDate(),
                    "notPerformed");
            if ("Yes".equalsIgnoreCase(yesOrNo)) {
                reminder.setFlag(LafReminder.FLAG_NOT_PERFORMED_YES);
            } else if ("No".equalsIgnoreCase(yesOrNo)) {
                //reminder.setFlag(LafReminder.FLAG_NOT_PERFORMED_NO);                              
            }
        }

        //mark completed reminders (will take prevalence over other marks)
        if (remindersCompleted != null) {
            //loop through all reminders and find if that reminder is completed or not
            for (int ii = 0; ii < reminders.size(); ii++) {
                LafReminder reminder = reminders.get(ii);
                LafReminder nextReminder = null;
                LafReminder previousReminder = null;

                //find next reminder of the same type
                for (int jj = ii + 1; jj < reminders.size(); jj++) {
                    nextReminder = reminders.get(jj);
                    if (nextReminder.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        break;
                    }
                }

                //find previous reminder of the same type
                for (int jj = ii - 1; jj >= 0; jj--) {
                    previousReminder = reminders.get(jj);
                    if (previousReminder.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        break;
                    }
                }

                //find if this reminder can be marked as completed or not
                for (LafReminder reminderCompl : remindersCompleted) {
                    if (reminderCompl.getFollowProcedure().equals(reminder.getFollowProcedure())) {
                        if (reminderCompl.getCompleteDate().equals(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (previousReminder == null
                                && reminderCompl.getCompleteDate().before(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (nextReminder == null
                                && reminderCompl.getCompleteDate().after(reminder.getTargetDate())) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (previousReminder != null
                                && reminderCompl.getCompleteDate().before(reminder.getTargetDate())
                                && reminderCompl.getCompleteDate().after(
                                        findMidDate(previousReminder.getTargetDate(), reminder.getTargetDate()))) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        } else if (nextReminder != null
                                && reminderCompl.getCompleteDate().after(reminder.getTargetDate())
                                && reminderCompl.getCompleteDate().before(
                                        findMidDate(reminder.getTargetDate(), nextReminder.getTargetDate()))) {
                            reminder.setFlag(LafReminder.FLAG_COMPLETED);
                            reminder.setResponseDate(reminderCompl.getCompleteDate());
                            break;
                        }
                    }
                }
            }
        }

        //mark skipped reminders
        for (int ii = 0; ii < reminders.size(); ii++) {
            LafReminder reminder = reminders.get(ii);
            if (reminder.getTargetDate().before(today) && reminder.getFlag() == null) {
                reminder.setFlag(LafReminder.FLAG_SKIPPED);
            }
        }

        return reminders;
    }

    /**
     * 
     * Find all reminders including those by guideline and those by patient doctors
     * 
     * find reminders by guideline -> find and add reminders by providers -> sort reminders by date 
     * 
     * @param pat given patient
     * @return all reminders sorted by target date
     */
    private List<LafReminder> findAllReminders(Patient pat) {
        //find cancer treatment summary encounter
        /*
        List<Encounter> encs = Context.getEncounterService().getEncountersByPatient(pat);           
        Integer encId = null;
        Date encDate = null;
        for(Encounter enc : encs) {          
           if(!enc.isVoided() && CANCER_TREATMENT_SUMMARY_ENCOUNTER.equals(enc.getEncounterType().getName())) {
         if((encId == null || enc.getEncounterDatetime().after(encDate))) {
            encId = enc.getId();
            encDate = enc.getEncounterDatetime();
            enc.getObs();
         }            
           }
        }
        */
        //find genetic_abnormality flag and answer 
        //block any follow-up tests from appearing:           
        //   1)       IF the patient answers YES to genetic abnormality, and 
        //   2)       THEN answers FAP/HNPCC/or INFLAMMATORY BOWEL DISORDER
        //find genetic abnormality flag      
        Concept cancerAbnormalityToldConcept = Context.getConceptService().getConcept(CANCER_ABNORMALITY_TOLD);
        Concept cancerAbnormalityToldYesConcept = Context.getConceptService()
                .getConcept(CANCER_ABNORMALITY_TOLD_YES);
        Concept cancerAbnormalityConcept = Context.getConceptService().getConcept(CANCER_ABNORMALITY);
        Concept cancerAbnormalityFapConcept = Context.getConceptService().getConcept(CANCER_ABNORMALITY_FAP);
        Concept cancerAbnormalityHnpccConcept = Context.getConceptService().getConcept(CANCER_ABNORMALITY_HNPCC);
        Concept cancerAbnormalityInflbdConcept = Context.getConceptService().getConcept(CANCER_ABNORMALITY_INFLBD);

        Obs cancerAbnormalityToldObs = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, cancerAbnormalityToldConcept));
        Concept cancerAbnormalityToldAns = (cancerAbnormalityToldObs == null ? null
                : cancerAbnormalityToldObs.getValueCoded());
        if (cancerAbnormalityToldAns != null && cancerAbnormalityToldAns.equals(cancerAbnormalityToldYesConcept)) {
            Obs cancerAbnormalityObs = findLatest(
                    Context.getObsService().getObservationsByPersonAndConcept(pat, cancerAbnormalityConcept));
            Concept cancerAbnormalityAns = (cancerAbnormalityObs == null ? null
                    : cancerAbnormalityObs.getValueCoded());
            if (cancerAbnormalityAns != null && (cancerAbnormalityAns.equals(cancerAbnormalityFapConcept)
                    || cancerAbnormalityAns.equals(cancerAbnormalityHnpccConcept)
                    || cancerAbnormalityAns.equals(cancerAbnormalityInflbdConcept))) {
                return null;
            }
        }

        //find surgery date
        Concept surgeryDateConcept = Context.getConceptService().getConcept(SURGERY_DATE);
        Obs surgeryDate = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, surgeryDateConcept));
        Date surgDate = surgeryDate == null ? null : surgeryDate.getValueDatetime();
        if (surgDate == null) {
            log.warn("No surgery is found for this patient: " + pat);
            return null;
        }

        //find rediation type
        Concept radiationTypeConcept = Context.getConceptService().getConcept(RADIATION_TYPE);
        Obs radiationType = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, radiationTypeConcept));
        Concept radType = radiationType == null ? null : radiationType.getValueCoded();

        //find cancer type
        Concept cancerTypeConcept = Context.getConceptService().getConcept(CANCER_TYPE);
        Obs cancerType = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, cancerTypeConcept));
        Concept type = cancerType == null ? null : cancerType.getValueCoded();
        //find cancer stage
        Concept cancerStageConcept = Context.getConceptService().getConcept(CANCER_STAGE);
        Obs cancerStage = findLatest(
                Context.getObsService().getObservationsByPersonAndConcept(pat, cancerStageConcept));
        Concept stage = cancerStage == null ? null : cancerStage.getValueCoded();

        //find follow-up years
        List<LafGuideline> guidelines = guidelineDao.getLafGuideline(type, stage);

        log.debug("Get guidelins: type=" + type + ", stage=" + stage + ", guidelines=" + guidelines);
        //create reminder entries
        List<LafReminder> reminders = new ArrayList<LafReminder>();
        if (guidelines != null) {
            for (LafGuideline guideline : guidelines) {
                Date[] dates = findTargetDates(surgDate, radType, guideline.getFollowYears());
                if (dates == null) {
                    continue;
                }
                for (Date dt : dates) {
                    LafReminder reminder = new LafReminder();
                    reminder.setPatient(pat);
                    reminder.setFollowProcedure(guideline.getFollowProcedure());
                    reminder.setTargetDate(dt);
                    //update cancer_patient_reminder table
                    //reminderDao.saveLafReminder(reminder);
                    reminders.add(reminder);
                }
            }
        } else {
            log.error("Guideline is not found for cancer type:" + type + " and cancer stage: " + stage);
        }

        //add follow-up care recommended by patient's personal provider
        List<LafReminder> remindersByProvider = getRemindersByProvider(pat);
        if (remindersByProvider != null && !remindersByProvider.isEmpty()) {
            reminders.addAll(remindersByProvider);
        }

        //sort guidelines by target date
        Collections.sort(reminders, LafReminder.getDateComparator());
        return reminders;
    }

    /**
      * Find target dates based solely on follow-up care frequency rule
      * 
      * @param surgeryDate
      * @param followYears
      * @return
      */
    private Date[] findTargetDates(Date surgDate, Concept radiationType, String followYears) {
        String[] split1 = followYears.split(":");
        String[] split2 = split1[0].split(",");

        if (surgDate == null) {
            log.debug("No reminder will be generated because no surgery is found for this patient.");
            return null;
        }

        if (split1.length >= 2 && "NO RADIATION".equals(split1[1])) {
            if (radiationType != null) {
                return null;
            }
        }

        Date startDate = surgDate;
        Date[] targetDates = new Date[split2.length];
        for (int ii = 0; ii < split2.length; ii++) {
            targetDates[ii] = findDate(startDate, split2[ii]);
        }
        // TODO Auto-generated method stub
        return targetDates;
    }

    /**
      * Auto generated method comment
      * 
      * @param reminders
      * @param followProcedure
      * @return
      */
    private Date findNextTargetDate(List<LafReminder> reminders, Concept followProcedure) {

        Date today = new Date();

        Date nextTargetDate = null;
        LafReminder refReminder1 = null;
        LafReminder refReminder2 = null;
        for (LafReminder reminder : reminders) {
            if (!followProcedure.equals(reminder.getFollowProcedure())) {
                continue;
            }

            refReminder2 = reminder;

            if (refReminder1 != null) {
                if (today.before(refReminder2.getTargetDate())) {
                    Date refDate12 = findMidDate(refReminder1.getTargetDate(), refReminder2.getTargetDate());

                    if (today.before(refDate12) && !LafReminder.FLAG_COMPLETED.equals(refReminder1.getFlag())) {
                        //nextTargetDate = findDate(startDate, split2[ii]);
                        nextTargetDate = refReminder1.getTargetDate();
                    } else if (!LafReminder.FLAG_COMPLETED.equals(refReminder2.getFlag())) {
                        nextTargetDate = refReminder2.getTargetDate();
                        ;
                    }

                    break;
                }
            }

            refReminder1 = refReminder2;
        }
        // TODO Auto-generated method stub
        return nextTargetDate;
    }

    /**
      * Auto generated method comment
      * 
      * @param refDate1
      * @param refDate2
      * @return
      */
    private Date findMidDate(Date refDate1, Date refDate2) {
        // TODO Auto-generated method stub
        long diffDays = (refDate2.getTime() - refDate1.getTime()) / (1000 * 60 * 60 * 24);

        Calendar cal = Calendar.getInstance();
        cal.setTime(refDate1);
        cal.add(Calendar.DATE, (int) diffDays / 2);

        return cal.getTime();
    }

    /**
      * Auto generated method comment
          
      * @param startDate
      * @param string
      * @return
      */
    private Date findDate(Date startDate, String yearsAfter) {
        float yrs = Float.parseFloat(yearsAfter);
        int months = (int) (yrs * 12.0);
        Calendar cal = Calendar.getInstance();
        cal.setTime(startDate);
        cal.add(Calendar.MONTH, months);

        // TODO Auto-generated method stub
        return cal.getTime();
    }

    /**
      * Auto generated method comment
      * 
      * @param observationsByPersonAndConcept
      * @return
      */
    private Obs findLatest(List<Obs> observations) {
        Obs latest = null;

        if (observations != null) {
            for (Obs obs : observations) {
                if (obs != null && !obs.isVoided()) {
                    if (latest == null || latest.getDateCreated().before(obs.getDateCreated())) {
                        latest = obs;
                    }

                }
            }
        }

        return latest;
    }
}