energy.usef.agr.workflow.step.AgrNonUdiReOptimizePortfolioStub.java Source code

Java tutorial

Introduction

Here is the source code for energy.usef.agr.workflow.step.AgrNonUdiReOptimizePortfolioStub.java

Source

/*
 * Copyright 2015-2016 USEF Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package energy.usef.agr.workflow.step;

import static energy.usef.agr.workflow.step.AgrReOptimizePortfolioStubUtil.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import energy.usef.agr.dto.ConnectionGroupPortfolioDto;
import energy.usef.agr.dto.ConnectionPortfolioDto;
import energy.usef.agr.dto.ForecastPowerDataDto;
import energy.usef.agr.workflow.operate.reoptimize.ReOptimizePortfolioStepParameter.IN;
import energy.usef.agr.workflow.operate.reoptimize.ReOptimizePortfolioStepParameter.OUT_NON_UDI;
import energy.usef.core.util.DateTimeUtil;
import energy.usef.core.workflow.WorkflowContext;
import energy.usef.core.workflow.WorkflowStep;
import energy.usef.core.workflow.dto.FlexOrderDto;
import energy.usef.core.workflow.dto.PrognosisDto;

/**
 * A simple implementation of a workflow step to simulation the behavior of an Aggregator Re-optimize portfolio for non-udi
 * aggregators. This implementation only reduce the consumption of the connections. It will not do anything with production of power
 * and it will not increase the consumption of the connections.
 * <p>
 * The calculation done in this PBC is: 1. Calculate the target power per ptu with target = prognosis + ordered - summed forecast
 * consumption of all connections 2. Calculate the factor per ptu with factor = target / summed potential flex consumption for all
 * connections 3. Calculate the new forecast consumption per ptu with forecast = forecast + potential flex * factor
 * <p>
 * The PBC receives the following parameters as input : <ul> <li>PTU_DURATION : PTU duration.</li> <li>CURRENT_PTU_INDEX : Current
 * PTU index.</li> <li>PTU_DATE : Period of re-optimization.</li> <li>CONNECTION_PORTFOLIO_IN : List of connection group portfolios
 * {@link ConnectionPortfolioDto}.</li> <li>CONNECTION_GROUPS_TO_CONNECTIONS_MAP : map giving the relationship between each
 * connection group and its connections.</li> <li>RECEIVED_FLEXORDER_LIST : aggregate info and collection of {@link
 * FlexOrderDto}</li> <li>LATEST_A_PLAN_DTO_LIST : contains list of most recent {@link PrognosisDto} (A-plans)</li>
 * <li>LATEST_D_PROGNOSIS_DTO_LIST : contains list of most recent {@link PrognosisDto} (D-Prognoses)</li>
 * <li>RELEVANT_PROGNOSIS_LIST : contains list of prognosis relevant to FlexOrder.</li> </ul>
 * <p>
 * The PBC must output the modified connection portfolio and devise messages: <ul> <li>CONNECTION_PORTFOLIO_OUT : re-optimized
 * connection portfolio {@link ConnectionGroupPortfolioDto}.</li> </ul>
 */
public class AgrNonUdiReOptimizePortfolioStub implements WorkflowStep {
    private static final Logger LOGGER = LoggerFactory.getLogger(AgrNonUdiReOptimizePortfolioStub.class);

    /**
     * Invoke step to generate a random between min and max nr of messages and put hem on the WorkflowContext.
     *
     * @param context incoming workflow context
     * @return WorkflowContext containing a new list of deviceMessage
     */
    @SuppressWarnings("unchecked")
    @Override
    public WorkflowContext invoke(WorkflowContext context) {

        // Getting input parameters
        int ptuDuration = (int) context.getValue(IN.PTU_DURATION.name());
        int currentPtuIndex = (int) context.getValue(IN.CURRENT_PTU_INDEX.name());
        LocalDate period = (LocalDate) context.getValue(IN.PTU_DATE.name());
        List<ConnectionPortfolioDto> connectionPortfolio = context.get(IN.CONNECTION_PORTFOLIO_IN.name(),
                List.class);
        Map<String, List<String>> connectionGroupsToConnectionMap = context
                .get(IN.CONNECTION_GROUPS_TO_CONNECTIONS_MAP.name(), HashMap.class);
        List<FlexOrderDto> flexOrders = context.get(IN.RECEIVED_FLEXORDER_LIST.name(), List.class);
        List<PrognosisDto> dPrognosis = context.get(IN.LATEST_D_PROGNOSIS_DTO_LIST.name(), List.class);
        List<PrognosisDto> aPlans = context.get(IN.LATEST_A_PLAN_DTO_LIST.name(), List.class);

        LOGGER.info("Aggregator Re-optimize portfolio Stub started with {} connections in the portfolio.",
                connectionPortfolio.size());

        // validate the input
        if (!validateInput(context)) {
            return returnDefaultContext(context);
        }

        LocalDate currentDate = DateTimeUtil.getCurrentDate();

        // Do some mapping, summing and calculation for quick access later in the process
        Map<String, List<ConnectionPortfolioDto>> connectionPortfolioPerConnectionGroup = mapConnectionPortfolioPerConnectionGroup(
                connectionPortfolio, connectionGroupsToConnectionMap);
        Map<String, Map<Integer, BigInteger>> prognosisPowerPerPtuPerConnectionGroup = mapPrognosisPowerPerPtuPerConnectionGroup(
                aPlans, dPrognosis);
        Map<String, Map<Integer, BigInteger>> orderedPowerPerPtuPerConnectionGroup = sumOrderedPowerPerPtuPerConnectionGroup(
                flexOrders);
        Map<String, Map<Integer, BigInteger>> forecastPowerPerPtuPerConnectionGroup = sumForecastPowerPerPtuPerConnectionGroup(
                connectionPortfolioPerConnectionGroup, period, ptuDuration);
        Map<String, Map<Integer, BigInteger>> targetPowerPerPtuPerConnectionGroup = fetchTargetPowerPerPtuPerConnectionGroup(
                prognosisPowerPerPtuPerConnectionGroup, orderedPowerPerPtuPerConnectionGroup,
                forecastPowerPerPtuPerConnectionGroup);
        Map<String, Map<Integer, BigInteger>> sumPotentialFlexConsumptionPerPtuPerConnectionGroup = sumPotentialFlexConsumptionPerPtuPerConnectionGroup(
                connectionPortfolioPerConnectionGroup, period, ptuDuration);
        Map<String, Map<Integer, BigInteger>> sumPotentialFlexProductionPerPtuPerConnectionGroup = sumPotentialFlexProductionPerPtuPerConnectionGroup(
                connectionPortfolioPerConnectionGroup, period, ptuDuration);
        Map<String, Map<Integer, BigDecimal>> flexFactorPerPtuPerConnectionGroup = fetchFlexFactorPerPtuPerConnectionGroup(
                targetPowerPerPtuPerConnectionGroup, sumPotentialFlexConsumptionPerPtuPerConnectionGroup,
                sumPotentialFlexProductionPerPtuPerConnectionGroup);

        // Calculate the new portfolio consumption forecast (consumption + potentialFlex * factor)
        calculatePortfolioForecast(connectionPortfolioPerConnectionGroup, flexFactorPerPtuPerConnectionGroup,
                period, currentDate, currentPtuIndex);

        context.setValue(OUT_NON_UDI.CONNECTION_PORTFOLIO_OUT.name(), connectionPortfolio);
        return context;
    }

    // Calculate the new portfolio consumption forecasts (forecast = forecast + summedPotentialFlex * factor)
    private void calculatePortfolioForecast(
            Map<String, List<ConnectionPortfolioDto>> connectionPortfolioPerConnectionGroup,
            Map<String, Map<Integer, BigDecimal>> potentialFlexFactorPerPtuPerConnectionGroup, LocalDate ptuDate,
            LocalDate currentDate, final int currentPtuIndex) {
        boolean isAfterToday = ptuDate.compareTo(currentDate) > 0;
        connectionPortfolioPerConnectionGroup
                .forEach((connectionGroupId, connectionPortfolioList) -> connectionPortfolioList.stream()
                        .flatMap(connectionPortfolioDTO -> connectionPortfolioDTO.getConnectionPowerPerPTU()
                                .entrySet().stream())
                        .filter(entry -> isAfterToday || entry.getKey() >= currentPtuIndex).forEach(entry -> {
                            Integer ptuIndex = entry.getKey();
                            ForecastPowerDataDto forecastPowerData = entry.getValue().getForecast();

                            processxFlexIntoForecast(potentialFlexFactorPerPtuPerConnectionGroup, connectionGroupId,
                                    ptuIndex, forecastPowerData);

                        }));
    }

    private void processxFlexIntoForecast(
            Map<String, Map<Integer, BigDecimal>> potentialFlexFactorPerPtuPerConnectionGroup,
            String connectionGroupId, Integer ptuIndex, ForecastPowerDataDto forecastPowerData) {

        BigDecimal factor = potentialFlexFactorPerPtuPerConnectionGroup.get(connectionGroupId).get(ptuIndex);

        if (factor.compareTo(BigDecimal.ZERO) == 0) {
            // nothing to do
            return;
        }

        if (factor.compareTo(BigDecimal.ZERO) > 0) {
            // factor > 0, reduce consumption (increase of production is not (yet) supported by this stub)
            // new consumption power = forecast consumption + flex consumption * factor
            BigDecimal flexConsumption = new BigDecimal(forecastPowerData.getPotentialFlexConsumption());
            BigInteger factoredFlex = flexConsumption.multiply(factor).toBigInteger();

            if (factoredFlex.compareTo(BigInteger.ZERO) > 0) {
                return;
            }

            BigInteger newForecast = forecastPowerData.getAverageConsumption().add(factoredFlex);
            // consumption cannot be less than 0
            if (newForecast.compareTo(BigInteger.ZERO) < 0) {
                newForecast = BigInteger.ZERO;
            }
            forecastPowerData.setAverageConsumption(newForecast);
        } else {
            // factor < 0, reduce production (increase of consumption is not (yet) supported by this stub)
            // new production power = forecast production + flex production * factor
            BigDecimal flexProduction = new BigDecimal(forecastPowerData.getPotentialFlexProduction());
            BigInteger factoredFlex = flexProduction.multiply(factor).toBigInteger();

            if (factoredFlex.compareTo(BigInteger.ZERO) < 0) {
                return;
            }

            BigInteger newForecast = forecastPowerData.getAverageProduction().subtract(factoredFlex);
            // production cannot be less than 0
            if (newForecast.compareTo(BigInteger.ZERO) < 0) {
                newForecast = BigInteger.ZERO;
            }
            forecastPowerData.setAverageProduction(newForecast);
        }
    }

    @SuppressWarnings("unchecked")
    private boolean validateInput(WorkflowContext context) {
        boolean validInput = true;

        LocalDate ptuDate = context.get(IN.PTU_DATE.name(), LocalDate.class);
        List<ConnectionPortfolioDto> connectionPortfolio = context.get(IN.CONNECTION_PORTFOLIO_IN.name(),
                List.class);
        List<FlexOrderDto> flexOrders = context.get(IN.RECEIVED_FLEXORDER_LIST.name(), List.class);
        List<PrognosisDto> dPrognosis = context.get(IN.LATEST_D_PROGNOSIS_DTO_LIST.name(), List.class);
        List<PrognosisDto> aPlans = context.get(IN.LATEST_A_PLAN_DTO_LIST.name(), List.class);

        if (connectionPortfolio.isEmpty()) {
            validInput = false;
            LOGGER.info("No connections in portfolio, unable to re-optimize portfolio.");
        }

        LocalDate currentDate = DateTimeUtil.getCurrentDate();
        if (ptuDate.isBefore(currentDate)) {
            validInput = false;
            LOGGER.error("Unable to re-optimize portfolio for periods in the past: {}", ptuDate);
        }

        if (dPrognosis.isEmpty() && aPlans.isEmpty()) {
            validInput = false;
            LOGGER.error("D-Prognosis and/or A-Plans expected, but none were found in the input.");
        }
        return validInput;
    }

    private WorkflowContext returnDefaultContext(WorkflowContext context) {
        context.setValue(OUT_NON_UDI.CONNECTION_PORTFOLIO_OUT.name(), new ArrayList<ConnectionPortfolioDto>());
        LOGGER.info("Ended AgrReOptimizePortfolioStub workflow with empty return values");
        return context;
    }

}