mms-client 1.9.3__py3-none-any.whl → 1.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +276 -276
- mms_client/schemas/wsdl/omi-web-service.wsdl +262 -262
- mms_client/schemas/xsd/mi-market.xsd +2405 -2395
- mms_client/schemas/xsd/mi-outbnd-reports.xsd +1554 -1488
- mms_client/schemas/xsd/mi-report.xsd +379 -379
- mms_client/schemas/xsd/mpr.xsd +1858 -1816
- mms_client/schemas/xsd/omi.xsd +913 -793
- mms_client/services/base.py +48 -21
- mms_client/services/market.py +123 -1
- mms_client/services/omi.py +99 -0
- mms_client/types/award.py +6 -0
- mms_client/types/base.py +10 -6
- mms_client/types/bup.py +207 -0
- mms_client/types/fields.py +1 -1
- mms_client/types/market.py +15 -2
- mms_client/types/offer.py +6 -0
- mms_client/types/omi.py +26 -0
- mms_client/types/settlement.py +81 -0
- mms_client/types/surplus_capcity.py +187 -0
- mms_client/utils/errors.py +51 -0
- mms_client/utils/serialization.py +34 -18
- {mms_client-1.9.3.dist-info → mms_client-1.11.0.dist-info}/METADATA +10 -3
- mms_client-1.11.0.dist-info/RECORD +45 -0
- mms_client-1.9.3.dist-info/RECORD +0 -41
- {mms_client-1.9.3.dist-info → mms_client-1.11.0.dist-info}/LICENSE +0 -0
- {mms_client-1.9.3.dist-info → mms_client-1.11.0.dist-info}/WHEEL +0 -0
mms_client/services/base.py
CHANGED
|
@@ -28,6 +28,7 @@ from mms_client.types.transport import RequestDataType
|
|
|
28
28
|
from mms_client.types.transport import RequestType
|
|
29
29
|
from mms_client.types.transport import ResponseDataType
|
|
30
30
|
from mms_client.utils.errors import AudienceError
|
|
31
|
+
from mms_client.utils.errors import EnvelopeNodeNotFoundError
|
|
31
32
|
from mms_client.utils.errors import MMSClientError
|
|
32
33
|
from mms_client.utils.errors import MMSServerError
|
|
33
34
|
from mms_client.utils.errors import MMSValidationError
|
|
@@ -131,7 +132,7 @@ class ClientProto(Protocol):
|
|
|
131
132
|
envelope: E,
|
|
132
133
|
data: Union[P, List[P]],
|
|
133
134
|
config: EndpointConfiguration,
|
|
134
|
-
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
135
|
+
) -> Tuple[MultiResponse[E, P], Dict[str, bytes], bool]:
|
|
135
136
|
"""Submit a request to the MMS server and return the multi-response.
|
|
136
137
|
|
|
137
138
|
Arguments:
|
|
@@ -139,7 +140,10 @@ class ClientProto(Protocol):
|
|
|
139
140
|
data (Payload): The data to submit to the MMS server.
|
|
140
141
|
config (EndpointConfiguration): The configuration for the endpoint.
|
|
141
142
|
|
|
142
|
-
Returns:
|
|
143
|
+
Returns:
|
|
144
|
+
MultiResponse[E, P]: The multi-response from the MMS server.
|
|
145
|
+
Dict[str, bytes]: The attachments returned with the response.
|
|
146
|
+
bool: Whether or not the response was found.
|
|
143
147
|
"""
|
|
144
148
|
|
|
145
149
|
|
|
@@ -244,15 +248,15 @@ def mms_multi_endpoint(**kwargs):
|
|
|
244
248
|
envelope, callback = result, None
|
|
245
249
|
|
|
246
250
|
# Now, submit the request to the MMS server and get the response
|
|
247
|
-
resp, attachments = self.request_many(envelope, args[0], config)
|
|
251
|
+
resp, attachments, found = self.request_many(envelope, args[0], config)
|
|
248
252
|
|
|
249
253
|
# Call the callback function if it was provided
|
|
250
254
|
if callback:
|
|
251
|
-
callback(resp, attachments) # pragma: no cover
|
|
255
|
+
callback(resp, attachments, found) # pragma: no cover
|
|
252
256
|
|
|
253
257
|
# Finally, extract the data from the response and return it
|
|
254
258
|
logger.info(f"{config.name}: Returning {len(resp.data)} item(s).")
|
|
255
|
-
return resp.data
|
|
259
|
+
return resp.data if found else []
|
|
256
260
|
|
|
257
261
|
return wrapper
|
|
258
262
|
|
|
@@ -382,7 +386,9 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
382
386
|
envelope_type = config.response_envelope_type or type(envelope)
|
|
383
387
|
data_type = config.response_data_type or type(payload)
|
|
384
388
|
deserializer = config.serializer or config.service.serializer
|
|
385
|
-
data: Response[E, P] = deserializer.deserialize(
|
|
389
|
+
data: Response[E, P] = deserializer.deserialize(
|
|
390
|
+
config.name, resp.payload, envelope_type, data_type, config.for_report
|
|
391
|
+
)
|
|
386
392
|
self._verify_response(data, config)
|
|
387
393
|
|
|
388
394
|
# Return the response data and any attachments
|
|
@@ -391,12 +397,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
391
397
|
)
|
|
392
398
|
return data, attachments
|
|
393
399
|
|
|
394
|
-
def request_many(
|
|
400
|
+
def request_many( # pylint: disable=too-many-locals
|
|
395
401
|
self,
|
|
396
402
|
envelope: E,
|
|
397
403
|
payload: Union[P, List[P]],
|
|
398
404
|
config: EndpointConfiguration[E, P],
|
|
399
|
-
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
405
|
+
) -> Tuple[MultiResponse[E, P], Dict[str, bytes], bool]:
|
|
400
406
|
"""Submit a request to the MMS server and return the multi-response.
|
|
401
407
|
|
|
402
408
|
Arguments:
|
|
@@ -404,14 +410,17 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
404
410
|
payload (Payload): The data to submit to the MMS server.
|
|
405
411
|
config (EndpointConfiguration): The configuration for the endpoint.
|
|
406
412
|
|
|
407
|
-
Returns:
|
|
413
|
+
Returns:
|
|
414
|
+
MultiResponse[E, P]: The multi-response from the MMS server.
|
|
415
|
+
Dict[str, bytes]: The attachments returned with the response.
|
|
416
|
+
bool: Whether or not the response was found.
|
|
408
417
|
"""
|
|
409
418
|
# Create a new ZWrapper for the given service
|
|
410
419
|
wrapper = self._get_wrapper(config.service)
|
|
411
420
|
|
|
412
421
|
# First, create the MMS request from the payload and data.
|
|
413
422
|
is_list = isinstance(payload, list)
|
|
414
|
-
data_type = type(payload[0]) if is_list else type(payload) # type: ignore[index]
|
|
423
|
+
data_type: type = type(payload[0]) if is_list else type(payload) # type: ignore[index]
|
|
415
424
|
logger.debug(
|
|
416
425
|
(
|
|
417
426
|
f"{config.name}: Starting multi-request. Envelope: {type(envelope).__name__}, "
|
|
@@ -434,23 +443,41 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
434
443
|
# Now, extract the attachments from the response
|
|
435
444
|
attachments = {a.name: b64decode(a.data) for a in resp.attachments}
|
|
436
445
|
|
|
437
|
-
# Finally, deserialize and verify
|
|
438
|
-
envelope_type = config.response_envelope_type or type(envelope)
|
|
439
|
-
data_type = config.response_data_type or data_type
|
|
446
|
+
# Finally, deserialize the response and verify it
|
|
440
447
|
deserializer = config.serializer or config.service.serializer
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
envelope_type
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
448
|
+
try:
|
|
449
|
+
found = True
|
|
450
|
+
envelope_type = config.response_envelope_type or type(envelope)
|
|
451
|
+
resp_data_type = config.response_data_type or data_type
|
|
452
|
+
data: MultiResponse[E, P] = deserializer.deserialize_multi(
|
|
453
|
+
config.name,
|
|
454
|
+
resp.payload,
|
|
455
|
+
envelope_type,
|
|
456
|
+
resp_data_type, # type: ignore[arg-type]
|
|
457
|
+
config.for_report,
|
|
458
|
+
)
|
|
459
|
+
except EnvelopeNodeNotFoundError:
|
|
460
|
+
logger.warning(
|
|
461
|
+
f"{config.name}: Failed to deserialize multi-response. Attempting to deserialize as the request object."
|
|
462
|
+
)
|
|
463
|
+
found = False
|
|
464
|
+
envelope_type = type(envelope)
|
|
465
|
+
resp_data_type = data_type
|
|
466
|
+
data = deserializer.deserialize_multi(
|
|
467
|
+
config.name,
|
|
468
|
+
resp.payload,
|
|
469
|
+
envelope_type,
|
|
470
|
+
data_type, # type: ignore[arg-type]
|
|
471
|
+
config.for_report,
|
|
472
|
+
)
|
|
447
473
|
self._verify_multi_response(data, config)
|
|
448
474
|
|
|
449
475
|
# Return the response data and any attachments
|
|
450
476
|
logger.debug(
|
|
451
|
-
f"{config.name}: Returning multi-response. Envelope: {envelope_type.__name__}, Data:
|
|
477
|
+
f"{config.name}: Returning multi-response. Envelope: {envelope_type.__name__}, Data: "
|
|
478
|
+
f"{resp_data_type.__name__}"
|
|
452
479
|
)
|
|
453
|
-
return data, attachments
|
|
480
|
+
return data, attachments, found
|
|
454
481
|
|
|
455
482
|
def _to_mms_request(
|
|
456
483
|
self,
|
mms_client/services/market.py
CHANGED
|
@@ -12,6 +12,9 @@ from mms_client.services.base import mms_endpoint
|
|
|
12
12
|
from mms_client.services.base import mms_multi_endpoint
|
|
13
13
|
from mms_client.types.award import AwardQuery
|
|
14
14
|
from mms_client.types.award import AwardResponse
|
|
15
|
+
from mms_client.types.bup import BalancingUnitPriceQuery
|
|
16
|
+
from mms_client.types.bup import BalancingUnitPriceSubmit
|
|
17
|
+
from mms_client.types.market import Defaults
|
|
15
18
|
from mms_client.types.market import MarketCancel
|
|
16
19
|
from mms_client.types.market import MarketQuery
|
|
17
20
|
from mms_client.types.market import MarketSubmit
|
|
@@ -21,6 +24,8 @@ from mms_client.types.offer import OfferData
|
|
|
21
24
|
from mms_client.types.offer import OfferQuery
|
|
22
25
|
from mms_client.types.reserve import ReserveRequirement
|
|
23
26
|
from mms_client.types.reserve import ReserveRequirementQuery
|
|
27
|
+
from mms_client.types.settlement import SettlementQuery
|
|
28
|
+
from mms_client.types.settlement import SettlementResults
|
|
24
29
|
from mms_client.types.transport import RequestType
|
|
25
30
|
from mms_client.utils.serialization import SchemaType
|
|
26
31
|
from mms_client.utils.serialization import Serializer
|
|
@@ -40,7 +45,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
40
45
|
@mms_multi_endpoint(
|
|
41
46
|
name="MarketQuery_ReserveRequirementQuery",
|
|
42
47
|
service=config,
|
|
43
|
-
request_type=RequestType.
|
|
48
|
+
request_type=RequestType.MARKET,
|
|
44
49
|
response_envelope_type=MarketSubmit,
|
|
45
50
|
response_data_type=ReserveRequirement,
|
|
46
51
|
)
|
|
@@ -213,3 +218,120 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
213
218
|
user=self.user,
|
|
214
219
|
days=days,
|
|
215
220
|
)
|
|
221
|
+
|
|
222
|
+
@mms_endpoint(
|
|
223
|
+
name="MarketQuery_SettlementResultsFileListQuery",
|
|
224
|
+
service=config,
|
|
225
|
+
request_type=RequestType.MARKET,
|
|
226
|
+
response_envelope_type=MarketSubmit,
|
|
227
|
+
response_data_type=SettlementResults,
|
|
228
|
+
allowed_clients=[ClientType.BSP, ClientType.TSO],
|
|
229
|
+
)
|
|
230
|
+
def get_settlement_results(
|
|
231
|
+
self: ClientProto, request: SettlementQuery, days: int, date: Optional[Date] = None
|
|
232
|
+
) -> SettlementResults:
|
|
233
|
+
"""Query the MMS server for settlement results.
|
|
234
|
+
|
|
235
|
+
This endpoint is only accessible to BSPs and TSOs.
|
|
236
|
+
|
|
237
|
+
Arguments:
|
|
238
|
+
request (SettlementQuery): The query to submit to the MMS server.
|
|
239
|
+
days (int): The number of days ahead for which the data is being queried.
|
|
240
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
|
|
241
|
+
current date.
|
|
242
|
+
|
|
243
|
+
Returns: The settlement results that match the query.
|
|
244
|
+
"""
|
|
245
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
246
|
+
return MarketQuery( # type: ignore[return-value]
|
|
247
|
+
date=date or Date.today(),
|
|
248
|
+
participant=self.participant,
|
|
249
|
+
user=self.user,
|
|
250
|
+
days=days,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@mms_multi_endpoint(
|
|
254
|
+
name="MarketSubmit_BupSubmit", service=config, request_type=RequestType.MARKET, allowed_clients=[ClientType.BSP]
|
|
255
|
+
)
|
|
256
|
+
def put_bups(
|
|
257
|
+
self: ClientProto,
|
|
258
|
+
requests: List[BalancingUnitPriceSubmit],
|
|
259
|
+
date: Optional[Date] = None,
|
|
260
|
+
default: bool = False,
|
|
261
|
+
) -> List[BalancingUnitPriceSubmit]:
|
|
262
|
+
"""Submit multiple balancing unit prices to the MMS server.
|
|
263
|
+
|
|
264
|
+
This endpoint is only accessible to BSPs.
|
|
265
|
+
|
|
266
|
+
Arguments:
|
|
267
|
+
requests (List[BalancingUnitPriceSubmit]): The balancing unit prices to submit to the MMS server.
|
|
268
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value
|
|
269
|
+
defaults to the current date.
|
|
270
|
+
default (bool): Whether or not the balancing unit prices are the default.
|
|
271
|
+
|
|
272
|
+
Returns: A list of balancing unit prices that have been registered with the MMS server.
|
|
273
|
+
"""
|
|
274
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
275
|
+
return MarketSubmit( # type: ignore[return-value]
|
|
276
|
+
date=date or Date.today(),
|
|
277
|
+
participant=self.participant,
|
|
278
|
+
user=self.user,
|
|
279
|
+
defaults=Defaults(is_default=default),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@mms_endpoint(
|
|
283
|
+
name="MarketSubmit_BupSubmit", service=config, request_type=RequestType.MARKET, allowed_clients=[ClientType.BSP]
|
|
284
|
+
)
|
|
285
|
+
def put_bup(
|
|
286
|
+
self: ClientProto,
|
|
287
|
+
request: BalancingUnitPriceSubmit,
|
|
288
|
+
date: Optional[Date] = None,
|
|
289
|
+
default: bool = False,
|
|
290
|
+
) -> BalancingUnitPriceSubmit:
|
|
291
|
+
"""Submit a balancing unit price to the MMS server.
|
|
292
|
+
|
|
293
|
+
This endpoint is only accessible to BSPs.
|
|
294
|
+
|
|
295
|
+
Arguments:
|
|
296
|
+
request (BalancingUnitPriceSubmit): The balancing unit price to submit to the MMS server.
|
|
297
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults
|
|
298
|
+
to the current date.
|
|
299
|
+
default (bool): Whether or not the balancing unit price is the default.
|
|
300
|
+
|
|
301
|
+
Returns: The balancing unit price that has been registered with the MMS server.
|
|
302
|
+
"""
|
|
303
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
304
|
+
return MarketSubmit( # type: ignore[return-value]
|
|
305
|
+
date=date or Date.today(),
|
|
306
|
+
participant=self.participant,
|
|
307
|
+
user=self.user,
|
|
308
|
+
defaults=Defaults(is_default=default),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
@mms_multi_endpoint(
|
|
312
|
+
name="MarketQuery_BupQuery",
|
|
313
|
+
service=config,
|
|
314
|
+
request_type=RequestType.MARKET,
|
|
315
|
+
response_envelope_type=MarketSubmit,
|
|
316
|
+
response_data_type=BalancingUnitPriceSubmit,
|
|
317
|
+
)
|
|
318
|
+
def query_bups(
|
|
319
|
+
self: ClientProto, request: BalancingUnitPriceQuery, date: Optional[Date] = None
|
|
320
|
+
) -> List[BalancingUnitPriceSubmit]:
|
|
321
|
+
"""Query the MMS server for balancing unit prices.
|
|
322
|
+
|
|
323
|
+
This endpoint is accessible to all client types.
|
|
324
|
+
|
|
325
|
+
Arguments:
|
|
326
|
+
request (BalancingUnitPriceSubmit): The query to submit to the MMS server.
|
|
327
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults
|
|
328
|
+
to the current date.
|
|
329
|
+
|
|
330
|
+
Returns: A list of balancing unit prices that match the query.
|
|
331
|
+
"""
|
|
332
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
333
|
+
return MarketQuery( # type: ignore[return-value]
|
|
334
|
+
date=date or Date.today(),
|
|
335
|
+
participant=self.participant,
|
|
336
|
+
user=self.user,
|
|
337
|
+
)
|
mms_client/services/omi.py
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
"""Contains the client layer for making OMI requests to the MMS server."""
|
|
2
2
|
|
|
3
3
|
from logging import getLogger
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Optional
|
|
4
6
|
|
|
7
|
+
from pydantic_extra_types.pendulum_dt import Date
|
|
8
|
+
|
|
9
|
+
from mms_client.services.base import ClientProto
|
|
5
10
|
from mms_client.services.base import ServiceConfiguration
|
|
11
|
+
from mms_client.services.base import mms_endpoint
|
|
12
|
+
from mms_client.services.base import mms_multi_endpoint
|
|
13
|
+
from mms_client.types.omi import MarketQuery
|
|
14
|
+
from mms_client.types.omi import MarketSubmit
|
|
15
|
+
from mms_client.types.surplus_capcity import SurplusCapacityData
|
|
16
|
+
from mms_client.types.surplus_capcity import SurplusCapacityQuery
|
|
17
|
+
from mms_client.types.surplus_capcity import SurplusCapacitySubmit
|
|
18
|
+
from mms_client.types.transport import RequestType
|
|
6
19
|
from mms_client.utils.serialization import SchemaType
|
|
7
20
|
from mms_client.utils.serialization import Serializer
|
|
21
|
+
from mms_client.utils.web import ClientType
|
|
8
22
|
from mms_client.utils.web import Interface
|
|
9
23
|
|
|
10
24
|
# Set the default logger for the MMS client
|
|
@@ -16,3 +30,88 @@ class OMIClientMixin: # pylint: disable=unused-argument
|
|
|
16
30
|
|
|
17
31
|
# The configuration for the OMI service
|
|
18
32
|
config = ServiceConfiguration(Interface.OMI, Serializer(SchemaType.OMI, "MarketData"))
|
|
33
|
+
|
|
34
|
+
@mms_endpoint(
|
|
35
|
+
name="MarketSubmit_RemainingReserveData",
|
|
36
|
+
service=config,
|
|
37
|
+
request_type=RequestType.OMI,
|
|
38
|
+
response_data_type=SurplusCapacityData,
|
|
39
|
+
allowed_clients=[ClientType.BSP],
|
|
40
|
+
)
|
|
41
|
+
def put_surplus_capacity(
|
|
42
|
+
self: ClientProto, request: SurplusCapacitySubmit, date: Optional[Date] = None
|
|
43
|
+
) -> SurplusCapacityData:
|
|
44
|
+
"""Submit an offer to the MMS server.
|
|
45
|
+
|
|
46
|
+
This endpoint is only accessible to BSPs.
|
|
47
|
+
|
|
48
|
+
Arguments:
|
|
49
|
+
request (SurplusCapacitySubmit): The surplus capacity data to submit to the MMS server.
|
|
50
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults
|
|
51
|
+
to the current date.
|
|
52
|
+
|
|
53
|
+
Returns: The surplus capacity data that has been registered with the MMS server.
|
|
54
|
+
"""
|
|
55
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
56
|
+
return MarketSubmit( # type: ignore[return-value]
|
|
57
|
+
date=date or Date.today(),
|
|
58
|
+
participant=self.participant,
|
|
59
|
+
user=self.user,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@mms_multi_endpoint(
|
|
63
|
+
name="MarketSubmit_RemainingReserveData",
|
|
64
|
+
service=config,
|
|
65
|
+
request_type=RequestType.OMI,
|
|
66
|
+
response_data_type=SurplusCapacityData,
|
|
67
|
+
allowed_clients=[ClientType.BSP],
|
|
68
|
+
)
|
|
69
|
+
def put_surplus_capacities(
|
|
70
|
+
self: ClientProto, requests: List[SurplusCapacitySubmit], date: Optional[Date] = None
|
|
71
|
+
) -> List[SurplusCapacityData]:
|
|
72
|
+
"""Submit multiple surplus capacity data to the MMS server.
|
|
73
|
+
|
|
74
|
+
This endpoint is only accessible to BSPs.
|
|
75
|
+
|
|
76
|
+
Arguments:
|
|
77
|
+
requests (list[SurplusCapacitySubmit]): The surplus capacity data to submit to the MMS server.
|
|
78
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value
|
|
79
|
+
defaults to the current date.
|
|
80
|
+
|
|
81
|
+
Returns: A list of surplus capacity data that have been registered with the MMS server.
|
|
82
|
+
"""
|
|
83
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
84
|
+
return MarketSubmit( # type: ignore[return-value]
|
|
85
|
+
date=date or Date.today(),
|
|
86
|
+
participant=self.participant,
|
|
87
|
+
user=self.user,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@mms_multi_endpoint(
|
|
91
|
+
name="MarketQuery_RemainingReserveDataQuery",
|
|
92
|
+
service=config,
|
|
93
|
+
request_type=RequestType.OMI,
|
|
94
|
+
response_envelope_type=MarketSubmit,
|
|
95
|
+
response_data_type=SurplusCapacityData,
|
|
96
|
+
allowed_clients=[ClientType.BSP, ClientType.TSO],
|
|
97
|
+
)
|
|
98
|
+
def query_surplus_capacity(
|
|
99
|
+
self: ClientProto, request: SurplusCapacityQuery, date: Optional[Date] = None
|
|
100
|
+
) -> List[SurplusCapacityData]:
|
|
101
|
+
"""Query the MMS server for surplus capacity data.
|
|
102
|
+
|
|
103
|
+
This endpoint is only accessible to BSPs and TSOs.
|
|
104
|
+
|
|
105
|
+
Arguments:
|
|
106
|
+
request (SurplusCapacityQuery): The query to submit to the MMS server.
|
|
107
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to
|
|
108
|
+
the current date.
|
|
109
|
+
|
|
110
|
+
Returns: A list of surplus capacity data that match the query.
|
|
111
|
+
"""
|
|
112
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
113
|
+
return MarketQuery( # type: ignore[return-value]
|
|
114
|
+
date=date or Date.today(),
|
|
115
|
+
participant=self.participant,
|
|
116
|
+
user=self.user,
|
|
117
|
+
)
|
mms_client/types/award.py
CHANGED
|
@@ -192,6 +192,12 @@ class Award(Payload):
|
|
|
192
192
|
# The contract price, in JPY/kW/segment
|
|
193
193
|
contract_price: Decimal = price("ContractPrice", 10000.00)
|
|
194
194
|
|
|
195
|
+
# The unit price charged for the start up cost of the power, in JPY/kW/segment
|
|
196
|
+
start_up_unit_price: Decimal = price("StartUpUnitPrice", 10000.00, True)
|
|
197
|
+
|
|
198
|
+
# The unit price charged for the ramp down cost of the power, in JPY/kW/segment
|
|
199
|
+
ramp_down_unit_price: Decimal = price("RampDownUnitPrice", 10000.00, True)
|
|
200
|
+
|
|
195
201
|
# The performance evaluation coefficient, alpha
|
|
196
202
|
performance_evaluation_coefficient: Decimal = attr(name="PerfEvalCoeff", ge=0.00, le=100.0, decimal_places=2)
|
|
197
203
|
|
mms_client/types/base.py
CHANGED
|
@@ -35,6 +35,9 @@ class Message(BaseXmlModel):
|
|
|
35
35
|
# The message text. Not sure why this is called code in the XML.
|
|
36
36
|
code: str = attr(default="", name="Code", min_length=2, max_length=50, pattern=r"^[a-zA-Z_0-9\-]*$")
|
|
37
37
|
|
|
38
|
+
# A human-readable description of the message
|
|
39
|
+
description: str
|
|
40
|
+
|
|
38
41
|
|
|
39
42
|
class Messages(BaseXmlModel, search_mode="unordered", arbitrary_types_allowed=True):
|
|
40
43
|
"""Represents a collection of messages returned with a payload."""
|
|
@@ -49,21 +52,21 @@ class Messages(BaseXmlModel, search_mode="unordered", arbitrary_types_allowed=Tr
|
|
|
49
52
|
errors_raw: List[Element] = element(default=[], tag="Error", nillable=True, exclude=True)
|
|
50
53
|
|
|
51
54
|
@computed_element
|
|
52
|
-
def information(self) -> List[
|
|
55
|
+
def information(self) -> List[Message]:
|
|
53
56
|
"""Return the information messages."""
|
|
54
57
|
return self._parse_messages(self.information_raw)
|
|
55
58
|
|
|
56
59
|
@computed_element
|
|
57
|
-
def warnings(self) -> List[
|
|
60
|
+
def warnings(self) -> List[Message]:
|
|
58
61
|
"""Return the warning messages."""
|
|
59
62
|
return self._parse_messages(self.warnings_raw)
|
|
60
63
|
|
|
61
64
|
@computed_element
|
|
62
|
-
def errors(self) -> List[
|
|
65
|
+
def errors(self) -> List[Message]:
|
|
63
66
|
"""Return the error messages."""
|
|
64
67
|
return self._parse_messages(self.errors_raw)
|
|
65
68
|
|
|
66
|
-
def _parse_messages(self, raw: List[Element]) -> List[
|
|
69
|
+
def _parse_messages(self, raw: List[Element]) -> List[Message]:
|
|
67
70
|
"""Parse the messages from the XML tree.
|
|
68
71
|
|
|
69
72
|
Arguments:
|
|
@@ -73,10 +76,11 @@ class Messages(BaseXmlModel, search_mode="unordered", arbitrary_types_allowed=Tr
|
|
|
73
76
|
"""
|
|
74
77
|
messages = []
|
|
75
78
|
for item in raw:
|
|
79
|
+
text = item.text or ""
|
|
76
80
|
if message := item.attrib.get("Code"):
|
|
77
|
-
messages.append(message)
|
|
81
|
+
messages.append(Message(description=text, code=message))
|
|
78
82
|
else:
|
|
79
|
-
messages.append(
|
|
83
|
+
messages.append(Message(description=text))
|
|
80
84
|
return messages
|
|
81
85
|
|
|
82
86
|
|
mms_client/types/bup.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Contains objects for BUPs."""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from typing import List
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from pendulum import Timezone
|
|
10
|
+
from pydantic import field_serializer
|
|
11
|
+
from pydantic import field_validator
|
|
12
|
+
from pydantic_core import PydanticUndefined
|
|
13
|
+
from pydantic_extra_types.pendulum_dt import DateTime
|
|
14
|
+
from pydantic_xml import attr
|
|
15
|
+
from pydantic_xml import element
|
|
16
|
+
from pydantic_xml import wrapped
|
|
17
|
+
|
|
18
|
+
from mms_client.types.base import Payload
|
|
19
|
+
from mms_client.types.enums import AreaCode
|
|
20
|
+
from mms_client.types.fields import capacity
|
|
21
|
+
from mms_client.types.fields import company_short_name
|
|
22
|
+
from mms_client.types.fields import participant
|
|
23
|
+
from mms_client.types.fields import power_positive
|
|
24
|
+
from mms_client.types.fields import price
|
|
25
|
+
from mms_client.types.fields import resource_name
|
|
26
|
+
from mms_client.types.fields import resource_short_name
|
|
27
|
+
from mms_client.types.fields import system_code
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def abc_price(alias: str, optional: bool = False):
|
|
31
|
+
"""Create a field for an abc price.
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
35
|
+
to the JSON/XML key.
|
|
36
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
37
|
+
required, with no default.
|
|
38
|
+
|
|
39
|
+
Returns: A Pydantic Field object for the abc price.
|
|
40
|
+
"""
|
|
41
|
+
return attr(
|
|
42
|
+
default=None if optional else PydanticUndefined, name=alias, gt=-100000.0, lt=100000.0, decimal_places=1
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Status(Enum):
|
|
47
|
+
"""Enum representing the possible statuses of a pattern."""
|
|
48
|
+
|
|
49
|
+
INACTIVE = "0"
|
|
50
|
+
ACTIVE = "1"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StartupCostBand(Payload, tag="BandStartup"):
|
|
54
|
+
"""Represents a band of a startup cost."""
|
|
55
|
+
|
|
56
|
+
# The band number which must be unique within the startup cost and will identify this band in sequence.
|
|
57
|
+
number: int = attr(name="CaseNo", ge=1, le=10)
|
|
58
|
+
|
|
59
|
+
# The time at which the startup cost is applied. This value must be greater than or equal to 0.
|
|
60
|
+
stop_time_hours: int = attr(name="StopTime", ge=0, lt=1000)
|
|
61
|
+
|
|
62
|
+
# The V3 unit price charged for this band.
|
|
63
|
+
v3_unit_price: int = attr(name="V3", ge=0, lt=100000000)
|
|
64
|
+
|
|
65
|
+
# The remarks associated with this band.
|
|
66
|
+
remarks: Optional[str] = attr(default=None, name="Remark", min_length=1, max_length=30)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AbcBand(Payload, tag="BandAbc"):
|
|
70
|
+
"""Represents a band of an ABC, whatever that is."""
|
|
71
|
+
|
|
72
|
+
# The band number which must be unique within the ABC and will identify this band in sequence.
|
|
73
|
+
number: int = attr(name="Band", ge=1, le=5)
|
|
74
|
+
|
|
75
|
+
# The capacity from which the band is allowed to operate.
|
|
76
|
+
from_capacity: int = power_positive("FromCap")
|
|
77
|
+
|
|
78
|
+
# The a term of the band.
|
|
79
|
+
a: Decimal = abc_price("a")
|
|
80
|
+
|
|
81
|
+
# The b term of the band.
|
|
82
|
+
b: Decimal = abc_price("b")
|
|
83
|
+
|
|
84
|
+
# The c term of the band.
|
|
85
|
+
c: Decimal = abc_price("c")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BalancingUnitPriceBand(Payload):
|
|
89
|
+
"""Represents a band of a balancing unit price."""
|
|
90
|
+
|
|
91
|
+
# The band number which must be unique within the balancing unit price and will identify this band in sequence.
|
|
92
|
+
number: int = attr(name="Band", ge=1, le=20)
|
|
93
|
+
|
|
94
|
+
# The capacity from which the band is allowed to operate. If the resource_type on the associated resource is set to
|
|
95
|
+
# THERMAL or HYDRO then the value of this field must be greater than or equal to 0. Otherwise, the value of this
|
|
96
|
+
# field is unrestricted.
|
|
97
|
+
from_capacity: int = capacity("FromCap", -10000000)
|
|
98
|
+
|
|
99
|
+
# The V1 unit price charged for this band.
|
|
100
|
+
v1_unit_price: Decimal = price("V1", 10000.00)
|
|
101
|
+
|
|
102
|
+
# The V2 unit price charged for this band. This value is only valid when the contract_type on the associated
|
|
103
|
+
# resource is set to anything other than ONLY_POWER_SUPPLY_1.
|
|
104
|
+
v2_unit_price: Annotated[Decimal, price("V2", 10000.00, True)]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class BalancingUnitPrice(Payload):
|
|
108
|
+
"""Represents a balancing unit profile."""
|
|
109
|
+
|
|
110
|
+
# The V4 unit price charged for this pattern. This value is only valid when the contract_type on the associated
|
|
111
|
+
# resource is set to anything other than ONLY_POWER_SUPPLY_1.
|
|
112
|
+
v4_unit_price: Annotated[Decimal, price("V4", 10000.00, True)]
|
|
113
|
+
|
|
114
|
+
# The bands associated with this BUP.
|
|
115
|
+
bands: Annotated[List[BalancingUnitPriceBand], element(tag="BandBup", min_length=1, max_length=20)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Pattern(Payload):
|
|
119
|
+
"""Represents a pattern associated with a BUP."""
|
|
120
|
+
|
|
121
|
+
# A number identifying this pattern in the overall sequence of patterns
|
|
122
|
+
number: int = attr(name="PatternNo", ge=1, le=10)
|
|
123
|
+
|
|
124
|
+
# The status of the pattern
|
|
125
|
+
status: Status = attr(name="PatternStatus")
|
|
126
|
+
|
|
127
|
+
# Any comments associated with the pattern
|
|
128
|
+
remarks: Optional[str] = attr(default=None, name="PatternRemark", min_length=1, max_length=50)
|
|
129
|
+
|
|
130
|
+
# The balancing unit profile associated with this pattern
|
|
131
|
+
balancing_unit_profile: Optional[BalancingUnitPrice] = element(default=None, tag="Bup")
|
|
132
|
+
|
|
133
|
+
# The quadratic pricing bands associated with this pattern
|
|
134
|
+
abc: Annotated[Optional[List[AbcBand]], wrapped(default=None, path="Abc", min_length=1, max_length=5)]
|
|
135
|
+
|
|
136
|
+
# The startup cost bands associated with this pattern
|
|
137
|
+
startup_costs: Annotated[
|
|
138
|
+
Optional[List[StartupCostBand]], wrapped(default=None, path="StartupCost", min_length=1, max_length=10)
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BalancingUnitPriceSubmit(Payload, tag="BupSubmit"):
|
|
143
|
+
"""Represents the data included with a BUP."""
|
|
144
|
+
|
|
145
|
+
# The resource with which the BUP is associated
|
|
146
|
+
resource_code: str = resource_name("ResourceName")
|
|
147
|
+
|
|
148
|
+
# The start date and time for the validity period of the BUP
|
|
149
|
+
start: DateTime = attr(name="StartTime")
|
|
150
|
+
|
|
151
|
+
# The end date and time for the validity period of the BUP
|
|
152
|
+
end: DateTime = attr(name="EndTime")
|
|
153
|
+
|
|
154
|
+
# The patterns associated with this BUP
|
|
155
|
+
patterns: Annotated[List[Pattern], element(tag="PatternData", max_length=10)]
|
|
156
|
+
|
|
157
|
+
# The name of the BSP participant submitting the BUP. This will only be populated when the object is returned.
|
|
158
|
+
participant_name: Optional[str] = participant("BspParticipantName", True)
|
|
159
|
+
|
|
160
|
+
# The name of the company submitting the BUP. This will only be populated when the object is returned.
|
|
161
|
+
company: Optional[str] = company_short_name("CompanyShortName", True)
|
|
162
|
+
|
|
163
|
+
# The area associated with the BUP. This will only be populated when the object is returned.
|
|
164
|
+
area: Optional[AreaCode] = attr(default=None, name="Area")
|
|
165
|
+
|
|
166
|
+
# The name of the resource being traded. This will only be populated when the object is returned.
|
|
167
|
+
resource_name: Optional[str] = resource_short_name("ResourceShortName", True)
|
|
168
|
+
|
|
169
|
+
# The MMS code of the business entity to which the registration applies. This will only be populated when the
|
|
170
|
+
# object is returned.
|
|
171
|
+
system_code: Optional[str] = system_code("SystemCode", True)
|
|
172
|
+
|
|
173
|
+
@field_serializer("start", "end")
|
|
174
|
+
def encode_datetime(self, value: DateTime) -> str:
|
|
175
|
+
"""Encode the datetime to an MMS-compliant ISO 8601 string."""
|
|
176
|
+
return value.replace(tzinfo=None).isoformat() if value else ""
|
|
177
|
+
|
|
178
|
+
@field_validator("start", "end")
|
|
179
|
+
def decode_datetime(cls, value: DateTime) -> DateTime: # pylint: disable=no-self-argument
|
|
180
|
+
"""Decode the datetime from an MMS-compliant ISO 8601 string."""
|
|
181
|
+
return value.replace(tzinfo=Timezone("Asia/Tokyo"))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class BalancingUnitPriceQuery(Payload, tag="BupQuery"):
|
|
185
|
+
"""Represents the data included with a BUP query."""
|
|
186
|
+
|
|
187
|
+
# Whether or not the BUP is the default
|
|
188
|
+
is_default: Optional[bool] = attr(default=None, name="StandingFlag")
|
|
189
|
+
|
|
190
|
+
# The resource with which the BUP is associated
|
|
191
|
+
resource_code: str = resource_name("ResourceName")
|
|
192
|
+
|
|
193
|
+
# The start date and time for the validity period of the BUP
|
|
194
|
+
start: Annotated[DateTime, attr(default=None, name="StartTime")]
|
|
195
|
+
|
|
196
|
+
# The end date and time for the validity period of the BUP
|
|
197
|
+
end: Annotated[DateTime, attr(default=None, name="EndTime")]
|
|
198
|
+
|
|
199
|
+
@field_serializer("start", "end")
|
|
200
|
+
def encode_datetime(self, value: DateTime) -> str:
|
|
201
|
+
"""Encode the datetime to an MMS-compliant ISO 8601 string."""
|
|
202
|
+
return value.replace(tzinfo=None).isoformat() if value else ""
|
|
203
|
+
|
|
204
|
+
@field_validator("start", "end")
|
|
205
|
+
def decode_datetime(cls, value: DateTime) -> DateTime: # pylint: disable=no-self-argument
|
|
206
|
+
"""Decode the datetime from an MMS-compliant ISO 8601 string."""
|
|
207
|
+
return value.replace(tzinfo=Timezone("Asia/Tokyo"))
|