Skip to content
Snippets Groups Projects
ViewDiagnosisStatisticsAction.java 15.7 KiB
Newer Older
HMoss's avatar
HMoss committed
package edu.ncsu.csc.itrust.action;

import java.text.DateFormat;
HMoss's avatar
HMoss committed
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import edu.ncsu.csc.itrust.beans.DiagnosisBean;
import edu.ncsu.csc.itrust.beans.DiagnosisStatisticsBean;
import edu.ncsu.csc.itrust.dao.DAOFactory;
import edu.ncsu.csc.itrust.dao.mysql.DiagnosesDAO;
import edu.ncsu.csc.itrust.dao.mysql.ICDCodesDAO;
import edu.ncsu.csc.itrust.exception.DBException;
import edu.ncsu.csc.itrust.exception.FormValidationException;
import edu.ncsu.csc.itrust.exception.ITrustException;

/**
 * Used for the View Diagnosis Statistics page. Can return a list of all Diagnoses
 * and get diagnosis statistics for a specified Zip code, Diagnosis code, and date range.
 */
public class ViewDiagnosisStatisticsAction {
    /** Database access methods for ICD codes (diagnoses) */
    private ICDCodesDAO icdDAO;
    /** Database access methods for diagnosis information */
    private DiagnosesDAO diagnosesDAO;
    /** ICD Code for malaria */
    private static final String ICD_MALARIA = "84.50";
    /** ICD Code for Influenza */
    private static final String ICD_INFLUENZA = "487.00";
    
    /**
     * Parses a string date into a java.util.Date object. Also checks that the date
     * is in the format MM/dd/yyyy and ensures that it is not in the future.
     * @param dateString string representation of the date
     * @return parsed date as a java.util.Date object
     * @throws FormValidationException if the date is invalid
     */
    private Date parseDate(String dateString) throws FormValidationException {
        Date next;
        try {
            DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
            formatter.setLenient(false);
            next = formatter.parse(dateString);
        } catch (ParseException e) {
            throw new FormValidationException("Enter dates in MM/dd/yyyy");
        }
        if (next.after(new Date())) {
            throw new FormValidationException("Provided date is in the future.");
        }
        return next;
    }

    /**
     * Throws an exception if the string provided is not a valid zip code.
     * @param zip zip code as a string
     * @throws FormValidationException if the zip code is invalid
     */
    private void validateZipCode(String zip) throws FormValidationException {
        if (!zip.matches("([0-9]{5})|([0-9]{5}-[0-9]{4})")) {
            throw new FormValidationException("Zip Code must be 5 digits!");
        }
    }

    /**
     * Throws an exception if the ICDCode is not valid.
     * @param icdCode the ICD code
     * @throws ITrustException wrapper for a DB error
     * @throws FormValidationException if the ICD code is not valid
     */
    private void validateIcdCode(String icdCode) throws ITrustException, FormValidationException {
        boolean validCode = false;
        for(DiagnosisBean diag : getDiagnosisCodes()) {
            if (diag.getICDCode().equals(icdCode)) {
                validCode = true;
            }
        }
        if (validCode == false) {
            throw new FormValidationException("ICDCode must be valid diagnosis!");
        }
    }

    /**
     * Constructor for the action. Initializes DAO fields
     * @param factory The session's factory for DAOs
     */
    public ViewDiagnosisStatisticsAction(DAOFactory factory) {
        this.icdDAO = factory.getICDCodesDAO();
        this.diagnosesDAO = factory.getDiagnosesDAO();
    }
    
    /**
     * Gets all the diagnosis codes in iTrust and returns them in a list of beans.
     * 
     * @return List of DiagnosisBeans correlating to all ICDCodes
     * @throws ITrustException
     */
    public List<DiagnosisBean> getDiagnosisCodes() throws ITrustException  {
        return icdDAO.getAllICDCodes();
    }
    
    /**
     * Gets the counts of local and regional diagnoses for the specified input
     * 
     * @param lowerDate The beginning date for the time range
     * @param upperDate The ending date for the time range
     * @param icdCode The diagnosis code to examine
     * @param zip The zip code to examine
     * @return A bean containing the local and regional counts
     * @throws FormValidationException
     * @throws ITrustException
     */
    public DiagnosisStatisticsBean getDiagnosisStatistics(String lowerDate, String upperDate, 
            String icdCode, String zip) throws FormValidationException, ITrustException {
        DiagnosisStatisticsBean dsBean;
        if (lowerDate == null || upperDate == null || icdCode == null) {
            return null;
        }
        
        validateZipCode(zip);
        validateIcdCode(icdCode);
HMoss's avatar
HMoss committed

        Date lower = parseDate(lowerDate);
        Date upper = parseDate(upperDate);
        if (lower.after(upper)) {
            throw new FormValidationException("Start date must be before end date!");
        }
HMoss's avatar
HMoss committed

        dsBean = diagnosesDAO.getDiagnosisCounts(icdCode, zip, lower, upper);
    
        return dsBean;
    /**
     * Gets the counts of local and regional diagnoses for the specified input (lower date 8 weeks prior to upper date)
     *
     * @param upperDate The ending date for the time range
     * @param icdCode The diagnosis code to examine
     * @param zip The zip code to examine
     * @return A bean containing the local and regional counts
     * @throws FormValidationException
     * @throws ITrustException
     */
    public DiagnosisStatisticsBean getDiagnosisStatistics(String upperDate, String icdCode, String zip) throws FormValidationException, ITrustException {
        DiagnosisStatisticsBean dsBean;
        try {

            if (upperDate == null || icdCode == null)
                return null;

            Date upper = new SimpleDateFormat("MM/dd/yyyy").parse(upperDate);

            //calculate lower date by finding the date that is 8 weeks prior to upperDate
            Calendar cal = Calendar.getInstance();
            cal.setTime(upper);
            cal.add(Calendar.HOUR, -56*24);
            Date lower = cal.getTime();

            if (!zip.matches("([0-9]{5})|([0-9]{5}-[0-9]{4})"))
                throw new FormValidationException("Zip Code must be 5 digits!");

            boolean validCode = false;
            for(DiagnosisBean diag : getDiagnosisCodes()) {
                if (diag.getICDCode().equals(icdCode))
                    validCode = true;
            }
            if (validCode == false) {
                throw new FormValidationException("ICDCode must be valid diagnosis!");
            }

            dsBean = diagnosesDAO.getDiagnosisCounts(icdCode, zip, lower, upper);

        } catch (ParseException e) {
            throw new FormValidationException("Enter date in MM/dd/yyyy");
        }


        return dsBean;
    }

    public List<DiagnosisStatisticsBean> getDiagnosisTrends(String dateString, String icdCode, 
            String zip) throws FormValidationException, ITrustException {
        ArrayList<DiagnosisStatisticsBean> dsBean = null;
        if (dateString == null || icdCode == null) {
        validateIcdCode(icdCode);
        validateZipCode(zip);
        Date next = parseDate(dateString);
        
        dsBean = new ArrayList<>();
            
        Calendar cal = Calendar.getInstance();
        cal.setTime(next);
        for (int i = 0; i < 8; i++) {
            Date weekDate = cal.getTime();
            dsBean.add(diagnosesDAO.getCountForWeekBefore(icdCode, zip, weekDate));
            cal.add(Calendar.HOUR, -24*7);
        }
    
    /**
     * Gets the local and regional counts for the specified week and calculates the prior average.
     * 
     * @param startDate a date in the week to analyze
     * @param icdCode the diagnosis to analyze
     * @param zip the area to analyze
     * @param threshold threshold
     * @return statistics for the week and previous averages
     * @throws FormValidationException
     * @throws DBException
     */
    public ArrayList<DiagnosisStatisticsBean> getEpidemicStatistics(String startDate, 
            String icdCode, String zip, String threshold) 
            throws FormValidationException, DBException {
        if (startDate == null || icdCode == null) {
            return null;
        if (!(icdCode.equals(ICD_MALARIA) || icdCode.equals(ICD_INFLUENZA)) ) {
            throw new FormValidationException("Invalid ICD code.");
        }
        if(ICD_MALARIA.equals(icdCode)){
                Integer.parseInt(threshold);
            } catch(NumberFormatException e) {
                throw new FormValidationException("Threshold must be an integer.");
            }
        }
        Date lower = parseDate(startDate);
        
        DiagnosisStatisticsBean dbWeek = diagnosesDAO.getCountForWeekOf(icdCode, zip, lower);
        DiagnosisStatisticsBean dbAvg = new DiagnosisStatisticsBean(zip, 0, 0, lower, lower);
        
        Calendar cal = Calendar.getInstance();
        
        Date start = diagnosesDAO.findEarliestIncident(icdCode); //start, which is set to earliest incident
        Calendar startCal = Calendar.getInstance();
        if(start != null)
            startCal.setTime(start);
        
        ArrayList<DiagnosisStatisticsBean> ret = new ArrayList<DiagnosisStatisticsBean>();
        if (start == null) {
            ret.add(dbWeek);
            ret.add(dbAvg);
            return ret;
        }
        cal.setTime(lower); //cal, which is set to lower
        Calendar lowerCal = Calendar.getInstance();
        lowerCal.setTime(lower);
        int weekOfYr = cal.get(Calendar.WEEK_OF_YEAR);
        
        cal.set(Calendar.YEAR, startCal.get(Calendar.YEAR));  //cal's year then gets set to start's year
        ArrayList<DiagnosisStatisticsBean> dbList = new ArrayList<DiagnosisStatisticsBean>();
        
        boolean differentYears = cal.get(Calendar.YEAR) != lowerCal.get(Calendar.YEAR);
        while( cal.getTime().before(lower) && differentYears) {
            dbList.add( diagnosesDAO.getCountForWeekOf(icdCode, zip, cal.getTime()) );
            cal.add(Calendar.YEAR, 1);
            cal.set(Calendar.WEEK_OF_YEAR, weekOfYr);
            cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        }
        
        long avg = 0;
        long avgRegion = 0;
        if (dbList.size() > 0) {
            for (DiagnosisStatisticsBean d : dbList) {
                avg += d.getZipStats();
                avgRegion += d.getRegionStats();
            }
            avg /= dbList.size();
            avgRegion /= dbList.size();
        }
        
        dbAvg.setRegionStats(avgRegion);
        dbAvg.setZipStats(avg);
        
        ret.add(dbWeek);
        ret.add(dbAvg);
        return ret;
    }
    
    /**
     * Determines if an Influenza Epidemic is happening
     * 
     * @param weekDate a date in the currently evaluated week
     * @param zip the zip code to analyze
     * @return whether or not there is an epidemic
     * @throws ParseException
     * @throws DBException
     */
    public boolean isFluEpidemic(String weekDate, String zip) throws ParseException, DBException {
        Date wkDate = new SimpleDateFormat("MM/dd/yyyy").parse(weekDate);
        
        Calendar cal = Calendar.getInstance();
        
        int weekOfYr = cal.get(Calendar.WEEK_OF_YEAR);
        double threshold = calcInfluenzaThreshold(weekOfYr);
        DiagnosisStatisticsBean prev1 = 
            diagnosesDAO.getCountForWeekBefore(ICD_INFLUENZA, zip, cal.getTime());
        DiagnosisStatisticsBean prev2 =  
            diagnosesDAO.getCountForWeekBefore(ICD_INFLUENZA, zip, cal.getTime());
        long weekL1 = prev1.getRegionStats();
        long weekL2 = prev2.getRegionStats();

        boolean epidemicL1 = weekL1 > threshold;
        boolean epidemicL2 = weekL2 > threshold;
    }
    
    /**
     * Calculates the threshold of an influenza epidemic
     * 
     * @param weekNumber the week of the year
     * @return the epidemic threshold for flu cases
     */
    private double calcInfluenzaThreshold(double weekNumber) {
        return 5.34 
            + 0.271 * weekNumber 
            + 3.45 * Math.sin(2 * Math.PI * weekNumber / 52.0) 
            + 8.41 * Math.cos(2 * Math.PI * weekNumber / 52.0);
    }
    
    /**
     * Determines whether a Malaria epidemic is happening for two consecutive weeks
     * 
     * @param weekDate a date in the currently evaluated week
     * @param zip the zip code to analyze
     * @param thresholdStr the threshold for an epidemic
     * @return whether or not there is an epidemic
     * @throws DBException
     * @throws ParseException
     */
    public boolean isMalariaEpidemic(String weekDate, String zip, String thresholdStr) 
            throws DBException, ParseException {
        Date wkDate = new SimpleDateFormat("MM/dd/yyyy").parse(weekDate);
        
        ArrayList<DiagnosisStatisticsBean> historyWkL1 = new ArrayList<DiagnosisStatisticsBean>();
        ArrayList<DiagnosisStatisticsBean> historyWkL2 = new ArrayList<DiagnosisStatisticsBean>();
        
        double threshold = Double.parseDouble(thresholdStr);
        
        Calendar cal = Calendar.getInstance();
        DiagnosisStatisticsBean current = 
            diagnosesDAO.getCountForWeekOf(ICD_MALARIA, zip, cal.getTime());
        DiagnosisStatisticsBean prev1 = 
            diagnosesDAO.getCountForWeekBefore(ICD_MALARIA, zip, cal.getTime());
        long weekTotalL1 = prev1.getRegionStats();
        
        cal.add(Calendar.HOUR, -7*24);
        DiagnosisStatisticsBean prev2 = 
            diagnosesDAO.getCountForWeekBefore(ICD_MALARIA, zip, cal.getTime());
        long weekTotalL2 = prev2.getRegionStats();
        
        cal.setTime(wkDate);
        
        //Find earliest Malaria Case. Set calendar's year to that year
        Date startData = diagnosesDAO.findEarliestIncident(ICD_MALARIA);
        if (startData == null) {
            if (current.getRegionStats() > 0) {
                return true;
            }
            return false;
        }
        Calendar startDateCal = Calendar.getInstance();
        startDateCal.setTime(startData);
        Calendar wkDateCal = Calendar.getInstance();
        wkDateCal.setTime(wkDate);
        cal.set(Calendar.YEAR, startDateCal.get(Calendar.YEAR));
        
        boolean differentYears = cal.get(Calendar.YEAR) != wkDateCal.get(Calendar.YEAR);
        while (cal.getTime().before(wkDate) && differentYears) {
            historyWkL1.add( diagnosesDAO.getCountForWeekBefore(ICD_MALARIA, zip, cal.getTime()) );
            cal.add(Calendar.HOUR, -7*24);
            historyWkL2.add( diagnosesDAO.getCountForWeekBefore(ICD_MALARIA, zip, cal.getTime()) );
            cal.add(Calendar.HOUR, 7*24);

            cal.add(Calendar.YEAR, 1);
        long totalHistL1 = 0;
        long totalHistL2 = 0;
        long countHistL1 = historyWkL1.size();
        long countHistL2 = historyWkL1.size();
        for (DiagnosisStatisticsBean d : historyWkL1) {
            totalHistL1 += d.getRegionStats();
        }
        for (DiagnosisStatisticsBean d : historyWkL2) {
                
        double avgL1 = ((double)totalHistL1) / ((double)countHistL1);
        double avgL2 = ((double)totalHistL2) / ((double)countHistL2);

        boolean epidemicL1 = (weekTotalL1*100.0) / threshold > avgL1 
            || (countHistL1 == 0 && weekTotalL1 != 0);
        boolean epidemicL2 = (weekTotalL2*100.0) / threshold > avgL2 
            || (countHistL2 == 0 && weekTotalL2 != 0);
HMoss's avatar
HMoss committed
}