mms-client 1.10.0__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.
@@ -420,7 +420,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
420
420
 
421
421
  # First, create the MMS request from the payload and data.
422
422
  is_list = isinstance(payload, list)
423
- 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]
424
424
  logger.debug(
425
425
  (
426
426
  f"{config.name}: Starting multi-request. Envelope: {type(envelope).__name__}, "
@@ -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
@@ -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
+ )
@@ -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
 
@@ -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"))
@@ -4,7 +4,9 @@ from enum import Enum
4
4
  from typing import Optional
5
5
 
6
6
  from pydantic_extra_types.pendulum_dt import Date
7
+ from pydantic_xml import BaseXmlModel
7
8
  from pydantic_xml import attr
9
+ from pydantic_xml import element
8
10
 
9
11
  from mms_client.types.base import Envelope
10
12
  from mms_client.types.fields import participant
@@ -32,12 +34,19 @@ class BaseMarketRequest(Envelope):
32
34
  user: str = attr(name="UserName", min_length=1, max_length=12, pattern=r"^[A-Z0-9]*$")
33
35
 
34
36
 
37
+ class Defaults(BaseXmlModel):
38
+ """Represents the default settings to apply when submitting a market request."""
39
+
40
+ # Whether or not the submission represents the default
41
+ is_default: bool = attr(name="StandingFlag")
42
+
43
+
35
44
  class MarketQuery(BaseMarketRequest):
36
45
  """Represents the base fields for a market query."""
37
46
 
38
47
  # If the market type is specified as "DAM" (day-ahead market), the number of days should be specified as "1".
39
48
  # Otherwise, this field indicates the number of days ahead for which the data is being queried.
40
- days: int = attr(default=1, name="NumOfDays", ge=1, le=7)
49
+ days: Optional[int] = attr(default=None, name="NumOfDays", ge=1, le=7)
41
50
 
42
51
 
43
52
  class MarketSubmit(BaseMarketRequest):
@@ -48,7 +57,11 @@ class MarketSubmit(BaseMarketRequest):
48
57
 
49
58
  # If the market type is specified as "DAM" (day-ahead market), the number of days should be specified as "1".
50
59
  # Otherwise, this field indicates the number of days ahead for which the data is being submitted.
51
- days: int = attr(default=1, name="NumOfDays", ge=1, le=31)
60
+ days: Optional[int] = attr(default=None, name="NumOfDays", ge=1, le=31)
61
+
62
+ # Default values to include with the submission. The request will be rejected if this is included in a request
63
+ # where it is not allowed.
64
+ defaults: Optional[Defaults] = element(default=None, tag="StandingData")
52
65
 
53
66
 
54
67
  class MarketCancel(BaseMarketRequest):
mms_client/types/offer.py CHANGED
@@ -72,6 +72,12 @@ class OfferStack(Payload):
72
72
  # The unit price of the power, in JPY/kW/segment
73
73
  unit_price: Decimal = price("OfferUnitPrice", 10000.00)
74
74
 
75
+ # The unit price charged for the start up cost of the power, in JPY/kW/segment
76
+ start_up_unit_price: Annotated[Decimal, price("StartUpUnitPrice", 10000.00, True)]
77
+
78
+ # The unit price charged for the ramp down cost of the power, in JPY/kW/segment
79
+ ramp_down_unit_price: Annotated[Decimal, price("RampDownUnitPrice", 10000.00, True)]
80
+
75
81
  # The ID of the offer to which this stack belongs
76
82
  id: Optional[str] = offer_id("OfferId", True)
77
83
 
@@ -0,0 +1,26 @@
1
+ """Contains objects for OMI information."""
2
+
3
+ from pydantic_extra_types.pendulum_dt import Date
4
+ from pydantic_xml import attr
5
+
6
+ from mms_client.types.base import Envelope
7
+ from mms_client.types.fields import participant
8
+
9
+
10
+ class MarketSubmit(Envelope):
11
+ """Represents the base fields for a market registration request."""
12
+
13
+ # Date of the transaction in the format "YYYY-MM-DD"
14
+ date: Date = attr(name="Date")
15
+
16
+ # MMS code of the business entity to which the requesting user belongs, and will be used to track the user who made
17
+ # the request. This value will be checked against the certificate used to make the request.
18
+ participant: str = participant("ParticipantName")
19
+
20
+ # The user name of the person making the request. This value is used to track the user who made the request, and
21
+ # will be checked against the certificate used to make the request.
22
+ user: str = attr(name="UserName", min_length=1, max_length=12, pattern=r"^[A-Z0-9]*$")
23
+
24
+
25
+ class MarketQuery(MarketSubmit):
26
+ """Represents the base fields for a market query."""
@@ -0,0 +1,81 @@
1
+ """Contains objects for MMS settlement."""
2
+
3
+ from typing import Annotated
4
+ from typing import List
5
+ from typing import Optional
6
+
7
+ from pendulum import Timezone
8
+ from pydantic import field_serializer
9
+ from pydantic import field_validator
10
+ from pydantic_core import PydanticUndefined
11
+ from pydantic_extra_types.pendulum_dt import Date
12
+ from pydantic_extra_types.pendulum_dt import DateTime
13
+ from pydantic_xml import attr
14
+ from pydantic_xml import element
15
+
16
+ from mms_client.types.base import Payload
17
+ from mms_client.types.fields import company_short_name
18
+ from mms_client.types.fields import participant
19
+
20
+
21
+ def file_name(alias: str, optional: bool = False):
22
+ """Create a field for a file name.
23
+
24
+ Arguments:
25
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
26
+ to the JSON/XML key.
27
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
28
+ required, with no default.
29
+
30
+ Returns: A Pydantic Field object for the file name.
31
+ """
32
+ return attr(
33
+ default=None if optional else PydanticUndefined,
34
+ name=alias,
35
+ min_length=18,
36
+ max_length=60,
37
+ pattern=r"^([A-Z0-9]{4}_){2}[A-Z0-9_-]{4,46}\.(pdf|zip|csv|xml)$",
38
+ )
39
+
40
+
41
+ class SettlementFile(Payload):
42
+ """Represents a settlement file."""
43
+
44
+ # The name of the settlement file as it is recorded in the system
45
+ name: str = file_name("Name")
46
+
47
+ # The name of the participant (only valid if operating as a TSO)
48
+ participant: Optional[str] = participant("ParticipantName", True)
49
+
50
+ # The name of the company associated with the file (only valid if operating as a TSO)
51
+ company: Optional[str] = company_short_name("CompanyShortName", True)
52
+
53
+ # When the file was submitted (not sure why this can be None but it's in the spec)
54
+ submission_time: Optional[DateTime] = attr(name="SubmissionTime", default=None)
55
+
56
+ # The date when settlement occurred (not included if settlement is in the future)
57
+ settlement_date: Optional[Date] = attr(name="SttlDate", default=None)
58
+
59
+ # The size of the file in bytes, if it has been uploaded
60
+ size: Optional[int] = attr(name="FileSize", default=None, ge=0, lt=1000000000)
61
+
62
+ @field_serializer("submission_time")
63
+ def encode_datetime(self, value: DateTime) -> str:
64
+ """Encode the datetime to an MMS-compliant ISO 8601 string."""
65
+ return value.replace(tzinfo=None).isoformat() if value else ""
66
+
67
+ @field_validator("submission_time")
68
+ def decode_datetime(cls, value: DateTime) -> DateTime: # pylint: disable=no-self-argument
69
+ """Decode the datetime from an MMS-compliant ISO 8601 string."""
70
+ return value.replace(tzinfo=Timezone("Asia/Tokyo"))
71
+
72
+
73
+ class SettlementResults(Payload):
74
+ """Contains a list of settlement files that can be requested separately later."""
75
+
76
+ # The file results that were retrieved by the query
77
+ files: Annotated[List[SettlementFile], element(tag="File", min_length=1)]
78
+
79
+
80
+ class SettlementQuery(Payload, tag="SettlementResultsFileListQuery"):
81
+ """Represents a request to query settlement results file list."""