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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -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 *

View File

@@ -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,
}
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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})",
}
)

View File

@@ -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)

View File

@@ -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})",
}
)

View File

@@ -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")

View File

@@ -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}",
}
)

View File

@@ -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"
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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",
},
)

View File

@@ -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",
}
)

View File

@@ -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