Repository: bo4e/BO4E-python (language: python)

Issue:
Implement COM Tarifberechnungsparameter
https://www.bo4e.de/dokumentation/komponenten/com-tarifberechnungsparameter
- #88 
- #104 
- #145 
- #166 


Discussion / hints:
@rmorlock einige tarif-bo geschichten hängen hier dran. hast du schon eien draft hierfür?

Fix patch:
diff --git a/src/bo4e/com/tarifberechnungsparameter.py b/src/bo4e/com/tarifberechnungsparameter.py
new file mode 100644
index 000000000..77c96bc42
--- /dev/null
+++ b/src/bo4e/com/tarifberechnungsparameter.py
@@ -0,0 +1,103 @@
+"""
+Contains Tarifberechnungsparameter class
+and corresponding marshmallow schema for de-/serialization
+"""
+from decimal import Decimal
+from typing import List, Optional
+
+import attr
+from marshmallow import fields
+from marshmallow_enum import EnumField  # type:ignore[import]
+
+from bo4e.com.com import COM, COMSchema
+from bo4e.com.preis import Preis, PreisSchema
+from bo4e.com.tarifpreis import Tarifpreis, TarifpreisSchema
+from bo4e.enum.messpreistyp import Messpreistyp
+from bo4e.enum.tarifkalkulationsmethode import Tarifkalkulationsmethode
+
+# yes. there is no description in the official docs.
+# https://github.com/Hochfrequenz/BO4E-python/issues/328
+
+# pylint: disable=too-few-public-methods, empty-docstring, too-many-instance-attributes
+@attr.s(auto_attribs=True, kw_only=True)
+class Tarifberechnungsparameter(COM):
+    """ """
+
+    # there are no required attributes
+    # optional attributes
+
+    #: Gibt an, wie die Einzelpreise des Tarifes zu verarbeiten sind
+    berechnungsmethode: Optional[Tarifkalkulationsmethode] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Tarifkalkulationsmethode))
+    )
+    #: True, falls der Messpreis im Grundpreis (GP) enthalten ist
+    messpreis_in_gp_enthalten: Optional[bool] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(bool))
+    )
+
+    messpreis_beruecksichtigen: Optional[bool] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(bool))
+    )
+    """
+    True, falls bei der Bildung des Durchschnittspreises für die Höchst- und Mindestpreisbetrachtung der Messpreis mit
+    berücksichtigt wird
+    """
+
+    #: Typ des Messpreises
+    messpreistyp: Optional[Messpreistyp] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Messpreistyp))
+    )
+
+    #: Im Preis bereits eingeschlossene Leistung (für Gas)
+    kw_inklusive: Optional[Decimal] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Decimal))
+    )
+    # todo: type decimal is most likely wrong: https://github.com/Hochfrequenz/BO4E-python/issues/327
+
+    #: Intervall, indem die über "kwInklusive" hinaus abgenommene Leistung kostenpflichtig wird (z.B. je 5 kW 20 EURO)
+    kw_weitere_mengen: Optional[Decimal] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Decimal))
+    )
+    # todo: type decimal is most likely wrong: https://github.com/Hochfrequenz/BO4E-python/issues/327
+
+    #: Höchstpreis für den Durchschnitts-Arbeitspreis NT
+    hoechstpreis_n_t: Optional[Preis] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Preis))
+    )
+    #: Höchstpreis für den Durchschnitts-Arbeitspreis HT
+    hoechstpreis_h_t: Optional[Preis] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Preis))
+    )
+    #: Mindestpreis für den Durchschnitts-Arbeitspreis
+    mindestpreis: Optional[Preis] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Preis))
+    )
+    #: Liste mit zusätzlichen Preisen, beispielsweise Messpreise und/oder Leistungspreise
+    zusatzpreise: Optional[List[Tarifpreis]] = attr.ib(
+        default=None,
+        validator=attr.validators.optional(
+            attr.validators.deep_iterable(
+                member_validator=attr.validators.instance_of(Tarifpreis),
+                iterable_validator=attr.validators.instance_of(list),
+            )
+        ),
+    )
+
+
+class TarifberechnungsparameterSchema(COMSchema):
+    """
+    Schema for de-/serialization of Tarifberechnungsparameter
+    """
+
+    class_name = Tarifberechnungsparameter
+    # optional attributes
+    berechnungsmethode = EnumField(Tarifkalkulationsmethode)
+    messpreis_in_gp_enthalten = fields.Bool()
+    messpreis_beruecksichtigen = fields.Bool()
+    messpreistyp = EnumField(Messpreistyp)
+    kw_inklusive = fields.Decimal(as_string=True)
+    kw_weitere_mengen = fields.Decimal(as_string=True)
+    hoechstpreis_n_t = fields.Nested(PreisSchema, data_key="hoechstpreisHT")
+    hoechstpreis_h_t = fields.Nested(PreisSchema, data_key="hoechstpreisNT")
+    mindestpreis = fields.Nested(PreisSchema)
+    zusatzpreise = fields.List(fields.Nested(TarifpreisSchema))
diff --git a/src/bo4e/com/tarifpreis.py b/src/bo4e/com/tarifpreis.py
index 562b503ec..cad767d23 100644
--- a/src/bo4e/com/tarifpreis.py
+++ b/src/bo4e/com/tarifpreis.py
@@ -31,7 +31,7 @@ class Tarifpreis(Preis):
 
 class TarifpreisSchema(PreisSchema):
     """
-    Schema for de-/serialization of Tarifpreis.
+    Schema for de-/serialization of Tarifpreis
     """
 
     class_name = Tarifpreis  # type:ignore[assignment]


Test patch:
diff --git a/tests/test_preis.py b/tests/test_preis.py
index b01cf2e6b..147bf0990 100644
--- a/tests/test_preis.py
+++ b/tests/test_preis.py
@@ -7,7 +7,7 @@
 from bo4e.enum.preisstatus import Preisstatus
 from bo4e.enum.waehrungseinheit import Waehrungseinheit
 
-example_preis = Preis(wert=Decimal(2.53), einheit=Waehrungseinheit.EUR, bezugswert=Mengeneinheit.KWH)
+example_preis = Preis(wert=Decimal(12.5), einheit=Waehrungseinheit.EUR, bezugswert=Mengeneinheit.KWH)
 
 
 class TestPreis:
diff --git a/tests/test_tarifberechnungsparameter.py b/tests/test_tarifberechnungsparameter.py
new file mode 100644
index 000000000..1a6210ffc
--- /dev/null
+++ b/tests/test_tarifberechnungsparameter.py
@@ -0,0 +1,43 @@
+from decimal import Decimal
+
+import pytest  # type:ignore[import]
+
+from bo4e.com.fremdkostenposition import Fremdkostenposition
+from bo4e.com.tarifberechnungsparameter import Tarifberechnungsparameter, TarifberechnungsparameterSchema
+from bo4e.enum.messpreistyp import Messpreistyp
+from bo4e.enum.tarifkalkulationsmethode import Tarifkalkulationsmethode
+from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
+from tests.test_preis import example_preis  # type:ignore[import]
+from tests.test_tarifpreis import example_tarifpreis  # type:ignore[import]
+
+
+class TestFremdkostenposition:
+    @pytest.mark.parametrize(
+        "tarifberechnungsparameter",
+        [
+            pytest.param(
+                Tarifberechnungsparameter(
+                    berechnungsmethode=Tarifkalkulationsmethode.ZONEN,
+                    messpreis_in_gp_enthalten=True,
+                    kw_inklusive=Decimal(12.5),
+                    kw_weitere_mengen=Decimal(12.5),
+                    messpreistyp=Messpreistyp.MESSPREIS_G6,
+                    messpreis_beruecksichtigen=True,
+                    hoechstpreis_h_t=example_preis,
+                    hoechstpreis_n_t=example_preis,
+                    mindestpreis=example_preis,
+                    zusatzpreise=[example_tarifpreis],
+                ),
+                id="maximal attributes",
+            ),
+        ],
+    )
+    def test_serialization_roundtrip(self, tarifberechnungsparameter: Fremdkostenposition):
+        """
+        Test de-/serialisation
+        """
+        assert_serialization_roundtrip(tarifberechnungsparameter, TarifberechnungsparameterSchema())
+
+    def test_missing_required_attribute(self):
+        _ = Tarifberechnungsparameter()
+        # ok, we're done. no exception here because there are no required attributes
diff --git a/tests/test_tarifpreis.py b/tests/test_tarifpreis.py
index 7e7c838a7..ea475bd02 100644
--- a/tests/test_tarifpreis.py
+++ b/tests/test_tarifpreis.py
@@ -8,18 +8,20 @@
 from bo4e.enum.preistyp import Preistyp
 from bo4e.enum.waehrungseinheit import Waehrungseinheit
 
+example_tarifpreis = Tarifpreis(
+    wert=Decimal(12.5),
+    einheit=Waehrungseinheit.EUR,
+    bezugswert=Mengeneinheit.KWH,
+    preistyp=Preistyp.ARBEITSPREIS_HT,
+)
 
-class TestPreis:
+
+class TestTarifpreis:
     def test_tarifpreis_only_required(self):
         """
         Test de-/serialisation of Tarifpreis (only has required attributes).
         """
-        tarifpreis = Tarifpreis(
-            wert=Decimal(3.78),
-            einheit=Waehrungseinheit.EUR,
-            bezugswert=Mengeneinheit.KWH,
-            preistyp=Preistyp.ARBEITSPREIS_HT,
-        )
+        tarifpreis = example_tarifpreis
 
         schema = TarifpreisSchema()
         json_string = schema.dumps(tarifpreis, ensure_ascii=False)