SecurityAnalysisResultXml.java
/**
* Copyright (c) 2021, 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.cne.converter;
import com.powsybl.cne.model.ContingencySeries;
import com.powsybl.cne.model.Measurement;
import com.powsybl.cne.model.MonitoredRegisteredResource;
import com.powsybl.cne.model.RegisteredResource;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.commons.xml.XmlUtil;
import com.powsybl.security.SecurityAnalysisResult;
import org.apache.commons.io.output.WriterOutputStream;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @author Thomas Adam {@literal <tadam at silicom.fr>}
*/
public final class SecurityAnalysisResultXml {
private SecurityAnalysisResultXml() {
}
private static XMLStreamWriter initializeWriter(WriterOutputStream os) throws XMLStreamException {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, CneConstants.INDENT, os);
writer.writeStartElement(CneConstants.ROOT_ELEMENT);
writer.writeAttribute("xsi:schemaLocation", "iec62325-451-n-cne_v2_0.xsd");
writer.writeDefaultNamespace("urn:iec62325.351:tc57wg16:451-n:cnedocument:2:0");
writer.writeNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
return writer;
}
private static void writeMainAttributes(SecurityAnalysisResultXmlWriterContext context) throws XMLStreamException {
XMLStreamWriter writer = context.getWriter();
CneExportOptions parameters = context.getParameters();
// mRID
writer.writeComment(" mRID proposal including hour in UTC and local time, only constraint is the limit of 35 characters ");
writer.writeStartElement(CneConstants.MRID);
writer.writeCharacters(parameters.getMRID());
writer.writeEndElement(); // mRID
// revisionNumber
writer.writeStartElement(CneConstants.REVISION_NUMBER);
writer.writeCharacters(Integer.toString(1));
writer.writeEndElement(); // revisionNumber
// type
writer.writeComment(" type B15 for \"Network constraint document\" : \"A document providing the network constraint situations used for the load flow studies. A network constraint situation includes contingencies, monitored elements and remedial actions.\" ");
writer.writeStartElement(CneConstants.TYPE);
writer.writeCharacters("B15");
writer.writeEndElement(); // type
// process.processType
writer.writeComment(" processType A01 for \"Day-ahead\" ");
writer.writeComment(" processType A40 for \"Intraday\" ");
writer.writeComment(" processType A45 for \"Two days ahead\" ");
writer.writeComment(" processType A14 for \"Forecast\" : \"The data contained in the document are to be handled in short, medium, long term forecasting process\" ");
writer.writeComment(" processType A16 for \"Realised\" : \"The process for the treatment of realised data as opposed to forecast data.\" ");
writer.writeStartElement(CneConstants.PROCESS_PROCESS_TYPE);
writer.writeCharacters("A01");
writer.writeEndElement(); // process.processType
// sender_MarketParticipant
// mRID
writer.writeComment(" A36 for \"Capacity Coordinator\" and A04 for \"System Operator\" ");
writer.writeStartElement(CneConstants.SENDER_MARKET_PARTICIPANT_MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A01");
writer.writeCharacters(parameters.getSenderMarketParticipantMRID());
writer.writeEndElement(); // sender_MarketParticipant.mRID
// type
writer.writeStartElement(CneConstants.SENDER_MARKET_PARTICIPANT_TYPE);
writer.writeCharacters("A36");
writer.writeEndElement(); // sender_MarketParticipant.marketRole.type
// receiver_MarketParticipant
// mRID
writer.writeStartElement(CneConstants.RECEIVER_MARKET_PARTICIPANT_MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A01");
writer.writeCharacters(parameters.getReceiverMarketParticipantMRID());
writer.writeEndElement(); // receiver_MarketParticipant.mRID
// type
writer.writeStartElement(CneConstants.RECEIVER_MARKET_PARTICIPANT_TYPE);
writer.writeCharacters("A36");
writer.writeEndElement(); // receiver_MarketParticipant.marketRole.type
// createdDateTime
writer.writeStartElement(CneConstants.CREATED_DATETIME);
writer.writeCharacters(parameters.getCreatedDatetime());
writer.writeEndElement(); // createdDateTime
// time_Period.timeInterval
writer.writeComment(" optional fields: \"docStatus\", \"Received_MarketDocument\", \"Related_MarketDocument\" -> we could add those additional fields if needed ");
writer.writeComment(" \"Related_MarketDocument\" could contains the name of the grid model considered ? ");
writer.writeComment(" \"Related_MarketDocument\" could contains the filename of the CRAC document containing the list of Contingencies ? ");
writer.writeComment(" time_Period.timeInterval could be reduced to 15 min length for SN security analysis ");
writer.writeStartElement(CneConstants.TIME_PERIOD + "." + CneConstants.TIME_INTERVAL);
writeTimeInterval(writer, parameters);
writer.writeEndElement(); // time_Period.timeInterval
// Last comments
writer.writeComment(" optional fields: \"domain.mRID\" used for CC to specify the region, not really needed for CSA ");
}
private static void writeTimeInterval(XMLStreamWriter writer, CneExportOptions parameters) throws XMLStreamException {
// start
writer.writeStartElement(CneConstants.START);
writer.writeCharacters(parameters.getTimePeriodStart());
writer.writeEndElement(); // start
// end
writer.writeStartElement(CneConstants.END);
writer.writeCharacters(parameters.getTimePeriodEnd());
writer.writeEndElement(); // end
}
private static void writeTimeSeries(SecurityAnalysisResultXmlWriterContext context) throws XMLStreamException {
XMLStreamWriter writer = context.getWriter();
CneExportOptions parameters = context.getParameters();
writer.writeComment(" One timeseries to provide ");
writer.writeStartElement(CneConstants.TIME_SERIES);
writer.writeComment(" mRID convention to be agreed, could include additional information ");
// mRID
writer.writeStartElement(CneConstants.MRID);
writer.writeCharacters(parameters.getTimeSeriesMRID());
writer.writeEndElement(); // mRID
writer.writeComment(" B37 - Constraint situation : Constraint situation \"The timeseries describes the constraint situation for a given TimeInterval. A constraint situation can be: - composed of a list of network elements in outage associated for each outage to a list of network elements on which remedial actions have been carried out accordingly to contingency process - or it can be an external constraint. ");
writer.writeComment(" B54 - Network Constraint Situation : The TimeSeries describes the network elements to be taken into account to simulate a network constraint during the network load flow studies. The network situation includes the contingencies, the remedial actions, the monitored network elements and the potential additional constraints. ");
// businessType
writer.writeStartElement(CneConstants.BUSINESS_TYPE);
writer.writeCharacters("B37");
writer.writeEndElement(); // businessType
writer.writeComment(" A01: sequential fixed size block. It maked no sense to use A03 ");
// curveType
writer.writeStartElement(CneConstants.CURVE_TYPE);
writer.writeCharacters("A01");
writer.writeEndElement(); // curveType
// Period
writer.writeStartElement(CneConstants.PERIOD);
// timeInterval
writer.writeStartElement(CneConstants.TIME_INTERVAL);
writeTimeInterval(writer, parameters);
writer.writeEndElement(); // timeInterval
writer.writeComment(" resolutions should be reduced for security analysis of snapshot -> \"PT15M\" ");
// resolution
writer.writeStartElement(CneConstants.RESOLUTION);
writer.writeCharacters("PT60M");
writer.writeEndElement(); // resolution
// Point
writer.writeStartElement(CneConstants.POINT);
// position
writer.writeStartElement(CneConstants.POSITION);
writer.writeCharacters(Integer.toString(1));
writer.writeEndElement(); // position
writer.writeComment(" One constraint series for N state flows and one per contingency with constraint ");
// PreContingencies
writePreContingencyResult(context);
// PostContingencies
writePostContingencyResult(context);
writer.writeEndElement(); // Point
writer.writeEndElement(); // Period
writer.writeEndElement(); // TimeSeries
}
private static void writePreContingencyResult(SecurityAnalysisResultXmlWriterContext context) throws XMLStreamException {
final XMLStreamWriter writer = context.getWriter();
CneExportOptions parameters = context.getParameters();
// Constraint_Series
writer.writeStartElement(CneConstants.CONSTRAINT_SERIES);
// mRID
writer.writeComment(" this mRID will identify the CO ? only ");
writer.writeStartElement(CneConstants.MRID);
writer.writeCharacters("N State constraints");
writer.writeEndElement(); // mRID
writer.writeComment(" Mandatory - Check which business type is the most appropriate for reporting a constraint ");
// businessType
writer.writeStartElement(CneConstants.BUSINESS_TYPE);
writer.writeCharacters("B56");
writer.writeEndElement(); // businessType
writer.writeComment(" No contingency in this one because N state constraints are reported ");
// Monitored_RegisteredResource
writeMonitoredRegisteredResource(writer, parameters, context.getPreMonitoredRegisteredResources());
writer.writeEndElement(); // Constraint_Series
}
private static void writeMonitoredRegisteredResource(final XMLStreamWriter writer, CneExportOptions parameters, List<MonitoredRegisteredResource> equipments) throws XMLStreamException {
// Monitored_RegisteredResource
for (MonitoredRegisteredResource equipment : equipments) {
writer.writeStartElement(CneConstants.MONITORED_REGISTERED_RESOURCE);
// mRID
writer.writeStartElement(CneConstants.MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A02");
writer.writeCharacters(equipment.getEquipmentId());
writer.writeEndElement(); // mRID
// Name
writer.writeStartElement(CneConstants.NAME);
writer.writeCharacters(equipment.getEquipmentName());
writer.writeEndElement(); // Name
writer.writeComment(" We can use in/out to be able to filter by constraint location ");
// in_Domain.mRID
writer.writeStartElement(CneConstants.IN_DOMAIN_MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A01");
writer.writeCharacters(parameters.getInDomainMRID());
writer.writeEndElement(); // in_Domain.mRID
// out_Domain.mRID
writer.writeStartElement(CneConstants.OUT_DOMAIN_MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A01");
writer.writeCharacters(parameters.getOutDomainMRID());
writer.writeEndElement(); // out_Domain.mRID
writer.writeComment(" Some work to be done to decide which measurement and with which units to be included here : %, Amperes, ");
// Measurements
writeMeasurements(writer, equipment.getMeasurementList());
writer.writeEndElement(); // Monitored_RegisteredResource
}
}
private static void writeMeasurements(final XMLStreamWriter writer, List<Measurement> measurementList) throws XMLStreamException {
for (Measurement m : measurementList) {
// Measurements
writer.writeStartElement(CneConstants.MEASUREMENTS);
// measurementType
writer.writeStartElement(CneConstants.MEASUREMENT_TYPE);
writer.writeCharacters(m.getMeasurementType().name());
writer.writeEndElement(); //measurementType
// unitSymbol
writer.writeStartElement(CneConstants.UNIT_SYMBOL);
writer.writeCharacters(m.getUnitSymbol().name());
writer.writeEndElement(); // unitSymbol
// analogValues.value
writer.writeStartElement(CneConstants.ANALOG_VALUES_VALUE);
writer.writeCharacters(String.valueOf(m.getAnalogValue()));
writer.writeEndElement(); // analogValues.value
writer.writeEndElement(); // Measurements
}
}
private static void writePostContingencyResult(SecurityAnalysisResultXmlWriterContext context) throws XMLStreamException {
final XMLStreamWriter writer = context.getWriter();
CneExportOptions parameters = context.getParameters();
for (ContingencySeries key : context.getPostMonitoredRegisteredResources().keySet()) {
// Constraint_Series
writer.writeStartElement(CneConstants.CONSTRAINT_SERIES);
// mRID
writer.writeComment(" this mRID will identify the CO ? only ");
writer.writeStartElement(CneConstants.MRID);
writer.writeCharacters(key.getContingencyId());
writer.writeEndElement(); // mRID
writer.writeComment(" Mandatory - Check which business type is the most appropriate for reporting a constraint ");
// businessType
writer.writeStartElement(CneConstants.BUSINESS_TYPE);
writer.writeCharacters("B56");
writer.writeEndElement(); // businessType
// Contingency_Series
writer.writeStartElement(CneConstants.CONTINGENCY_SERIES);
writer.writeComment(" Ensuring stability of mRID will be complex without an external Co Dictionary ");
// mRID
writer.writeStartElement(CneConstants.MRID);
writer.writeCharacters(key.getContingencyId());
writer.writeEndElement(); // mRID
// Name
writer.writeStartElement(CneConstants.NAME);
writer.writeCharacters(key.getContingencyName());
writer.writeEndElement(); // Name
for (RegisteredResource registeredResource : key.getRegisteredResourceList()) {
// RegisteredResource
writer.writeStartElement(CneConstants.REGISTERED_RESOURCE);
// mRID
writer.writeStartElement(CneConstants.MRID);
writer.writeAttribute(CneConstants.CODING_SCHEME, "A02");
writer.writeCharacters(registeredResource.getId());
writer.writeEndElement(); // mRID
// Name
writer.writeStartElement(CneConstants.NAME);
writer.writeCharacters(registeredResource.getName());
writer.writeEndElement(); // Name
writer.writeEndElement(); // RegisteredResource
}
writer.writeEndElement(); // Contingency_Series
// Monitored_RegisteredResource
writeMonitoredRegisteredResource(writer, parameters, context.getPostMonitoredRegisteredResources().get(key));
writer.writeEndElement(); // Constraint_Series
}
}
public static void write(SecurityAnalysisResult result, CneExportOptions options, Writer writer) throws IOException {
try (var os = new WriterOutputStream(writer, StandardCharsets.UTF_8)) {
var context = new SecurityAnalysisResultXmlWriterContext(result, options, initializeWriter(os));
// Write root metadata
writeMainAttributes(context);
// Write TimeSeries token
writeTimeSeries(context);
// Write last comments & closure tokens
context.getWriter().writeComment(" optional class \"Reason\" could be used to provide additional information. Each reason node have a reason code and a reason text field (free text) ");
context.getWriter().writeComment(" A95:Complementary information, B01:Incomplete document, B18:Failure, B27:Calculation process failed ");
context.getWriter().writeEndElement();
context.getWriter().writeEndDocument();
context.getWriter().flush();
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}
}