Skip to content

Commit 0139082

Browse files
authored
Merge pull request #383 from Baltic-RCC/dev
Release to production
2 parents e19e2bb + 14f7635 commit 0139082

File tree

15 files changed

+633
-234
lines changed

15 files changed

+633
-234
lines changed

.github/workflows/publish-workers.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
- emfos-model-validator
2020
- emfos-model-merger
2121
- emfos-task-generator
22+
- emfos-model-quality
2223

2324
steps:
2425
- uses: actions/checkout@v4
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[MAIN]
22
MAX_ITERATION = 15
33
BALANCE_THRESHOLD = 2
4+
CONSTANT_POWER_FACTOR = False
5+
POWER_FACTOR_THRESHOLD = 1
46
DEBUG = True
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[MAIN]
2-
INPUT_RMQ_QUEUE = object-storage.models.quality
2+
INPUT_RMQ_QUEUE = object-storage.models.quality.cgm
33
ELK_QUALITY_INDEX = emfos-model-quality
44
ELK_STATISTICS_INDEX = emfos-model-statistics
5-
BORDER_LIMIT = 250
5+
BORDER_LIMIT = 250
6+
LINE_LIMIT_TEMPERATURE = 25 C
7+
IGM_RULE_SET = impedance,line_rating
8+
CGM_RULE_SET = kruonis,rtec,outage,lt_pl_xborder

config/model_validator/model_validator.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ INPUT_RMQ_QUEUE = object-storage.models.validation
33
OUTPUT_RMQ_EXCHANGE = emf-quality.test
44
VALIDATION_ELK_INDEX = emfos-igm-validation
55
METADATA_ELK_INDEX = emfos-opde-models
6+
ENABLE_LVL8_REPORTS = False
7+
QAS_EIC = EIC
8+
QAS_MSG_TYPE = MSG_TYPE
69
ELK_ID_FROM_METADATA_FIELDS = opde:Id,data-source
710
ELK_ID_HASHING = True
811
ENABLE_DYNAMIC_VALIDATION_SETTINGS = False

emf/common/converters/iec_schedule_to_ndjson.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def get_metadata_from_xml(xml, include_namespace=True, prefix_root=False):
9999
return properties_dict
100100

101101

102-
def parse_iec_xml(element_tree: bytes, return_values_per_mtu: bool = True, mtu_resolution: str = 'PT1H'):
102+
def parse_iec_xml(element_tree: bytes, return_values_per_mtu: bool = True, mtu_resolution: str = 'PT15M'):
103103
"""Parses iec xml to dictionary, meta on the same row with value and start/end time"""
104104
# TODO make return_values_per_mtu argument in parameters
105105
# TODO - maybe first analyse the xml, by getting all elements and try to match names, ala point_element_name = unique_element_namelist.contains("point") etc.
Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
22
import sys
33

4+
import pandas as pd
5+
46
logger = logging.getLogger(__name__)
57

68
logging.basicConfig(
@@ -12,6 +14,7 @@
1214

1315
def sum_on_KEY(data, KEY, precision=1):
1416
return round(data.query("KEY == @KEY").VALUE.astype(float).sum(), precision)
17+
1518
def get_load_and_generation_ssh(data):
1619
logger.info("Getting Load and Generation data") # TODO add wrapper with timing and logging
1720
return {
@@ -133,7 +136,6 @@ def get_tieflow_data(data):
133136

134137

135138
# Add SV results
136-
# if sv_results := data.type_tableview("SvPowerFlow") is not None:
137139
try:
138140
tieflow_data = tieflow_data.merge(data.type_tableview("SvPowerFlow"),
139141
left_on="TieFlow.Terminal",
@@ -151,9 +153,7 @@ def get_tieflow_data(data):
151153

152154
# Fix some names
153155
tieflow_data = tieflow_data.rename(columns={
154-
"IdentifiedObject.energyIdentCodeEic_Terminal": "IdentifiedObject.energyIdentCodeEic_ControlArea",
155-
"IdentifiedObject.energyIdentCodeEic": "IdentifiedObject.energyIdentCodeEic_Line"
156-
})
156+
"IdentifiedObject.energyIdentCodeEic_Terminal": "IdentifiedObject.energyIdentCodeEic_ControlArea"})
157157

158158
# Add cross borders data
159159
def merge_sort_strings(row, col1, col2, delimiter='-'):
@@ -173,10 +173,7 @@ def get_system_metrics(data, tieflow_data=None, load_and_generation=None):
173173
# Use only Interchange Control Area Tieflows
174174
tieflow_type = "http://iec.ch/TC57/2013/CIM-schema-cim16#ControlAreaTypeKind.Interchange"
175175
tieflow_data = get_tieflow_data(data)
176-
try:
177-
tieflow_data = tieflow_data.query("`ControlArea.type` == @tieflow_type")
178-
except:
179-
tieflow_data = tieflow_data[tieflow_data['ControlArea.type'] == tieflow_type]
176+
tieflow_data = tieflow_data[tieflow_data['ControlArea.type'] == tieflow_type]
180177

181178

182179
if load_and_generation is None or load_and_generation.empty:
@@ -189,22 +186,15 @@ def get_system_metrics(data, tieflow_data=None, load_and_generation=None):
189186
tieflow_np = tieflow_data[data_columns].sum().to_dict()
190187

191188
# Summing values where BoundaryPoint.isDirectCurrent is False
192-
try:
193-
tieflow_acnp = tieflow_data.query("`BoundaryPoint.isDirectCurrent` == False")[data_columns].sum().to_dict()
194-
except:
195-
tieflow_acnp = tieflow_data[tieflow_data['BoundaryPoint.isDirectCurrent'] == False][data_columns].sum().to_dict()
189+
tieflow_acnp = tieflow_data[tieflow_data['BoundaryPoint.isDirectCurrent'] == False][data_columns].sum().to_dict()
196190

197191
# Processing HVDC tieflow data
198192
try:
199-
tieflow_hvdc = tieflow_data.query("`BoundaryPoint.isDirectCurrent` == True")[
200-
['IdentifiedObject.energyIdentCodeEic_Line'] + data_columns].set_index('IdentifiedObject.energyIdentCodeEic_Line').to_dict("index")
193+
tieflow_hvdc = tieflow_data[tieflow_data['BoundaryPoint.isDirectCurrent'] == True][
194+
['IdentifiedObject.energyIdentCodeEic_Line'] + data_columns].set_index(
195+
'IdentifiedObject.energyIdentCodeEic_Line').drop_duplicates().to_dict("index")
201196
except:
202-
try:
203-
tieflow_hvdc = tieflow_data[tieflow_data['BoundaryPoint.isDirectCurrent'] == True][
204-
['IdentifiedObject.energyIdentCodeEic_Line'] + data_columns].set_index(
205-
'IdentifiedObject.energyIdentCodeEic_Line').to_dict("index")
206-
except:
207-
tieflow_hvdc = None
197+
tieflow_hvdc = {}
208198

209199
# Calculating total_load, generation, and net position
210200
load = load_and_generation["EnergyConsumer.p"].sum()
@@ -225,7 +215,6 @@ def get_system_metrics(data, tieflow_data=None, load_and_generation=None):
225215
'tieflow_abs': tieflow_abs,
226216
'tieflow_np': tieflow_np,
227217
'tieflow_acnp': tieflow_acnp,
228-
'tieflow_hvdc': tieflow_hvdc,
229218
}
230219

231220
# Fixes for ELK data storage

emf/common/integrations/object_storage/schedules.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ def query_hvdc_schedules(time_horizon: str,
3131
area_eic_map = json.loads(f.read())
3232
hvdc_eic_map = {}
3333

34-
# Define utc start/end times from timestamp
35-
utc_start = datetime.fromisoformat(scenario_timestamp) - timedelta(minutes=30)
36-
utc_end = datetime.fromisoformat(scenario_timestamp) + timedelta(minutes=30)
34+
# Define utc end time from timestamp
35+
utc_end = datetime.fromisoformat(scenario_timestamp) + timedelta(minutes=15)
3736

3837
# Define business type by time horizon
3938
business_type = "B63" if time_horizon in ["1D", "ID"] else "B67"
@@ -47,7 +46,7 @@ def query_hvdc_schedules(time_horizon: str,
4746
# Get HVDC schedules
4847
schedules_df = service.query_schedules_from_elk(
4948
index="emfos-schedules*",
50-
utc_start=utc_start.isoformat(),
49+
utc_start=scenario_timestamp,
5150
utc_end=utc_end.isoformat(),
5251
metadata=metadata,
5352
period_overlap=True,
@@ -101,9 +100,8 @@ def query_acnp_schedules(time_horizon: str,
101100
with open(config.paths.cgm_worker.default_area_eic_map, "rb") as f:
102101
area_eic_map = json.loads(f.read())
103102

104-
# Define utc start/end times from timestamp
105-
utc_start = datetime.fromisoformat(scenario_timestamp) - timedelta(minutes=30)
106-
utc_end = datetime.fromisoformat(scenario_timestamp) + timedelta(minutes=30)
103+
# Define utc end time from timestamp
104+
utc_end = datetime.fromisoformat(scenario_timestamp) + timedelta(minutes=15)
107105

108106
# Define metadata dictionary
109107
metadata = {
@@ -114,7 +112,7 @@ def query_acnp_schedules(time_horizon: str,
114112
# Get AC area schedules
115113
schedules_df = service.query_schedules_from_elk(
116114
index="emfos-schedules*",
117-
utc_start=utc_start.isoformat(),
115+
utc_start=scenario_timestamp,
118116
utc_end=utc_end.isoformat(),
119117
metadata=metadata,
120118
period_overlap=True,

emf/common/loadflow_tool/loadflow_settings.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,17 @@
1515
# TODO - NOT AVAILABLE - cim:PowerFlowSettings.transformerRatioTapControlPriority "1" ;
1616
# TODO - USE IN SCALING - eumd:PowerFlowSettings.maxIterationNumberAIC "15" ;
1717

18-
#DEFAULT settings applicable for all processes
18+
# DEFAULT settings applicable for all processes
1919
OPENLOADFLOW_DEFAULT_PROVIDER = {
2020
'slackBusesIds': '',
2121
'lowImpedanceBranchMode': 'REPLACE_BY_ZERO_IMPEDANCE_LINE',
2222
'voltageRemoteControl': 'True',
23-
24-
# Legacy, not found in code, can be removed
25-
# 'throwsExceptionInCaseOfSlackDistributionFailure': 'False',
26-
2723
'loadPowerFactorConstant': 'True', # cim:PowerFlowSettings.loadVoltageDependency TODO - check this
2824
'plausibleActivePowerLimit': '5000.0',
2925
'slackBusPMaxMismatch': '0.1', #slackBusDistributionThreshold
3026
'voltagePerReactivePowerControl': 'False',
31-
32-
# Legacy, not found in code, can be removed, replaced with generator, transformer etc control
33-
# 'reactivePowerRemoteControl': 'False',
34-
3527
'newtonRaphsonConvEpsPerEq': '1.0E-4',
3628
'voltageTargetPriorities': 'GENERATOR,TRANSFORMER,SHUNT',
37-
# 'voltageInitModeOverride': None,
3829
'transformerVoltageControlMode': 'AFTER_GENERATOR_VOLTAGE_CONTROL',
3930
'shuntVoltageControlMode': 'INCREMENTAL_VOLTAGE_CONTROL',
4031
'minPlausibleTargetVoltage': '0.8',
@@ -49,10 +40,6 @@
4940
# 'debugDir': '',
5041
'incrementalTransformerVoltageControlOuterLoopMaxTapShift': '3', # TODO - check this
5142
'secondaryVoltageControl': 'False',
52-
53-
# Legacy, not found in code, can be removed
54-
# 'controllerToPilotPointVoltageSensiEpsilon': '0.01',
55-
5643
'reactiveLimitsMaxPqPvSwitch': '3',
5744
'newtonRaphsonStoppingCriteriaType': 'UNIFORM_CRITERIA',
5845
'maxActivePowerMismatch': '0.01', # cim:PowerFlowSettings.activePowerTolerance
@@ -96,8 +83,7 @@
9683

9784

9885
# Deviation of default provider from the default
99-
#############
100-
#Used for CGM main merging process
86+
## Used for CGM main merging process
10187
__IGM_VALIDATION_PROVIDER = {
10288
'slackBusSelectionMode': 'MOST_MESHED',
10389
'referenceBusSelectionMode':'GENERATOR_REFERENCE_PRIORITY',
@@ -130,12 +116,11 @@
130116
'maxAngleMismatch': '1.0E-5', # cim:PowerFlowSettings.voltageAngleLimit "10" TODO - How to convert
131117
'slackBusPMaxMismatch': '0.09', # To fulfill QOCDC SV_INJECTION_LIMIT = 0.1'
132118
'disableVoltageControlOfGeneratorsOutsideActivePowerLimits': 'true', # supress q part of igm-ssh-vs-cgm-ssh error
133-
134-
# 'extrapolateReactiveLimits': 'true',
135119
'disableInconsistentVoltageControls': 'true',
136120
'transformerVoltageControlMode': 'INCREMENTAL_VOLTAGE_CONTROL',
137121
'shuntVoltageControlMode': 'INCREMENTAL_VOLTAGE_CONTROL',
138122
'phaseShifterControlMode': 'INCREMENTAL',
123+
139124
}
140125
__EU_RELAXED_PROVIDER = {
141126
'slackBusSelectionMode': 'MOST_MESHED',
@@ -153,15 +138,13 @@
153138
'maxAngleMismatch': '1.0E-5', # cim:PowerFlowSettings.voltageAngleLimit "10" ; TODO - How to convert
154139
'slackBusPMaxMismatch': '0.09', # To fulfill QOCDC SV_INJECTION_LIMIT = 0.1
155140
'disableVoltageControlOfGeneratorsOutsideActivePowerLimits': 'true', # supress q part of igm-ssh-vs-cgm-ssh error
156-
157141
'disableInconsistentVoltageControls': 'true',
158142
'transformerVoltageControlMode': 'INCREMENTAL_VOLTAGE_CONTROL',
159143
'shuntVoltageControlMode': 'INCREMENTAL_VOLTAGE_CONTROL',
160144
'phaseShifterControlMode': 'INCREMENTAL',
161145
}
162146

163-
164-
#Baltic merge parameters
147+
## Baltic merge parameters
165148
__BA_DEFAULT_PROVIDER = {
166149
'slackBusSelectionMode': 'MOST_MESHED',
167150
'generatorReactivePowerRemoteControl': 'True',
@@ -206,31 +189,26 @@
206189
'slackBusPMaxMismatch': '0.09', # To fulfill QOCDC SV_INJECTION_LIMIT = 0.1
207190
'disableVoltageControlOfGeneratorsOutsideActivePowerLimits': 'true', # supress q part of igm-ssh-vs-cgm-ssh error
208191
}
209-
############
210192

211193
# Preparing PROVIDER settings options from default settings
212-
#############
213-
#Used for CGM main merging process
194+
## Used for CGM main merging process
214195
IGM_VALIDATION_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
215196
IGM_VALIDATION_PROVIDER.update(__IGM_VALIDATION_PROVIDER)
216197
EU_DEFAULT_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
217198
EU_DEFAULT_PROVIDER.update(__EU_DEFAULT_PROVIDER)
218199
EU_RELAXED_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
219200
EU_RELAXED_PROVIDER.update(__EU_RELAXED_PROVIDER)
220201

221-
#Baltic merge parameters
202+
## Baltic merge parameters
222203
BA_DEFAULT_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
223204
BA_DEFAULT_PROVIDER.update(__BA_DEFAULT_PROVIDER)
224205
BA_RELAXED_1_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
225206
BA_RELAXED_1_PROVIDER.update(__BA_RELAXED_1_PROVIDER)
226207
BA_RELAXED_2_PROVIDER = OPENLOADFLOW_DEFAULT_PROVIDER.copy()
227208
BA_RELAXED_2_PROVIDER.update(__BA_RELAXED_2_PROVIDER)
228-
##############
229-
230209

231210
# Prepare pypowsybl loadflow parameters classes
232-
##############
233-
#Used for CGM main merging process
211+
## Used for CGM main merging process
234212
IGM_VALIDATION = pypowsybl.loadflow.Parameters(
235213
#voltage_init_mode=pypowsybl._pypowsybl.VoltageInitMode.UNIFORM_VALUES, # cim:PowerFlowSettings.flatStart "true"
236214
transformer_voltage_control_on=True, # cim:PowerFlowSettings.transformerRatioTapControlPriority "1"
@@ -247,6 +225,7 @@
247225
connected_component_mode=pypowsybl._pypowsybl.ConnectedComponentMode.MAIN,
248226
provider_parameters=IGM_VALIDATION_PROVIDER,
249227
)
228+
250229
EU_DEFAULT = pypowsybl.loadflow.Parameters(
251230
#voltage_init_mode=pypowsybl._pypowsybl.VoltageInitMode.UNIFORM_VALUES, # cim:PowerFlowSettings.flatStart "true"
252231
transformer_voltage_control_on=True, # @cim:PowerFlowSettings.transformerRatioTapControlPriority": "1"
@@ -263,6 +242,7 @@
263242
connected_component_mode=pypowsybl._pypowsybl.ConnectedComponentMode.ALL,
264243
provider_parameters=EU_DEFAULT_PROVIDER,
265244
)
245+
266246
EU_RELAXED = pypowsybl.loadflow.Parameters(
267247
#voltage_init_mode=pypowsybl._pypowsybl.VoltageInitMode.UNIFORM_VALUES, # cim:PowerFlowSettings.flatStart "true"
268248
transformer_voltage_control_on=True, # cim:PowerFlowSettings.transformerRatioTapControlPriority "0"
@@ -280,7 +260,7 @@
280260
provider_parameters=EU_RELAXED_PROVIDER,
281261
)
282262

283-
#Baltic merge parameters
263+
## Baltic merge parameters
284264
BA_DEFAULT = pypowsybl.loadflow.Parameters(
285265
voltage_init_mode=pypowsybl._pypowsybl.VoltageInitMode.UNIFORM_VALUES, # cim:PowerFlowSettings.flatStart "true"
286266
transformer_voltage_control_on=True, # @cim:PowerFlowSettings.transformerRatioTapControlPriority": "1"
@@ -297,6 +277,7 @@
297277
connected_component_mode=pypowsybl._pypowsybl.ConnectedComponentMode.ALL,
298278
provider_parameters=BA_DEFAULT_PROVIDER,
299279
)
280+
300281
BA_RELAXED_1 = pypowsybl.loadflow.Parameters(
301282
voltage_init_mode=pypowsybl._pypowsybl.VoltageInitMode.UNIFORM_VALUES, # cim:PowerFlowSettings.flatStart "true"
302283
transformer_voltage_control_on=False, # cim:PowerFlowSettings.transformerRatioTapControlPriority "0"
@@ -330,4 +311,3 @@
330311
connected_component_mode=pypowsybl._pypowsybl.ConnectedComponentMode.ALL,
331312
provider_parameters=BA_RELAXED_2_PROVIDER,
332313
)
333-
#################

emf/model_merger/model_merger.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -404,19 +404,22 @@ def handle(self, task_object: dict, properties: dict, **kwargs):
404404
logger.debug(f"Post processing took: {(post_p_end - post_p_start).total_seconds()} seconds")
405405
logger.debug(f"Merging took: {(merge_end - merge_start).total_seconds()} seconds")
406406

407-
# Upload to OPDM
407+
# Upload to OPDM
408408
if model_upload_to_opdm:
409-
try:
410-
self.opdm_service = opdm.OPDM()
411-
for item in serialized_data:
412-
logger.info(f"Uploading to OPDM: {item.name}")
413-
time.sleep(2)
414-
async_call(function=self.opdm_service.publication_request,
415-
callback=log_opdm_response,
416-
file_path_or_file_object=item)
417-
merged_model.uploaded_to_opde = True
418-
except Exception as error:
419-
logging.error(f"Unexpected error on uploading to OPDM: {error}", exc_info=True)
409+
if merged_model.loadflow[0]['status'] == 'CONVERGED': # Only upload if the model LF is solved
410+
try:
411+
self.opdm_service = opdm.OPDM()
412+
for item in serialized_data:
413+
logger.info(f"Uploading to OPDM: {item.name}")
414+
time.sleep(4)
415+
async_call(function=self.opdm_service.publication_request,
416+
callback=log_opdm_response,
417+
file_path_or_file_object=item)
418+
merged_model.uploaded_to_opde = True
419+
except Exception as error:
420+
logging.error(f"Unexpected error on uploading to OPDM: {error}", exc_info=True)
421+
else:
422+
logger.info(f"Model not uploaded to OPDM due to convergance issue: {merged_model.loadflow[0]['status']}")
420423

421424
# Create zipped model data
422425
merged_model_object = BytesIO()

0 commit comments

Comments
 (0)