V1_0.java [src/java/m/wqm/nutappmgtscores] Revision: 69891c32fc5b880eed59e1555d59faf1d3520056  Date: Fri May 27 10:18:56 MDT 2016
package m.wqm.nutappmgtscores;

import csip.ModelDataService;
import csip.ServiceException;
import csip.utils.JSONUtils;
import csip.utils.Dates;
import csip.annotations.Polling;
import csip.annotations.Resource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Map;
import javax.ws.rs.Path;
import oms3.annotations.Name;
import oms3.annotations.Description;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.codehaus.jettison.json.JSONException;
import wqm.utils.DBResources;
import static wqm.utils.DBResources.WQM_ID;

@Name("WQM-16: Nutrient Application Management Scores (NutAppMgtScores)")
@Description("This service computes scores for adjusting the rate, timing, "
        + "and method of applying nutrients to mitigate nitrogen leaching, "
        + "and nitrogen and phosphorus runoff loss potential.")
@Path("m/nutappmgtscores/1.0")
@Polling(first = 10000, next = 2000)
@Resource(from = DBResources.class)
public class V1_0 extends ModelDataService {

    //Response
    private ArrayList<Result> result;

    //private class variables for this service
    private int AoAId = 0;
    private String p_soil_test_result = "err";
    private ArrayList<Crop> cropList;
    private String error_msg = "";

    @Override
    protected void preProcess() throws Exception {
        try {
            AoAId = getIntParam("aoa_id", 0);
            p_soil_test_result = getStringParam("p_soil_test_result", "err");
            cropList = new ArrayList<>();

            JSONArray cropIds = getJSONArrayParam("cropIds");
            for (int i = 0; i < cropIds.length(); i++) {
                JSONArray applicationList = null;
                Map<String, JSONObject> mgtCropId = JSONUtils.preprocess(cropIds.getJSONArray(i));

                if (JSONUtils.checkKeyExistsB(mgtCropId, "applicationList")) {
                    applicationList = JSONUtils.getJSONArrayParam(mgtCropId, "applicationList");
                }

                cropList.add(new Crop(JSONUtils.getIntParam(mgtCropId, "mgt_crop_id", 0), JSONUtils.getStringParam(mgtCropId, "crop_plant_date", "err"),
                        JSONUtils.getDoubleParam(mgtCropId, "crop_yield", 0), JSONUtils.getStringParam(mgtCropId, "crop_yield_units", "err"),
                        applicationList, p_soil_test_result));
            }
        } catch (ServiceException | JSONException ex) {
            LOG.log(Level.SEVERE, "Error in processing the request JSON for WQM-16!", ex);
            throw new ServiceException("Error in processing the request JSON.", ex);
        }

        ValidateInput();

    }

    @Override
    protected void doProcess() throws Exception {
        String ret_val = EXEC_OK;
        int n_app_timing_score = 100;
        int p_app_timing_score = 100;
        int app_method_score = -1;
        int n_app_rate_score = 0;
        int p_app_rate_score = 0;
        int this_crop_id = 0;
        String query;

        if (!error_msg.isEmpty()) {
            ret_val = error_msg;
        } else {
            result = new ArrayList<>();

            try (Connection conn = getResourceJDBC(WQM_ID);
                    Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                            ResultSet.CONCUR_READ_ONLY)) {
                for (Crop crop : cropList) {

                    String crop_type = crop.getCropType();
//////TODO:  Remember to check for errors from the classes....   ///////
                    if (!crop_type.isEmpty()) {
                        //#Update N and P application management rate scores for each crop
                        int[] app_rate_scores = crop.getNutrientApplicationRateScores();
                        if (!(error_msg = crop.getErrorMsg()).isEmpty()) {
                            break;
                        }

                        //Cummulative sum of N and P application management scores over all crops for the AoA
                        n_app_rate_score += app_rate_scores[Crop.N_APP_RATE_SCORE];
                        p_app_rate_score += app_rate_scores[Crop.P_APP_RATE_SCORE];

                        //#Compute N and P application timing scores for the crop and update timing scores for the AoA
                        int[] app_time_scores = crop.getNutrientApplicationTimingScores();
                        if (!(error_msg = crop.getErrorMsg()).isEmpty()) {
                            break;
                        }

                        //  There is a problem with the logic of the specification here...It does not take into account the actual timing of applications when
                        //  there are mulitple crops and multiple years invovled....these final values are probably not correct.  The "-1" below is placed here
                        //  in an attempt to fix the missing database values problem that also exists with this logic at the nutrient level.
                        if ((app_time_scores[Crop.N_APP_TIMING_SCORE] < n_app_timing_score) || (n_app_timing_score == -1)) {
                            n_app_timing_score = app_time_scores[Crop.N_APP_TIMING_SCORE];
                        }

                        if ((app_time_scores[Crop.P_APP_TIMING_SCORE] < p_app_timing_score) || (p_app_timing_score == -1)) {
                            p_app_timing_score = app_time_scores[Crop.P_APP_TIMING_SCORE];
                        }

                        //#If any nutrient application for any crop is not incorporated, the method score for the AoA is zero
                        if (!crop.allNutrientsIncorporated()) {
                            app_method_score = 0;
                        }
                        if (!(error_msg = crop.getErrorMsg()).isEmpty()) {
                            break;
                        }
                    } else {
                        ret_val = error_msg = "Cannot get crop_type. " + crop.getErrorMsg();
                    }
                }

                //  If all crops' nutrients were incorporated this value will still be -1
                if (app_method_score == -1) {
                    query = "SELECT app_mgt_score FROM wqm.wqm_nutrient_application_mgt_scores "
                            + "WHERE app_mgt_kind='Method' AND app_mgt_factor='incorporate';";
                    try (ResultSet resultSet = statement.executeQuery(query)) {
                        if (resultSet.first()) {
                            app_method_score = resultSet.getInt("app_mgt_score");
                        }
                    }

                    //#Compute application management scores for nitrogen in groundwater, nitrogen in surface water, and phosphorus in surface water
                    int nleach_app_mgt_score = n_app_rate_score + n_app_timing_score + app_method_score;
                    int nsurf_app_mgt_score = n_app_rate_score + n_app_timing_score + app_method_score;
                    int psurf_app_mgt_score = p_app_rate_score + p_app_timing_score + app_method_score;
                    result.add(new Result(AoAId, nleach_app_mgt_score, nsurf_app_mgt_score, psurf_app_mgt_score, n_app_rate_score, n_app_timing_score, p_app_rate_score, p_app_timing_score, app_method_score));
                }
            } catch (ServiceException | SQLException ex) {
                LOG.log(Level.SEVERE, "SQL problem for WQM-16!", ex);
                throw new ServiceException("SQL problem", ex);
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, "Error in processing for WQM-16!", ex);
                throw new ServiceException("Exception", ex);
            }
        }

    }

    @Override
    //writing the results back to JSON
    protected void postProcess() throws Exception {
        try {
            for (Result rs1 : result) {
                JSONArray tmpArr = new JSONArray();
                tmpArr.put(JSONUtils.dataDesc("AoAId", rs1.getAoAId(), "Area of Analysis Identifier"));
                tmpArr.put(JSONUtils.dataDesc("nleach_app_mgt_score", rs1.getNleachAppMgtScore(), "Nitrogen Application Management Score for Mitigating Leaching Loss Potential"));
                tmpArr.put(JSONUtils.dataDesc("nsurf_app_mgt_score", rs1.getNsurfAppMgtScore(), "Nitrogen Application Management Score for Mitigating Surface Runoff Loss Potential"));
                tmpArr.put(JSONUtils.dataDesc("psurf_app_mgt_score", rs1.getPsurfAppMgtScore(), "Phosphorus Application Management Score for Mitigating Surface Runoff Loss Potential"));
                tmpArr.put(JSONUtils.dataDesc("n_app_rate_score", rs1.getnAppRateScore(), "Nitrogen Application Rate Mitigation Score"));
                tmpArr.put(JSONUtils.dataDesc("n_app_timing_score", rs1.getnAppTimingScore(), "Nitrogen Application Timing Mitigation Score"));
                tmpArr.put(JSONUtils.dataDesc("p_app_rate_score", rs1.getpAppRateScore(), "Phosphorus Application Rate Mitigation Score"));
                tmpArr.put(JSONUtils.dataDesc("p_app_timing_score", rs1.getpAppTimingScore(), "Phosphorus Application Timing Mitigation Score"));
                tmpArr.put(JSONUtils.dataDesc("app_method_score", rs1.getAppMethodScore(), "Nutrient Application Method Mitigation Score"));
                putResult("Nutrition Application Summary", tmpArr, "Nutrition Application Summary");
            }
        } catch (JSONException ex) {
            LOG.log(Level.SEVERE, "Error in processing the response JSON for WQM-16!", ex);
            throw new ServiceException("Error in processing the response JSON.", ex);
        }
    }

    private boolean ValidateInput() {
        for (Crop tCrop : cropList) {
            if (!tCrop.validate()) {
                error_msg += "; " + tCrop.getErrorMsg();
            }
        }

        return (error_msg.isEmpty());
    }

    public class Crop {

        //  Begin Crop Class
        public static final int N_APP_RATE_SCORE = 0;
        public static final int P_APP_RATE_SCORE = 1;
        public static final int N_APP_TIMING_SCORE = 0;
        public static final int P_APP_TIMING_SCORE = 1;

        private int mgtCropId;
        private String cropPlantDate;
        private Date plantDate;
        private double cropYield;
        private String cropYieldUnits;
        private ArrayList<Crop.NutrientApplication> nutrientApplicationList;
        private Result calc_result;
        private String cropType = "";
        private String error_msg = "";
        private Boolean multipleNutrientApplication = false;
        private int n_app_timing_score = 100;
        private int p_app_timing_score = 100;
        private int app_method_score = 0;
        private int[] appRateScore = new int[2];
        private int[] appNutrientTimingScore = new int[2];
        private String pSoilTestResult = "None";

        public Crop(int mgtCropId, String cropPlantDate, double cropYield,
                String cropYieldUnits, JSONArray applicationList, String pSoilTestResult) throws ServiceException {
            this.mgtCropId = mgtCropId;
            this.cropPlantDate = cropPlantDate;
            this.cropYield = cropYield;
            this.cropYieldUnits = cropYieldUnits;
            this.pSoilTestResult = pSoilTestResult;

            nutrientApplicationList = new ArrayList<Crop.NutrientApplication>();

            try {
                plantDate = getCropPlantDate();
                if (null != applicationList) {
                    for (int j = 0; j < applicationList.length(); j++) {
                        Map<String, JSONObject> application = JSONUtils.preprocess(applicationList.getJSONArray(j));
                        nutrientApplicationList.add(new Crop.NutrientApplication(JSONUtils.getStringParam(application, "nutrient_application_date", "err"), JSONUtils.getStringParam(application, "incorporated", ""), JSONUtils.getJSONArrayParam(application, "application")));
                    }
                }
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, "Error in processing for WQM-16!", ex);
                throw new ServiceException("Exception", ex);
            }

            multipleNutrientApplication = (nutrientApplicationList.size() > 1);
            appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = -1;
            appNutrientTimingScore[N_APP_TIMING_SCORE] = appNutrientTimingScore[P_APP_TIMING_SCORE] = -1;
        }

        //Get methods
        public int getMgtCropId() {
            return mgtCropId;
        }

        public String getErrorMsg() {
            for (Crop.NutrientApplication nutrientApplied : nutrientApplicationList) {
                error_msg += nutrientApplied.getErrorMsg();
            }
            return error_msg;
        }

        public String getCropType() throws ServiceException {
            if ((cropType.isEmpty()) && (error_msg.isEmpty())) {

                String query = "SELECT wqm_crop_type FROM wqm.wqm_crops WHERE wqm_crop_id="
                        + mgtCropId + " AND wqm_crop_units='" + cropYieldUnits + "';";
                try (Connection conn = getResourceJDBC(WQM_ID);
                        Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
                    try (ResultSet resultSet = statement.executeQuery(query)) {
                        if (!resultSet.first()) {
                            error_msg += "That crop, " + mgtCropId + ", and crop yield units, " + cropYieldUnits + ", was not found in the database";
                        } else {
                            cropType = resultSet.getString("wqm_crop_type");
                        }
                    }
                } catch (ServiceException | SQLException ex) {
                    LOG.log(Level.SEVERE, "SQL problem for WQM-16!", ex);
                    throw new ServiceException("SQL problem", ex);
                }
            }
            return cropType;
        }

        public final Date getCropPlantDate() throws Exception {
            String[] parse = cropPlantDate.split("-");
            Calendar date = new GregorianCalendar(Integer.parseInt(parse[0]),
                    Integer.parseInt(parse[1]) - 1, Integer.parseInt(parse[2]));
            return date.getTime();
        }

        public double getCropYield() {
            return cropYield;
        }

        public String getCropYieldUnits() {
            return cropYieldUnits;
        }

        public ArrayList getNutrientApplicationList() {
            return nutrientApplicationList;
        }

        public int[] getNutrientApplicationRateScores() throws ServiceException {
            if ((appRateScore[N_APP_RATE_SCORE] < 0) || (appRateScore[P_APP_RATE_SCORE] < 0)) {
                try (Connection conn = getResourceJDBC(WQM_ID);
                        Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
                    if (nutrientApplicationList.isEmpty()) {
                        ResultSet resultSet;

                        appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                        String queryNitrogen = "SELECT app_mgt_score "
                                + "FROM wqm.wqm_nutrient_application_mgt_scores "
                                + "WHERE nutrient='Nitrogen' AND app_mgt_kind='Rate' AND app_mgt_factor='none';";
                        String queryPhosporous = "SELECT app_mgt_score "
                                + "FROM wqm.wqm_nutrient_application_mgt_scores "
                                + "WHERE nutrient='Phosphorus' AND app_mgt_kind='Rate' "
                                + "AND app_mgt_factor='none' AND soil_test_result='" + pSoilTestResult + "';";

                        resultSet = statement.executeQuery(queryNitrogen);
                        if (resultSet.first()) {
                            appRateScore[N_APP_RATE_SCORE] = resultSet.getInt("app_mgt_score");
                        }

                        resultSet = statement.executeQuery(queryPhosporous);
                        if (resultSet.first()) {
                            appRateScore[P_APP_RATE_SCORE] = resultSet.getInt("app_mgt_score");
                        }
                    } else {
                        ResultSet resultSet;
                        String query;

                        double nrate = 0.0;
                        double prate = 0.0;

                        double wqm_crop_pct_dmat = 0.0;
                        double wqm_pct_nitrogen = 0.0;
                        double wqm_pct_phosphorus = 0.0;
                        double wqm_crop_yield = 0.0;

                        int ncrop_app_rate_score = 0;
                        int pcrop_app_rate_score = 0;

                        for (Crop.NutrientApplication nutrient : nutrientApplicationList) {
                            ArrayList<Crop.NutrientApplication.Nutrient> nutrientAppliedList = nutrient.getNutrientList();
                            for (Crop.NutrientApplication.Nutrient nApplied : nutrientAppliedList) {
                                switch (nApplied.getNutrientApplied()) {  //Note:  This switch requires JDK 1.7 or above
                                    case "Nitrogen":
                                        nrate += nApplied.getApplicationRate();
                                        break;
                                    case "Phosphorus":
                                        prate += nApplied.getApplicationRate();
                                        break;
                                }
                            }
                        }

                        query = "SELECT wqm_crop_pct_dmat, wqm_pct_nitrogen, wqm_pct_phosphorus, wqm_crop_yield "
                                + "FROM wqm.wqm_crops WHERE wqm_crop_id=" + mgtCropId + ";";
                        resultSet = statement.executeQuery(query);
                        if (resultSet.first()) {
                            wqm_crop_pct_dmat = resultSet.getDouble("wqm_crop_pct_dmat");
                            wqm_pct_nitrogen = resultSet.getDouble("wqm_pct_nitrogen");
                            wqm_pct_phosphorus = resultSet.getDouble("wqm_pct_phosphorus");
                            wqm_crop_yield = resultSet.getDouble("wqm_crop_yield");

                            //  Added wqm_crop_yield to the calc per bug #462839
                            double n_growout = cropYield * wqm_crop_yield * wqm_crop_pct_dmat * wqm_pct_nitrogen;
                            double p_growout = cropYield * wqm_crop_yield * wqm_crop_pct_dmat * wqm_pct_phosphorus;

                            double n_remove_ratio = nrate / n_growout;
                            double p_remove_ratio = prate / p_growout;

                            query = "SELECT app_mgt_score FROM wqm.wqm_nutrient_application_mgt_scores "
                                    + "WHERE nutrient = 'Nitrogen' AND app_mgt_kind = 'Rate' AND app_mgt_factor=";

                            switch (cropType) {
                                case "Small Grain":  //Note the case conflicts here...
                                    query += "'small grain'";
                                    break;

                                default:
                                    query += "'other'";
                            }
                            query += " AND remove_ratio_1 <= " + n_remove_ratio + "AND remove_ratio_2 > " + n_remove_ratio + ";";

                            resultSet = statement.executeQuery(query);
                            if (resultSet.first()) {
                                ncrop_app_rate_score = resultSet.getInt("app_mgt_score");
                            } else {
                                appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                            }

                            pcrop_app_rate_score = -1;
                            //#Compute P application management rate scores based on removal ratio and soil test result
                            switch (pSoilTestResult) {
                                case "High":
                                    if (p_remove_ratio >= 1.2) {
                                        pcrop_app_rate_score = 0;
                                    }
                                    break;
                                case "Medium":
                                case "Low":
                                    if (p_remove_ratio >= 1.6) {
                                        pcrop_app_rate_score = 0;
                                    }
                                    break;
                                case "None":
                                    if (p_remove_ratio >= 1.2) {
                                        pcrop_app_rate_score = 0;
                                    }
                                    break;
                            }

                            if (pcrop_app_rate_score == -1) {
                                query = "SELECT app_mgt_score FROM wqm.wqm_nutrient_application_mgt_scores "
                                        + "WHERE nutrient='Phosphorus' AND app_mgt_kind='Rate' "
                                        + "AND app_mgt_factor='app' AND soil_test_result='"
                                        + pSoilTestResult + "' AND remove_ratio_1 <= " + p_remove_ratio
                                        + " AND remove_ratio_2 > " + p_remove_ratio + ";";
                                resultSet = statement.executeQuery(query);
                                if (resultSet.first()) {
                                    pcrop_app_rate_score = resultSet.getInt("app_mgt_score");
                                }
                            }

                            if (pcrop_app_rate_score != -1) {
                                //#Update N and P application management rate scores for the AoA
                                appRateScore[N_APP_RATE_SCORE] = ncrop_app_rate_score;
                                appRateScore[P_APP_RATE_SCORE] = pcrop_app_rate_score;
                            } else {//We have a problem...no validity in any following computations if we proceed since the last query failed to find any data.  What would you like to do?
                                appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                            }
                        } else { //We have a problem...no validity in any following computations if we proceed.  What would you like to do?
                            appRateScore[N_APP_RATE_SCORE] = appRateScore[P_APP_RATE_SCORE] = 0;
                        }
                    }
                } catch (ServiceException | SQLException ex) {
                    error_msg += ex.getMessage();
                    LOG.log(Level.SEVERE, "SQL problem for WQM-16!", ex);
                    throw new ServiceException("SQL problem", ex);
                }
            }

            return appRateScore;
        }

        public int[] getNutrientApplicationTimingScores() throws ServiceException, Exception {
            if ((appNutrientTimingScore[N_APP_TIMING_SCORE] < 0) || (appNutrientTimingScore[P_APP_TIMING_SCORE] < 0)) {
                int tempNScore = 100;
                int tempPScore = 100;

                for (Crop.NutrientApplication nutrientApplication : nutrientApplicationList) {
                    //  Call each application compare its results to the last and adjust main score if necessary.
                    int[] tempAppTimingScores = nutrientApplication.getNutrientTimingScores(multipleNutrientApplication);

                    if (tempAppTimingScores[N_APP_TIMING_SCORE] < tempNScore) {
                        tempNScore = tempAppTimingScores[N_APP_TIMING_SCORE];
                    }

                    if (tempAppTimingScores[P_APP_TIMING_SCORE] < tempPScore) {
                        tempPScore = tempAppTimingScores[P_APP_TIMING_SCORE];
                    }
                }

                appNutrientTimingScore[P_APP_TIMING_SCORE] = tempPScore;
                appNutrientTimingScore[N_APP_TIMING_SCORE] = tempNScore;
            }

            return appNutrientTimingScore;
        }

        public Boolean allNutrientsIncorporated() {
            Boolean ret_val = true;
            for (Crop.NutrientApplication nutrientApplication : nutrientApplicationList) {
                if (!nutrientApplication.allNutrientsIncorporated()) {
                    ret_val = false;
                    break;
                }
            }

            return ret_val;
        }

        public String processCropData() {
            String ret_val = "";

            return ret_val;
        }

        public Boolean validate() {
            for (Crop.NutrientApplication tNutrientApplication : nutrientApplicationList) {
                if (!tNutrientApplication.validate()) {
                    error_msg += "; " + tNutrientApplication.getErrorMsg();
                }
            }

            return (error_msg.isEmpty());
        }

        //  Inner Classes
        class NutrientApplication {

            class Nutrient {

                private final String nutrientApplied;
                private final double applicationRate;

                Nutrient(String nutrientApplied, double applicationRate) throws ServiceException {
                    this.nutrientApplied = nutrientApplied;
                    this.applicationRate = applicationRate;

                    if (!nutrientApplied.equals("Nitrogen") && !nutrientApplied.equals("Phosphorus")) {
                        throw new ServiceException("Invalid input data.  Bad nutrient name");
                    }
                }

                //Get Methods
                public String getNutrientApplied() {
                    return nutrientApplied;
                }

                public double getApplicationRate() {
                    return applicationRate;
                }

                public String getErrorMsg() {
                    return error_msg;
                }

                public Boolean validate() {
                    return (error_msg.isEmpty());
                }
            }
            private String error_msg = "";
            private String applicationDate;
            private boolean incorporated;
            private ArrayList<Crop.NutrientApplication.Nutrient> nutrientList;
            private int appMethodScore = 0;
            private int[] nutrientTimingScores = new int[2];

            NutrientApplication(String applicationDate, String incorporated, JSONArray applications) throws ServiceException {
                this.applicationDate = applicationDate;
                nutrientTimingScores[N_APP_TIMING_SCORE] = nutrientTimingScores[P_APP_TIMING_SCORE] = -1;
                if ((!"true".equalsIgnoreCase(incorporated)) && (!"false".equalsIgnoreCase(incorporated))) {
                    throw new ServiceException("Invalid incorporation value for nutrient application " + applicationDate);
                } else {
                    this.incorporated = Boolean.parseBoolean(incorporated);
                    nutrientList = new ArrayList<>();
                    try {
                        for (int k = 0; k < applications.length(); k++) {
                            Map<String, JSONObject> nutrient = JSONUtils.preprocess(applications.getJSONArray(k));
                            nutrientList.add(new Crop.NutrientApplication.Nutrient(JSONUtils.getStringParam(nutrient, "nutrient_applied", "err"), JSONUtils.getDoubleParam(nutrient, "application_rate", 0)));
                        }
                    } catch (JSONException ex) {
                        LOG.log(Level.SEVERE, "Error in processing for WQM-16!", ex);
                        throw new ServiceException("JSONException", ex);
                    }
                }
            }

            //Get Methods
            public Date getApplicationDate() throws Exception {
                String[] parse = applicationDate.split("-");
                //Some say should set to use UTC first...do we wanna do that?
                Calendar date = new GregorianCalendar(Integer.parseInt(parse[0]),
                        Integer.parseInt(parse[1]) - 1, Integer.parseInt(parse[2]));
                return date.getTime();
            }

            public boolean isIncorporated() {
                return incorporated;
            }

            public ArrayList getNutrientList() {
                return nutrientList;
            }

            public String getErrorMsg() {
                for (Crop.NutrientApplication.Nutrient nutrient : nutrientList) {
                    error_msg += nutrient.getErrorMsg();
                }
                return error_msg;
            }

            public Boolean validate() {
                for (Crop.NutrientApplication.Nutrient nutrient : nutrientList) {
                    if (!nutrient.validate()) {
                        error_msg += "; " + nutrient.getErrorMsg();
                    }
                }
                return (error_msg.isEmpty());
            }

            public int[] getNutrientTimingScores(Boolean split) throws ServiceException, Exception {
                if ((nutrientTimingScores[N_APP_TIMING_SCORE] < 0) || (nutrientTimingScores[P_APP_TIMING_SCORE] < 0)) {
                    int tempNScore = 100;
                    int tempPScore = 100;

                    for (Crop.NutrientApplication.Nutrient nutrient : nutrientList) {
                        int app_timing_score;
                        try (Connection conn = getResourceJDBC(WQM_ID);
                                Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
                            long app_day_diff = Dates.diffInMillis(getCropPlantDate(), getApplicationDate());
                            app_day_diff = TimeUnit.MILLISECONDS.toDays(app_day_diff);
                            String query = "SELECT app_mgt_score "
                                    + "FROM wqm.wqm_nutrient_application_mgt_scores WHERE nutrient='";
                            query += nutrient.getNutrientApplied() + "' ";
                            query += "AND app_mgt_kind='Timing' AND app_mgt_factor='" + (split ? "split" : "nosplit") + "'AND days_fr_plant_1 <= " + app_day_diff + "AND days_fr_plant_2 > " + app_day_diff + ";";
                            try (ResultSet resultSet = statement.executeQuery(query)) {
                                if (!resultSet.first()) {
                                    app_timing_score = -1;
                                } else {
                                    app_timing_score = resultSet.getInt("app_mgt_score");
                                }
                                switch (nutrient.getNutrientApplied()) {
                                    case "Nitrogen":
                                        if (app_timing_score < tempNScore) {
                                            tempNScore = app_timing_score;
                                        }
                                        break;
                                    case "Phosphorus":
                                        if (app_timing_score < tempPScore) {
                                            tempPScore = app_timing_score;
                                        }
                                        break;
                                    default:
                                        error_msg += "Invalid nutrient name specified";
                                }
                            }
                        } catch (ServiceException | SQLException ex) {
                            LOG.log(Level.SEVERE, "SQL problem for WQM-16!", ex);
                            throw new ServiceException("SQL problem", ex);
                        }
                        if (error_msg.isEmpty()) {
                            nutrientTimingScores[N_APP_TIMING_SCORE] = tempNScore;
                            nutrientTimingScores[P_APP_TIMING_SCORE] = tempPScore;
                        }
                    }
                }
                return nutrientTimingScores;
            }

            public Boolean allNutrientsIncorporated() {
                return incorporated;
            }
        }
    }

    static class Result {

        int AoAId;
        int nleach_app_mgt_score;
        int nsurf_app_mgt_score;
        int psurf_app_mgt_score;
        int n_app_rate_score;
        int n_app_timing_score;
        int p_app_rate_score;
        int p_app_timing_score;
        int app_method_score;

        public Result(int AoAId, int nleach_app_mgt_score, int nsurf_app_mgt_score,
                int psurf_app_mgt_score, int n_app_rate_score, int n_app_timing_score,
                int p_app_rate_score, int p_app_timing_score, int app_method_score) {
            this.AoAId = AoAId;
            this.nleach_app_mgt_score = nleach_app_mgt_score;
            this.nsurf_app_mgt_score = nsurf_app_mgt_score;
            this.psurf_app_mgt_score = psurf_app_mgt_score;
            this.n_app_rate_score = n_app_rate_score;
            this.n_app_timing_score = n_app_timing_score;
            this.p_app_rate_score = p_app_rate_score;
            this.p_app_timing_score = p_app_timing_score;
            this.app_method_score = app_method_score;
        }

        //Getter Methods
        public int getAoAId() {
            return AoAId;
        }

        public int getNleachAppMgtScore() {
            return nleach_app_mgt_score;
        }

        public int getNsurfAppMgtScore() {
            return nsurf_app_mgt_score;
        }

        public int getPsurfAppMgtScore() {
            return psurf_app_mgt_score;
        }

        public int getnAppRateScore() {
            return n_app_rate_score;
        }

        public int getnAppTimingScore() {
            return n_app_timing_score;
        }

        public int getpAppRateScore() {
            return p_app_rate_score;
        }

        public int getpAppTimingScore() {
            return p_app_timing_score;
        }

        public int getAppMethodScore() {
            return app_method_score;
        }
    }

}