diff --git a/docs/api/bo4e.com.rst b/docs/api/bo4e.com.rst index 33b329894..ff24ac4b4 100644 --- a/docs/api/bo4e.com.rst +++ b/docs/api/bo4e.com.rst @@ -100,6 +100,14 @@ bo4e.com.externereferenz module :undoc-members: :show-inheritance: +bo4e.com.fremdkostenposition module +----------------------------------- + +.. automodule:: bo4e.com.fremdkostenposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.geokoordinaten module ------------------------------ @@ -260,6 +268,14 @@ bo4e.com.steuerbetrag module :undoc-members: :show-inheritance: +bo4e.com.tagesvektor module +--------------------------- + +.. automodule:: bo4e.com.tagesvektor + :members: + :undoc-members: + :show-inheritance: + bo4e.com.tarifeinschraenkung module ----------------------------------- @@ -284,6 +300,14 @@ bo4e.com.unterschrift module :undoc-members: :show-inheritance: +bo4e.com.verbrauch module +------------------------- + +.. automodule:: bo4e.com.verbrauch + :members: + :undoc-members: + :show-inheritance: + bo4e.com.vertragskonditionen module ----------------------------------- diff --git a/src/bo4e/com/fremdkostenposition.py b/src/bo4e/com/fremdkostenposition.py new file mode 100644 index 000000000..e4a0101a1 --- /dev/null +++ b/src/bo4e/com/fremdkostenposition.py @@ -0,0 +1,115 @@ +""" +Contains Fremdkostenposition and corresponding marshmallow schema for de-/serialization +""" +from datetime import datetime +from typing import Optional + +import attr +from marshmallow import fields, post_load + +from bo4e.com.betrag import Betrag, BetragSchema +from bo4e.com.com import COM, COMSchema +from bo4e.com.menge import Menge, MengeSchema +from bo4e.com.preis import Preis, PreisSchema + + +# pylint: disable=too-few-public-methods, too-many-instance-attributes +@attr.s(auto_attribs=True, kw_only=True) +class Fremdkostenposition(COM): + """ + Eine Kostenposition im Bereich der Fremdkosten + """ + + # required attributes + #: Ein Titel für die Zeile. Hier kann z.B. der Netzbetreiber eingetragen werden, wenn es sich um Netzkosten handelt + positionstitel: str = attr.ib(validator=attr.validators.instance_of(str)) + + betrag_kostenposition: Betrag = attr.ib(validator=attr.validators.instance_of(Betrag)) + """Der errechnete Gesamtbetrag der Position als Ergebnis der Berechnung oder + """ + # todo: validate above calculation, see https://github.com/Hochfrequenz/BO4E-python/issues/282 + + #: Bezeichnung für den Artikel für den die Kosten ermittelt wurden. Beispiel: Arbeitspreis HT + artikelbezeichnung: str = attr.ib(validator=attr.validators.instance_of(str)) + + #: Der Preis für eine Einheit. Beispiele: 5,8200 ct/kWh oder 55 €/Jahr. + einzelpreis: Preis = attr.ib(validator=attr.validators.instance_of(Preis)) + + # optional attributes + #: inklusiver von-Zeitpunkt der Kostenzeitscheibe + von: Optional[datetime] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(datetime)) + ) + #: exklusiver bis-Zeitpunkt der Kostenzeitscheibe + bis: Optional[datetime] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(datetime)) + ) + # todo: implement von/bis validation as soon as https://github.com/Hochfrequenz/BO4E-python/pull/266 is merged + + #: Die Menge, die in die Kostenberechnung eingeflossen ist. Beispiel: 3.660 kWh + menge: Optional[Menge] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(Menge)) + ) + + zeitmenge: Optional[Menge] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(Menge)) + ) + """ + Wenn es einen zeitbasierten Preis gibt (z.B. €/Jahr), dann ist hier die Menge angegeben mit der die Kosten berechnet + wurden. Z.B. 138 Tage. + """ + + #: Detaillierung des Artikels (optional). Beispiel: 'Drehstromzähler' + artikeldetail: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + + #: Der Name des Marktpartners, der die Preise festlegt, bzw. die Kosten in Rechnung stellt + marktpartnername: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + + #: Die Codenummer (z.B. BDEW-Codenummer) des Marktpartners, der die Preise festlegt / die Kosten in Rechnung stellt + marktpartnercode: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + + #: EIC-Code des Regel- oder Marktgebietes eingetragen. Z.B. '10YDE-EON------1' für die Regelzone TenneT + gebietcode_eic: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + # todo: see issue https://github.com/Hochfrequenz/BO4E-python/issues/147 for EIC validation + + #: Link zum veröffentlichten Preisblatt + link_preisblatt: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + + +class FremdkostenpositionSchema(COMSchema): + """ + Schema for de-/serialization of Fremdkostenposition + """ + + # required attributes + positionstitel = fields.Str() + betrag_kostenposition = fields.Nested(BetragSchema) + artikelbezeichnung = fields.Str() + einzelpreis = fields.Nested(PreisSchema) + + # optional attributes + von = fields.DateTime(allow_none=True) + bis = fields.DateTime(allow_none=True) + menge = fields.Nested(MengeSchema, allow_none=True) + zeitmenge = fields.Nested(MengeSchema, allow_none=True) + artikeldetail = fields.Str(allow_none=True) + marktpartnername = fields.Str(allow_none=True) + marktpartnercode = fields.Str(allow_none=True) + gebietcode_eic = fields.Str(allow_none=True) + link_preisblatt = fields.Str(allow_none=True) + + # pylint: disable=no-self-use, unused-argument + @post_load + def deserialize(self, data, **kwargs) -> Fremdkostenposition: + """Deserialize JSON to Fremdkostenposition object""" + return Fremdkostenposition(**data) diff --git a/src/bo4e/com/tagesvektor.py b/src/bo4e/com/tagesvektor.py index 3270bfc61..4e7e8b8cd 100644 --- a/src/bo4e/com/tagesvektor.py +++ b/src/bo4e/com/tagesvektor.py @@ -10,7 +10,6 @@ from bo4e.com.com import COM, COMSchema from bo4e.com.zeitreihenwertkompakt import Zeitreihenwertkompakt, ZeitreihenwertkompaktSchema - # pylint: disable=too-few-public-methods from bo4e.validators import check_list_length_at_least_one diff --git a/src/bo4e/com/verbrauch.py b/src/bo4e/com/verbrauch.py index 91cb85206..c12ea2f92 100644 --- a/src/bo4e/com/verbrauch.py +++ b/src/bo4e/com/verbrauch.py @@ -12,7 +12,7 @@ from bo4e.com.com import COM, COMSchema from bo4e.enum.mengeneinheit import Mengeneinheit from bo4e.enum.wertermittlungsverfahren import Wertermittlungsverfahren -from bo4e.validators import obis_validator, check_bis_is_later_than_von +from bo4e.validators import check_bis_is_later_than_von, obis_validator # pylint: disable=too-few-public-methods diff --git a/src/bo4e/validators.py b/src/bo4e/validators.py index 844f93552..c26e7384c 100644 --- a/src/bo4e/validators.py +++ b/src/bo4e/validators.py @@ -2,7 +2,7 @@ Contains validators for BO s and COM s classes. """ from datetime import datetime -from typing import Protocol, Optional +from typing import Optional, Protocol import attr.validators diff --git a/tests/test_fremdkostenposition.py b/tests/test_fremdkostenposition.py new file mode 100644 index 000000000..fabf099eb --- /dev/null +++ b/tests/test_fremdkostenposition.py @@ -0,0 +1,108 @@ +from datetime import datetime, timezone +from decimal import Decimal + +import pytest # type:ignore[import] + +from bo4e.com.betrag import Betrag +from bo4e.com.fremdkostenposition import Fremdkostenposition, FremdkostenpositionSchema +from bo4e.com.preis import Preis +from bo4e.com.preisstaffel import Preisstaffel +from bo4e.enum.mengeneinheit import Mengeneinheit +from bo4e.enum.preisstatus import Preisstatus +from bo4e.enum.waehrungscode import Waehrungscode +from bo4e.enum.waehrungseinheit import Waehrungseinheit +from tests.serialization_helper import assert_serialization_roundtrip # type:ignore[import] +from tests.test_menge import example_menge # type:ignore[import] +from tests.test_sigmoidparameter import example_sigmoidparameter # type:ignore[import] + + +class TestFremdkostenposition: + @pytest.mark.parametrize( + "fremdkostenposition, expected_json_dict", + [ + pytest.param( + Fremdkostenposition( + positionstitel="Mudders Preisstaffel", + artikelbezeichnung="Dei Mudder ihr Preisstaffel", + einzelpreis=Preis( + wert=Decimal(3.50), + einheit=Waehrungseinheit.EUR, + bezugswert=Mengeneinheit.KWH, + status=Preisstatus.ENDGUELTIG, + ), + betrag_kostenposition=Betrag( + waehrung=Waehrungscode.EUR, + wert=Decimal(12.5), + ), + ), + { + "einzelpreis": {"wert": "3.5", "status": "ENDGUELTIG", "bezugswert": "KWH", "einheit": "EUR"}, + "bis": None, + "artikeldetail": None, + "positionstitel": "Mudders Preisstaffel", + "artikelbezeichnung": "Dei Mudder ihr Preisstaffel", + "von": None, + "marktpartnercode": None, + "menge": None, + "marktpartnername": None, + "zeitmenge": None, + "gebietcodeEic": None, + "betragKostenposition": {"wert": "12.5", "waehrung": "EUR"}, + "linkPreisblatt": None, + }, + id="only required attributes", + ), + pytest.param( + Fremdkostenposition( + positionstitel="Vadders Preisstaffel", + von=datetime(2013, 5, 1, tzinfo=timezone.utc), + bis=datetime(2014, 5, 1, tzinfo=timezone.utc), + artikelbezeichnung="Deim Vadder sei Preisstaffel", + link_preisblatt="http://foo.bar/", + zeitmenge=example_menge, + einzelpreis=Preis( + wert=Decimal(3.50), + einheit=Waehrungseinheit.EUR, + bezugswert=Mengeneinheit.KWH, + status=Preisstatus.ENDGUELTIG, + ), + betrag_kostenposition=Betrag( + waehrung=Waehrungscode.EUR, + wert=Decimal(12.5), + ), + menge=example_menge, + artikeldetail="foo", + marktpartnercode="986543210123", + marktpartnername="Mein MP", + gebietcode_eic="not an eic code but validation will follow in ticket 146", + ), + { + "marktpartnername": "Mein MP", + "artikelbezeichnung": "Deim Vadder sei Preisstaffel", + "menge": {"einheit": "MWH", "wert": "3.410000000000000142108547152020037174224853515625"}, + "artikeldetail": "foo", + "zeitmenge": {"einheit": "MWH", "wert": "3.410000000000000142108547152020037174224853515625"}, + "positionstitel": "Vadders Preisstaffel", + "marktpartnercode": "986543210123", + "bis": "2014-05-01T00:00:00+00:00", + "von": "2013-05-01T00:00:00+00:00", + "einzelpreis": {"einheit": "EUR", "status": "ENDGUELTIG", "wert": "3.5", "bezugswert": "KWH"}, + "betragKostenposition": {"wert": "12.5", "waehrung": "EUR"}, + "gebietcodeEic": "not an eic code but validation will follow in ticket 146", + "linkPreisblatt": "http://foo.bar/", + }, + id="required and optional attributes", + ), + ], + ) + def test_serialization_roundtrip(self, fremdkostenposition: Fremdkostenposition, expected_json_dict: dict): + """ + Test de-/serialisation of Fremdkostenposition. + """ + assert_serialization_roundtrip(fremdkostenposition, FremdkostenpositionSchema(), expected_json_dict) + + def test_missing_required_attribute(self): + with pytest.raises(TypeError) as excinfo: + _ = Fremdkostenposition() + + assert "missing 4 required" in str(excinfo.value)