V1_0.java [src/java/m/wqm/wqm16_nutappmgtscores] Revision:   Date:
package m.wqm.wqm16_nutappmgtscores;

import csip.ModelDataService;
import csip.api.server.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 csip.annotations.Name;
import csip.annotations.Description;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import java.util.concurrent.TimeUnit;
import org.codehaus.jettison.json.JSONException;
import wqm.utils.DBQueries;
import wqm.utils.DBResources;
import static wqm.utils.DBResources.WQM_ID;

/**
 * WQM-16: Nutrient Application Management Scores
 *
 * @author Rumpal Sidhu
 * @version 1.0
 */
@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 {

    private int aoaId;
    private String pSoilTestResult;
    private ArrayList<Crop> cropList = new ArrayList<>();
    private double nAppRateScore, pAppRateScore, nleachAppMgtScore, nsurfAppMgtScore, psurfAppMgtScore;
    private int appMethodScore = 0, nAppTimingScore = 100, pAppTimingScore = 100;

    @Override
    protected void preProcess() throws ServiceException, JSONException {
        aoaId = parameter().getInt("AoAId", 0);
        pSoilTestResult = parameter().getString("p_soil_test_result", null);
        JSONArray crops = parameter().getJSONArray("crop_list");
        for (int i = 0; i < crops.length(); i++) {
            Map<String, JSONObject> mgtCropId = JSONUtils.preprocess(crops.getJSONArray(i));
            int id = JSONUtils.getIntParam(mgtCropId, "crop_id", 0);
            String plantDate = JSONUtils.getStringParam(mgtCropId, "crop_plant_date", null);
            Date cropPlantDate = getDate(plantDate);
            double yield = JSONUtils.getDoubleParam(mgtCropId, "crop_yield", 0);
            String units = JSONUtils.getStringParam(mgtCropId, "crop_yield_units", null);

            ArrayList<NutrientApplication> nutrientApplicationList = new ArrayList<>();
            if (JSONUtils.checkKeyExistsB(mgtCropId, "application_list")) {
                JSONArray applicationList = JSONUtils.getJSONArrayParam(mgtCropId, "application_list");
                for (int j = 0; j < applicationList.length(); j++) {
                    Map<String, JSONObject> application = JSONUtils.preprocess(applicationList.getJSONArray(j));
                    String nutrient_application_date = JSONUtils.getStringParam(application, "nutrient_application_date", null);
                    Date applicationDate = getDate(nutrient_application_date);
                    boolean incorporated = JSONUtils.getBooleanParam(application, "incorporated", false);

                    ArrayList<Nutrient> nutrientList = new ArrayList<>();
                    JSONArray appList = JSONUtils.getJSONArrayParam(application, "application");
                    for (int k = 0; k < appList.length(); k++) {
                        Map<String, JSONObject> app = JSONUtils.preprocess(appList.getJSONArray(k));
                        String nutrientApplied = JSONUtils.getStringParam(app, "nutrient_applied", null);
                        int applicationRate = JSONUtils.getIntParam(app, "application_rate", 0);
                        nutrientList.add(new Nutrient(nutrientApplied, applicationRate));
                    }
                    nutrientApplicationList.add(new NutrientApplication(applicationDate, incorporated, nutrientList));
                }
            }
            cropList.add(new Crop(id, cropPlantDate, yield, units, nutrientApplicationList));
        }
    }

    @Override
    protected void doProcess() throws ServiceException, SQLException {
        try (Connection connection = resources().getJDBC(WQM_ID);) {
            compute(connection);
        }
    }

    @Override
    protected void postProcess() throws JSONException {
        results().put("AoAId", aoaId, "Area of Analysis Identifier");
        results().put("nleach_app_mgt_score", nleachAppMgtScore, "Nitrogen Application Management Score for Mitigating Leaching Loss Potential");
        results().put("nsurf_app_mgt_score", nsurfAppMgtScore, "Nitrogen Application Management Score for Mitigating Surface Runoff Loss Potential");
        results().put("psurf_app_mgt_score", psurfAppMgtScore, "Phosphorus Application Management Score for Mitigating Surface Runoff Loss Potential");
        results().put("n_app_rate_score", nAppRateScore, "Nitrogen Application Rate Mitigation Score");
        results().put("n_app_timing_score", nAppTimingScore, "Nitrogen Application Timing Mitigation Score");
        results().put("p_app_rate_score", pAppRateScore, "Phosphorus Application Rate Mitigation Score");
        results().put("p_app_timing_score", pAppTimingScore, "Phosphorus Application Timing Mitigation Score");
        results().put("app_method_score", appMethodScore, "Nutrient Application Method Mitigation Score");
    }

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

    private void compute(Connection connection) throws SQLException {
        for (Crop crop : cropList) {

            if (crop.nutrientApplicationList.isEmpty()) {
                computeEmpty(connection);
            } else {
                computeAppRateScore(connection, crop);
                computeAppTimimgScore(connection, crop);
            }

            nleachAppMgtScore = nAppRateScore + nAppTimingScore + appMethodScore;
            nsurfAppMgtScore = nAppRateScore + nAppTimingScore + appMethodScore;
            psurfAppMgtScore = pAppRateScore + pAppTimingScore + appMethodScore;

        }
    }

    private void computeEmpty(Connection connection) throws SQLException {
        try (Statement statement = connection.createStatement();) {
            try (ResultSet resultSet = statement.executeQuery(DBQueries.WQM16Query02("nitrogen", "rate", "none", null, null, null));) {
                while (resultSet.next()) {
                    nAppRateScore = resultSet.getInt("app_mgt_score");
                }
            }

            try (ResultSet resultSet = statement.executeQuery(DBQueries.WQM16Query02("phosphorus", "rate", "none", pSoilTestResult, null, null));) {
                while (resultSet.next()) {
                    pAppRateScore = resultSet.getInt("app_mgt_score");
                }
            }
        }
    }

    private void computeAppRateScore(Connection connection, Crop crop) throws SQLException {
        try (Statement statement = connection.createStatement();) {
            String cropType = "";
            double ncropAppRateScore = 0, pcropAppRateScore = 0, nRate = 0, pRate = 0;
            double cropPctDmat = 0, pctNitrogen = 0, pctPhosphorus = 0;

            //Compute N and P application rates for the crop
            for (NutrientApplication nutApp : crop.nutrientApplicationList) {
                for (Nutrient nutrient : nutApp.nutrientList) {
                    switch (nutrient.nutrientApplied.toUpperCase()) {
                        case "NITROGEN":
                            nRate += nutrient.application_Rate;
                            break;
                        case "PHOSPHORUS":
                            pRate += nutrient.application_Rate;
                            break;
                    }
                }
            }

            try (ResultSet resultSet = statement.executeQuery(DBQueries.WQM16Query01(crop.id, null));) {
                while (resultSet.next()) {
                    cropType = resultSet.getString("crop_type");
                    cropPctDmat = resultSet.getDouble("pct_dmat");
                    pctNitrogen = resultSet.getDouble("pct_nitrogen");
                    pctPhosphorus = resultSet.getDouble("pct_phosphorus");
                }
            }
            
            /**
             * 1. Compute N removal ratio for the crop, 2. Compute N application
             * management rate score for the crop
             */
            double nRemoveRatio = nRate / (crop.yield * cropPctDmat * pctNitrogen);
            String query = DBQueries.WQM16Query02("nitrogen", "rate", cropType.equalsIgnoreCase("small grain") ? "small grain" : "other", null, nRemoveRatio, null);

            try (ResultSet resultSet = statement.executeQuery(query)) {
                while (resultSet.next()) {
                    ncropAppRateScore = resultSet.getInt("app_mgt_score");
                }
            }

            //Update N application management rate score for the AoA
            nAppRateScore += ncropAppRateScore;

            /**
             * 1. Compute P removal ratio for the crop, 2. Compute P application
             * management rate score for the crop
             */
            query = null;
            double pRemoveratio = pRate / (crop.yield * cropPctDmat * pctPhosphorus);
            switch (pSoilTestResult.toUpperCase()) {
                case "HIGH":
                case "NONE":
                    if (pRemoveratio >= 1.2) {
                        pcropAppRateScore = 0;
                    } else {
                        query = DBQueries.WQM16Query02("phosphorus", "rate", "app", pSoilTestResult, pRemoveratio, null);
                    }
                    break;
                case "MEDIUM":
                case "LOW":
                    if (pRemoveratio >= 1.6) {
                        pcropAppRateScore = 0;
                    } else {
                        query = DBQueries.WQM16Query02("phosphorus", "rate", "app", pSoilTestResult, pRemoveratio, null);
                    }
                    break;
            }

            if (query != null) {
                try (ResultSet resultSet = statement.executeQuery(query)) {
                    while (resultSet.next()) {
                        pcropAppRateScore = resultSet.getInt("app_mgt_score");
                    }
                }
            }

            //Update P application management rate score for the AoA
            pAppRateScore += pcropAppRateScore;
        }
    }

    private void computeAppTimimgScore(Connection connection, Crop crop) throws SQLException {
        try (Statement statement = connection.createStatement();) {
            int incorporated = 0;
            /**
             * 1. Compute N and P application timing scores for the crop 2.
             * Update timing scores for the AoA
             */
            for (NutrientApplication nu : crop.nutrientApplicationList) {
                String appType = crop.nutrientApplicationList.size() == 1 ? "nosplit" : "split";
                long app_day_diff = Dates.diffInMillis(crop.plantDate, nu.applicationDate);
                app_day_diff = TimeUnit.MILLISECONDS.toDays(app_day_diff);

                for (Nutrient nutrient : nu.nutrientList) {

                    try (ResultSet resultSet = statement.executeQuery(DBQueries.WQM16Query02(nutrient.nutrientApplied, "timing", appType, null, null, (int) app_day_diff))) {
                        Integer score = null;
                        if (resultSet.next()) {
                            score = resultSet.getInt("app_mgt_score");
                        }

                        switch (nutrient.nutrientApplied.toUpperCase()) {
                            case "NITROGEN":
                                if (score == null) {
                                    nAppTimingScore = 0;
                                } else if (score < nAppTimingScore) {
                                    nAppTimingScore = score;
                                }
                                break;
                            case "PHOSPHORUS":
                                if (score == null) {
                                    pAppTimingScore = 0;
                                } else if (score < pAppTimingScore) {
                                    pAppTimingScore = score;
                                }
                                break;
                        }
                    }
                }
                if (nu.incorporated) {
                    incorporated++;
                }
            }
            if (incorporated == crop.nutrientApplicationList.size()) {
                try (ResultSet resultSet = statement.executeQuery(DBQueries.WQM16Query02(null, "method", "incorporate", null, null, null));) {
                    if (resultSet.next()) {
                        appMethodScore = resultSet.getInt("app_mgt_score");
                    }
                }
            }
        }
    }

    static class Crop {

        protected int id;
        protected Date plantDate;
        protected double yield;
        protected String yieldUnits;
        protected ArrayList<NutrientApplication> nutrientApplicationList;

        public Crop(int id, Date date, double yield, String yieldUnits, ArrayList<NutrientApplication> nutrientApplicationList) {
            this.id = id;
            this.plantDate = date;
            this.yield = yield;
            this.yieldUnits = yieldUnits;
            this.nutrientApplicationList = nutrientApplicationList;
        }

    }

    static class NutrientApplication {

        protected Date applicationDate;
        protected boolean incorporated;
        protected ArrayList<Nutrient> nutrientList;

        public NutrientApplication(Date date, boolean incorporated, ArrayList<Nutrient> nutrientList) {
            this.applicationDate = date;
            this.incorporated = incorporated;
            this.nutrientList = nutrientList;
        }

    }

    static class Nutrient {

        protected String nutrientApplied;
        protected double application_Rate;

        public Nutrient(String nutrientApplied, double application_Rate) {
            this.nutrientApplied = nutrientApplied;
            this.application_Rate = application_Rate;
        }

    }
}