diff --git a/generate_or_validate_json_schemas.py b/generate_or_validate_json_schemas.py index 84f52f96e..42d7bd0b3 100644 --- a/generate_or_validate_json_schemas.py +++ b/generate_or_validate_json_schemas.py @@ -106,6 +106,19 @@ def get_schema_json_dict(cls: Any) -> dict[str, Any]: schema_json_dict = TypeAdapter(cls).json_schema(schema_generator=GenerateJsonSchema) else: raise ValueError(f"Class {cls} is neither a pydantic BaseModel nor an enum.") + if {"allOf", "$defs"} == set(schema_json_dict.keys()): + assert ( + len(schema_json_dict["allOf"]) == 1 + ), "Internal error: Assumed circular reference but structure is unexpected" + # This is the case for schemas containing circular references + reference_pattern = re.compile(r"^#/\$defs/(?P\w+)$") + reference_match = reference_pattern.fullmatch(schema_json_dict["allOf"][0]["$ref"]) + assert ( + reference_match is not None + ), "Internal Error: Reference string has unexpected format: {schema_json_dict['allOf'][0]['$ref']}" + schema_json_dict_to_merge = schema_json_dict["$defs"][reference_match.group("cls_name")] + del schema_json_dict["allOf"] + schema_json_dict.update(schema_json_dict_to_merge) if "$defs" in schema_json_dict: del schema_json_dict["$defs"] return schema_json_dict @@ -191,7 +204,7 @@ def traverse_dict(obj: dict[str, Any]) -> None: ) def generate_or_validate_json_schemas(mode: Literal["validate", "generate"], target_version: str) -> None: """generate json schemas for all BOs and COMs""" - _logger.debug("Mode: %s, target version: %s", mode, target_version) + _logger.info("Mode: %s, target version: %s", mode, target_version) packages = ["bo", "com", "enum"] if mode == "generate": diff --git a/src/bo4e/__init__.py b/src/bo4e/__init__.py index 9364f5f61..255977773 100644 --- a/src/bo4e/__init__.py +++ b/src/bo4e/__init__.py @@ -19,9 +19,11 @@ "Geschaeftspartner", "Kosten", "Lastgang", + "Lokationszuordnung", "Marktlokation", "Marktteilnehmer", "Messlokation", + "Netzlokation", "Person", "Preisblatt", "PreisblattDienstleistung", @@ -33,10 +35,12 @@ "Region", "Regionaltarif", "Standorteigenschaften", + "SteuerbareRessource", "Tarif", "Tarifinfo", "Tarifkosten", "Tarifpreisblatt", + "TechnischeRessource", "Vertrag", "Zaehler", "Zeitreihe", @@ -66,7 +70,6 @@ "KriteriumWert", "MarktgebietInfo", "Menge", - "Messlokationszuordnung", "PositionsAufAbschlag", "Preis", "Preisgarantie", @@ -115,6 +118,7 @@ "Bemessungsgroesse", "Bilanzierungsmethode", "Dienstleistungstyp", + "EMobilitaetsart", "Energierichtung", "Erzeugungsart", "Gasqualitaet", @@ -124,6 +128,7 @@ "Geschaeftspartnerrolle", "Gueltigkeitstyp", "Kalkulationsmethode", + "Konfigurationsprodukt", "Kontaktart", "Kostenklasse", "Kundengruppe", @@ -159,6 +164,8 @@ "Registeranzahl", "Rollencodetyp", "Sparte", + "Speicherart", + "SteuerkanalLeistungsbeschreibung", "Steuerkennzeichen", "StrEnum", "Tarifkalkulationsmethode", @@ -166,6 +173,8 @@ "Tarifregionskriterium", "Tariftyp", "Tarifzeit", + "TechnischeRessourceNutzung", + "TechnischeRessourceVerbrauchsart", "Themengebiet", "Titel", "Typ", @@ -201,9 +210,11 @@ from .bo.geschaeftspartner import Geschaeftspartner from .bo.kosten import Kosten from .bo.lastgang import Lastgang +from .bo.lokationszuordnung import Lokationszuordnung from .bo.marktlokation import Marktlokation from .bo.marktteilnehmer import Marktteilnehmer from .bo.messlokation import Messlokation +from .bo.netzlokation import Netzlokation from .bo.person import Person from .bo.preisblatt import Preisblatt from .bo.preisblattdienstleistung import PreisblattDienstleistung @@ -215,10 +226,12 @@ from .bo.region import Region from .bo.regionaltarif import Regionaltarif from .bo.standorteigenschaften import Standorteigenschaften +from .bo.steuerbareressource import SteuerbareRessource from .bo.tarif import Tarif from .bo.tarifinfo import Tarifinfo from .bo.tarifkosten import Tarifkosten from .bo.tarifpreisblatt import Tarifpreisblatt +from .bo.technischeressource import TechnischeRessource from .bo.vertrag import Vertrag from .bo.zaehler import Zaehler from .bo.zeitreihe import Zeitreihe @@ -243,6 +256,7 @@ from .com.fremdkostenposition import Fremdkostenposition from .com.geokoordinaten import Geokoordinaten from .com.katasteradresse import Katasteradresse +from .com.konfigurationsprodukt import Konfigurationsprodukt from .com.kontaktweg import Kontaktweg from .com.konzessionsabgabe import Konzessionsabgabe from .com.kostenblock import Kostenblock @@ -250,7 +264,6 @@ from .com.kriteriumwert import KriteriumWert from .com.marktgebietinfo import MarktgebietInfo from .com.menge import Menge -from .com.messlokationszuordnung import Messlokationszuordnung from .com.positionsaufabschlag import PositionsAufAbschlag from .com.preis import Preis from .com.preisgarantie import Preisgarantie @@ -301,6 +314,7 @@ from .enum.bemessungsgroesse import Bemessungsgroesse from .enum.bilanzierungsmethode import Bilanzierungsmethode from .enum.dienstleistungstyp import Dienstleistungstyp +from .enum.emobilitaetsart import EMobilitaetsart from .enum.energierichtung import Energierichtung from .enum.erzeugungsart import Erzeugungsart from .enum.gasqualitaet import Gasqualitaet @@ -345,6 +359,8 @@ from .enum.registeranzahl import Registeranzahl from .enum.rollencodetyp import Rollencodetyp from .enum.sparte import Sparte +from .enum.speicherart import Speicherart +from .enum.steuerkanalleistungsbeschreibung import SteuerkanalLeistungsbeschreibung from .enum.steuerkennzeichen import Steuerkennzeichen from .enum.strenum import StrEnum from .enum.tarifkalkulationsmethode import Tarifkalkulationsmethode @@ -352,6 +368,8 @@ from .enum.tarifregionskriterium import Tarifregionskriterium from .enum.tariftyp import Tariftyp from .enum.tarifzeit import Tarifzeit +from .enum.technischeressourcenutzung import TechnischeRessourceNutzung +from .enum.technischeressourceverbrauchsart import TechnischeRessourceVerbrauchsart from .enum.themengebiet import Themengebiet from .enum.titel import Titel from .enum.typ import Typ diff --git a/src/bo4e/bo/lokationszuordnung.py b/src/bo4e/bo/lokationszuordnung.py new file mode 100644 index 000000000..e856d4dc5 --- /dev/null +++ b/src/bo4e/bo/lokationszuordnung.py @@ -0,0 +1,53 @@ +""" +Contains Lokationszuordnung class +""" + +from typing import TYPE_CHECKING, Annotated, Optional + +from pydantic import Field + +from ..enum.typ import Typ +from ..utils import postprocess_docstring +from .geschaeftsobjekt import Geschaeftsobjekt + +if TYPE_CHECKING: + from ..bo.marktlokation import Marktlokation + from ..bo.messlokation import Messlokation + from ..bo.netzlokation import Netzlokation + from ..bo.steuerbareressource import SteuerbareRessource + from ..bo.technischeressource import TechnischeRessource + from ..com.zeitspanne import Zeitspanne + + +@postprocess_docstring +class Lokationszuordnung(Geschaeftsobjekt): + """ + Modell für die Abbildung der Referenz auf die Lokationsbündelstruktur. Diese gibt an welche Marktlokationen, + Messlokationen, Netzlokationen, technische/steuerbaren Ressourcen an einer Lokation vorhanden sind. + + .. raw:: html + + + + .. HINT:: + `Lokationszuordnung JSON Schema `_ + """ + + typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.LOKATIONSZUORDNUNG + + #: Liste mit referenzierten Marktlokationen + marktlokationen: Optional[list["Marktlokation"]] = None + #: Liste mit referenzierten Messlokationen + messlokationen: Optional[list["Messlokation"]] = None + #: Liste mit referenzierten Netzlokationen + netzlokationen: Optional[list["Netzlokation"]] = None + #: Liste mit referenzierten technischen Ressourcen + technische_ressourcen: Optional[list["TechnischeRessource"]] = None + #: Liste mit referenzierten steuerbaren Ressourcen + steuerbare_ressourcen: Optional[list["SteuerbareRessource"]] = None + #: Zeitspanne der Gültigkeit + gueltigkeit: Optional["Zeitspanne"] = None + #: Verknüpfungsrichtung z.B. Malo-Melo [TODO: Eventuell anderer Datentyp] + zuordnungstyp: Optional[str] = None + #: Code, der angibt wie die Lokationsbündelstruktur zusammengesetzt ist (zu finden unter "Codeliste der Lokationsbündelstrukturen" auf https://www.edi-energy.de/index.php?id=38) + lokationsbuendelcode: Optional[str] = None diff --git a/src/bo4e/bo/marktlokation.py b/src/bo4e/bo/marktlokation.py index 6f84ac38e..d94cd0cce 100644 --- a/src/bo4e/bo/marktlokation.py +++ b/src/bo4e/bo/marktlokation.py @@ -16,7 +16,6 @@ from ..com.adresse import Adresse from ..com.geokoordinaten import Geokoordinaten from ..com.katasteradresse import Katasteradresse - from ..com.messlokationszuordnung import Messlokationszuordnung from ..com.verbrauch import Verbrauch from ..com.zaehlwerk import Zaehlwerk from ..enum.bilanzierungsmethode import Bilanzierungsmethode @@ -28,6 +27,7 @@ from ..enum.sparte import Sparte from ..enum.verbrauchsart import Verbrauchsart from .geschaeftspartner import Geschaeftspartner + from .lokationszuordnung import Lokationszuordnung # pylint: disable=no-name-in-module @@ -47,6 +47,7 @@ class Marktlokation(Geschaeftsobjekt): """ typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.MARKTLOKATION + #: Identifikationsnummer einer Marktlokation, an der Energie entweder verbraucht, oder erzeugt wird. marktlokations_id: Optional[str] = None #: Sparte der Marktlokation, z.B. Gas oder Strom @@ -80,28 +81,6 @@ class Marktlokation(Geschaeftsobjekt): gasqualitaet: Optional["Gasqualitaet"] = None #: Geschäftspartner, dem diese Marktlokation gehört endkunde: Optional["Geschaeftspartner"] = None - zugehoerige_messlokation: Optional["Messlokationszuordnung"] = None # todo: rename to plural - """ - Aufzählung der Messlokationen, die zu dieser Marktlokation gehören. - Es können 3 verschiedene Konstrukte auftreten: - - Beziehung 1 : 0 : Hier handelt es sich um Pauschalanlagen ohne Messung. D.h. die Verbrauchsdaten sind direkt über - die Marktlokation abgreifbar. - Beziehung 1 : 1 : Das ist die Standard-Beziehung für die meisten Fälle. In diesem Fall gibt es zu einer - Marktlokation genau eine Messlokation. - Beziehung 1 : N : Hier liegt beispielsweise eine Untermessung vor. Der Verbrauch einer Marklokation berechnet sich - hier aus mehreren Messungen. - - Es gibt praktisch auch noch die Beziehung N : 1, beispielsweise bei einer Zweirichtungsmessung bei der durch eine - Messeinrichtung die Messung sowohl für die Einspreiseseite als auch für die Aussspeiseseite erfolgt. - Da Abrechnung und Bilanzierung jedoch für beide Marktlokationen getrennt erfolgt, werden nie beide Marktlokationen - gemeinsam betrachtet. Daher lässt sich dieses Konstrukt auf zwei 1:1-Beziehung zurückführen, - wobei die Messlokation in beiden Fällen die gleiche ist. - - In den Zuordnungen sind ist die arithmetische Operation mit der der Verbrauch einer Messlokation zum Verbrauch einer - Marktlokation beitrögt mit aufgeführt. - Der Standard ist hier die Addition. - """ # only one of the following three optional attributes can be set #: Die Adresse, an der die Energie-Lieferung oder -Einspeisung erfolgt @@ -127,3 +106,8 @@ class Marktlokation(Geschaeftsobjekt): zaehlwerke: Optional[list["Zaehlwerk"]] = None verbrauchsmengen: Optional[list["Verbrauch"]] = None zaehlwerke_der_beteiligten_marktrolle: Optional[list["Zaehlwerk"]] = None + + #: Lokationszuordnung, um bspw. die zugehörigen Messlokationen anzugeben + lokationszuordnungen: Optional[list["Lokationszuordnung"]] = None + #: Lokationsbuendel Code, der die Funktion dieses BOs an der Lokationsbuendelstruktur beschreibt. + lokationsbuendel_objektcode: Optional[str] = None diff --git a/src/bo4e/bo/messlokation.py b/src/bo4e/bo/messlokation.py index dc1808af4..a2eb9f713 100644 --- a/src/bo4e/bo/messlokation.py +++ b/src/bo4e/bo/messlokation.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ..bo.geraet import Geraet + from ..bo.lokationszuordnung import Lokationszuordnung from ..com.adresse import Adresse from ..com.dienstleistung import Dienstleistung from ..com.geokoordinaten import Geokoordinaten @@ -39,6 +40,7 @@ class Messlokation(Geschaeftsobjekt): """ typ: Annotated[Optional["Typ"], Field(alias="_typ")] = Typ.MESSLOKATION + #: Die Messlokations-Identifikation; Das ist die frühere Zählpunktbezeichnung messlokations_id: Optional[str] = None #: Sparte der Messlokation, z.B. Gas oder Strom @@ -83,3 +85,7 @@ class Messlokation(Geschaeftsobjekt): Alternativ zu einer postalischen Adresse und Geokoordinaten kann hier eine Ortsangabe mittels Gemarkung und Flurstück erfolgen. """ + #: Lokationszuordnung, um bspw. die zugehörigen Marktlokationen anzugeben + lokationszuordnungen: Optional[list["Lokationszuordnung"]] = None + #: Lokationsbuendel Code, der die Funktion dieses BOs an der Lokationsbuendelstruktur beschreibt. + lokationsbuendel_objektcode: Optional[str] = None diff --git a/src/bo4e/bo/netzlokation.py b/src/bo4e/bo/netzlokation.py new file mode 100644 index 000000000..b4e37cf97 --- /dev/null +++ b/src/bo4e/bo/netzlokation.py @@ -0,0 +1,63 @@ +""" +Contains Netzlokation class +and corresponding marshmallow schema for de-/serialization +""" + +from typing import TYPE_CHECKING, Annotated, Optional + +from pydantic import Field + +from ..enum.typ import Typ +from ..utils import postprocess_docstring +from .geschaeftsobjekt import Geschaeftsobjekt + +if TYPE_CHECKING: + from ..bo.lokationszuordnung import Lokationszuordnung + from ..com.konfigurationsprodukt import Konfigurationsprodukt + from ..com.menge import Menge + from ..com.verwendungszweckpromarktrolle import VerwendungszweckProMarktrolle + from ..enum.marktrolle import Marktrolle + from ..enum.sparte import Sparte + + +# pylint: disable=too-many-instance-attributes, too-few-public-methods + + +@postprocess_docstring +class Netzlokation(Geschaeftsobjekt): + """ + Object containing information about a Netzlokation + + .. raw:: html + + + + .. HINT:: + `Netzlokation JSON Schema `_ + + """ + + typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.NETZLOKATION + + #: Identifikationsnummer einer Netzlokation, an der Energie entweder verbraucht, oder erzeugt wird + netzlokations_id: Optional[str] = None + #: Sparte der Netzlokation, z.B. Gas oder Strom. + sparte: Optional["Sparte"] = None + #: Netzanschlussleistungsmenge der Netzlokation + netzanschlussleistung: Optional["Menge"] = None + #: Codenummer des grundzuständigen Messstellenbetreibers, der für diese Netzlokation zuständig ist. + grundzustaendiger_msb_codenr: Optional[str] = None + #: Ob ein Steuerkanal der Netzlokation zugeordnet ist und somit die Netzlokation gesteuert werden kann. + steuerkanal: Optional[bool] = None + #: Die OBIS-Kennzahl für die Netzlokation + obiskennzahl: Optional[str] = None + #: Verwendungungszweck der Werte Netzlokation + verwendungszweck: Optional["VerwendungszweckProMarktrolle"] = None + #: Produkt-Daten der Netzlokation + konfigurationsprodukte: Optional[list["Konfigurationsprodukt"]] = None + #: Eigenschaft des Messstellenbetreibers an der Lokation + eigenschaft_msb_lokation: Optional["Marktrolle"] = None + #: Lokationszuordnung, um bspw. die zugehörigen Messlokationen anzugeben + lokationszuordnungen: Optional[list["Lokationszuordnung"]] = None + #: Lokationsbuendel Code, der die Funktion dieses BOs an der Lokationsbuendelstruktur beschreibt. + lokationsbuendel_objektcode: Optional[str] = None diff --git a/src/bo4e/bo/steuerbareressource.py b/src/bo4e/bo/steuerbareressource.py new file mode 100644 index 000000000..89bca3186 --- /dev/null +++ b/src/bo4e/bo/steuerbareressource.py @@ -0,0 +1,52 @@ +""" +Contains steuerbare Ressource class +and corresponding marshmallow schema for de-/serialization +""" + +from typing import TYPE_CHECKING, Annotated, Optional + +from pydantic import Field + +from ..enum.typ import Typ +from ..utils import postprocess_docstring +from .geschaeftsobjekt import Geschaeftsobjekt + +if TYPE_CHECKING: + from ..bo.lokationszuordnung import Lokationszuordnung + from ..com.konfigurationsprodukt import Konfigurationsprodukt + from ..enum.marktrolle import Marktrolle + from ..enum.steuerkanalleistungsbeschreibung import SteuerkanalLeistungsbeschreibung + +# pylint: disable=too-many-instance-attributes, too-few-public-methods + + +@postprocess_docstring +class SteuerbareRessource(Geschaeftsobjekt): + """ + Object containing information about a steuerbare Ressource + + .. raw:: html + + + + .. HINT:: + `SteuerbareRessource JSON Schema `_ + + """ + + typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.STEUERBARERESSOURCE + + #: Id der steuerbaren Ressource + steuerbare_ressource_id: Optional[str] = None + #: Leistungsbeschreibung des Steuerkanals + steuerkanal_leistungsbeschreibung: Optional["SteuerkanalLeistungsbeschreibung"] = None + #: Angabe des Messstellenbetreibers, der der Steuerbaren Ressource zugeordnet ist. + zugeordnete_msb_codenummer: Optional[str] = None + #: Produkt-Daten der Steuerbaren Ressource + konfigurationsprodukte: Optional[list["Konfigurationsprodukt"]] = None + #: Eigenschaft des Messstellenbetreibers an der Lokation + eigenschaft_msb_lokation: Optional["Marktrolle"] = None + #: Lokationszuordnung, um bspw. die zugehörigen Messlokationen anzugeben + lokationszuordnungen: Optional[list["Lokationszuordnung"]] = None + #: Lokationsbuendel Code, der die Funktion dieses BOs an der Lokationsbuendelstruktur beschreibt. + lokationsbuendel_objektcode: Optional[str] = None diff --git a/src/bo4e/bo/technischeressource.py b/src/bo4e/bo/technischeressource.py new file mode 100644 index 000000000..4a3257961 --- /dev/null +++ b/src/bo4e/bo/technischeressource.py @@ -0,0 +1,72 @@ +""" +Contains technische Ressource class +and corresponding marshmallow schema for de-/serialization +""" + +from typing import TYPE_CHECKING, Annotated, Optional + +from pydantic import Field + +from ..enum.typ import Typ +from ..utils import postprocess_docstring +from .geschaeftsobjekt import Geschaeftsobjekt + +if TYPE_CHECKING: + from ..bo.lokationszuordnung import Lokationszuordnung + from ..com.menge import Menge + from ..enum.emobilitaetsart import EMobilitaetsart + from ..enum.erzeugungsart import Erzeugungsart + from ..enum.speicherart import Speicherart + from ..enum.technischeressourcenutzung import TechnischeRessourceNutzung + from ..enum.technischeressourceverbrauchsart import TechnischeRessourceVerbrauchsart + from ..enum.waermenutzung import Waermenutzung + +# pylint: disable=too-many-instance-attributes, too-few-public-methods + + +@postprocess_docstring +class TechnischeRessource(Geschaeftsobjekt): + """ + Object containing information about a technische Ressource + + .. raw:: html + + + + .. HINT:: + `TechnischeRessource JSON Schema `_ + + """ + + typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.TECHNISCHERESSOURCE + + #: Identifikationsnummer einer technischen Ressource + technische_ressource_id: Optional[str] = None + #: Vorgelagerte Messlokation ID + vorgelagerte_messlokation_id: Optional[str] = None + #: Referenz auf die der technischen Ressource zugeordneten Marktlokation + zugeordnete_marktlokation_id: Optional[str] = None + #: Referenz auf die der technischen Ressource zugeordneten Steuerbaren Ressource + zugeordnete_steuerbare_ressource_id: Optional[str] = None + #: Nennleistung (Aufnahme) + nennleistungaufnahme: Optional["Menge"] = None + #: Nennleistung (Abgabe) + nennleistungabgabe: Optional["Menge"] = None + #: Speicherkapazität + speicherkapazitaet: Optional["Menge"] = None + #: Art und Nutzung der technischen Ressource + technische_ressource_nutzung: Optional["TechnischeRessourceNutzung"] = None + #: Verbrauchsart der technischen Ressource + technische_ressource_verbrauchsart: Optional["TechnischeRessourceVerbrauchsart"] = None + #: Wärmenutzung + waermenutzung: Optional["Waermenutzung"] = None + #: Art der E-Mobilität + emobilitaetsart: Optional["EMobilitaetsart"] = None + #: Art der Erzeugung der Energie + erzeugungsart: Optional["Erzeugungsart"] = None + #: Art des Speichers + speicherart: Optional["Speicherart"] = None + #: Lokationszuordnung, um bspw. die zugehörigen Messlokationen anzugeben + lokationszuordnungen: Optional[list["Lokationszuordnung"]] = None + #: Lokationsbuendel Code, der die Funktion dieses BOs an der Lokationsbuendelstruktur beschreibt. + lokationsbuendel_objektcode: Optional[str] = None diff --git a/src/bo4e/com/konfigurationsprodukt.py b/src/bo4e/com/konfigurationsprodukt.py new file mode 100644 index 000000000..a8701cfc5 --- /dev/null +++ b/src/bo4e/com/konfigurationsprodukt.py @@ -0,0 +1,35 @@ +""" +Contains Konfigurationsprodukt class +and corresponding marshmallow schema for de-/serialization +""" + +from typing import TYPE_CHECKING, Optional + +from ..utils import postprocess_docstring +from .com import COM + +if TYPE_CHECKING: + from ..bo.marktteilnehmer import Marktteilnehmer + + +# pylint: disable=too-many-instance-attributes, too-few-public-methods + + +@postprocess_docstring +class Konfigurationsprodukt(COM): + """ + Object containing information about a Konfigurationsprodukt + + .. raw:: html + + + + .. HINT:: + `Konfigurationsprodukt JSON Schema `_ + + """ + + produktcode: Optional[str] = None + leistungskurvendefinition: Optional[str] = None + schaltzeitdefinition: Optional[str] = None + marktpartner: Optional["Marktteilnehmer"] = None diff --git a/src/bo4e/com/messlokationszuordnung.py b/src/bo4e/com/messlokationszuordnung.py deleted file mode 100644 index 618156a90..000000000 --- a/src/bo4e/com/messlokationszuordnung.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Contains Messlokationszuordnung class -and corresponding marshmallow schema for de-/serialization -""" - -from typing import TYPE_CHECKING, Optional - -import pydantic - -from ..utils import postprocess_docstring -from .com import COM - -if TYPE_CHECKING: - from ..enum.arithmetische_operation import ArithmetischeOperation - - -# pylint: disable=too-few-public-methods - - -@postprocess_docstring -class Messlokationszuordnung(COM): - """ - Mit dieser Komponente werden Messlokationen zu Marktlokationen zugeordnet. - Dabei kann eine arithmetische Operation (Addition, Subtraktion, Multiplikation, Division) angegeben werden, - mit der die Messlokation zum Verbrauch der Marktlokation beiträgt. - - .. raw:: html - - - - .. HINT:: - `Messlokationszuordnung JSON Schema `_ - - """ - - messlokations_id: Optional[str] = None - """ - ID der zugeordneten Messlokation - """ - arithmetik: Optional["ArithmetischeOperation"] = None - - gueltig_seit: Optional[pydantic.AwareDatetime] = None - """ - inklusives Beginndatum - """ - gueltig_bis: Optional[pydantic.AwareDatetime] = None - """ - exklusives Endedatum - """ diff --git a/src/bo4e/enum/emobilitaetsart.py b/src/bo4e/enum/emobilitaetsart.py new file mode 100644 index 000000000..f4f0ff38a --- /dev/null +++ b/src/bo4e/enum/emobilitaetsart.py @@ -0,0 +1,12 @@ +# pylint: disable=missing-module-docstring +from bo4e.enum.strenum import StrEnum + + +class EMobilitaetsart(StrEnum): + """ + Art der E-Mobilität + """ + + WALLBOX = "WALLBOX" + E_MOBILITAETSLADESAEULE = "E_MOBILITAETSLADESAEULE" + LADEPARK = "LADEPARK" diff --git a/src/bo4e/enum/speicherart.py b/src/bo4e/enum/speicherart.py new file mode 100644 index 000000000..5cf6804a7 --- /dev/null +++ b/src/bo4e/enum/speicherart.py @@ -0,0 +1,13 @@ +# pylint: disable=missing-module-docstring +from bo4e.enum.strenum import StrEnum + + +class Speicherart(StrEnum): + """ + Art der Speicherung + """ + + WASSERSTOFFSPEICHER = "WASSERSTOFFSPEICHER" + PUMPSPEICHER = "PUMPSPEICHER" + BATTERIESPEICHER = "BATTERIESPEICHER" + SONSTIGE_SPEICHERART = "SONSTIGE_SPEICHERART" diff --git a/src/bo4e/enum/steuerkanalleistungsbeschreibung.py b/src/bo4e/enum/steuerkanalleistungsbeschreibung.py new file mode 100644 index 000000000..83a9c0ca9 --- /dev/null +++ b/src/bo4e/enum/steuerkanalleistungsbeschreibung.py @@ -0,0 +1,11 @@ +# pylint: disable=missing-module-docstring +from bo4e.enum.strenum import StrEnum + + +class SteuerkanalLeistungsbeschreibung(StrEnum): + """ + Beschreibung des Steuerkanals + """ + + AN_AUS = "AN_AUS" + GESTUFT = "GESTUFT" diff --git a/src/bo4e/enum/technischeressourcenutzung.py b/src/bo4e/enum/technischeressourcenutzung.py new file mode 100644 index 000000000..2b08db03a --- /dev/null +++ b/src/bo4e/enum/technischeressourcenutzung.py @@ -0,0 +1,12 @@ +# pylint: disable=missing-module-docstring +from bo4e.enum.strenum import StrEnum + + +class TechnischeRessourceNutzung(StrEnum): + """ + Nutzung der technischen Ressource + """ + + STROMVERBRAUCHSART = "STROMVERBRAUCHSART" + STROMERZEUGUNGSART = "STROMERZEUGUNGSART" + SPEICHER = "SPEICHER" diff --git a/src/bo4e/enum/technischeressourceverbrauchsart.py b/src/bo4e/enum/technischeressourceverbrauchsart.py new file mode 100644 index 000000000..c0d4168ee --- /dev/null +++ b/src/bo4e/enum/technischeressourceverbrauchsart.py @@ -0,0 +1,13 @@ +# pylint: disable=missing-module-docstring +from bo4e.enum.strenum import StrEnum + + +class TechnischeRessourceVerbrauchsart(StrEnum): + """ + Verbrauchsart der technischen Ressource + """ + + KRAFT_LICHT = "KRAFT_LICHT" + WAERME = "WAERME" + E_MOBILITAET = "E_MOBILITAET" + STRASSENBELEUCHTUNG = "STRASSENBELEUCHTUNG" diff --git a/src/bo4e/enum/typ.py b/src/bo4e/enum/typ.py index 1124bb99e..d3dcdaf46 100644 --- a/src/bo4e/enum/typ.py +++ b/src/bo4e/enum/typ.py @@ -22,8 +22,11 @@ class Typ(StrEnum): LASTGANG = "LASTGANG" MARKTLOKATION = "MARKTLOKATION" MESSLOKATION = "MESSLOKATION" + NETZLOKATION = "NETZLOKATION" MARKTTEILNEHMER = "MARKTTEILNEHMER" NETZNUTZUNGSRECHNUNG = "NETZNUTZUNGSRECHNUNG" + TECHNISCHERESSOURCE = "TECHNISCHERESSOURCE" + STEUERBARERESSOURCE = "STEUERBARERESSOURCE" PERSON = "PERSON" PREISBLATT = "PREISBLATT" PREISBLATTDIENSTLEISTUNG = "PREISBLATTDIENSTLEISTUNG" @@ -43,3 +46,4 @@ class Typ(StrEnum): VERTRAG = "VERTRAG" ZAEHLER = "ZAEHLER" ZEITREIHE = "ZEITREIHE" + LOKATIONSZUORDNUNG = "LOKATIONSZUORDNUNG" diff --git a/tests/test_konfigurationsprodukt.py b/tests/test_konfigurationsprodukt.py new file mode 100644 index 000000000..d8682459c --- /dev/null +++ b/tests/test_konfigurationsprodukt.py @@ -0,0 +1,27 @@ +import pytest + +from bo4e import Konfigurationsprodukt, Marktteilnehmer +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestKonfigurationsprodukt: + @pytest.mark.parametrize( + "konfigurationsprodukt", + [ + pytest.param( + Konfigurationsprodukt( + produktcode="1212121212", + leistungskurvendefinition="", + schaltzeitdefinition="", + marktpartner=Marktteilnehmer(), + ), + id="all attributes at first level", + ), + ], + ) + def test_serialization_roundtrip(self, konfigurationsprodukt: Konfigurationsprodukt) -> None: + """ + Test de-/serialisation of Konfigurationsprodukt. + """ + + assert_serialization_roundtrip(konfigurationsprodukt) diff --git a/tests/test_lokationszuordnung.py b/tests/test_lokationszuordnung.py new file mode 100644 index 000000000..f16a7aad7 --- /dev/null +++ b/tests/test_lokationszuordnung.py @@ -0,0 +1,38 @@ +import pytest +from pydantic import AwareDatetime + +from bo4e import ( + Lokationszuordnung, + Marktlokation, + Messlokation, + Netzlokation, + SteuerbareRessource, + TechnischeRessource, + Zeitspanne, +) +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestLokationszuordnung: + @pytest.mark.parametrize( + "lokationszuordnung", + [ + pytest.param( + Lokationszuordnung( + marktlokationen=[Marktlokation()], + messlokationen=[Messlokation()], + netzlokationen=[Netzlokation()], + technische_ressourcen=[TechnischeRessource()], + steuerbare_ressourcen=[SteuerbareRessource()], + gueltigkeit=Zeitspanne(), + zuordnungstyp="Zuordnungstyp", + lokationsbuendelcode="9992 00000 125 6", + ), + ), + ], + ) + def test_serialization_roundtrip(self, lokationszuordnung: Lokationszuordnung) -> None: + """ + Test de-/serialisation of Lokationszuordnung. + """ + assert_serialization_roundtrip(lokationszuordnung) diff --git a/tests/test_marktlokation.py b/tests/test_marktlokation.py index 161b18de0..9c9cfaa84 100644 --- a/tests/test_marktlokation.py +++ b/tests/test_marktlokation.py @@ -6,6 +6,7 @@ Energierichtung, Geschaeftspartner, Kundentyp, + Lokationszuordnung, Marktlokation, Netzebene, Sparte, @@ -28,6 +29,8 @@ class TestMaLo: netzebene=Netzebene.NSP, endkunde=Geschaeftspartner(), kundengruppen=[Kundentyp.GEWERBE, Kundentyp.PRIVAT], + lokationsbuendel_objektcode="9992 00000 125 6", + lokationszuordnungen=[Lokationszuordnung()], ) ) ], diff --git a/tests/test_messlokation.py b/tests/test_messlokation.py index a939090cf..9d3be647c 100644 --- a/tests/test_messlokation.py +++ b/tests/test_messlokation.py @@ -1,6 +1,6 @@ import pytest -from bo4e import Adresse, Dienstleistung, Geraet, Messlokation, Netzebene, Sparte, Zaehler +from bo4e import Adresse, Dienstleistung, Geraet, Lokationszuordnung, Messlokation, Netzebene, Sparte, Zaehler from tests.serialization_helper import assert_serialization_roundtrip @@ -25,6 +25,8 @@ class TestMeLo: messlokationszaehler=[Zaehler()], grundzustaendiger_msb_codenr="9910125000002", messadresse=Adresse(), + lokationsbuendel_objektcode="9992 00000 125 6", + lokationszuordnungen=[Lokationszuordnung()], ) ) ], diff --git a/tests/test_messlokationszuordnung.py b/tests/test_messlokationszuordnung.py deleted file mode 100644 index 62877fbc5..000000000 --- a/tests/test_messlokationszuordnung.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import datetime, timezone - -import pytest - -from bo4e import ArithmetischeOperation, Messlokationszuordnung -from tests.serialization_helper import assert_serialization_roundtrip - - -class TestMesslokationszuordnung: - @pytest.mark.parametrize( - "messlokationszuordnung", - [ - pytest.param( - Messlokationszuordnung( - messlokations_id="DE0010688516810000000000000012345", - arithmetik=ArithmetischeOperation.ADDITION, - gueltig_seit=datetime(year=2021, month=1, day=13).replace(tzinfo=timezone.utc), - gueltig_bis=datetime(year=2021, month=5, day=4).replace(tzinfo=timezone.utc), - ) - ) - ], - ) - def test_serialization_roundtrip(self, messlokationszuordnung: Messlokationszuordnung) -> None: - """ - Test de-/serialisation of Messlokationszuordnung. - """ - - assert_serialization_roundtrip(messlokationszuordnung) diff --git a/tests/test_netzlokation.py b/tests/test_netzlokation.py new file mode 100644 index 000000000..8263bc9e2 --- /dev/null +++ b/tests/test_netzlokation.py @@ -0,0 +1,44 @@ +import pytest + +from bo4e import ( + Konfigurationsprodukt, + Lokationszuordnung, + Marktrolle, + Menge, + Netzlokation, + Sparte, + Verwendungszweck, + VerwendungszweckProMarktrolle, +) +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestNetzlokation: + @pytest.mark.parametrize( + "netzlokation", + [ + pytest.param( + Netzlokation( + netzlokations_id="3784658734657", + sparte=Sparte.GAS, + netzanschlussleistung=Menge(), + grundzustaendiger_msb_codenr="1829371872392", + steuerkanal=False, + obiskennzahl="82376487236", + verwendungszweck=VerwendungszweckProMarktrolle( + marktrolle=Marktrolle.LF, + Zwecke=[Verwendungszweck.BILANZKREISABRECHNUNG], + ), + konfigurationsprodukte=[Konfigurationsprodukt()], + eigenschaft_msb_lokation=Marktrolle.LF, + lokationsbuendel_objektcode="9992 00000 125 6", + lokationszuordnungen=[Lokationszuordnung()], + ), + ), + ], + ) + def test_serialization_roundtrip(self, netzlokation: Netzlokation) -> None: + """ + Test de-/serialisation of Netzlokation. + """ + assert_serialization_roundtrip(netzlokation) diff --git a/tests/test_steuerbareressource.py b/tests/test_steuerbareressource.py new file mode 100644 index 000000000..e978fe262 --- /dev/null +++ b/tests/test_steuerbareressource.py @@ -0,0 +1,31 @@ +import pytest + +from bo4e import Marktrolle, SteuerbareRessource +from bo4e.bo.lokationszuordnung import Lokationszuordnung +from bo4e.com.konfigurationsprodukt import Konfigurationsprodukt +from bo4e.enum.steuerkanalleistungsbeschreibung import SteuerkanalLeistungsbeschreibung +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestSteuerbareRessource: + @pytest.mark.parametrize( + "steuerbare_ressource", + [ + pytest.param( + SteuerbareRessource( + steuerbare_ressource_id="3784658734657", + steuerkanal_leistungsbeschreibung=SteuerkanalLeistungsbeschreibung.AN_AUS, + zugeordnete_msb_codenummer="1829371872392", + konfigurationsprodukte=[Konfigurationsprodukt()], + eigenschaft_msb_lokation=Marktrolle.LF, + lokationsbuendel_objektcode="9992 00000 125 6", + lokationszuordnungen=[Lokationszuordnung()], + ), + ), + ], + ) + def test_serialization_roundtrip(self, steuerbare_ressource: SteuerbareRessource) -> None: + """ + Test de-/serialisation of SteuerbareRessource. + """ + assert_serialization_roundtrip(steuerbare_ressource) diff --git a/tests/test_technischeressource.py b/tests/test_technischeressource.py new file mode 100644 index 000000000..c5188efd1 --- /dev/null +++ b/tests/test_technischeressource.py @@ -0,0 +1,48 @@ +from decimal import Decimal + +import pytest + +from bo4e import ( + EMobilitaetsart, + Erzeugungsart, + Lokationszuordnung, + Menge, + Speicherart, + TechnischeRessource, + TechnischeRessourceNutzung, + TechnischeRessourceVerbrauchsart, + Waermenutzung, +) +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestTechnischeRessource: + @pytest.mark.parametrize( + "technische_ressource", + [ + pytest.param( + TechnischeRessource( + technische_ressource_id="3784658734657", + vorgelagerte_messlokation_id="37846587343434", + zugeordnete_marktlokation_id="12242432423", + zugeordnete_steuerbare_ressource_id="281238912739", + nennleistungaufnahme=Menge(wert=Decimal(1000.0)), + nennleistungabgabe=Menge(wert=Decimal(1000.0)), + speicherkapazitaet=Menge(wert=Decimal(1000.0)), + technische_ressource_nutzung=TechnischeRessourceNutzung.SPEICHER, + technische_ressource_verbrauchsart=TechnischeRessourceVerbrauchsart.KRAFT_LICHT, + waermenutzung=Waermenutzung.SPEICHERHEIZUNG, + emobilitaetsart=EMobilitaetsart.WALLBOX, + erzeugungsart=Erzeugungsart.WIND, + speicherart=Speicherart.WASSERSTOFFSPEICHER, + lokationsbuendel_objektcode="9992 00000 125 6", + lokationszuordnungen=[Lokationszuordnung()], + ), + ), + ], + ) + def test_serialization_roundtrip(self, technische_ressource: TechnischeRessource) -> None: + """ + Test de-/serialisation of TechnischeRessource. + """ + assert_serialization_roundtrip(technische_ressource)