Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
from .enums import *
|
||||
from .messages import *
|
||||
|
||||
ACCEPTED = AcceptedRejected.ACCEPTED
|
||||
REJECTED = AcceptedRejected.REJECTED
|
||||
|
||||
__all__ = [
|
||||
"AcceptedRejected",
|
||||
"AcceptedDisputed",
|
||||
"AvailableRequested",
|
||||
"AgrPortfolioQuery",
|
||||
"AgrPortfolioQueryResponse",
|
||||
"AgrPortfolioQueryResponseCongestionPoint",
|
||||
"AgrPortfolioQueryResponseConnection",
|
||||
"AgrPortfolioQueryResponseDSOPortfolio",
|
||||
"AgrPortfolioQueryResponseDSOView",
|
||||
"AgrPortfolioUpdate",
|
||||
"AgrPortfolioUpdateConnection",
|
||||
"AgrPortfolioUpdateResponse",
|
||||
"ContractSettlement",
|
||||
"ContractSettlementISP",
|
||||
"ContractSettlementPeriod",
|
||||
"DPrognosis",
|
||||
"DPrognosisISP",
|
||||
"DPrognosisResponse",
|
||||
"DsoPortfolioQuery",
|
||||
"DsoPortfolioQueryCongestionPoint",
|
||||
"DsoPortfolioQueryConnection",
|
||||
"DsoPortfolioQueryResponse",
|
||||
"DsoPortfolioUpdate",
|
||||
"DsoPortfolioUpdateCongestionPoint",
|
||||
"DsoPortfolioUpdateConnection",
|
||||
"DsoPortfolioUpdateResponse",
|
||||
"FlexMessage",
|
||||
"FlexOffer",
|
||||
"FlexOfferOption",
|
||||
"FlexOfferOptionISP",
|
||||
"FlexOfferResponse",
|
||||
"FlexOfferRevocation",
|
||||
"FlexOfferRevocationResponse",
|
||||
"FlexOrder",
|
||||
"FlexOrderISP",
|
||||
"FlexOrderResponse",
|
||||
"FlexOrderSettlement",
|
||||
"FlexOrderSettlementISP",
|
||||
"FlexOrderSettlementStatus",
|
||||
"FlexOrderStatus",
|
||||
"FlexRequest",
|
||||
"FlexRequestISP",
|
||||
"FlexRequestResponse",
|
||||
"FlexReservationUpdate",
|
||||
"FlexReservationUpdateISP",
|
||||
"FlexReservationUpdateResponse",
|
||||
"FlexSettlement",
|
||||
"FlexSettlementResponse",
|
||||
"Metering",
|
||||
"MeteringISP",
|
||||
"MeteringProfile",
|
||||
"MeteringProfileEnum",
|
||||
"MeteringResponse",
|
||||
"MeteringUnit",
|
||||
"PayloadMessage",
|
||||
"PayloadMessageResponse",
|
||||
"SignedMessage",
|
||||
"TestMessage",
|
||||
"TestMessageResponse",
|
||||
"UsefRole",
|
||||
"RedispatchBy",
|
||||
]
|
||||
|
||||
routing_map = {
|
||||
AgrPortfolioQuery: ("AGR", "CRO"),
|
||||
AgrPortfolioQueryResponse: ("CRO", "AGR"),
|
||||
AgrPortfolioUpdate: ("AGR", "CRO"),
|
||||
AgrPortfolioUpdateResponse: ("CRO", "AGR"),
|
||||
DPrognosis: ("AGR", "DSO"),
|
||||
DPrognosisResponse: ("DSO", "AGR"),
|
||||
DsoPortfolioQuery: ("DSO", "CRO"),
|
||||
DsoPortfolioQueryResponse: ("CRO", "DSO"),
|
||||
DsoPortfolioUpdate: ("DSO", "CRO"),
|
||||
DsoPortfolioUpdateResponse: ("CRO", "DSO"),
|
||||
FlexOffer: ("AGR", "DSO"),
|
||||
FlexOfferResponse: ("DSO", "AGR"),
|
||||
FlexOfferRevocation: ("AGR", "DSO"),
|
||||
FlexOfferRevocationResponse: ("DSO", "AGR"),
|
||||
FlexOrder: ("DSO", "AGR"),
|
||||
FlexOrderResponse: ("AGR", "DSO"),
|
||||
FlexRequest: ("DSO", "AGR"),
|
||||
FlexRequestResponse: ("AGR", "DSO"),
|
||||
FlexReservationUpdate: ("DSO", "AGR"),
|
||||
FlexReservationUpdateResponse: ("AGR", "DSO"),
|
||||
FlexSettlement: ("DSO", "AGR"),
|
||||
FlexSettlementResponse: ("AGR", "DSO"),
|
||||
Metering: ("AGR", "DSO"),
|
||||
MeteringResponse: ("DSO", "AGR"),
|
||||
}
|
||||
|
||||
request_response_map = {
|
||||
AgrPortfolioQuery: AgrPortfolioQueryResponse,
|
||||
AgrPortfolioUpdate: AgrPortfolioUpdateResponse,
|
||||
DPrognosis: DPrognosisResponse,
|
||||
DsoPortfolioQuery: DsoPortfolioQueryResponse,
|
||||
DsoPortfolioUpdate: DsoPortfolioUpdateResponse,
|
||||
FlexOffer: FlexOfferResponse,
|
||||
FlexOfferRevocation: FlexOfferRevocationResponse,
|
||||
FlexOrder: FlexOrderResponse,
|
||||
FlexRequest: FlexRequestResponse,
|
||||
FlexReservationUpdate: FlexReservationUpdateResponse,
|
||||
FlexSettlement: FlexSettlementResponse,
|
||||
Metering: MeteringResponse,
|
||||
TestMessage: TestMessageResponse,
|
||||
}
|
||||
|
||||
origin_map = {key: origin for key, (origin, destination) in routing_map.items()}
|
||||
destination_map = {key: destination for key, (origin, destination) in routing_map.items()}
|
||||
@@ -0,0 +1 @@
|
||||
DEFAULT_TIME_ZONE = "Europe/Amsterdam"
|
||||
@@ -0,0 +1,26 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class AcceptedDisputed(StrEnum):
|
||||
ACCEPTED = "Accepted"
|
||||
DISPUTED = "Disputed"
|
||||
|
||||
|
||||
class AcceptedRejected(StrEnum):
|
||||
ACCEPTED = "Accepted"
|
||||
REJECTED = "Rejected"
|
||||
|
||||
|
||||
class AvailableRequested(StrEnum):
|
||||
AVAILABLE = "Available"
|
||||
REQUESTED = "Requested"
|
||||
|
||||
|
||||
class RedispatchBy(StrEnum):
|
||||
AGR = "AGR"
|
||||
DSO = "DSO"
|
||||
|
||||
class UsefRole(StrEnum):
|
||||
AGR = "AGR"
|
||||
CRO = "CRO"
|
||||
DSO = "DSO"
|
||||
@@ -0,0 +1,16 @@
|
||||
from .agr_portfolio_query import *
|
||||
from .agr_portfolio_update import *
|
||||
from .d_prognosis import *
|
||||
from .dso_portfolio_query import *
|
||||
from .dso_portfolio_update import *
|
||||
from .flex_message import *
|
||||
from .flex_offer import *
|
||||
from .flex_offer_revocation import *
|
||||
from .flex_order import *
|
||||
from .flex_request import *
|
||||
from .flex_reservation_update import *
|
||||
from .flex_settlement import *
|
||||
from .metering import *
|
||||
from .payload_message import *
|
||||
from .signed_message import *
|
||||
from .test_message import *
|
||||
@@ -0,0 +1,231 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate
|
||||
|
||||
from ..defaults import DEFAULT_TIME_ZONE
|
||||
from ..enums import RedispatchBy
|
||||
from ..validations import validate_list
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQueryResponseConnection:
|
||||
"""
|
||||
:ivar entity_address: EntityAddress of the Connection.
|
||||
"""
|
||||
class Meta:
|
||||
name = "AGRPortfolioQueryResponseConnection"
|
||||
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQueryResponseCongestionPoint:
|
||||
"""
|
||||
:ivar connection:
|
||||
:ivar entity_address: EntityAddress of the CongestionPoint.
|
||||
:ivar mutex_offers_supported: Indicates whether the DSO accepts
|
||||
mutual exclusive FlexOffers on this CongestionPoint.
|
||||
:ivar day_ahead_redispatch_by: Indicates which party is responsible
|
||||
for day-ahead redispatch.
|
||||
:ivar intraday_redispatch_by: Indicates which party is responsible
|
||||
for intraday ahead redispatch, AGR or DSO. If not specified,
|
||||
there will be no intraday trading on this CongestionPoint.
|
||||
"""
|
||||
class Meta:
|
||||
name = "AGRPortfolioQueryResponseCongestionPoint"
|
||||
|
||||
connections: List[AgrPortfolioQueryResponseConnection] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Connection",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
mutex_offers_supported: bool = field(
|
||||
metadata={
|
||||
"name": "MutexOffersSupported",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
day_ahead_redispatch_by: RedispatchBy = field(
|
||||
metadata={
|
||||
"name": "DayAheadRedispatchBy",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
intraday_redispatch_by: Optional[RedispatchBy] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IntradayRedispatchBy",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('connections', self.connections, AgrPortfolioQueryResponseConnection, 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQueryResponseDSOPortfolio:
|
||||
class Meta:
|
||||
name = "AGRPortfolioQueryResponseDSOPortfolio"
|
||||
|
||||
congestion_points: List[AgrPortfolioQueryResponseCongestionPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "CongestionPoint",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
dso_domain: str = field(
|
||||
metadata={
|
||||
"name": "DSO-Domain",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('congestion_points', self.congestion_points, AgrPortfolioQueryResponseCongestionPoint, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQueryResponseDSOView:
|
||||
class Meta:
|
||||
name = "AGRPortfolioQueryResponseDSOView"
|
||||
|
||||
dso_portfolios: List[AgrPortfolioQueryResponseDSOPortfolio] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DSO-Portfolio",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
connections: List[AgrPortfolioQueryResponseConnection] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Connection",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('dso_portfolios', self.dso_portfolios, AgrPortfolioQueryResponseDSOPortfolio, 1)
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQueryResponse(PayloadMessageResponse):
|
||||
"""
|
||||
:ivar dso_view:
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar period: The Period that the portfolio is valid.
|
||||
"""
|
||||
class Meta:
|
||||
name = "AGRPortfolioQueryResponse"
|
||||
|
||||
agr_portfolio_query_message_id: str = field(
|
||||
metadata={
|
||||
"name": "AGRPortfolioQueryMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
dso_views: List[AgrPortfolioQueryResponseDSOView] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DSO-View",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
self.dso_views = validate_list('dso_views', self.dso_views, AgrPortfolioQueryResponseDSOView, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioQuery(PayloadMessage):
|
||||
"""
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar period: The Period for which the AGR requests the portfolio
|
||||
information.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
name = "AGRPortfolioQuery"
|
||||
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,101 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate
|
||||
|
||||
from ..defaults import DEFAULT_TIME_ZONE
|
||||
from ..validations import validate_list
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioUpdateConnection:
|
||||
"""
|
||||
A connection that the AGR want the CRO to update.
|
||||
|
||||
:ivar entity_address: EntityAddress of the Connection entity being
|
||||
updated.
|
||||
:ivar start_period: The first Period hat the AGR represents the
|
||||
prosumer at this Connection.
|
||||
:ivar end_period: The last Period that the AGR represents the
|
||||
prosumer at this Connection, if applicable.
|
||||
"""
|
||||
class Meta:
|
||||
name = "AGRPortfolioUpdateConnection"
|
||||
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
start_period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "StartPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
end_period: Optional[XmlDate] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "EndPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioUpdateResponse(PayloadMessageResponse):
|
||||
class Meta:
|
||||
name = "AGRPortfolioUpdateResponse"
|
||||
|
||||
agr_portfolio_update_message_id: str = field(
|
||||
metadata={
|
||||
"name": "AGRPortfolioUpdateMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class AgrPortfolioUpdate(PayloadMessage):
|
||||
"""
|
||||
:ivar connection:
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
"""
|
||||
class Meta:
|
||||
name = "AGRPortfolioUpdate"
|
||||
|
||||
connections: List[AgrPortfolioUpdateConnection] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Connection",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('connections', self.connections, AgrPortfolioUpdateConnection, 1)
|
||||
@@ -0,0 +1,118 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from ..validations import validate_list
|
||||
from .flex_message import FlexMessage
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DPrognosisISP:
|
||||
"""
|
||||
:ivar power: Power specified for this ISP in Watts. Also see the
|
||||
important notes about the sign of this attribute in the main
|
||||
documentation entry for the ISP element.
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
"""
|
||||
class Meta:
|
||||
name = "D-PrognosisISP"
|
||||
|
||||
power: int = field(
|
||||
metadata={
|
||||
"name": "Power",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderStatus:
|
||||
flex_order_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexOrderMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
is_validated: bool = field(
|
||||
metadata={
|
||||
"name": "IsValidated",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DPrognosisResponse(PayloadMessageResponse):
|
||||
class Meta:
|
||||
name = "D-PrognosisResponse"
|
||||
|
||||
d_prognosis_message_id: str = field(
|
||||
metadata={
|
||||
"name": "D-PrognosisMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
flex_order_statuses: List[FlexOrderStatus] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "FlexOrderStatus",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DPrognosis(FlexMessage):
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar revision: Revision of this message. A sequence number that
|
||||
must be incremented each time a new revision of a prognosis is
|
||||
sent. The combination of SenderDomain and PrognosisSequence
|
||||
should be unique
|
||||
"""
|
||||
class Meta:
|
||||
name = "D-Prognosis"
|
||||
|
||||
isps: List[DPrognosisISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
revision: int = field(
|
||||
metadata={
|
||||
"name": "Revision",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, DPrognosisISP, 1)
|
||||
@@ -0,0 +1,164 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate
|
||||
|
||||
from ..defaults import DEFAULT_TIME_ZONE
|
||||
from ..validations import validate_list
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioQueryConnection:
|
||||
"""
|
||||
A Connection that is part of the congestion point.
|
||||
|
||||
:ivar entity_address: EntityAddress of the Connection.
|
||||
:ivar agr_domain: The internet domain of the AGR that represents the
|
||||
prosumer connected on this Connection, if applicable.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioQueryConnection"
|
||||
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
agr_domain: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AGR-Domain",
|
||||
"type": "Attribute",
|
||||
"pattern": r"([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioQueryCongestionPoint:
|
||||
"""
|
||||
:ivar connection:
|
||||
:ivar entity_address: EntityAddress of the Connection.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioQueryCongestionPoint"
|
||||
|
||||
connections: List[DsoPortfolioQueryConnection] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Connection",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('connections', self.connections, DsoPortfolioQueryConnection, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioQueryResponse(PayloadMessageResponse):
|
||||
"""
|
||||
:ivar congestion_point:
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar period: The Period for which the AGR requests the portfolio
|
||||
information.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioQueryResponse"
|
||||
|
||||
dso_portfolio_query_message_id: str = field(
|
||||
metadata={
|
||||
"name": "DSOPortfolioQueryMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
congestion_point: Optional[DsoPortfolioQueryCongestionPoint] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "CongestionPoint",
|
||||
"type": "Element",
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioQuery(PayloadMessage):
|
||||
"""
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar period: The Period for which the AGR requests the portfolio
|
||||
information.
|
||||
:ivar entity_address: EntityAddress of the CongestionPoint
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioQuery"
|
||||
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,181 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate
|
||||
|
||||
from ..defaults import DEFAULT_TIME_ZONE
|
||||
from ..enums import RedispatchBy
|
||||
from ..validations import validate_list
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioUpdateConnection:
|
||||
"""
|
||||
A connection that the DSO wants the CRO to update.
|
||||
|
||||
:ivar entity_address: EntityAddress of the Connection.
|
||||
:ivar start_period: The first Period that the Connection is part of
|
||||
this CongestionPoint.
|
||||
:ivar end_period: The last Period that the Connection is part of
|
||||
this CongestionPoint.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioUpdateConnection"
|
||||
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
start_period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "StartPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
end_period: Optional[XmlDate] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "EndPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioUpdateCongestionPoint:
|
||||
"""
|
||||
A congestion point that the DSO wants the CRO to update.
|
||||
|
||||
:ivar connection:
|
||||
:ivar entity_address: EntityAddress of the Connection.
|
||||
:ivar start_period: The first Period that the Connection is part of
|
||||
this CongestionPoint.
|
||||
:ivar end_period: The last Period that the Connection is part of
|
||||
this CongestionPoint.
|
||||
:ivar mutex_offers_supported: Indicates whether the DSO accepts
|
||||
mutual exclusive FlexOffers on this CongestionPoint.
|
||||
:ivar day_ahead_redispatch_by: Indicates which party is responsible
|
||||
for day-ahead redispatch.
|
||||
:ivar intraday_redispatch_by: Indicates which party is responsible
|
||||
for intraday ahead redispatch, AGR or DSO. If not specified,
|
||||
there will be no intraday trading on this CongestionPoint.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioUpdateCongestionPoint"
|
||||
|
||||
connections: List[DsoPortfolioUpdateConnection] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Connection",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
entity_address: str = field(
|
||||
metadata={
|
||||
"name": "EntityAddress",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
start_period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "StartPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
end_period: Optional[XmlDate] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "EndPeriod",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
}
|
||||
)
|
||||
mutex_offers_supported: bool = field(
|
||||
metadata={
|
||||
"name": "MutexOffersSupported",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
day_ahead_redispatch_by: RedispatchBy = field(
|
||||
metadata={
|
||||
"name": "DayAheadRedispatchBy",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
intraday_redispatch_by: Optional[RedispatchBy] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "IntradayRedispatchBy",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('connections', self.connections, DsoPortfolioUpdateConnection, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioUpdateResponse(PayloadMessageResponse):
|
||||
class Meta:
|
||||
name = "DSOPortfolioUpdateResponse"
|
||||
|
||||
dso_portfolio_update_message_id: str = field(
|
||||
metadata={
|
||||
"name": "DSOPortfolioUpdateResponseMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DsoPortfolioUpdate(PayloadMessage):
|
||||
"""
|
||||
:ivar congestion_point:
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
"""
|
||||
class Meta:
|
||||
name = "DSOPortfolioUpdate"
|
||||
|
||||
congestion_points: List[DsoPortfolioUpdateCongestionPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "CongestionPoint",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('congestion_points', self.congestion_points, DsoPortfolioUpdateCongestionPoint, 1)
|
||||
@@ -0,0 +1,61 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from xsdata.models.datatype import XmlDate, XmlDuration
|
||||
|
||||
from ..defaults import DEFAULT_TIME_ZONE
|
||||
from .payload_message import PayloadMessage
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexMessage(PayloadMessage):
|
||||
"""
|
||||
:ivar isp_duration: ISO 8601 time interval (minutes only, for
|
||||
example PT15M) indicating the duration of the ISPs referenced in
|
||||
this message. Although the ISP length is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant ISP duration.
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar period: Day (in yyyy-mm-dd format) the ISPs referenced in this
|
||||
Flex* message belong to.
|
||||
:ivar congestion_point: Entity Address of the Congestion Point this
|
||||
D-Prognosis applies to.
|
||||
"""
|
||||
isp_duration: XmlDuration = field(
|
||||
metadata={
|
||||
"name": "ISP-Duration",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
default=DEFAULT_TIME_ZONE,
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
congestion_point: str = field(
|
||||
metadata={
|
||||
"name": "CongestionPoint",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,199 @@
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from ..validations import validate_decimal, validate_list
|
||||
from .flex_message import FlexMessage
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOfferOptionISP:
|
||||
"""
|
||||
:ivar power: Power specified for this ISP in Watts. Also see the
|
||||
important notes about the sign of this attribute in the main
|
||||
documentation entry for the ISP element.
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
"""
|
||||
class Meta:
|
||||
name = "FlexOfferOptionISP"
|
||||
|
||||
power: int = field(
|
||||
metadata={
|
||||
"name": "Power",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOfferOption:
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar option_reference: The identification of this option.
|
||||
:ivar price: The asking price for the flexibility offered in this
|
||||
option.
|
||||
:ivar min_activation_factor: The minimal activation factor for this
|
||||
OfferOption. An AGR may choose to include MinActivationFactor in
|
||||
FlexOffers even if the DSO is not interested in partial
|
||||
activation. In that case the DSO will simply use an
|
||||
ActivationFactor of 1.00 in every FlexOrder.
|
||||
"""
|
||||
isps: List[FlexOfferOptionISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
option_reference: str = field(
|
||||
metadata={
|
||||
"name": "OptionReference",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
price: Decimal = field(
|
||||
metadata={
|
||||
"name": "Price",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"fraction_digits": 4,
|
||||
}
|
||||
)
|
||||
min_activation_factor: Decimal = field(
|
||||
default=Decimal("1.00"),
|
||||
metadata={
|
||||
"name": "MinActivationFactor",
|
||||
"type": "Attribute",
|
||||
"min_inclusive": Decimal("0.01"),
|
||||
"max_inclusive": Decimal("1.00"),
|
||||
"fraction_digits": 2,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, FlexOfferOptionISP, 1)
|
||||
self.price = validate_decimal('price', self.price, 4)
|
||||
self.min_activation_factor = validate_decimal('min_activation_factor', self.min_activation_factor, 2)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOfferResponse(PayloadMessageResponse):
|
||||
flex_offer_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexOfferMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOffer(FlexMessage):
|
||||
"""
|
||||
:ivar offer_option:
|
||||
:ivar expiration_date_time: Date and time, including the time zone
|
||||
(ISO 8601 formatted as per http://www.w3.org/TR/NOTE-datetime)
|
||||
until which the FlexOffer is valid.
|
||||
:ivar flex_request_message_id: MessageID of the FlexRequest message
|
||||
this request is based on. Mandatory if and only if solicited.
|
||||
:ivar contract_id: Reference to the concerning contract, if
|
||||
applicable. The contract may be either bilateral or commoditized
|
||||
market contract.
|
||||
:ivar d_prognosis_message_id: MessageID of the D-Prognosis this
|
||||
request is based on, if it has been agreed that the baseline is
|
||||
based on D-prognoses.
|
||||
:ivar baseline_reference: Identification of the baseline prognosis,
|
||||
if another baseline methodology is used than based on
|
||||
D-prognoses
|
||||
:ivar currency: ISO 4217 code indicating the currency that applies
|
||||
to the price of the FlexOffer.
|
||||
"""
|
||||
offer_options: List[FlexOfferOption] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "OfferOption",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
expiration_date_time: str = field(
|
||||
metadata={
|
||||
"name": "ExpirationDateTime",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{0,9})?([+-]\d{2}:\d{2}|Z)",
|
||||
}
|
||||
)
|
||||
unsolicited: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Unsolicited",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
flex_request_message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "FlexRequestMessageID",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
contract_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
d_prognosis_message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "D-PrognosisMessageID",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
baseline_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "BaselineReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
currency: str = field(
|
||||
default="EUR",
|
||||
metadata={
|
||||
"name": "Currency",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[A-Z]{3}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('offer_options', self.offer_options, FlexOfferOption, 1)
|
||||
if not self.unsolicited and self.flex_request_message_id is None:
|
||||
raise TypeError("FlexRequestMessageId is required if Unsolicited is not True")
|
||||
@@ -0,0 +1,32 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOfferRevocationResponse(PayloadMessageResponse):
|
||||
flex_offer_revocation_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexOfferRevocationMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOfferRevocation(PayloadMessage):
|
||||
"""
|
||||
:ivar flex_offer_message_id: MessageID of the FlexOffer message that
|
||||
is being revoked: this FlexOffer must have been accepted
|
||||
previously.
|
||||
"""
|
||||
flex_offer_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexOfferMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,194 @@
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from ..validations import validate_decimal, validate_list
|
||||
from .flex_message import FlexMessage
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderISP:
|
||||
"""
|
||||
:ivar power: Power specified for this ISP in Watts. Also see the
|
||||
important notes about the sign of this attribute in the main
|
||||
documentation entry for the ISP element.
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
"""
|
||||
class Meta:
|
||||
name = "FlexOrderISP"
|
||||
|
||||
power: int = field(
|
||||
metadata={
|
||||
"name": "Power",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderResponse(PayloadMessageResponse):
|
||||
flex_order_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexOrderMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrder(FlexMessage):
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar unsolicited: Indicates whether this FlexOrder is intended to
|
||||
be unsolicited (i.e. without a preceding FlexOffer).
|
||||
:ivar service_type: Service type for this order, the service type
|
||||
determines response characteristics such as latency or asset
|
||||
participation type.
|
||||
:ivar flex_offer_message_id: MessageID of the FlexOffer message this
|
||||
order is based on.
|
||||
:ivar contract_id: Reference to the concerning bilateral contract,
|
||||
if applicable.
|
||||
:ivar d_prognosis_message_id: MessageID of the D-Prognosis this
|
||||
request is based on, if it has been agreed that the baseline is
|
||||
based on D-prognoses.
|
||||
:ivar baseline_reference: Identification of the baseline prognosis,
|
||||
if another baseline methodology is used than based on
|
||||
D-prognoses
|
||||
:ivar price: The price for the flexibility ordered. Usually, the
|
||||
price should match the price of the related FlexOffer.
|
||||
:ivar currency: ISO 4217 code indicating the currency that applies
|
||||
to the price of the FlexOffer.
|
||||
:ivar order_reference: Order number assigned by the DSO originating
|
||||
the FlexOrder. To be stored by the AGR and used in the
|
||||
settlement phase.
|
||||
:ivar option_reference: The OptionReference from the OfferOption
|
||||
chosen from the FlexOffer.
|
||||
:ivar activation_factor: The activation factor for this OfferOption.
|
||||
The ActivationFactor must be greater than or equal to the
|
||||
MinActivationFactor in the OfferOption chosen from the
|
||||
FlexOffer.
|
||||
"""
|
||||
isps: List[FlexOrderISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
unsolicited: Optional[bool] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Unsolicited",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
service_type: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ServiceType",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
flex_offer_message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "FlexOfferMessageID",
|
||||
"type": "Attribute",
|
||||
"required": False,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
contract_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
d_prognosis_message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "D-PrognosisMessageID",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
baseline_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "BaselineReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
price: Decimal = field(
|
||||
metadata={
|
||||
"name": "Price",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"fraction_digits": 4,
|
||||
}
|
||||
)
|
||||
currency: str = field(
|
||||
metadata={
|
||||
"name": "Currency",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[A-Z]{3}",
|
||||
}
|
||||
)
|
||||
order_reference: str = field(
|
||||
metadata={
|
||||
"name": "OrderReference",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
option_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OptionReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
activation_factor: Decimal = field(
|
||||
default=Decimal("1.00"),
|
||||
metadata={
|
||||
"name": "ActivationFactor",
|
||||
"type": "Attribute",
|
||||
"min_inclusive": Decimal("0.01"),
|
||||
"max_inclusive": Decimal("1.00"),
|
||||
"fraction_digits": 2,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list("isps", self.isps, FlexOrderISP, 1)
|
||||
self.price = validate_decimal("price", self.price, 4)
|
||||
self.activation_factor = validate_decimal(
|
||||
"activation_factor", self.activation_factor, 2
|
||||
)
|
||||
if not self.unsolicited and self.flex_offer_message_id is None:
|
||||
raise TypeError(
|
||||
"FlexOfferMessageId is required if Unsolicited is not True"
|
||||
)
|
||||
@@ -0,0 +1,134 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from ..enums import AvailableRequested
|
||||
from ..validations import validate_list
|
||||
from .flex_message import FlexMessage
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexRequestISP:
|
||||
"""
|
||||
:ivar disposition:
|
||||
:ivar min_power: Power specified for this ISP in Watts. Also see the
|
||||
important notes about the sign of this attribute in the main
|
||||
documentation entry for the ISP element.
|
||||
:ivar max_power: Power specified for this ISP in Watts. Also see the
|
||||
important notes about the sign of this attribute in the main
|
||||
documentation entry for the ISP element.
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
"""
|
||||
class Meta:
|
||||
name = "FlexRequestISP"
|
||||
|
||||
disposition: Optional[AvailableRequested] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Disposition",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
min_power: int = field(
|
||||
metadata={
|
||||
"name": "MinPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
max_power: int = field(
|
||||
metadata={
|
||||
"name": "MaxPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexRequestResponse(PayloadMessageResponse):
|
||||
|
||||
flex_request_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexRequestMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexRequest(FlexMessage):
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar revision: Revision of this message, a sequence number that
|
||||
must be incremented each time a new revision of a FlexRequest
|
||||
message is sent.
|
||||
:ivar expiration_date_time: Date and time, including the time zone
|
||||
(ISO 8601 formatted as per http://www.w3.org/TR/NOTE-datetime)
|
||||
until which the FlexRequest message is valid.
|
||||
:ivar contract_id: Reference to the concerning contract, if
|
||||
applicable. The contract may be either bilateral or commoditized
|
||||
market contract. Each contract may specify multiple service-
|
||||
types.
|
||||
:ivar service_type: Service type for this request, the service type
|
||||
determines response characteristics such as latency or asset
|
||||
participation type.
|
||||
"""
|
||||
isps: List[FlexRequestISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
revision: int = field(
|
||||
metadata={
|
||||
"name": "Revision",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
expiration_date_time: str = field(
|
||||
metadata={
|
||||
"name": "ExpirationDateTime",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{0,9})?([+-]\d{2}:\d{2}|Z)",
|
||||
}
|
||||
)
|
||||
contract_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
service_type: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ServiceType",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, FlexRequestISP, 1)
|
||||
@@ -0,0 +1,90 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from ..validations import validate_list
|
||||
from .flex_message import FlexMessage
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexReservationUpdateISP:
|
||||
"""
|
||||
:ivar power: Remaining reserved power specified for this ISP in
|
||||
Watts.
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
"""
|
||||
class Meta:
|
||||
name = "FlexReservationUpdateISP"
|
||||
|
||||
power: int = field(
|
||||
metadata={
|
||||
"name": "Power",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexReservationUpdateResponse(PayloadMessageResponse):
|
||||
|
||||
flex_reservation_update_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexReservationUpdateMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexReservationUpdate(FlexMessage):
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar contract_id: Reference to the bilateral contract in question.
|
||||
:ivar reference: Message reference, assigned by the DSO originating
|
||||
the FlexReservationUpdate.
|
||||
"""
|
||||
isps: List[FlexReservationUpdateISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
contract_id: str = field(
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
reference: str = field(
|
||||
metadata={
|
||||
"name": "Reference",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, FlexReservationUpdateISP, 1)
|
||||
@@ -0,0 +1,451 @@
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate
|
||||
|
||||
from ..enums import AcceptedDisputed
|
||||
from ..validations import validate_decimal, validate_list
|
||||
from .payload_message import PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ContractSettlementISP:
|
||||
"""
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
:ivar reserved_power: Amount of flex power that has been reserved
|
||||
(and not released using a FlexReservationUpdate message).
|
||||
:ivar requested_power: Amount of flex power that has been both
|
||||
reserved in advance and has been requested using a FlexRequest
|
||||
(i.e. the lowest amount of flex power for this ISP). If there
|
||||
was no FlexRequest, this field is omitted.
|
||||
:ivar available_power: Amount of flex power that is considered
|
||||
available based on the FlexRequest in question. In case
|
||||
RequestedPower=0, AvailablePower is defined so that the offered
|
||||
power is allowed to be between 0 and AvailablePower in terms of
|
||||
compliancy (see Appendix 'Rationale for information exchange in
|
||||
flexibility request' for details). In case RequestedPower ≠0,
|
||||
AvailablePower is defined so that the offered power is allowed
|
||||
to exceed the amount of requested power up to AvailablePower. If
|
||||
this is relevant for settlement, the DSO can include this field.
|
||||
:ivar offered_power: Amount of flex power that has been reserved in
|
||||
advance, requested using a FlexRequest and covered in an offer
|
||||
from the AGR. If there was no offer, this field is omitted. If
|
||||
there were multiple offers, only the one is considered that is
|
||||
most compliant .
|
||||
:ivar ordered_power: Amount of flex power that has been ordered
|
||||
using a FlexOrder message that was based on a FlexOffer, both
|
||||
linked to this contract. If there was no order, this field is
|
||||
omitted.
|
||||
"""
|
||||
class Meta:
|
||||
name = "ContractSettlementISP"
|
||||
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
reserved_power: int = field(
|
||||
metadata={
|
||||
"name": "ReservedPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
requested_power: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "RequestedPower",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
available_power: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "AvailablePower",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
offered_power: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OfferedPower",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
ordered_power: Optional[int] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OrderedPower",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ContractSettlementPeriod:
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar period: Period the being settled.
|
||||
"""
|
||||
isps: List[ContractSettlementISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ContractSettlement:
|
||||
"""
|
||||
:ivar period:
|
||||
:ivar contract_id: Reference to the concerning bilateral contract.
|
||||
"""
|
||||
periods: List[ContractSettlementPeriod] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
contract_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('periods', self.periods, ContractSettlementPeriod, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderSettlementStatus:
|
||||
"""
|
||||
:ivar order_reference: Order reference assigned by the DSO when
|
||||
originating the FlexOrder.
|
||||
:ivar disposition: Indication whether the AGR accepts the order
|
||||
settlement details provided by the DSO (and will invoice
|
||||
accordingly), or disputes these details.
|
||||
:ivar dispute_reason: In case the order settlement was disputed,
|
||||
this attribute must contain a human-readable description of the
|
||||
reason.
|
||||
"""
|
||||
order_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OrderReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
disposition: AcceptedDisputed = field(
|
||||
metadata={
|
||||
"name": "Disposition",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
dispute_reason: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "DisputeReason",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderSettlementISP:
|
||||
"""
|
||||
:ivar start: Number of the first ISPs this element refers to. The
|
||||
first ISP of a day has number 1.
|
||||
:ivar duration: The number of the ISPs this element represents.
|
||||
Optional, default value is 1.
|
||||
:ivar baseline_power: Power originally forecast (as per the
|
||||
referenced baseline) for this ISP in Watts.
|
||||
:ivar ordered_flex_power: Amount of flex power ordered (as per the
|
||||
referenced FlexOrder message) for this ISP in Watts.
|
||||
:ivar actual_power: Actual amount of power for this ISP in Watts, as
|
||||
measured/determined by the DSO and allocated to the AGR.
|
||||
:ivar delivered_flex_power: Actual amount of flex power delivered
|
||||
for this ISP in Watts, as determined by the DSO.
|
||||
:ivar power_deficiency: Amount of flex power sold but not delivered
|
||||
for this ISP in Watts, as determined by the DSO.
|
||||
"""
|
||||
class Meta:
|
||||
name = "FlexOrderSettlementISP"
|
||||
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
duration: int = field(
|
||||
default=1,
|
||||
metadata={
|
||||
"name": "Duration",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
baseline_power: int = field(
|
||||
metadata={
|
||||
"name": "BaselinePower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
ordered_flex_power: int = field(
|
||||
metadata={
|
||||
"name": "OrderedFlexPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
actual_power: int = field(
|
||||
metadata={
|
||||
"name": "ActualPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
delivered_flex_power: int = field(
|
||||
metadata={
|
||||
"name": "DeliveredFlexPower",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
power_deficiency: int = field(
|
||||
default=0,
|
||||
metadata={
|
||||
"name": "PowerDeficiency",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexOrderSettlement:
|
||||
"""
|
||||
:ivar isp:
|
||||
:ivar order_reference: Order reference assigned by the DSO when
|
||||
originating the FlexOrder.
|
||||
:ivar period:
|
||||
:ivar contract_id: Reference to the concerning bilateral contract,
|
||||
if it is linked to it
|
||||
:ivar d_prognosis_message_id: MessageID of the Prognosis message
|
||||
(more specifically: the D-Prognosis) the FlexOrder is based on,
|
||||
if it has been agreed that the baseline is based on D-prognoses.
|
||||
:ivar baseline_reference: Identification of the baseline prognosis,
|
||||
if another baseline methodology is used than based on
|
||||
D-prognoses.
|
||||
:ivar congestion_point: Entity Address of the Congestion Point the
|
||||
FlexOrder applies to.
|
||||
:ivar price: The price accepted for supplying the ordered amount of
|
||||
flexibility as per the referenced FlexOrder messages.
|
||||
:ivar penalty: Penalty due a non-zero PowerDeficiency
|
||||
:ivar net_settlement: Net settlement amount for this Period: Price
|
||||
minus Penalty.
|
||||
"""
|
||||
isps: List[FlexOrderSettlementISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
order_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "OrderReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
contract_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ContractID",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
d_prognosis_message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "D-PrognosisMessageID",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
baseline_reference: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "BaselineReference",
|
||||
"type": "Attribute",
|
||||
}
|
||||
)
|
||||
congestion_point: str = field(
|
||||
metadata={
|
||||
"name": "CongestionPoint",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(ea1\.[0-9]{4}-[0-9]{2}\..{1,244}:.{1,244}|ean\.[0-9]{12,34})",
|
||||
}
|
||||
)
|
||||
price: Decimal = field(
|
||||
metadata={
|
||||
"name": "Price",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"fraction_digits": 4,
|
||||
}
|
||||
)
|
||||
penalty: Decimal = field(
|
||||
default=Decimal("0"),
|
||||
metadata={
|
||||
"name": "Penalty",
|
||||
"type": "Attribute",
|
||||
"fraction_digits": 4,
|
||||
}
|
||||
)
|
||||
net_settlement: Decimal = field(
|
||||
metadata={
|
||||
"name": "NetSettlement",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"fraction_digits": 4,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, FlexOrderSettlementISP, 1)
|
||||
self.price = validate_decimal('price', self.price, 4)
|
||||
self.penalty = validate_decimal('penalty', self.penalty, 4)
|
||||
self.net_settlement = validate_decimal('net_settlement', self.net_settlement, 4)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexSettlementResponse(PayloadMessageResponse):
|
||||
flex_settlement_message_id: str = field(
|
||||
metadata={
|
||||
"name": "FlexSettlementMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
flex_order_settlement_statuses: List[FlexOrderSettlementStatus] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "FlexOrderSettlementStatus",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list(
|
||||
"flex_order_settlement_statuses",
|
||||
self.flex_order_settlement_statuses,
|
||||
FlexOrderSettlementStatus,
|
||||
1,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FlexSettlement(PayloadMessageResponse):
|
||||
"""
|
||||
:ivar flex_order_settlement:
|
||||
:ivar contract_settlement:
|
||||
:ivar period_start: First Period of the settlement period this
|
||||
message applies to.
|
||||
:ivar period_end: Last Period of the settlement period this message
|
||||
applies to.
|
||||
:ivar currency: ISO 4217 code indicating the currency that applies
|
||||
to all amounts (flex price, penalty and net settlement) in this
|
||||
message.
|
||||
"""
|
||||
flex_order_settlements: List[FlexOrderSettlement] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "FlexOrderSettlement",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
contract_settlements: List[ContractSettlement] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ContractSettlement",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
period_start: XmlDate = field(
|
||||
metadata={
|
||||
"name": "PeriodStart",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
period_end: XmlDate = field(
|
||||
metadata={
|
||||
"name": "PeriodEnd",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
currency: str = field(
|
||||
metadata={
|
||||
"name": "Currency",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[A-Z]{3}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list(
|
||||
"flex_order_settlements", self.flex_order_settlements, FlexOrderSettlement, 1
|
||||
)
|
||||
validate_list(
|
||||
"contract_settlements", self.contract_settlements, ContractSettlement, 1
|
||||
)
|
||||
@@ -0,0 +1,202 @@
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDate, XmlDuration
|
||||
|
||||
from ..validations import validate_list
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
# pylint: disable=missing-class-docstring,duplicate-code
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class MeteringISP:
|
||||
"""
|
||||
:ivar start: Number of the ISP this element refers to. The first ISP
|
||||
of a day has number 1.
|
||||
:ivar value: Metering, energy or price value at the end of this ISP,
|
||||
in the designated profile units.
|
||||
"""
|
||||
class Meta:
|
||||
name = "MeteringISP"
|
||||
|
||||
start: int = field(
|
||||
metadata={
|
||||
"name": "Start",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
value: Decimal = field(
|
||||
metadata={
|
||||
"name": "Value",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MeteringProfileEnum(Enum):
|
||||
"""
|
||||
:cvar POWER: The average active power during ISP, considering both
|
||||
import and export energy. Power=(ImportEnergy-
|
||||
ExportEnergy)*(60/ISP-Length-Minutes). For example with a 15
|
||||
minute ISP length we have a multiplier of 4, with a 30 minute
|
||||
ISP length we have a multiplier of 2. Including the power
|
||||
profile is recommended. It is expected that in the following
|
||||
major version the power will become a mandatory value.
|
||||
:cvar IMPORT_ENERGY: Imported active energy, consumed during the ISP
|
||||
:cvar EXPORT_ENERGY: Exported active energy, generated during the
|
||||
ISP
|
||||
:cvar IMPORT_METER_READING: Cumulative metered imported active
|
||||
energy reading, at the end of the ISP
|
||||
:cvar EXPORT_METER_READING: Cumulative metered exported active
|
||||
energy reading, at the end of the ISP
|
||||
"""
|
||||
POWER = "Power"
|
||||
IMPORT_ENERGY = "ImportEnergy"
|
||||
EXPORT_ENERGY = "ExportEnergy"
|
||||
IMPORT_METER_READING = "ImportMeterReading"
|
||||
EXPORT_METER_READING = "ExportMeterReading"
|
||||
|
||||
|
||||
class MeteringUnit(Enum):
|
||||
"""
|
||||
:cvar K_W: kW must be used with Power profile values.
|
||||
:cvar K_WH: kWh must be used with energy profile values
|
||||
(ImportEnergy,ExportEnergy,ImportMeterReading,ExportMeterReading).
|
||||
"""
|
||||
K_W = "kW"
|
||||
K_WH = "kWh"
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class MeteringProfile:
|
||||
"""
|
||||
A profile carries a sequence of ISPs with a defined type of metering data.
|
||||
"""
|
||||
isps: List[MeteringISP] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ISP",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
profile_type: MeteringProfileEnum = field(
|
||||
metadata={
|
||||
"name": "ProfileType",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
unit: MeteringUnit = field(
|
||||
metadata={
|
||||
"name": "Unit",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('isps', self.isps, MeteringISP, 1)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class MeteringResponse(PayloadMessageResponse):
|
||||
metering_message_id: str = field(
|
||||
metadata={
|
||||
"name": "MeteringMessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Metering(PayloadMessage):
|
||||
"""
|
||||
:ivar profile:
|
||||
:ivar revision: Revision of this message. A sequence number that
|
||||
must be incremented each time a new revision of a metering
|
||||
message is sent.
|
||||
:ivar isp_duration: ISO 8601 time interval (minutes only, for
|
||||
example PT15M) indicating the duration of the ISPs referenced in
|
||||
this message. Although the ISP length is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant ISP duration.
|
||||
:ivar time_zone: Time zone ID (as per the IANA time zone database,
|
||||
http://www.iana.org/time-zones, for example: Europe/Amsterdam)
|
||||
indicating the UTC offset that applies to the Period referenced
|
||||
in this message. Although the time zone is a market-wide fixed
|
||||
value, making this assumption explicit in each message is
|
||||
important for validation purposes, allowing implementations to
|
||||
reject messages with an errant UTC offset.
|
||||
:ivar currency: ISO 4217 code indicating the currency that applies
|
||||
to the price of the Tariff Rates. Only required if ImportTariff
|
||||
or ExportTariff profiles are included.
|
||||
:ivar period: Day (in yyyy-mm-dd format) the ISPs referenced in this
|
||||
Metering message belong to.
|
||||
:ivar ean: EAN of the meter the message applies to.
|
||||
"""
|
||||
profiles: List[MeteringProfile] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Profile",
|
||||
"type": "Element",
|
||||
"min_occurs": 1,
|
||||
}
|
||||
)
|
||||
revision: int = field(
|
||||
metadata={
|
||||
"name": "Revision",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
isp_duration: XmlDuration = field(
|
||||
metadata={
|
||||
"name": "ISP-Duration",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
time_zone: str = field(
|
||||
metadata={
|
||||
"name": "TimeZone",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(Africa|America|Australia|Europe|Pacific)/[a-zA-Z0-9_/]{3,}",
|
||||
}
|
||||
)
|
||||
currency: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Currency",
|
||||
"type": "Attribute",
|
||||
"pattern": r"[A-Z]{3}",
|
||||
}
|
||||
)
|
||||
period: XmlDate = field(
|
||||
metadata={
|
||||
"name": "Period",
|
||||
"type": "Attribute",
|
||||
"format": "%Y-%m-%d",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
ean: str = field(
|
||||
metadata={
|
||||
"name": "EAN",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[Ee][0-9]{18}",
|
||||
}
|
||||
)
|
||||
|
||||
def __post_init__(self):
|
||||
validate_list('profiles', self.profiles, MeteringProfile, 1)
|
||||
@@ -0,0 +1,115 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from ..enums import AcceptedRejected
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PayloadMessage:
|
||||
"""
|
||||
:ivar version: Version of the Shapeshifter specification used by the
|
||||
USEF participant sending this message.
|
||||
:ivar sender_domain: The Internet domain of the USEF participant
|
||||
sending this message. When receiving a message, its value should
|
||||
match the value specified in the SignedMessage wrapper:
|
||||
otherwise, the message must be rejected as invalid. When
|
||||
replying to this message, this attribute is used to look up the
|
||||
USEF endpoint the reply message should be delivered to.
|
||||
:ivar recipient_domain: Internet domain of the participant this
|
||||
message is intended for. When sending a message, this attribute,
|
||||
combined with the RecipientRole, is used to look up the USEF
|
||||
endpoint the message should be delivered to.
|
||||
:ivar time_stamp: Date and time this message was created, including
|
||||
the time zone (ISO 8601 formatted as per
|
||||
http://www.w3.org/TR/NOTE-datetime).
|
||||
:ivar message_id: Unique identifier (UUID/GUID as per IETF RFC 4122)
|
||||
for this message, to be generated when composing each message.
|
||||
:ivar conversation_id: Unique identifier (UUID/GUID as per IETF RFC
|
||||
4122) used to correlate responses with requests, to be generated
|
||||
when composing the first message in a conversation and
|
||||
subsequently copied from the original message to each reply
|
||||
message.
|
||||
"""
|
||||
|
||||
version: Optional[str] = field(
|
||||
default="3.1.0",
|
||||
metadata={
|
||||
"name": "Version",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"(\d+\.\d+\.\d+)",
|
||||
}
|
||||
)
|
||||
sender_domain: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "SenderDomain",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}",
|
||||
}
|
||||
)
|
||||
recipient_domain: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "RecipientDomain",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}",
|
||||
}
|
||||
)
|
||||
time_stamp: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TimeStamp",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{0,9})?([+-]\d{2}:\d{2}|Z)",
|
||||
}
|
||||
)
|
||||
message_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "MessageID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
conversation_id: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "ConversationID",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PayloadMessageResponse(PayloadMessage):
|
||||
"""
|
||||
:ivar reference_message_id: MessageID of the message that has just
|
||||
been accepted or rejected.
|
||||
:ivar result: Indication whether the query was executed successfully
|
||||
or failed.
|
||||
:ivar rejection_reason: In case the query failed, this attribute
|
||||
must contain a human-readable description of the failure reason.
|
||||
"""
|
||||
|
||||
result: Optional[AcceptedRejected] = field(
|
||||
default=AcceptedRejected.ACCEPTED,
|
||||
metadata={
|
||||
"name": "Result",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
rejection_reason: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "RejectionReason",
|
||||
"type": "Attribute",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from ..enums import UsefRole
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class SignedMessage:
|
||||
"""The SignedMessage element represents the secure wrapper used to submit USEF
|
||||
XML messages from the local message queue to the message queue of a remote
|
||||
participant.
|
||||
|
||||
It contains minimal metadata (which is distinct from the common
|
||||
metadata used for all other messages), allowing the recipient to
|
||||
look up the sender's cryptographic scheme and public keys, and the
|
||||
actual XML message, as transformed (signed/sealed) using that
|
||||
cryptographic scheme.
|
||||
|
||||
:ivar sender_domain: The Internet domain of the USEF participant
|
||||
sending this message. Upon receiving a message, the recipient
|
||||
should validate that its value matches the corresponding
|
||||
attribute value specified in the inner XML message, once un-
|
||||
sealed: if not, the message must be rejected as invalid.
|
||||
:ivar sender_role: The USEF role of the participant sending this
|
||||
message: AGR, BRP, CRO, DSO or MDC. Receive-time validation
|
||||
should take place as described for the SenderDomain attribute
|
||||
above.
|
||||
:ivar body: The Base-64 encoded inner XML message contained in this
|
||||
wrapper, as transformed (signed/sealed) using the sender's
|
||||
cryptographic scheme. The recipient can determine which scheme
|
||||
applies using a DNS or configuration file lookup, based on the
|
||||
combination of SenderDomain and SenderRole.
|
||||
"""
|
||||
|
||||
sender_domain: str = field(
|
||||
metadata={
|
||||
"name": "SenderDomain",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"pattern": r"([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}",
|
||||
}
|
||||
)
|
||||
sender_role: UsefRole = field(
|
||||
metadata={
|
||||
"name": "SenderRole",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
body: bytes = field(
|
||||
metadata={
|
||||
"name": "Body",
|
||||
"type": "Attribute",
|
||||
"required": True,
|
||||
"format": "base64",
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .payload_message import PayloadMessage, PayloadMessageResponse
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class TestMessage(PayloadMessage):
|
||||
__test__ = False # Tell pytest to ignore this class
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class TestMessageResponse(PayloadMessageResponse):
|
||||
__test__ = False # Tell pytest to ignore this class
|
||||
@@ -0,0 +1,32 @@
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
|
||||
def validate_decimal(name: str, value: int | float | Decimal | str, digits: int):
|
||||
"""
|
||||
Validates that the decimal is acceptable, and returns it with the correct number of digits.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
value = Decimal(value)
|
||||
except InvalidOperation as exc:
|
||||
raise ValueError(f"{name} must be a valid numeric value, not '{value}'") from exc
|
||||
if not isinstance(value, (int, float, Decimal)):
|
||||
raise TypeError(f"'{name}' must be a numeric type, not {type(value)}")
|
||||
return Decimal(f"{value:.{digits}f}")
|
||||
|
||||
|
||||
def validate_list(name, value, item_type, length):
|
||||
"""
|
||||
Validates that the list is of the correct type, length and content type.
|
||||
"""
|
||||
if not isinstance(value, list):
|
||||
raise TypeError(f"'{name}' must be a list, not {type(value)}")
|
||||
if len(value) < length:
|
||||
raise ValueError(f"'Length of list '{name}' must be {length} or greater, not {len(value)}")
|
||||
for index, item in enumerate(value):
|
||||
if not isinstance(item, item_type):
|
||||
raise TypeError(
|
||||
f"Not all items of property {name} were of type {item_type}: "
|
||||
f"item at index {index} was of type {type(item)}"
|
||||
)
|
||||
return value
|
||||
Reference in New Issue
Block a user