Skip to content

Add AHB Models + Logic #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 31, 2024
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
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Das Paket ist auf PyPI verfügbar und kann mit pip installiert werden:
pip install fundamend
```

### Message Implementation Guides (MIG) deserialisieren
```python
from pathlib import Path
from fundamend import MigReader, MessageImplementationGuide
Expand All @@ -44,10 +45,40 @@ mig = reader.read()
assert isinstance(mig, MessageImplementationGuide)
assert mig.format == "UTILTS"
```
Das vollständige Beispiel findet sich in den [unittests](unittests).

Aktuell (Version 0.1) können nur MIGs gelesen werden.
Der AHB-Teil soll aber folgen.
### Anwendungshandbuch (AHB) deserialisieren
```python
from pathlib import Path
from fundamend import AhbReader, Anwendungshandbuch

# Angenommen, ahb_utilts.xml enthält:
# <?xml version="1.0" encoding="UTF-8"?>
# <AHB Versionsnummer="1.1d"
# Veroeffentlichungsdatum="02.04.2024"
# Author="BDEW">
# <AWF Pruefidentifikator="25001" Beschreibung="Berechnungsformel" Kommunikation_von="NB an MSB / LF">
# ...
# </AWF>
# </AHB>

reader = AhbReader(Path("pfad/zur/ahb_utils.xml"))
ahb = reader.read()
assert isinstance(ahb, Anwendungshandbuch)
assert {awf.pruefidentifikator for awf in ahb.anwendungsfaelle} == {
"25001",
"25002",
"25003",
"25004",
"25005",
"25006",
"25007",
"25008",
"25009",
}
```

Die vollständigen Beispiele finden sich in den [unittests](unittests).


## Verwendung und Mitwirken
Der Code ist MIT-lizenziert und kann daher frei verwendet werden.
Expand Down
2 changes: 2 additions & 0 deletions domain-specific-terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ oder
ist
als
paket
beginn
referenz
6 changes: 3 additions & 3 deletions src/fundamend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pip install xml-fundamend
"""

from .models import MessageImplementationGuide
from .reader import MigReader
from .models import Anwendungshandbuch, MessageImplementationGuide
from .reader import AhbReader, MigReader

__all__ = ["MigReader", "MessageImplementationGuide"]
__all__ = ["MigReader", "MessageImplementationGuide", "AhbReader", "Anwendungshandbuch"]
3 changes: 2 additions & 1 deletion src/fundamend/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""contains model classes representing MIGs and AHBs"""

from .anwendungshandbuch import Anwendungshandbuch
from .messageimplementationguide import MessageImplementationGuide

__all__ = ["MessageImplementationGuide"]
__all__ = ["MessageImplementationGuide", "Anwendungshandbuch"]
187 changes: 187 additions & 0 deletions src/fundamend/models/anwendungshandbuch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
""""model classes for Anwendungshandbücher (AHB)"""

# pylint:disable=duplicate-code
# the structures are similar, still we decided against inheritance, so there's naturally a little bit of duplication

from dataclasses import dataclass
from datetime import date


@dataclass(kw_only=True, eq=True, frozen=True)
class Code:
"""
a single code element inside an AHB DataElement
"""

# Example:
# <Code Name="Netznutzungszeiten-Nachricht" Description="" AHB_Status="X">UTILTS</Code>
# properties are similar to MIG data model, still we like to keep them separate instead of inheriting
name: str # e.g. 'Netznutzungszeiten-Nachricht'
description: str | None = None # e.g. ''
value: str | None # e.g. 'UTILTS'
ahb_status: str #: e.g. 'X' # new for AHB


@dataclass(kw_only=True, eq=True, frozen=True)
class DataElement:
"""
A single data element inside a AHB Segment.
This models both the 'Datenelement' and the 'Gruppendatenelement'
"""

# Example:
# <D_0065 Name="Nachrichtentyp-Kennung">
# <Code Name="Netznutzungszeiten-Nachricht" Description="" AHB_Status="X">UTILTS</Code>
# </D_0065>
id: str # e.g. 'D_0065'
name: str # e.g. 'Nachrichtentyp-Kennung'
codes: list[Code]


@dataclass(eq=True, kw_only=True, frozen=True)
class DataElementGroup:
"""
a group of data elements, German 'Datenelementgruppe' inside the AHB
"""

# "Die Datenelementgruppe C0829 enthält mehrere Gruppendatenelemente. Diese Datenelementgruppe enthält das
# Gruppendatenelement DE3039, hier wird die MP-ID angegeben, sowie das DE3055, welches die code-vergebende Stelle
# definiert. Diese Datenelementgruppe enthält des Weiteren das Gruppendatenelement DE113110, welches nur in der MIG
# und nicht im AHB aufgeführt wird, um den Aufbau der EDIFACT Nachricht korrekt umsetzen zu können."
# Quelle: Allgemeine Festlegungen Kapitel 6.1, Seite 52

# Example:
# <C_C002 Name="Dokumenten-/Nachrichtenname">
# <D_1001 Name="Dokumentenname, Code">
# <Code Name="Berechnungsformel" Description="" AHB_Status="X">Z36</Code>
# </D_1001>
# </C_C002>

id: str # e.g. 'C_C082'
name: str # e.g. 'Dokumenten-/Nachrichtenname'
data_elements: list[DataElement]


@dataclass(frozen=True, eq=True, unsafe_hash=True, kw_only=True)
class Segment:
"""
a segment inside an AHB
"""

# Example:
# pylint:disable=line-too-long
# <S_BGM Name="Beginn der Nachricht" Number="00002" AHB_Status="Muss">
# <C_C002 Name="Dokumenten-/Nachrichtenname">
# <D_1001 Name="Dokumentenname, Code">
# <Code Name="Berechnungsformel" Description="" AHB_Status="X">Z36</Code>
# </D_1001>
# </C_C002>
# <C_C106 Name="Dokumenten-/Nachrichten-Identifikation">
# <D_1004 Name="Dokumentennummer" AHB_Status="X"/>
# </C_C106>
# </S_BGM>
id: str #: e.g. 'BGM'
name: str #: e.g. 'Beginn der Nachricht'
number: str #: e.g. '00002'
ahb_status: str | None #: e.g. 'Muss'
data_elements: list[DataElement | DataElementGroup]


@dataclass(kw_only=True, eq=True, frozen=True)
class SegmentGroup:
"""
a "Segmentgruppe" inside an AHB
"""

# Example:
# <G_SG6 Name="Prüfidentifikator" AHB_Status="Muss">
# <S_RFF Name="Prüfidentifikator" Number="00012" AHB_Status="Muss">
# <C_C506 Name="Referenz">
# <D_1153 Name="Referenz, Qualifier">
# <Code Name="Prüfidentifikator" Description="" AHB_Status="X">Z13</Code>
# </D_1153>
# <D_1154 Name="Referenz, Identifikation">
# <Code Name="Berechnungsformel" Description="" AHB_Status="X">25001</Code>
# </D_1154>
# </C_C506>
# </S_RFF>
# </G_SG6>
id: str #: e.g. 'SG6'
name: str #: e.g. 'Prüfidentifikator'
ahb_status: str | None #: e.g. 'Muss'
segments: list[Segment]
segment_groups: list["SegmentGroup"]


@dataclass(kw_only=True, eq=True, frozen=True)
class Anwendungsfall:
"""One Anwendungsfall "AWF" corresponds to one Prüfidentifikator or Type of Message"""

# Example:
# <AWF Pruefidentifikator="25001" Beschreibung="Berechnungsformel" Kommunikation_von="NB an MSB / LF">
# <M_UTILTS>
# <S_UNH Name="Nachrichten-Kopfsegment" Number="00001" AHB_Status="Muss">
# ...
# </S_UNT>
# </M_UTILTS>
# </AWF>
pruefidentifikator: str #: e.g. '25001'
beschreibung: str #: e.g. 'Berechnungsformel'
kommunikation_von: str #: e.g. 'NB an MSB / LF'
format: str #: e.g. 'UTILTS'
segments: list[Segment]
segment_groups: list[SegmentGroup]


@dataclass(kw_only=True, eq=True, frozen=True)
class Bedingung:
"""Ein ConditionKeyConditionText Mapping"""

nummer: str #: e.g. '1'
text: str #: e.g. 'Nur MP-ID aus Sparte Strom'


@dataclass(kw_only=True, eq=True, frozen=True)
class UbBedingung:
"""Eine UB-Bedingung"""

# Example:
# <UB_Bedingung Nummer="[UB1]">([931] ∧ [932] [490]) ⊻ ([931] ∧ [933] [491])</UB_Bedingung>
nummer: str #: e.g. 'UB1'
text: str #: e.g. '([931] ∧ [932] [490]) ⊻ ([931] ∧ [933] [491])'


@dataclass(kw_only=True, eq=True, frozen=True)
class Paket:
"""Ein Bedingungspaket/PackageKeyConditionText Mapping"""

# Example:
# <Paket Nummer="[1P]">--</Paket>
nummer: str #: e.g. '1P'
text: str #: e.g. '--'


@dataclass(kw_only=True, eq=True, frozen=True)
class Anwendungshandbuch:
"""
Ein Anwendungshandbuch bündelt verschiedene Nachrichtentypen/Anwendungsfälle im selben Format oder mit der selben
regulatorischen Grundlage und stellt gemeinsame Pakete & Bedingungen bereit.
"""

# Example:
# <AHB Versionsnummer="1.1d" Veroeffentlichungsdatum="02.04.2024" Author="BDEW">
# <AWF Pruefidentifikator="25001" Beschreibung="Berechnungsformel" Kommunikation_von="NB an MSB / LF">
# </AWF>
# </AHB>
veroeffentlichungsdatum: date
"""publishing date"""

autor: str
"""author, most likely 'BDEW'"""

versionsnummer: str
"""e.g. '1.1d'"""
anwendungsfaelle: list[Anwendungsfall] #: die einzelnen Prüfidendifikatoren
bedingungen: list[Bedingung]
ub_bedingungen: list[UbBedingung]
pakete: list[Paket]
3 changes: 2 additions & 1 deletion src/fundamend/reader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""classes for reading xml documents"""

from .ahbreader import AhbReader
from .migreader import MigReader

__all__ = ["MigReader"]
__all__ = ["MigReader", "AhbReader"]
Loading