Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2033cb0
✨Implement BO Regionaltarif
hf-aschloegl Jan 21, 2022
073ec87
✅ Add tests
hf-aschloegl Jan 21, 2022
cf05132
📦📝Generated docs
hf-aschloegl Jan 21, 2022
7c45786
Implement COM TarifpreisstaffelProOrt
hf-kklein Jan 22, 2022
072ce37
Imiplement COM TarifpreispositionProOrt
hf-kklein Jan 22, 2022
32970e7
pylint
hf-kklein Jan 22, 2022
ab76f2f
Implement BO Tarif
hf-kklein Jan 22, 2022
6beb6c4
Merge branch 'master' into tarif
hf-kklein Jan 23, 2022
4d443e4
✅ Add test with minimal attributes
hf-krechan Jan 23, 2022
7d48342
Update src/bo4e/com/tarifpreispositionproort.py
hf-kklein Jan 23, 2022
a0043ac
Update src/bo4e/com/tarifpreispositionproort.py
hf-kklein Jan 23, 2022
85223db
Update src/bo4e/bo/tarif.py
hf-kklein Jan 23, 2022
431d82d
Update src/bo4e/bo/tarif.py
hf-kklein Jan 23, 2022
6a5e7e0
Update tests/test_tarifpreisstaffelproort.py
hf-kklein Jan 23, 2022
6ed5e76
Update tests/test_tarifpreisstaffelproort.py
hf-kklein Jan 23, 2022
fd72520
Update tests/test_tarifpreispositionproort.py
hf-kklein Jan 23, 2022
70d7215
Update tests/test_tarifpreisstaffelproort.py
hf-kklein Jan 23, 2022
73dd6e9
Update tests/test_tarifpreispositionproort.py
hf-kklein Jan 23, 2022
e5ab4db
Update src/bo4e/bo/tarif.py
hf-kklein Jan 23, 2022
cd81da2
Update src/bo4e/bo/tarif.py
hf-kklein Jan 23, 2022
13ee89a
Merge branch 'master' into tarif
hf-kklein Jan 23, 2022
c671ba2
use data_key
hf-kklein Jan 23, 2022
231e256
Merge remote-tracking branch 'origin/tarif' into tarif
hf-kklein Jan 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/bo4e/bo/tarif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
Contains Tarif class and corresponding marshmallow schema for de-/serialization
"""

from datetime import datetime
from typing import List, Optional

import attr
from marshmallow import fields

from bo4e.bo.tarifinfo import Tarifinfo, TarifinfoSchema
from bo4e.com.aufabschlagregional import AufAbschlagRegional, AufAbschlagRegionalSchema
from bo4e.com.preisgarantie import Preisgarantie, PreisgarantieSchema
from bo4e.com.tarifberechnungsparameter import Tarifberechnungsparameter, TarifberechnungsparameterSchema
from bo4e.com.tarifeinschraenkung import Tarifeinschraenkung, TarifeinschraenkungSchema
from bo4e.com.tarifpreispositionproort import TarifpreispositionProOrt, TarifpreispositionProOrtSchema
from bo4e.enum.botyp import BoTyp
from bo4e.validators import check_list_length_at_least_one


# pylint: disable=too-few-public-methods
@attr.s(auto_attribs=True, kw_only=True)
class Tarif(Tarifinfo):
"""
Abbildung eines Tarifs mit regionaler Zuordnung von Preisen und Auf- und Abschlägen
"""

bo_typ: BoTyp = attr.ib(default=BoTyp.TARIF)
# required attributes
#: Gibt an, wann der Preis zuletzt angepasst wurde
preisstand: datetime = attr.ib(validator=attr.validators.instance_of(datetime))
#: Für die Berechnung der Kosten sind die hier abgebildeten Parameter heranzuziehen
berechnungsparameter: Tarifberechnungsparameter = attr.ib(
validator=attr.validators.instance_of(Tarifberechnungsparameter)
)
#: Die festgelegten Preise mit regionaler Eingrenzung z.B. für Arbeitspreis, Grundpreis etc.
tarifpreise: List[TarifpreispositionProOrt] = attr.ib(
validator=attr.validators.deep_iterable(
member_validator=attr.validators.instance_of(TarifpreispositionProOrt),
iterable_validator=check_list_length_at_least_one,
)
)

# optional attributes
#: Auf- und Abschläge auf die Preise oder Kosten mit regionaler Eingrenzung
tarif_auf_abschlaege: Optional[List[AufAbschlagRegional]] = attr.ib(
default=None,
validator=attr.validators.optional(
attr.validators.deep_iterable(
member_validator=attr.validators.instance_of(AufAbschlagRegional),
iterable_validator=attr.validators.instance_of(list),
)
),
)
# todo: fix inconsistency: RegionalerAufAbschlag vs. AufAbschlagRegional
# https://github.com/Hochfrequenz/BO4E-python/issues/345

#: Preisgarantie für diesen Tarif
preisgarantie: Optional[Preisgarantie] = attr.ib(
default=None,
validator=attr.validators.optional(
attr.validators.instance_of(Preisgarantie),
),
)
# todo: fix inconsistency with regionaltarif https://github.com/Hochfrequenz/BO4E-python/issues/346
#: Die Bedingungen und Einschränkungen unter denen ein Tarif angewendet werden kann
tarifeinschraenkung: Optional[Tarifeinschraenkung] = attr.ib(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tarifeinschraenkung: Optional[Tarifeinschraenkung] = attr.ib(
tarifeinschraenkungen: Optional[Tarifeinschraenkung] = attr.ib(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das ist absicht. Da ist die bestehende Doku glaube ich Quatsch. Wir sollten nichts als Plural benennen, was kein Plural ist.

default=None, validator=attr.validators.optional(attr.validators.instance_of(Tarifeinschraenkung))
)


class TarifSchema(TarifinfoSchema):
"""
Schema for de-/serialization of Tarif
"""

class_name = Tarif # type:ignore[assignment]

# required attributes
preisstand = fields.DateTime()
berechnungsparameter = fields.Nested(TarifberechnungsparameterSchema)
tarifpreise = fields.List(fields.Nested(TarifpreispositionProOrtSchema))

# optional attributes
tarif_auf_abschlaege = fields.List(
fields.Nested(AufAbschlagRegionalSchema), allow_none=True, data_key="tarifAufAbschlaege"
)
preisgarantie = fields.Nested(PreisgarantieSchema, allow_none=True)
tarifeinschraenkung = fields.Nested(TarifeinschraenkungSchema, allow_none=True)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tarifeinschraenkung = fields.Nested(TarifeinschraenkungSchema, allow_none=True)
tarifeinschraenkungen = fields.Nested(TarifeinschraenkungSchema, allow_none=True)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 changes: 2 additions & 2 deletions src/bo4e/com/aufabschlagstaffelproort.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@attr.s(auto_attribs=True, kw_only=True)
class AufAbschlagstaffelProOrt(COM):
"""
Gibt den Wert eines Auf- oder Abschlags und dessen Staffelgrenzen an.
Gibt den Wert eines Auf- oder Abschlags und dessen Staffelgrenzen an
"""

# required attributes
Expand All @@ -29,7 +29,7 @@ class AufAbschlagstaffelProOrt(COM):

class AufAbschlagstaffelProOrtSchema(COMSchema):
"""
Schema for de-/serialization of AufAbschlagstaffelProOrt.
Schema for de-/serialization of AufAbschlagstaffelProOrt
"""

class_name = AufAbschlagstaffelProOrt
Expand Down
10 changes: 5 additions & 5 deletions src/bo4e/com/tarifpreisposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ class Tarifpreisposition(COM):
"""

# required attributes
# Angabe des Preistypes (z.B. Grundpreis)
#: Angabe des Preistypes (z.B. Grundpreis)
preistyp: Preistyp = attr.ib(validator=attr.validators.instance_of(Preistyp))
# Einheit des Preises (z.B. EURO)
#: Einheit des Preises (z.B. EURO)
einheit: Waehrungseinheit = attr.ib(validator=attr.validators.instance_of(Waehrungseinheit))
# Größe, auf die sich die Einheit bezieht, beispielsweise kWh, Jahr
#: Größe, auf die sich die Einheit bezieht, beispielsweise kWh, Jahr
bezugseinheit: Mengeneinheit = attr.ib(validator=attr.validators.instance_of(Mengeneinheit))
# Hier sind die Staffeln mit ihren Preisenangaben definiert
#: Hier sind die Staffeln mit ihren Preisenangaben definiert
preisstaffeln: List[Preisstaffel] = attr.ib(
validator=[
attr.validators.deep_iterable(
Expand All @@ -43,7 +43,7 @@ class Tarifpreisposition(COM):
)

# optional attributes
# Gibt an, nach welcher Menge die vorgenannte Einschränkung erfolgt (z.B. Jahresstromverbrauch in kWh)
#: Gibt an, nach welcher Menge die vorgenannte Einschränkung erfolgt (z.B. Jahresstromverbrauch in kWh)
mengeneinheitstaffel: Optional[Mengeneinheit] = attr.ib(
default=None, validator=attr.validators.optional(attr.validators.instance_of(Mengeneinheit))
)
Expand Down
52 changes: 52 additions & 0 deletions src/bo4e/com/tarifpreispositionproort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Contains TarifpreispositionProOrt class
and corresponding marshmallow schema for de-/serialization
"""

from typing import List

import attr
from marshmallow import fields

from bo4e.com.com import COM, COMSchema
from bo4e.com.tarifpreisstaffelproort import TarifpreisstaffelProOrt, TarifpreisstaffelProOrtSchema
from bo4e.validators import check_list_length_at_least_one


# pylint: disable=too-few-public-methods
@attr.s(auto_attribs=True, kw_only=True)
class TarifpreispositionProOrt(COM):
"""
Mit dieser Komponente können Tarifpreise verschiedener Typen abgebildet werden
"""

# required attributes
#: Postleitzahl des Ortes für den der Preis gilt
postleitzahl: str = attr.ib(validator=attr.validators.matches_re(r"^\d{5}$"))
#: Ort für den der Preis gilt
ort: str = attr.ib(validator=attr.validators.instance_of(str))
#: ene't-Netznummer des Netzes in dem der Preis gilt
netznr: str = attr.ib(validator=attr.validators.instance_of(str))
# Hier sind die Staffeln mit ihren Preisenangaben definiert
preisstaffeln: List[TarifpreisstaffelProOrt] = attr.ib(
validator=[
attr.validators.deep_iterable(
member_validator=attr.validators.instance_of(TarifpreisstaffelProOrt),
iterable_validator=check_list_length_at_least_one,
),
]
)
# there are no optional attributes


class TarifpreispositionProOrtSchema(COMSchema):
"""
Schema for de-/serialization of TarifpreispositionProOrt.
"""

class_name = TarifpreispositionProOrt
# required attributes
postleitzahl = fields.Str()
ort = fields.Str()
netznr = fields.Str()
preisstaffeln = fields.List(fields.Nested(TarifpreisstaffelProOrtSchema))
50 changes: 50 additions & 0 deletions src/bo4e/com/tarifpreisstaffelproort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Contains TarifpreisstaffelProOrt class
and corresponding marshmallow schema for de-/serialization
"""
from decimal import Decimal

import attr
from marshmallow import fields

from bo4e.com.com import COM, COMSchema


# pylint: disable=too-few-public-methods
@attr.s(auto_attribs=True, kw_only=True)
class TarifpreisstaffelProOrt(COM):
"""
Gibt die Staffelgrenzen der jeweiligen Preise an
"""

# todo: decimal doesn't make sense here imo
# https://github.com/Hochfrequenz/BO4E-python/issues/344

# required attributes
#: Der Arbeitspreis in ct/kWh
arbeitspreis: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal))
#: Der Arbeitspreis für Verbräuche in der Niedertarifzeit in ct/kWh
arbeitspreis_n_t: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal))
#: Der Grundpreis in Euro/Jahr
grundpreis: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal))
#: Unterer Wert, ab dem die Staffel gilt (inklusive)
staffelgrenze_von: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal))
#: Oberer Wert, bis zu dem die Staffel gilt (exklusive)
staffelgrenze_bis: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal))

# there are no optional attributes


class TarifpreisstaffelProOrtSchema(COMSchema):
"""
Schema for (de)serialization of TarifpreisstaffelProOrt
"""

class_name = TarifpreisstaffelProOrt

# required attributes
arbeitspreis = fields.Decimal(as_string=True)
arbeitspreis_n_t = fields.Decimal(as_string=True, data_key="arbeitspreisNT")
grundpreis = fields.Decimal(as_string=True)
staffelgrenze_von = fields.Decimal(as_string=True, data_key="staffelgrenzeVon")
staffelgrenze_bis = fields.Decimal(as_string=True, data_key="staffelgrenzeBis")
1 change: 1 addition & 0 deletions src/bo4e/enum/botyp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class BoTyp(StrEnum):
REGION = "REGION"
REGIONALTARIF = "REGIONALTARIF"
STANDORTEIGENSCHAFTEN = "STANDORTEIGENSCHAFTEN"
TARIF = "TARIF"
TARIFINFO = "TARIFINFO"
TARIFKOSTEN = "TARIFKOSTEN"
TARIFPREISBLATT = "TARIFPREISBLATT"
Expand Down
36 changes: 19 additions & 17 deletions tests/test_aufabschlagregional.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,31 @@
from bo4e.enum.waehrungseinheit import Waehrungseinheit
from tests.serialization_helper import assert_serialization_roundtrip # type:ignore[import]

example_aufabschlagregional = AufAbschlagRegional(
bezeichnung="foo",
betraege=[
AufAbschlagProOrt(
postleitzahl="01187",
ort="Dresden",
netznr="2",
staffeln=[
AufAbschlagstaffelProOrt(
wert=Decimal(2.5),
staffelgrenze_von=Decimal(1),
staffelgrenze_bis=Decimal(5),
)
],
),
],
)


class TestAufAbschlagRegional:
@pytest.mark.parametrize(
"aufabschlagregional, expected_json_dict",
[
pytest.param(
AufAbschlagRegional(
bezeichnung="foo",
betraege=[
AufAbschlagProOrt(
postleitzahl="01187",
ort="Dresden",
netznr="2",
staffeln=[
AufAbschlagstaffelProOrt(
wert=Decimal(2.5),
staffelgrenze_von=Decimal(1),
staffelgrenze_bis=Decimal(5),
)
],
),
],
),
example_aufabschlagregional,
{
"bezeichnung": "foo",
"betraege": [
Expand Down
81 changes: 81 additions & 0 deletions tests/test_tarif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from datetime import datetime, timezone

import pytest # type:ignore[import]

from bo4e.bo.tarif import Tarif, TarifSchema
from bo4e.enum.kundentyp import Kundentyp
from bo4e.enum.sparte import Sparte
from bo4e.enum.tarifart import Tarifart
from bo4e.enum.tarifmerkmal import Tarifmerkmal
from bo4e.enum.tariftyp import Tariftyp
from tests.serialization_helper import assert_serialization_roundtrip # type:ignore[import]
from tests.test_aufabschlagregional import example_aufabschlagregional # type:ignore[import]
from tests.test_energiemix import example_energiemix # type:ignore[import]
from tests.test_marktteilnehmer import example_marktteilnehmer # type:ignore[import]
from tests.test_preisgarantie import example_preisgarantie # type:ignore[import]
from tests.test_regionaletarifpreisposition import example_regionale_tarifpreisposition # type:ignore[import]
from tests.test_tarifberechnungsparameter import example_tarifberechnungsparameter # type:ignore[import]
from tests.test_tarifeinschraenkung import example_tarifeinschraenkung # type:ignore[import]
from tests.test_tarifpreispositionproort import example_tarifpreispositionproort # type:ignore[import]
from tests.test_vertragskonditionen import example_vertragskonditionen # type:ignore[import]
from tests.test_zeitraum import example_zeitraum # type:ignore[import]


class TestTarif:
@pytest.mark.parametrize(
"tarif",
[
pytest.param(
Tarif(
preisstand=datetime(2022, 2, 1, 0, 0, 0, tzinfo=timezone.utc),
berechnungsparameter=example_tarifberechnungsparameter,
tarif_auf_abschlaege=[example_aufabschlagregional],
tarifpreise=[example_tarifpreispositionproort],
preisgarantie=example_preisgarantie,
tarifeinschraenkung=example_tarifeinschraenkung,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tarifeinschraenkung=example_tarifeinschraenkung,
tarifeinschraenkungen=example_tarifeinschraenkung,

# below are the attributes of tarifinfo
bezeichnung="foo",
anbietername="der beste stromanbieter",
sparte=Sparte.STROM,
kundentypen=[Kundentyp.PRIVAT, Kundentyp.GEWERBE],
tarifart=Tarifart.MEHRTARIF,
tariftyp=Tariftyp.GRUND_ERSATZVERSORGUNG,
tarifmerkmale=[Tarifmerkmal.HEIZSTROM],
website="https://foo.inv",
bemerkung="super billig aber auch super dreckig",
vertragskonditionen=example_vertragskonditionen,
zeitliche_gueltigkeit=example_zeitraum,
energiemix=example_energiemix,
anbieter=example_marktteilnehmer,
),
id="required and optional attributes",
),
pytest.param(
Tarif(
preisstand=datetime(2022, 2, 1, 0, 0, 0, tzinfo=timezone.utc),
berechnungsparameter=example_tarifberechnungsparameter,
tarifpreise=[example_tarifpreispositionproort],
# below are the attributes of tarifinfo
bezeichnung="foo",
anbietername="der beste stromanbieter",
sparte=Sparte.STROM,
kundentypen=[Kundentyp.PRIVAT, Kundentyp.GEWERBE],
tarifart=Tarifart.MEHRTARIF,
tariftyp=Tariftyp.GRUND_ERSATZVERSORGUNG,
tarifmerkmale=[Tarifmerkmal.HEIZSTROM],
anbieter=example_marktteilnehmer,
),
id="only required attributes",
),
],
)
def test_serialization_roundtrip(self, tarif: Tarif):
"""
Test de-/serialisation
"""
assert_serialization_roundtrip(tarif, TarifSchema())

def test_missing_required_attribute(self):
with pytest.raises(TypeError) as excinfo:
_ = Tarif()
assert "missing 11 required" in str(excinfo.value) # 3 from Tarif + 8 from tarifinfo
Loading