mms-client 1.0.6__tar.gz → 1.1.0__tar.gz

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.
Files changed (37) hide show
  1. {mms_client-1.0.6 → mms_client-1.1.0}/PKG-INFO +18 -2
  2. {mms_client-1.0.6 → mms_client-1.1.0}/README.md +6 -1
  3. {mms_client-1.0.6 → mms_client-1.1.0}/pyproject.toml +18 -1
  4. mms_client-1.1.0/src/mms_client/services/registration.py +77 -0
  5. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/base.py +3 -1
  6. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/enums.py +8 -0
  7. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/fields.py +150 -2
  8. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/offer.py +16 -4
  9. mms_client-1.1.0/src/mms_client/types/registration.py +47 -0
  10. mms_client-1.1.0/src/mms_client/types/resource.py +1064 -0
  11. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/transport.py +1 -1
  12. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/utils/serialization.py +18 -8
  13. mms_client-1.0.6/src/mms_client/services/registration.py +0 -13
  14. {mms_client-1.0.6 → mms_client-1.1.0}/LICENSE +0 -0
  15. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/__init__.py +0 -0
  16. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/client.py +0 -0
  17. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/py.typed +0 -0
  18. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +0 -0
  19. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/wsdl/omi-web-service.wsdl +0 -0
  20. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/xsd/mi-market.xsd +0 -0
  21. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/xsd/mi-outbnd-reports.xsd +0 -0
  22. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/xsd/mi-report.xsd +0 -0
  23. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/xsd/mpr.xsd +0 -0
  24. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/schemas/xsd/omi.xsd +0 -0
  25. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/security/__init__.py +0 -0
  26. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/security/certs.py +0 -0
  27. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/security/crypto.py +0 -0
  28. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/services/__init__.py +0 -0
  29. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/services/base.py +0 -0
  30. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/services/market.py +0 -0
  31. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/services/omi.py +0 -0
  32. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/services/report.py +0 -0
  33. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/__init__.py +0 -0
  34. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/types/market.py +0 -0
  35. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/utils/__init__.py +0 -0
  36. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/utils/errors.py +0 -0
  37. {mms_client-1.0.6 → mms_client-1.1.0}/src/mms_client/utils/web.py +0 -0
@@ -1,12 +1,23 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.0.6
3
+ Version: 1.1.0
4
4
  Summary: API client for accessing the MMS
5
+ Home-page: https://github.com/ElectroRoute-Japan/mms-client
5
6
  Author: Ryan Wood
6
7
  Author-email: ryan.wood@electroroute.co.jp
7
8
  Requires-Python: >=3.11,<4.0
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Framework :: Pydantic :: 2
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
8
16
  Classifier: Programming Language :: Python :: 3
9
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
10
21
  Requires-Dist: backoff (>=2.2.1,<3.0.0)
11
22
  Requires-Dist: cryptography (>=42.0.5,<43.0.0)
12
23
  Requires-Dist: lxml (>=5.1.0,<6.0.0)
@@ -32,6 +43,9 @@ The underlying API sends and receives XML documents. Each of these request or re
32
43
 
33
44
  After the data has been converted and added to the outer request object, it is sent to the appropriate server endpoint via a Zeep client. The client certificate is also injected into the request using a PCKS12 adaptor.
34
45
 
46
+ # Serialization
47
+ This library relies on Pydantic 2 and the pydantic-xml library for serialization/deserialization. As such, any type in this library can be converted to not only XML, but to JSON as well. This is extremely useful if you're trying to build a pass-through API service or something similar.
48
+
35
49
  ## Client Types
36
50
  Clients cannot call any and all endpoints in this API, willy-nilly. Some endpoints are restricted to particular clients. At the moment, there are two clients: Balance Service Providers (BSPs) and Transmission Service Operators (TSOs). Most likely you're operating as a BSP, in which case you'll have access to all endpoints. However, it makes little sense for a TSO to be able to submit bids on their own power, so they are restricted to a read-only role in most cases.
37
51
 
@@ -43,7 +57,7 @@ The MMS has two separate endpoints, depending on whether you want to access mark
43
57
  # Object Hierarchy
44
58
  The object hierarchy contained in this project is not trivial, and perhaps that reflects a bit of overengineering on our part. However, we chose the paradigm we did to reduce the number of types which had to be exposed to the user. The diagram below indicates how this hierarchy works:
45
59
 
46
- ![MMS Client Object Hierarchy](https://github.com/ElectroRoute-Japan/mms-client/blob/main/docs/mmsclient_hierarchy.drawio.png)
60
+ ![MMS Client Object Hierarchy](https://github.com/ElectroRoute-Japan/mms-client/raw/main/docs/mmsclient_hierarchy.drawio.png)
47
61
 
48
62
  Note that there are some types here that are shared between the request and response: mainly, anything inheriting from `mms_client.types.base.Envelop` or `mms_client.types.base.Payload`. For users of the client, only the Payload types need ever be used. Everything else has been obfuscated away, so it is unecessary for the user to have access to these. However, we will explain our reasoning here to provide additional context.
49
63
 
@@ -184,6 +198,8 @@ This client is not complete. Currently, it supports the following endpoints:
184
198
  - MarketSubmit_OfferData
185
199
  - MarketQuery_OfferQuery
186
200
  - MarketCancel_OfferCancel
201
+ - RegistrationSubmit_Resource
202
+ - RegistrationQuery_Resource
187
203
 
188
204
  We can add support for additional endpoints as time goes on, and independent contribution is, of course, welcome. However, support for attachments is currently limited because none of the endpoints we support currently require them. We have implemented attachment support up to the client level, but we haven't developed an architecture for submitting them through an endpoint yet.
189
205
 
@@ -11,6 +11,9 @@ The underlying API sends and receives XML documents. Each of these request or re
11
11
 
12
12
  After the data has been converted and added to the outer request object, it is sent to the appropriate server endpoint via a Zeep client. The client certificate is also injected into the request using a PCKS12 adaptor.
13
13
 
14
+ # Serialization
15
+ This library relies on Pydantic 2 and the pydantic-xml library for serialization/deserialization. As such, any type in this library can be converted to not only XML, but to JSON as well. This is extremely useful if you're trying to build a pass-through API service or something similar.
16
+
14
17
  ## Client Types
15
18
  Clients cannot call any and all endpoints in this API, willy-nilly. Some endpoints are restricted to particular clients. At the moment, there are two clients: Balance Service Providers (BSPs) and Transmission Service Operators (TSOs). Most likely you're operating as a BSP, in which case you'll have access to all endpoints. However, it makes little sense for a TSO to be able to submit bids on their own power, so they are restricted to a read-only role in most cases.
16
19
 
@@ -22,7 +25,7 @@ The MMS has two separate endpoints, depending on whether you want to access mark
22
25
  # Object Hierarchy
23
26
  The object hierarchy contained in this project is not trivial, and perhaps that reflects a bit of overengineering on our part. However, we chose the paradigm we did to reduce the number of types which had to be exposed to the user. The diagram below indicates how this hierarchy works:
24
27
 
25
- ![MMS Client Object Hierarchy](https://github.com/ElectroRoute-Japan/mms-client/blob/main/docs/mmsclient_hierarchy.drawio.png)
28
+ ![MMS Client Object Hierarchy](https://github.com/ElectroRoute-Japan/mms-client/raw/main/docs/mmsclient_hierarchy.drawio.png)
26
29
 
27
30
  Note that there are some types here that are shared between the request and response: mainly, anything inheriting from `mms_client.types.base.Envelop` or `mms_client.types.base.Payload`. For users of the client, only the Payload types need ever be used. Everything else has been obfuscated away, so it is unecessary for the user to have access to these. However, we will explain our reasoning here to provide additional context.
28
31
 
@@ -163,6 +166,8 @@ This client is not complete. Currently, it supports the following endpoints:
163
166
  - MarketSubmit_OfferData
164
167
  - MarketQuery_OfferQuery
165
168
  - MarketCancel_OfferCancel
169
+ - RegistrationSubmit_Resource
170
+ - RegistrationQuery_Resource
166
171
 
167
172
  We can add support for additional endpoints as time goes on, and independent contribution is, of course, welcome. However, support for attachments is currently limited because none of the endpoints we support currently require them. We have implemented attachment support up to the client level, but we haven't developed an architecture for submitting them through an endpoint yet.
168
173
 
@@ -1,10 +1,24 @@
1
1
  [tool.poetry]
2
2
  name = "mms_client"
3
- version = "v1.0.6"
3
+ version = "v1.1.0"
4
4
  description = "API client for accessing the MMS"
5
5
  authors = ["Ryan Wood <ryan.wood@electroroute.co.jp>"]
6
6
  readme = "README.md"
7
7
  packages = [{ include = "mms_client", from = "src" }]
8
+ homepage = "https://github.com/ElectroRoute-Japan/mms-client"
9
+ classifiers = [
10
+ "Development Status :: 5 - Production/Stable",
11
+ "Framework :: Pydantic :: 2",
12
+ "Framework :: Pytest",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: The Unlicense (Unlicense)",
15
+ "Natural Language :: English",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ "Typing :: Typed",
21
+ ]
8
22
 
9
23
  [tool.poetry.dependencies]
10
24
  python = "^3.11"
@@ -99,6 +113,9 @@ omit = ["*/__init__.py"]
99
113
  [tool.coverage.report]
100
114
  fail_under = 100
101
115
 
116
+ [tool.coverage.html]
117
+ directory = "coverage_html_report"
118
+
102
119
  [build-system]
103
120
  requires = ["poetry-core"]
104
121
  build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,77 @@
1
+ """Contains the client layer for making registration requests to the MMS server."""
2
+
3
+ from datetime import date as Date
4
+ from typing import List
5
+ from typing import Optional
6
+
7
+ from mms_client.services.base import ClientProto
8
+ from mms_client.services.base import ServiceConfiguration
9
+ from mms_client.services.base import mms_endpoint
10
+ from mms_client.services.base import mms_multi_endpoint
11
+ from mms_client.types.registration import QueryAction
12
+ from mms_client.types.registration import QueryType
13
+ from mms_client.types.registration import RegistrationQuery
14
+ from mms_client.types.registration import RegistrationSubmit
15
+ from mms_client.types.resource import ResourceData
16
+ from mms_client.types.resource import ResourceQuery
17
+ from mms_client.types.transport import RequestType
18
+ from mms_client.utils.serialization import SchemaType
19
+ from mms_client.utils.serialization import Serializer
20
+ from mms_client.utils.web import ClientType
21
+ from mms_client.utils.web import Interface
22
+
23
+
24
+ class RegistrationClientMixin: # pylint: disable=unused-argument
25
+ """Registration client for the MMS server."""
26
+
27
+ # The configuration for the registration service
28
+ config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.REGISTRATION, "RegistrationData"))
29
+
30
+ @mms_endpoint("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, ClientType.BSP)
31
+ def put_resource(self: ClientProto, request: ResourceData) -> ResourceData:
32
+ """Submit a new resource to the MMS server.
33
+
34
+ This endpoint is only accessible to BSPs.
35
+
36
+ Arguments:
37
+ request (ResourceData): The resource to register with the MMS server.
38
+
39
+ Returns: The resource that has been registered with the MMS server.
40
+ """
41
+ # For some reason, the registration DTOs require that the participant ID exist on the payload rather than on
42
+ # the envelope so we need to set it before we return the envelope.
43
+ request.participant = self.participant
44
+
45
+ # Create and return the registration submit DTO.
46
+ return RegistrationSubmit() # type: ignore[return-value]
47
+
48
+ @mms_multi_endpoint(
49
+ "RegistrationQuery_Resource",
50
+ config,
51
+ RequestType.REGISTRATION,
52
+ resp_envelope_type=RegistrationSubmit,
53
+ resp_data_type=ResourceData,
54
+ )
55
+ def query_resources(
56
+ self: ClientProto, request: ResourceQuery, action: QueryAction, date: Optional[Date] = None
57
+ ) -> List[ResourceData]:
58
+ """Query resources from the MMS server.
59
+
60
+ Arguments:
61
+ request (ResourceQuery): The query to send to the MMS server.
62
+ action (QueryAction): The type of query being made. NORMAL for all records or LATEST for the most recent.
63
+ date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
64
+ current date.
65
+
66
+ Returns: A list of resources that match the query.
67
+ """
68
+ # For some reason, the registration DTOs require that the participant ID exist on the payload rather than on
69
+ # the envelope so we need to set it before we return the envelope.
70
+ request.participant = self.participant
71
+
72
+ # Inject our parameters into the query and return it.
73
+ return RegistrationQuery( # type: ignore[return-value]
74
+ action=action,
75
+ query_type=QueryType.TRADE,
76
+ date=date or Date.today(),
77
+ )
@@ -14,6 +14,8 @@ from pydantic_xml import BaseXmlModel
14
14
  from pydantic_xml import attr
15
15
  from pydantic_xml import element
16
16
 
17
+ from mms_client.types.fields import transaction_id
18
+
17
19
 
18
20
  class ValidationStatus(Enum):
19
21
  """Represents the status of the validation check done on an element."""
@@ -67,7 +69,7 @@ class ProcessingStatistics(BaseXmlModel):
67
69
  time_ms: Optional[int] = attr(default=None, name="ProcessingTimeMs")
68
70
 
69
71
  # The transaction ID of the request
70
- transaction_id: Optional[str] = attr(default=None, name="TransactionID", min_length=8, max_length=10)
72
+ transaction_id: Optional[str] = transaction_id("TransactionId", True)
71
73
 
72
74
  # When the request was received, in the format "DDD MMM DD HH:MM:SS TZ YYYY"
73
75
  timestamp: str = attr(default="", name="TimeStamp")
@@ -1,6 +1,7 @@
1
1
  """Contains enums common to all MMS types."""
2
2
 
3
3
  from enum import Enum
4
+ from enum import IntEnum
4
5
 
5
6
 
6
7
  class AreaCode(Enum):
@@ -16,3 +17,10 @@ class AreaCode(Enum):
16
17
  SHIKOKU = "08"
17
18
  KYUSHU = "09"
18
19
  OKINAWA = "10"
20
+
21
+
22
+ class Frequency(IntEnum):
23
+ """Represents the frequency of power sources."""
24
+
25
+ EAST = 50
26
+ WEST = 60
@@ -4,7 +4,13 @@ from pydantic_core import PydanticUndefined
4
4
  from pydantic_xml import attr
5
5
 
6
6
  # Describes the regular expression required by the MMS API for Japanese text
7
- JAPANESE_TEXT = r"^[\u3000-\u30FF\uFF00-\uFF60\uFFA0-\uFFEF\u4E00-\u9FEA]*$"
7
+ JAPANESE_TEXT = r"[\u3000-\u30FF\uFF00-\uFF60\uFFA0-\uFFEF\u4E00-\u9FEA]*"
8
+
9
+ # Describes the regular expression required by the MMS API for ASCII text
10
+ ASCII_TEXT = r"[a-zA-Z0-9 ~!@#$*()_+}{:?>`='/.,%;\^\|\-\]\[\\&lt;&amp;&quot;]*"
11
+
12
+ # Describes the regular expression required by the MMS API for Japanese or ASCII text
13
+ JAPANESE_ASCII_TEXT = f"{JAPANESE_TEXT}|{ASCII_TEXT}"
8
14
 
9
15
 
10
16
  def participant(alias: str, optional: bool = False):
@@ -43,6 +49,41 @@ def operator_code(alias: str, optional: bool = False):
43
49
  )
44
50
 
45
51
 
52
+ def transaction_id(alias: str, optional: bool = False):
53
+ """Create a field for a transaction ID.
54
+
55
+ Arguments:
56
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
57
+ to the JSON/XML key.
58
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
59
+ required, with no default.
60
+
61
+ Returns: A Pydantic Field object for the transaction ID.
62
+ """
63
+ return attr(
64
+ default=None if optional else PydanticUndefined,
65
+ name=alias,
66
+ min_length=8,
67
+ max_length=10,
68
+ pattern=r"^[a-zA-Z0-9]{8,10}$",
69
+ )
70
+
71
+
72
+ def capacity(alias: str, minimum: int, optional: bool = False):
73
+ """Create a field for a capacity value.
74
+
75
+ Arguments:
76
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
77
+ to the JSON/XML key.
78
+ minimum (int): The minimum value for the capacity field.
79
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
80
+ required, with no default.
81
+
82
+ Returns: A Pydantic Field object for the capacity value.
83
+ """
84
+ return attr(default=None if optional else PydanticUndefined, name=alias, ge=minimum, le=10000000)
85
+
86
+
46
87
  def power_positive(alias: str, optional: bool = False):
47
88
  """Create a field for a positive power value.
48
89
 
@@ -68,7 +109,21 @@ def price(alias: str, optional: bool = False):
68
109
 
69
110
  Returns: A Pydantic Field object for the price value.
70
111
  """
71
- return attr(default=None if optional else PydanticUndefined, name=alias, ge=0.00, le=10000.00, decimal_places=2)
112
+ return attr(default=None if optional else PydanticUndefined, name=alias, ge=0.00, lt=10000.00, decimal_places=2)
113
+
114
+
115
+ def percentage(alias: str, optional: bool = False):
116
+ """Create a field for a percentage value.
117
+
118
+ Arguments:
119
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
120
+ to the JSON/XML key.
121
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
122
+ required, with no default.
123
+
124
+ Returns: A Pydantic Field object for the percentage value.
125
+ """
126
+ return attr(default=None if optional else PydanticUndefined, name=alias, ge=0.0, le=100.0, decimal_places=1)
72
127
 
73
128
 
74
129
  def dr_patter_number(alias: str, optional: bool = False):
@@ -85,6 +140,22 @@ def dr_patter_number(alias: str, optional: bool = False):
85
140
  return attr(default=None if optional else PydanticUndefined, name=alias, ge=1, le=20)
86
141
 
87
142
 
143
+ def pattern_name(alias: str, optional: bool = False):
144
+ """Create a field for a pattern name.
145
+
146
+ Arguments:
147
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
148
+ to the JSON/XML key.
149
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
150
+ required, with no default.
151
+
152
+ Returns: A Pydantic Field object for the pattern name.
153
+ """
154
+ return attr(
155
+ default=None if optional else PydanticUndefined, name=alias, min_length=1, max_length=20, pattern=JAPANESE_TEXT
156
+ )
157
+
158
+
88
159
  def company_short_name(alias: str, optional: bool = False):
89
160
  """Create a field for a company short name.
90
161
 
@@ -101,6 +172,44 @@ def company_short_name(alias: str, optional: bool = False):
101
172
  )
102
173
 
103
174
 
175
+ def address(alias: str, optional: bool = False):
176
+ """Create a field for an address.
177
+
178
+ Arguments:
179
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
180
+ to the JSON/XML key.
181
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
182
+ required, with no default.
183
+
184
+ Returns: A Pydantic Field object for the address.
185
+ """
186
+ return attr(
187
+ default=None if optional else PydanticUndefined, name=alias, min_length=1, max_length=50, pattern=JAPANESE_TEXT
188
+ )
189
+
190
+
191
+ def phone(alias: str, first_part: bool, optional: bool = False):
192
+ """Create a field for a phone number.
193
+
194
+ Arguments:
195
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
196
+ to the JSON/XML key.
197
+ first_part (bool): If True, the field will be the first part of the phone number. If False, the field will be the
198
+ second part of the phone number.
199
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
200
+ required, with no default.
201
+
202
+ Returns: A Pydantic Field object for the phone number.
203
+ """
204
+ return attr(
205
+ default=None if optional else PydanticUndefined,
206
+ name=alias,
207
+ min_length=1,
208
+ max_length=5 if first_part else 4,
209
+ pattern=r"^[0-9]*$",
210
+ )
211
+
212
+
104
213
  def resource_name(alias: str, optional: bool = False):
105
214
  """Create a field for a resource name.
106
215
 
@@ -151,3 +260,42 @@ def system_code(alias: str, optional: bool = False):
151
260
  return attr(
152
261
  default=None if optional else PydanticUndefined, name=alias, min_length=5, max_length=5, pattern=r"^[A-Z0-9]*$"
153
262
  )
263
+
264
+
265
+ def minute(alias: str, optional: bool = False):
266
+ """Create a field for a minute value.
267
+
268
+ Arguments:
269
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
270
+ to the JSON/XML key.
271
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
272
+ required, with no default.
273
+
274
+ Returns: A Pydantic Field object for the minute value.
275
+ """
276
+ return attr(
277
+ default=None if optional else PydanticUndefined,
278
+ name=alias,
279
+ ge=0,
280
+ le=99,
281
+ )
282
+
283
+
284
+ def hour(alias: str, optional: bool = False):
285
+ """Create a field for an hour value.
286
+
287
+ Arguments:
288
+ alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
289
+ to the JSON/XML key.
290
+ optional (bool): If True, the field will be optional with a default of None. If False, the field will be
291
+ required, with no default.
292
+
293
+ Returns: A Pydantic Field object for the hour value.
294
+ """
295
+ return attr(
296
+ default=None if optional else PydanticUndefined,
297
+ name=alias,
298
+ ge=0.0,
299
+ lt=100.0,
300
+ decimal_places=1,
301
+ )
@@ -61,13 +61,24 @@ class OfferStack(Payload):
61
61
  mandatory.
62
62
  """
63
63
 
64
- # A number used to identify this PQ pair within the offer
64
+ # A number used to identify this PQ pair within the offer. Ensure that there are no duplicates of the combination
65
+ # of the same resource, pattern number, start date and time, and bid management number in the submitted data. In
66
+ # case of multiple bids in the same time slot and for the same resource, each bid must have a unique number. Enter
67
+ # '1' for a single bid.
65
68
  number: int = attr(name="StackNumber", ge=1, le=20)
66
69
 
67
- # The minimum quantity that must be provided before the offer can be awarded
70
+ # The minimum quantity that must be provided before the offer can be awarded. For resources with dedicated line
71
+ # control or monitoring methods, must be 5000kW or higher. For resources with simple command (online) control
72
+ # or monitoring methods, must be 1000kW or higher.
68
73
  minimum_quantity_kw: int = power_positive("MinimumQuantityInKw")
69
74
 
70
- # The primary bid quantity in kW
75
+ # The primary bid quantity in kW. Must be equal to or greater than minimum_quantity_kw. For non-VPP resources, the
76
+ # total bid volume of all records with the same resource and start date and time must be below the maximum supply
77
+ # capacity for the corresponding product category of the power source. However, in the case of tertiary adjustment
78
+ # power 2, it must be the value obtained by subtracting the total agreed capacity of effective tertiary adjustment
79
+ # power 1. For VPP power sources, the total bid volume of all records with the same power source code and start
80
+ # date and time must be below the maximum supply capacity registered for the corresponding product category in the
81
+ # pattern number.
71
82
  primary_qty_kw: Optional[int] = power_positive("PrimaryOfferQuantityInKw", True)
72
83
 
73
84
  # The first secondary bid quantity in kW
@@ -107,7 +118,8 @@ class OfferData(Payload):
107
118
  # The direction of the offer (buy, sell)
108
119
  direction: Direction = attr(name="Direction")
109
120
 
110
- # The type of market for which the offer is being submitted
121
+ # The type of market for which the offer is being submitted. Must be a valid pattern number for the submission date
122
+ # Required for VPP resources. Ensure there are no duplicate pattern numbers for the same resource and start time.
111
123
  pattern_number: Optional[int] = dr_patter_number("DrPatternNumber", True)
112
124
 
113
125
  # The name of the BSP participant submitting the offer
@@ -0,0 +1,47 @@
1
+ """Contains objects for registrations."""
2
+
3
+ # Have to use this becasue pydantic doesn't like pendulum.Date. I've submitted a PR to address this but it hasn't been
4
+ # merged or released yet.
5
+ from datetime import date as Date
6
+ from enum import Enum
7
+ from typing import Optional
8
+
9
+ from pydantic_xml import attr
10
+
11
+ from mms_client.types.base import Envelope
12
+
13
+
14
+ class QueryAction(Enum):
15
+ """Represents the type of query being made."""
16
+
17
+ NORMAL = "NORMAL"
18
+ LATEST = "LATEST"
19
+
20
+
21
+ class QueryType(Enum):
22
+ """Represents the type of data being queried."""
23
+
24
+ TRADE = "TRADE"
25
+
26
+
27
+ class RegistrationSubmit(Envelope):
28
+ """Represents the base fields for a registration request."""
29
+
30
+
31
+ class RegistrationQuery(Envelope):
32
+ """Represents the base fields for a registration query."""
33
+
34
+ # The query type being made.
35
+ # NORMAL: Retrieve all records that match the specified conditions.
36
+ # LATEST: Retrieve only the latest record that matches the specified conditions.
37
+ action: QueryAction = attr(default=QueryAction.NORMAL, name="Action")
38
+
39
+ # The type of data being queried
40
+ query_type: QueryType = attr(default=QueryType.TRADE, name="DateType")
41
+
42
+ # Date of the transaction in the format "YYYY-MM-DD"
43
+ date: Optional[Date] = attr(default=None, name="Date")
44
+
45
+
46
+ class RegistrationApproval(Envelope):
47
+ """Represents the base fields for a registration approval request."""