V1_0.java [src/java/m/nfat] Revision: default  Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * soilNitro Model-as-soilNitro-Service framework, API, and application suite.
 *
 * 2012-2017, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package m.nfat;

import csip.ModelDataService;
import csip.ServiceException;
import csip.annotations.Polling;
import csip.annotations.Resource;
import csip.utils.JSONUtils;
import database.DBQueries;
import database.DBResources;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.ws.rs.Path;
import rotation_utils.nodes.Event;
import rotation_utils.nodes.Fertilizer;
import rotation_utils.nodes.Rotation;
import csip.annotations.Description;
import csip.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import rotation_utils.nodes.Crop;
import rotation_utils.nodes.Management;
import rotation_utils.utils.TranslatorException;

/**
 * Energy-02: Compute Nitrogen Energy Consumption Awareness Output
 *
 * @version 1.0
 * @author Rumpal Sidhu
 */
@Name("Energy-02:Compute Nitrogen Energy Consumption Awareness Output")
@Description("This service computes fertilizer nitrogen application amounts and "
        + "costs for a crop rotation and uses crop nitrogen requirements and "
        + "optimum fertilization rates to calculate potential reductions to "
        + "the amount of fertilizer applied and cost savings.")
@Path("m/nfat/1.0")
@Polling(first = 10000, next = 2000)
@Resource(from = DBResources.class)

public class V1_0 extends ModelDataService {

    private double acres, soilNitrogen = 0;

    private ArrayList<LRotation> rotationList = new ArrayList<>();
    private ArrayList<Rotation> inputRotationList = new ArrayList<>();

    @Override
    public void preProcess() throws ServiceException, JSONException, TranslatorException {
        acres = getDoubleParam("aoa_acres");

        String soilNitroLBS = getStringParam("soil_nitrogen_lbs_acre");
        double soilNitrogenLbsAcre = Double.valueOf(soilNitroLBS.equals("null") ? "-1" : soilNitroLBS);

        //Validating
        JSONArray soilNitrogenArray = getJSONArrayParam("soil_nitrogen_ppm");
        if (!soilNitroLBS.equals("null") && soilNitrogenArray.length() > 0) {
            throw new ServiceException("Either the soil_nitrogen_lbs_acre should be null or soil_nitrogen_ppm should be an empty array.");
        }

        //Calculating soil nitrogen
        if (soilNitrogenLbsAcre == -1) {
            for (int i = 0; i < soilNitrogenArray.length(); i++) {
                Map<String, JSONObject> soilNitro = JSONUtils.preprocess(soilNitrogenArray.getJSONArray(i));
                double ppm = JSONUtils.getDoubleParam(soilNitro, "ppm", 0);
                double depth = JSONUtils.getDoubleParam(soilNitro, "depth", 0);
                soilNitrogen += ppm * 2 * depth / 6;
            }
        } else {
            soilNitrogen = soilNitrogenLbsAcre;
        }

        soilNitrogen *= acres;

        //Rotation data
        JSONArray rotationsArray = getJSONArrayParam("rotations");
        for (int i = 0; i < rotationsArray.length(); i++) {
            inputRotationList.add(Rotation.deserializeRot(rotationsArray.getJSONObject(i)));
        }
    }

    @Override
    public void doProcess() throws TranslatorException, ServiceException, SQLException {
        try (Connection connection = getResourceJDBC(DBResources.CRLMOD);
                Statement statement = connection.createStatement();) {
            rotationCalc(statement);
        }
    }

    public void rotationCalc(Statement statement) throws ServiceException, SQLException {

        for (Rotation rotation : inputRotationList) {
            double rotationNitrogenApplied = 0, rotationNitrogenCost = 0, rotationPotentialNSavings = 0, rotationPotentialNCostSavings = 0;
            ArrayList<CropInterval> cropIntervalList = null;

            for (Management management : rotation.managements) {
                List<Event> eventList = management.getManagementData();
                if (eventList != null) {
                    cropIntervalList = cropIntervalCalc(statement, eventList);
                    for (CropInterval ci : cropIntervalList) {
                        rotationNitrogenApplied += ci.getNitrogenApplied();
                        rotationNitrogenCost += ci.getNitrogenCost();
                        rotationPotentialNSavings += ci.getPotentialNsavings();
                        rotationPotentialNCostSavings += ci.getPotentialNcostSavings();
                    }
                }
            }

            rotationList.add(new LRotation(roundValues(rotationNitrogenApplied),
                    roundValues(rotationNitrogenCost),
                    roundValues(rotationPotentialNSavings),
                    roundValues(rotationPotentialNCostSavings),
                    cropIntervalList));
        }
    }

    public ArrayList<CropInterval> cropIntervalCalc(Statement statement, List<Event> eventList) throws ServiceException, SQLException {
        //Initializing list of crop intervals
        ArrayList<CropInterval> cropIntervalList = new ArrayList<>();

        //Variables to store the event list position for the harvest operation of the previous crop in the rotation
        int previousCropHarvet = 0;

        //Finding and adding crop intervals to the cropIntervalList
        for (int i = 0; i < eventList.size();) {

            if (eventList.get(i).getOperation().begin_growth) {
                Crop crop = eventList.get(i).getCrop();

                double nitrogenYield = 0;
                int cropType = 0;
                try (ResultSet resultSet = statement.executeQuery(DBQueries.ENERGY01(crop.getID()))) {
                    if (resultSet.next()) {
                        cropType = resultSet.getInt("wqm_crop_type");
                        nitrogenYield = resultSet.getDouble("wqm_nitrogen_yield");
                    }
                }

                if (cropType != 5 && cropType != 6) {
                    CropInterval cropInterval = new CropInterval();
                    cropInterval.setCropName(crop.getName());

                    LocalDate plantingDate = eventList.get(i).getDate();
                    int harvetEvent = 0;
                    for (int j = i + 1;; j++) {
                        if (eventList.get(j).getOperation().kill_crop) {
                            harvetEvent = j;
                            i = j + 1;
                            break;
                        }
                    }

                    //Obtaining the list of fertilizer(s) applied to a crop interval
                    ArrayList<LFertilizer> fertList = getFertilizerList(eventList, plantingDate, previousCropHarvet, harvetEvent);
                    double cropNitrogenApplied = 0;
                    double cropNitrogenCost = 0;
                    double fertCost = 0;
                    for (LFertilizer fert : fertList) {
                        cropNitrogenApplied += fert.getNitrogenApplied();
                        cropNitrogenCost += fert.getNitrogenCost();
                        fertCost = fert.getPrice();
                    }
                    cropInterval.setFerList(fertList);
                    cropInterval.setNitrogenApplied(roundValues(cropNitrogenApplied));
                    cropInterval.setNitrogenCost(roundValues(cropNitrogenCost));

                    double cropYield = crop.getYield() > 0 ? crop.getYield() : crop.defaultYield;
                    double cropNitrogenRequired = cropYield * nitrogenYield * acres;
                    cropInterval.setNitrogenReq(roundValues(cropNitrogenRequired));

                    double targetNitrogenAmount = cropNitrogenRequired * 1.2 - soilNitrogen;
                    cropInterval.setTargetNitrogenAmt(roundValues(targetNitrogenAmount));

                    double cropPotentialNSavings = cropNitrogenApplied - targetNitrogenAmount;
                    cropPotentialNSavings = cropPotentialNSavings < 0 ? 0 : cropPotentialNSavings;
                    cropInterval.setPotentialNsavings(roundValues(cropPotentialNSavings));

                    double cropPotentialNCostSavings = cropPotentialNSavings * (fertCost / 2000.0);
                    cropInterval.setPotentialNcostSavings(roundValues(cropPotentialNCostSavings));

                    previousCropHarvet = harvetEvent + 1;
                    cropIntervalList.add(cropInterval);
                } else {
                    i++;
                }
            } else {
                i++;
            }
        }
        return cropIntervalList;
    }

    public ArrayList<LFertilizer> getFertilizerList(List<Event> eventList, LocalDate plantingDate, int previousCropHarvet, int harvetEvent) {
        //Obtaining the list of fertilizer(s) applied to a crop interval
        ArrayList<LFertilizer> fertList = new ArrayList<>();
        for (int k = previousCropHarvet; k <= harvetEvent; k++) {
            LocalDate fertilizingDate;
            Event e = eventList.get(k);
            if (e.getFertilizer() != null) {
                LFertilizer fert = new LFertilizer();
                fertilizingDate = e.getDate();
                fert.setDate(fertilizingDate);
                fert.setOperationId(e.getOperation().id);
                fert.setOperationName(e.getOperation().name);

                if (fertilizingDate.isBefore(plantingDate)) {
                    long daysBeforePlanting = fertilizingDate.until(plantingDate, ChronoUnit.DAYS);
                    fert.setDaysBeforePlanting(daysBeforePlanting);
                    fert.setDayesAfterPlanting(-1);
                } else {
                    long daysAfterPlanting = plantingDate.until(fertilizingDate, ChronoUnit.DAYS);
                    fert.setDayesAfterPlanting(daysAfterPlanting);
                    fert.setDaysBeforePlanting(-1);
                }
                fertilizationCalc(e, fert);

                fertList.add(fert);
            }
        }
        return fertList;
    }

    public void fertilizationCalc(Event event, LFertilizer fert) {
        Fertilizer fertilizer = event.getFertilizer();
        if (fertilizer.getFertilizer_placement(event) != null && !fertilizer.getFertilizer_placement(event).isEmpty()) {
            String fertilizerPlacement = fertilizer.getFertilizer_placement(event);
            fert.setFertilizerPlacement(fertilizerPlacement);
        }

        double rate = fertilizer.getFertilizer_rate(event) != 0 ? fertilizer.getFertilizer_rate(event) : fertilizer.getDefaultRate();
        double nitrogenApplied = rate * fertilizer.getNitrogen_pct() / 100.0 * acres;
        fert.setNitrogenApplied(nitrogenApplied);

        double price = fertilizer.getFertilizer_price(event) != 0 ? fertilizer.getFertilizer_price(event) : fertilizer.getDefaultPrice();
        fert.setPrice(price);
        double nitrogenCost = rate * acres * price / 2000.0;
        fert.setNitrogenCost(nitrogenCost);
    }

    public double roundValues(double v) {
        return (Math.round(v * 100.0) / 100.0);
    }

    @Override
    public void postProcess() throws JSONException {
        JSONArray rotationArray = new JSONArray();
        for (LRotation rotation : rotationList) {
            JSONObject rotationObject = new JSONObject();

            JSONArray cropIntervalArr = new JSONArray();
            for (CropInterval cropInterval : rotation.getCropIntervalList()) {
                JSONObject cropIntervalObj = new JSONObject();
                cropIntervalObj.put("crop_name", cropInterval.getCropName());
                cropIntervalObj.put("crop_nitrogen_req", cropInterval.getNitrogenReq());
                cropIntervalObj.put("target_nitrogen_amt", cropInterval.getTargetNitrogenAmt());

                JSONArray fertilizerEventArray = new JSONArray();
                for (LFertilizer fertilizer : cropInterval.getFerList()) {
                    JSONObject fertilizerEventObj = new JSONObject();
                    fertilizerEventObj.put("fertilization_date", fertilizer.getDate());
                    fertilizerEventObj.put("operation_id", fertilizer.getOperationId());
                    fertilizerEventObj.put("operation_name", fertilizer.getOperationName());
                    if (fertilizer.getDaysBeforePlanting() != -1) {
                        fertilizerEventObj.put("days_before_planting", fertilizer.getDaysBeforePlanting());
                    } else {
                        fertilizerEventObj.put("days_after_planting", fertilizer.getDayesAfterPlanting());
                    }
                    fertilizerEventObj.put("fertilizer_placement", fertilizer.getFertilizerPlacement());
                    fertilizerEventObj.put("event_nitrogen_applied", fertilizer.getNitrogenApplied());
                    fertilizerEventObj.put("event_nitrogen_cost", fertilizer.getNitrogenCost());
                    fertilizerEventArray.put(fertilizerEventObj);
                }
                cropIntervalObj.put("fertilization_event", fertilizerEventArray);
                cropIntervalObj.put("crop_nitrogen_applied", cropInterval.getNitrogenApplied());
                cropIntervalObj.put("crop_nitrogen_cost", cropInterval.getNitrogenCost());
                cropIntervalObj.put("crop_potential_N_savings", cropInterval.getPotentialNsavings());
                cropIntervalObj.put("crop_potential_N_cost_savings", cropInterval.getPotentialNcostSavings());
                cropIntervalArr.put(cropIntervalObj);
            }
            rotationObject.put("crop_interval", cropIntervalArr);
            rotationObject.put("rotation_nitrogen_applied", rotation.getNitrogenApplied());
            rotationObject.put("rotation_nitrogen_cost", rotation.getNitrogenCost());
            rotationObject.put("rotation_potential_N_savings", rotation.getPotentialNSavings());
            rotationObject.put("rotation_potential_N_cost_savings", rotation.getPotentialNCostSavings());
            rotationArray.put(rotationObject);
        }
        putResult("rotation", rotationArray);
    }

    class LRotation {

        private double nitrogenApplied, nitrogenCost, potentialNSavings, potentialNCostSavings;
        private ArrayList<CropInterval> cropIntervalList;

        public LRotation(double nitrogenApplied, double nitrogenCost, double potentialNSavings, double potentialNCostSavings, ArrayList<CropInterval> cropList) {
            this.nitrogenApplied = nitrogenApplied;
            this.nitrogenCost = nitrogenCost;
            this.potentialNSavings = potentialNSavings;
            this.potentialNCostSavings = potentialNCostSavings;
            this.cropIntervalList = cropList;
        }

        public ArrayList<CropInterval> getCropIntervalList() {
            return cropIntervalList;
        }

        public double getNitrogenApplied() {
            return nitrogenApplied;
        }

        public double getNitrogenCost() {
            return nitrogenCost;
        }

        public double getPotentialNCostSavings() {
            return potentialNCostSavings;
        }

        public double getPotentialNSavings() {
            return potentialNSavings;
        }

    }

    class CropInterval {

        private String cropName;
        private double nitrogenReq;
        private double targetNitrogenAmt;
        private double nitrogenApplied, nitrogenCost, potentialNsavings, potentialNcostSavings;
        private ArrayList<LFertilizer> ferList;

        public CropInterval() {
        }

        public String getCropName() {
            return cropName;
        }

        public ArrayList<LFertilizer> getFerList() {
            return ferList;
        }

        public double getNitrogenApplied() {
            return nitrogenApplied;
        }

        public double getNitrogenCost() {
            return nitrogenCost;
        }

        public double getNitrogenReq() {
            return nitrogenReq;
        }

        public double getPotentialNcostSavings() {
            return potentialNcostSavings;
        }

        public double getPotentialNsavings() {
            return potentialNsavings;
        }

        public double getTargetNitrogenAmt() {
            return targetNitrogenAmt;
        }

        public void setCropName(String cropName) {
            this.cropName = cropName;
        }

        public void setFerList(ArrayList<LFertilizer> ferList) {
            this.ferList = ferList;
        }

        public void setNitrogenApplied(double nitrogenApplied) {
            this.nitrogenApplied = nitrogenApplied;
        }

        public void setNitrogenCost(double nitrogenCost) {
            this.nitrogenCost = nitrogenCost;
        }

        public void setNitrogenReq(double nitrogenReq) {
            this.nitrogenReq = nitrogenReq;
        }

        public void setPotentialNcostSavings(double potentialNcostSavings) {
            this.potentialNcostSavings = potentialNcostSavings;
        }

        public void setPotentialNsavings(double potentialNsavings) {
            this.potentialNsavings = potentialNsavings;
        }

        public void setTargetNitrogenAmt(double targetNitrogenAmt) {
            this.targetNitrogenAmt = targetNitrogenAmt;
        }

    }

    class LFertilizer {

        private LocalDate date;
        private String operationId, operationName;
        private long daysBeforePlanting;
        private long dayesAfterPlanting;
        private String fertilizerPlacement;
        private double price, nitrogenApplied, nitrogenCost;

        public LFertilizer() {
        }

        public void setDate(LocalDate date) {
            this.date = date;
        }

        public void setDayesAfterPlanting(long dayesAfterPlanting) {
            this.dayesAfterPlanting = dayesAfterPlanting;
        }

        public void setDaysBeforePlanting(long daysBeforePlanting) {
            this.daysBeforePlanting = daysBeforePlanting;
        }

        public void setFertilizerPlacement(String fertilizerPlacement) {
            this.fertilizerPlacement = fertilizerPlacement;
        }

        public void setNitrogenApplied(double nitrogenApplied) {
            this.nitrogenApplied = nitrogenApplied;
        }

        public void setNitrogenCost(double nitrogenCost) {
            this.nitrogenCost = nitrogenCost;
        }

        public void setOperationId(String operationId) {
            this.operationId = operationId;
        }

        public void setOperationName(String operationName) {
            this.operationName = operationName;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }

        public LocalDate getDate() {
            return date;
        }

        public long getDayesAfterPlanting() {
            return dayesAfterPlanting;
        }

        public long getDaysBeforePlanting() {
            return daysBeforePlanting;
        }

        public String getFertilizerPlacement() {
            return fertilizerPlacement;
        }

        public double getNitrogenApplied() {
            return nitrogenApplied;
        }

        public double getNitrogenCost() {
            return nitrogenCost;
        }

        public String getOperationId() {
            return operationId;
        }

        public String getOperationName() {
            return operationName;
        }

    }
}