JsonSerializationConstants.java

/*
 *  Copyright (c) 2020, RTE (http://www.rte-france.com)
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package com.powsybl.openrao.data.crac.io.json;

import com.fasterxml.jackson.core.JsonGenerator;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.RemedialAction;
import com.powsybl.openrao.data.crac.api.networkaction.ActionType;
import com.powsybl.openrao.data.crac.api.networkaction.SingleNetworkElementActionAdder;
import com.powsybl.openrao.data.crac.api.range.RangeType;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.VariationDirection;
import com.powsybl.openrao.data.crac.api.threshold.BranchThreshold;
import com.powsybl.openrao.data.crac.api.threshold.Threshold;
import com.powsybl.openrao.data.crac.api.usagerule.OnConstraint;
import com.powsybl.openrao.data.crac.api.usagerule.OnContingencyState;
import com.powsybl.openrao.data.crac.api.usagerule.OnFlowConstraintInCountry;
import com.powsybl.openrao.data.crac.api.usagerule.OnInstant;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import com.powsybl.openrao.data.crac.api.usagerule.UsageRule;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author Baptiste Seguinot {@literal <baptiste.seguinot at rte-france.com>}
 */
public final class JsonSerializationConstants {

    private JsonSerializationConstants() {
    }

    public static final String CRAC_IO_VERSION = "2.7";
    /*
    v1.1: addition of switchPairs
    v1.2: addition of injectionRangeAction
    v1.3: addition of hvdcRangeAction's and injectionRangeAction's initial setpoints + onFlowConstraintInCountry
    v1.4: addition of AngleCnecs; frm renamed to reliabilityMargin
    v1.5: addition of VoltageCnecs
    v1.6: replace FlowCNEC's rule by side, rename freeToUse to onInstant and onState to onContingencyState
    v1.7: addition of VoltageConstraints usage rules
    v1.8: addition of ShuntCompensator set-point action
    v1.9: addition of counterTradeRangeAction
    v2.0: addition of instants and change in usage method logic (now mandatory)
    v2.1: addition of ra-usage-limits
    v2.2: addition of contingency id in on-flow-constraint-in-country
    v2.3: addition of RELATIVE_TO_PREVIOUS_TIME_STEP RangeType, and border attribute for cnecs
    v2.4: new names for onConstraint and cnecId, side left/right -> one/two
    v2.5: elementary actions have new type coming from core remedial actions
    v2.6: addition of activation-cost and variation-costs for remedial actions
    v2.7: addition of timestamp
     */

    // headers
    public static final String TYPE = "type";
    public static final String VERSION = "version";
    public static final String INFO = "info";
    public static final String CRAC_TYPE = "CRAC";
    public static final String CRAC_INFO = "Generated by PowSyBl OpenRAO https://powsybl.readthedocs.io/projects/openrao";

    // field
    public static final String NETWORK_ELEMENTS_IDS = "networkElementsIds";
    public static final String NETWORK_ELEMENT_ID = "networkElementId";
    public static final String EXPORTING_NETWORK_ELEMENT_ID = "exportingNetworkElementId";
    public static final String IMPORTING_NETWORK_ELEMENT_ID = "importingNetworkElementId";
    public static final String NETWORK_ELEMENTS_NAME_PER_ID = "networkElementsNamePerId";
    public static final String NETWORK_ELEMENT_IDS_AND_KEYS = "networkElementIdsAndKeys";
    public static final String EXPORTING_COUNTRY = "exportingCountry";
    public static final String IMPORTING_COUNTRY = "importingCountry";

    public static final String GROUP_ID = "groupId";
    public static final String SPEED = "speed";

    public static final String CONTINGENCIES = "contingencies";
    public static final String CONTINGENCY_ID = "contingencyId";

    public static final String INSTANTS = "instants";
    public static final String INSTANT = "instant";
    public static final String INSTANT_KIND = "kind";

    public static final String CNEC_ID = "cnecId";

    public static final String FLOW_CNECS = "flowCnecs";
    public static final String FLOW_CNEC_ID = "flowCnecId";

    public static final String ANGLE_CNECS = "angleCnecs";
    public static final String ANGLE_CNEC_ID = "angleCnecId";

    public static final String VOLTAGE_CNECS = "voltageCnecs";

    public static final String VOLTAGE_CNEC_ID = "voltageCnecId";

    public static final String THRESHOLDS = "thresholds";
    public static final String RELIABILITY_MARGIN = "reliabilityMargin";
    public static final String FRM = "frm";
    public static final String OPTIMIZED = "optimized";
    public static final String MONITORED = "monitored";
    public static final String I_MAX = "iMax";
    public static final String NOMINAL_VOLTAGE = "nominalV";

    public static final String PST_RANGE_ACTIONS = "pstRangeActions";
    public static final String HVDC_RANGE_ACTIONS = "hvdcRangeActions";
    public static final String INJECTION_RANGE_ACTIONS = "injectionRangeActions";
    public static final String COUNTER_TRADE_RANGE_ACTIONS = "counterTradeRangeActions";

    public static final String NETWORK_ACTIONS = "networkActions";
    public static final String TOPOLOGICAL_ACTIONS = "topologicalActions";
    public static final String PST_SETPOINTS = "pstSetpoints";
    public static final String INJECTION_SETPOINTS = "injectionSetpoints";
    public static final String TERMINALS_CONNECTION_ACTIONS = "terminalsConnectionActions";
    public static final String SWITCH_ACTIONS = "switchActions";
    public static final String GENERATOR_ACTIONS = "generatorActions";
    public static final String LOAD_ACTIONS = "loadActions";
    public static final String DANGLINGLINE_ACTIONS = "danglingLineActions";
    public static final String SHUNTCOMPENSATOR_POSITION_ACTIONS = "shuntCompensatorPositionActions";
    public static final String PHASETAPCHANGER_TAPPOSITION_ACTIONS = "phaseTapChangerTapPositionActions";
    public static final String SWITCH_PAIRS = "switchPairs";

    public static final String USAGE_METHOD = "usageMethod";
    public static final String ON_INSTANT_USAGE_RULES = "onInstantUsageRules";
    public static final String FREE_TO_USE_USAGE_RULES = "freeToUseUsageRules"; // retro-compatibility only
    public static final String ON_CONTINGENCY_STATE_USAGE_RULES = "onContingencyStateUsageRules";
    public static final String ON_STATE_USAGE_RULES = "onStateUsageRules"; // retro-compatibility only
    public static final String ON_CONSTRAINT_USAGE_RULES = "onConstraintUsageRules";
    public static final String ON_FLOW_CONSTRAINT_USAGE_RULES = "onFlowConstraintUsageRules";
    public static final String ON_ANGLE_CONSTRAINT_USAGE_RULES = "onAngleConstraintUsageRules";
    public static final String ON_VOLTAGE_CONSTRAINT_USAGE_RULES = "onVoltageConstraintUsageRules";
    public static final String ON_FLOW_CONSTRAINT_IN_COUNTRY_USAGE_RULES = "onFlowConstraintInCountryUsageRules";

    public static final String ID = "id";
    public static final String NAME = "name";
    public static final String TIMESTAMP = "timestamp";
    public static final String EXTENSIONS = "extensions";

    public static final String RANGES = "ranges";
    public static final String SETPOINT = "setpoint";
    public static final String TAP_POSITION = "tapPosition";
    public static final String ACTIVE_POWER_VALUE = "activePowerValue";
    public static final String SECTION_COUNT = "sectionCount";
    public static final String OPERATOR = "operator";
    public static final String BORDER = "border";
    public static final String ACTION_TYPE = "actionType";
    public static final String RANGE_TYPE = "rangeType";
    public static final String INITIAL_SETPOINT = "initialSetpoint";
    public static final String INITIAL_TAP = "initialTap";
    public static final String TAP_TO_ANGLE_CONVERSION_MAP = "tapToAngleConversionMap";

    public static final String UNIT = "unit";
    public static final String RULE = "rule"; // retro-compatibility only
    public static final String SIDE = "side";
    public static final String MIN = "min";
    public static final String MAX = "max";

    public static final String COUNTRY = "country";

    public static final String ACTIVATION_COST = "activationCost";
    public static final String VARIATION_COSTS = "variationCosts";
    public static final String UP = "up";
    public static final String DOWN = "down";

    // instants
    public static final String PREVENTIVE_INSTANT_KIND = "PREVENTIVE";
    public static final String OUTAGE_INSTANT_KIND = "OUTAGE";
    public static final String AUTO_INSTANT_KIND = "AUTO";
    public static final String CURATIVE_INSTANT_KIND = "CURATIVE";

    // ra usage limits
    public static final String RA_USAGE_LIMITS_PER_INSTANT = "ra-usage-limits-per-instant";

    // units
    public static final String AMPERE_UNIT = "ampere";
    public static final String MEGAWATT_UNIT = "megawatt";
    public static final String DEGREE_UNIT = "degree";
    public static final String KILOVOLT_UNIT = "kilovolt";
    public static final String PERCENT_IMAX_UNIT = "percent_imax";
    public static final String TAP_UNIT = "tap";
    public static final String SECTION_COUNT_UNIT = "section_count";

    // rules, retro-compatibility only
    public static final String ON_LOW_VOLTAGE_LEVEL_RULE = "onLowVoltageLevel";
    public static final String ON_HIGH_VOLTAGE_LEVEL_RULE = "onHighVoltageLevel";
    public static final String ON_NON_REGULATED_SIDE_RULE = "onNonRegulatedSide";
    public static final String ON_REGULATED_SIDE_RULE = "onRegulatedSide";
    public static final String ON_LEFT_SIDE_RULE = "onLeftSide";
    public static final String ON_RIGHT_SIDE_RULE = "onRightSide";

    // threshold side
    public static final String LEFT_SIDE = "left";
    public static final String RIGHT_SIDE = "right";
    public static final int SIDE_ONE = 1;
    public static final int SIDE_TWO = 2;
    // usage methods
    public static final String UNAVAILABLE_USAGE_METHOD = "unavailable";
    public static final String FORCED_USAGE_METHOD = "forced";
    public static final String AVAILABLE_USAGE_METHOD = "available";
    public static final String UNDEFINED_USAGE_METHOD = "undefined";

    // range types
    public static final String ABSOLUTE_RANGE = "absolute";
    public static final String RELATIVE_TO_PREVIOUS_INSTANT_RANGE = "relativeToPreviousInstant";
    public static final String RELATIVE_TO_INITIAL_NETWORK_RANGE = "relativeToInitialNetwork";
    public static final String RELATIVE_TO_PREVIOUS_TIME_STEP = "relativeToPreviousTimeStep";

    // action types
    public static final String OPEN_ACTION = "open";
    public static final String CLOSE_ACTION = "close";

    // manipulate version
    public static int getPrimaryVersionNumber(String fullVersion) {
        return Integer.parseInt(divideVersionNumber(fullVersion)[0]);
    }

    public static int getSubVersionNumber(String fullVersion) {
        return Integer.parseInt(divideVersionNumber(fullVersion)[1]);
    }

    private static String[] divideVersionNumber(String fullVersion) {
        String[] dividedV = fullVersion.split("\\.");
        if (dividedV.length != 2 || !Arrays.stream(dividedV).allMatch(StringUtils::isNumeric)) {
            throw new OpenRaoException("json CRAC version number must be of the form vX.Y");
        }
        return dividedV;
    }

    // serialization of enums

    public static String seralizeInstantKind(InstantKind instantKind) {
        switch (instantKind) {
            case PREVENTIVE:
                return PREVENTIVE_INSTANT_KIND;
            case OUTAGE:
                return OUTAGE_INSTANT_KIND;
            case AUTO:
                return AUTO_INSTANT_KIND;
            case CURATIVE:
                return CURATIVE_INSTANT_KIND;
            default:
                throw new OpenRaoException(String.format("Unsupported instant kind %s", instantKind));
        }
    }

    public static InstantKind deseralizeInstantKind(String stringValue) {
        switch (stringValue.toUpperCase()) {
            case PREVENTIVE_INSTANT_KIND:
                return InstantKind.PREVENTIVE;
            case OUTAGE_INSTANT_KIND:
                return InstantKind.OUTAGE;
            case AUTO_INSTANT_KIND:
                return InstantKind.AUTO;
            case CURATIVE_INSTANT_KIND:
                return InstantKind.CURATIVE;
            default:
                throw new OpenRaoException(String.format("Unrecognized instant kind %s", stringValue));
        }
    }

    public static String serializeUnit(Unit unit) {
        switch (unit) {
            case AMPERE:
                return AMPERE_UNIT;
            case DEGREE:
                return DEGREE_UNIT;
            case MEGAWATT:
                return MEGAWATT_UNIT;
            case KILOVOLT:
                return KILOVOLT_UNIT;
            case PERCENT_IMAX:
                return PERCENT_IMAX_UNIT;
            case TAP:
                return TAP_UNIT;
            case SECTION_COUNT:
                return SECTION_COUNT_UNIT;
            default:
                throw new OpenRaoException(String.format("Unsupported unit %s", unit));
        }
    }

    public static Unit deserializeUnit(String stringValue) {
        switch (stringValue) {
            case AMPERE_UNIT:
                return Unit.AMPERE;
            case DEGREE_UNIT:
                return Unit.DEGREE;
            case MEGAWATT_UNIT:
                return Unit.MEGAWATT;
            case KILOVOLT_UNIT:
                return Unit.KILOVOLT;
            case PERCENT_IMAX_UNIT:
                return Unit.PERCENT_IMAX;
            case TAP_UNIT:
                return Unit.TAP;
            case SECTION_COUNT_UNIT:
                return Unit.SECTION_COUNT;
            default:
                throw new OpenRaoException(String.format("Unrecognized unit %s", stringValue));
        }
    }

    public static int serializeSide(TwoSides side) {
        return switch (side) {
            case ONE -> SIDE_ONE;
            case TWO -> SIDE_TWO;
        };
    }

    public static TwoSides deserializeSide(int value) {
        return switch (value) {
            case SIDE_ONE -> TwoSides.ONE;
            case SIDE_TWO -> TwoSides.TWO;
            default -> throw new OpenRaoException(String.format("Unrecognized side %d", value));
        };
    }

    public static TwoSides deserializeSide(String stringValue) {
        return switch (stringValue) {
            case LEFT_SIDE -> TwoSides.ONE;
            case RIGHT_SIDE -> TwoSides.TWO;
            default -> throw new OpenRaoException(String.format("Unrecognized side %s", stringValue));
        };
    }

    /**
     * Converts old BranchThresholdRule to TwoSides
     * For retro-compatibility purposes only
     */
    public static TwoSides convertBranchThresholdRuleToSide(String branchThresholdRule, Pair<Double, Double> nominalV) {
        switch (branchThresholdRule) {
            case ON_LEFT_SIDE_RULE, ON_REGULATED_SIDE_RULE: // This is true only when the network is in UCTE format
                return TwoSides.ONE;
            case ON_RIGHT_SIDE_RULE, ON_NON_REGULATED_SIDE_RULE: // This is true only when the network is in UCTE format.
                return TwoSides.TWO;
            case ON_LOW_VOLTAGE_LEVEL_RULE:
                if (Objects.isNull(nominalV) || Objects.isNull(nominalV.getLeft()) || Objects.isNull(nominalV.getRight()) || Double.isNaN(nominalV.getLeft()) || Double.isNaN(nominalV.getRight())) {
                    throw new OpenRaoException("ON_LOW_VOLTAGE_LEVEL thresholds can only be defined on FlowCnec whose nominalVoltages have been set on both sides");
                }
                if (nominalV.getLeft() <= nominalV.getRight()) {
                    return TwoSides.ONE;
                } else {
                    return TwoSides.TWO;
                }
            case ON_HIGH_VOLTAGE_LEVEL_RULE:
                if (Objects.isNull(nominalV) || Objects.isNull(nominalV.getLeft()) || Objects.isNull(nominalV.getRight()) || Double.isNaN(nominalV.getLeft()) || Double.isNaN(nominalV.getRight())) {
                    throw new OpenRaoException("ON_HIGH_VOLTAGE_LEVEL thresholds can only be defined on FlowCnec whose nominalVoltages have been set on both sides");
                }
                if (nominalV.getLeft() < nominalV.getRight()) {
                    return TwoSides.TWO;
                } else {
                    return TwoSides.ONE;
                }
            default:
                throw new OpenRaoException(String.format("Rule %s is not yet handled for thresholds on FlowCnec", branchThresholdRule));
        }
    }

    public static String serializeUsageMethod(UsageMethod usageMethod) {
        switch (usageMethod) {
            case UNAVAILABLE:
                return UNAVAILABLE_USAGE_METHOD;
            case FORCED:
                return FORCED_USAGE_METHOD;
            case AVAILABLE:
                return AVAILABLE_USAGE_METHOD;
            case UNDEFINED:
                return UNDEFINED_USAGE_METHOD;
            default:
                throw new OpenRaoException(String.format("Unsupported usage method %s", usageMethod));
        }
    }

    public static UsageMethod deserializeUsageMethod(String stringValue) {
        switch (stringValue) {
            case UNAVAILABLE_USAGE_METHOD:
                return UsageMethod.UNAVAILABLE;
            case FORCED_USAGE_METHOD:
                return UsageMethod.FORCED;
            case AVAILABLE_USAGE_METHOD:
                return UsageMethod.AVAILABLE;
            case UNDEFINED_USAGE_METHOD:
                return UsageMethod.UNDEFINED;
            default:
                throw new OpenRaoException(String.format("Unrecognized usage method %s", stringValue));
        }
    }

    public static String serializeRangeType(RangeType rangeType) {
        switch (rangeType) {
            case ABSOLUTE:
                return ABSOLUTE_RANGE;
            case RELATIVE_TO_PREVIOUS_INSTANT:
                return RELATIVE_TO_PREVIOUS_INSTANT_RANGE;
            case RELATIVE_TO_INITIAL_NETWORK:
                return RELATIVE_TO_INITIAL_NETWORK_RANGE;
            case RELATIVE_TO_PREVIOUS_TIME_STEP:
                return RELATIVE_TO_PREVIOUS_TIME_STEP;
            default:
                throw new OpenRaoException(String.format("Unsupported range type %s", rangeType));
        }
    }

    public static RangeType deserializeRangeType(String stringValue) {
        switch (stringValue) {
            case ABSOLUTE_RANGE:
                return RangeType.ABSOLUTE;
            case RELATIVE_TO_PREVIOUS_INSTANT_RANGE:
                return RangeType.RELATIVE_TO_PREVIOUS_INSTANT;
            case RELATIVE_TO_INITIAL_NETWORK_RANGE:
                return RangeType.RELATIVE_TO_INITIAL_NETWORK;
            case RELATIVE_TO_PREVIOUS_TIME_STEP:
                return RangeType.RELATIVE_TO_PREVIOUS_TIME_STEP;
            default:
                throw new OpenRaoException(String.format("Unrecognized range type %s", stringValue));
        }
    }

    public static String serializeActionType(ActionType actionType) {
        switch (actionType) {
            case OPEN:
                return OPEN_ACTION;
            case CLOSE:
                return CLOSE_ACTION;
            default:
                throw new OpenRaoException(String.format("Unsupported action type %s", actionType));
        }
    }

    public static ActionType deserializeActionType(String stringValue) {
        switch (stringValue) {
            case OPEN_ACTION:
                return ActionType.OPEN;
            case CLOSE_ACTION:
                return ActionType.CLOSE;
            default:
                throw new OpenRaoException(String.format("Unrecognized action type %s", stringValue));
        }
    }

    public static class ThresholdComparator implements Comparator<Threshold> {
        @Override
        public int compare(Threshold o1, Threshold o2) {
            String unit1 = serializeUnit(o1.getUnit());
            String unit2 = serializeUnit(o2.getUnit());
            if (unit1.equals(unit2)) {
                if (o1 instanceof BranchThreshold bt1 && o2 instanceof BranchThreshold bt2 &&
                    !bt1.getSide().equals(bt2.getSide())) {
                    return Integer.compare(serializeSide(bt1.getSide()), serializeSide(bt2.getSide()));
                }
                if (o1.min().isPresent()) {
                    return -1;
                }
                return 1;
            } else {
                return unit1.compareTo(unit2);
            }
        }
    }

    public static class UsageRuleComparator implements Comparator<UsageRule> {
        @Override
        public int compare(UsageRule o1, UsageRule o2) {
            if (!o1.getClass().equals(o2.getClass())) {
                return o1.getClass().toString().compareTo(o2.getClass().toString());
            }
            if (!o1.getInstant().equals(o2.getInstant())) {
                return o1.getInstant().comesBefore(o2.getInstant()) ? -1 : 1;
            }
            if (!o1.getUsageMethod().equals(o2.getUsageMethod())) {
                return serializeUsageMethod(o1.getUsageMethod()).compareTo(serializeUsageMethod(o2.getUsageMethod()));
            }
            if (o1 instanceof OnInstant) {
                return 0;
            }
            if (o1 instanceof OnContingencyState ocs1) {
                return ocs1.getState().getId().compareTo(((OnContingencyState) o2).getState().getId());
            }
            if (o1 instanceof OnFlowConstraintInCountry ofcic1) {
                return ofcic1.getCountry().toString().compareTo(((OnFlowConstraintInCountry) o2).getCountry().toString());
            }
            if (o1 instanceof OnConstraint<?> oc1) {
                return oc1.getCnec().getId().compareTo(((OnConstraint<?>) o2).getCnec().getId());
            }
            throw new OpenRaoException(String.format("Unknown usage rule type: %s", o1.getClass()));
        }
    }

    public static void deserializeNetworkElement(String networkElementId, Map<String, String> networkElementsNamesPerId, SingleNetworkElementActionAdder<?> adder) {
        if (networkElementsNamesPerId.containsKey(networkElementId)) {
            adder.withNetworkElement(networkElementId, networkElementsNamesPerId.get(networkElementId));
        } else {
            adder.withNetworkElement(networkElementId);
        }
    }

    public static void serializeActivationCost(RemedialAction<?> remedialAction, JsonGenerator gen) throws IOException {
        Optional<Double> activationCost = remedialAction.getActivationCost();
        if (activationCost.isPresent()) {
            gen.writeNumberField(JsonSerializationConstants.ACTIVATION_COST, activationCost.get());
        }
    }

    public static void serializeVariationCosts(RangeAction<?> rangeAction, JsonGenerator gen) throws IOException {
        Optional<Double> variationCostUp = rangeAction.getVariationCost(VariationDirection.UP);
        Optional<Double> variationCostDown = rangeAction.getVariationCost(VariationDirection.DOWN);
        if (variationCostUp.isEmpty() && variationCostDown.isEmpty()) {
            return;
        }
        gen.writeObjectFieldStart(JsonSerializationConstants.VARIATION_COSTS);
        if (variationCostUp.isPresent()) {
            gen.writeNumberField(JsonSerializationConstants.UP, variationCostUp.get());
        }
        if (variationCostDown.isPresent()) {
            gen.writeNumberField(JsonSerializationConstants.DOWN, variationCostDown.get());
        }
        gen.writeEndObject();
    }

    public static VariationDirection deserializeVariationDirection(String variationDirection) {
        if (JsonSerializationConstants.UP.equals(variationDirection)) {
            return VariationDirection.UP;
        } else if (JsonSerializationConstants.DOWN.equals(variationDirection)) {
            return VariationDirection.DOWN;
        } else {
            throw new OpenRaoException("Unexpected variation direction '%s'.".formatted(variationDirection));
        }
    }
}