Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions docs/api/bo4e.com.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------------------

Expand Down Expand Up @@ -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
-----------------------------------

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

Expand Down
115 changes: 115 additions & 0 deletions src/bo4e/com/fremdkostenposition.py
Original file line number Diff line number Diff line change
@@ -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 <Menge * Einzelpreis> oder
<Einzelpreis / (Anzahl Tage Jahr) * zeitmenge>"""
# 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)
1 change: 0 additions & 1 deletion src/bo4e/com/tagesvektor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/bo4e/com/verbrauch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/bo4e/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
108 changes: 108 additions & 0 deletions tests/test_fremdkostenposition.py
Original file line number Diff line number Diff line change
@@ -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)