From 092f371e99ad49231e191c347fac96149deb278c Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 5 Dec 2024 17:55:55 +0100 Subject: [PATCH 1/4] feat: support `` tag --- src/fundamend/reader/ahbreader.py | 8 +- src/fundamend/reader/element_distinction.py | 5 + src/fundamend/reader/migreader.py | 40 +- tox.ini | 2 +- ...ung_2024_04_02_with_Uebertragungsdatei.xml | 2028 +++++++++++++++++ ...ung_2024_04_02_with_Uebertragungsdatei.xml | 769 +++++++ unittests/test_ahbreader.py | 7 + unittests/test_migreader.py | 6 + 8 files changed, 2855 insertions(+), 10 deletions(-) create mode 100644 unittests/example_files/UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml create mode 100644 unittests/example_files/UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml diff --git a/src/fundamend/reader/ahbreader.py b/src/fundamend/reader/ahbreader.py index 85f30b5..3644a72 100644 --- a/src/fundamend/reader/ahbreader.py +++ b/src/fundamend/reader/ahbreader.py @@ -27,6 +27,7 @@ _is_format, _is_segment, _is_segment_group, + _is_uebertragungsdatei, ) # pylint:disable=duplicate-code @@ -38,7 +39,7 @@ def _to_code(element: ET.Element) -> Code: return Code( name=element.attrib["Name"], description=element.attrib["Description"] or None, - value=element.text, + value=element.text.strip(), ahb_status=element.attrib["AHB_Status"], ) @@ -220,7 +221,7 @@ def get_anwendungsfaelle(self) -> list[Anwendungsfall]: def _iter_segments_and_segment_groups(self, element: ET.Element) -> list[SegmentGroup | Segment]: """recursive function that builds a list of all segments and segment groups""" result: list[Segment | SegmentGroup] = [] - if _is_anwendungsfall(element) or _is_format(element): + if _is_anwendungsfall(element) or _is_format(element) or _is_uebertragungsdatei(element): for sub_element in element: result.extend(self._iter_segments_and_segment_groups(sub_element)) if _is_segment_group(element): @@ -231,7 +232,8 @@ def _iter_segments_and_segment_groups(self, element: ET.Element) -> list[Segment def _read_anwendungsfall(self, original_element: ET.Element) -> Anwendungsfall: segments_and_groups = [] - for element in self._element_tree.getroot(): + root = self._element_tree.getroot() + for element in root: segments_and_groups.extend(self._iter_segments_and_segment_groups(element)) return Anwendungsfall( pruefidentifikator=original_element.attrib["Pruefidentifikator"], diff --git a/src/fundamend/reader/element_distinction.py b/src/fundamend/reader/element_distinction.py index 10987db..5c1d1ae 100644 --- a/src/fundamend/reader/element_distinction.py +++ b/src/fundamend/reader/element_distinction.py @@ -46,3 +46,8 @@ def _is_format(element: ET.Element) -> bool: def _is_anwendungsfall(element: ET.Element) -> bool: """returns true iff the element is an AHB anwendungsfall""" return element.tag == "AWF" + + +def _is_uebertragungsdatei(element: ET.Element) -> bool: + """returns true iff the element is an Uebertragsdatei element (usually below the format level)""" + return element.tag == "Uebertragungsdatei" diff --git a/src/fundamend/reader/migreader.py b/src/fundamend/reader/migreader.py index 5b06749..8c30256 100644 --- a/src/fundamend/reader/migreader.py +++ b/src/fundamend/reader/migreader.py @@ -21,6 +21,7 @@ _is_data_element_group, _is_segment, _is_segment_group, + _is_uebertragungsdatei, ) @@ -117,6 +118,13 @@ def _to_segment_group(element: ET.Element) -> SegmentGroup: ) +def _get_first_tag_starting_with_m(element: ET.Element) -> ET.Element: + for elem in element.iter(): + if elem.tag.startswith("M_"): + return elem + raise ValueError("No element starting with M_ found") + + class MigReader: """ Accesses information from an XML based message implementation guide @@ -129,11 +137,18 @@ def __init__(self, xml_path: Path): self._xml_path = xml_path self._element_tree = ET.parse(self._xml_path) + def _get_format_root(self) -> ET.Element: + actual_root = self._element_tree.getroot() + if actual_root.tag == "Uebertragungsdatei": + return actual_root.find("//*[starts-with(name(), 'M_')][1]") + return actual_root + def get_publishing_date(self) -> date: """ returns the publishing date of the message implementation guide """ - raw_value = self._element_tree.getroot().attrib["Veroeffentlichungsdatum"] # e.g. '24.10.2023' + root = self._element_tree.getroot() # might be either or + raw_value = root.attrib["Veroeffentlichungsdatum"] # e.g. '24.10.2023' result = datetime.strptime(raw_value, "%d.%m.%Y").date() return result @@ -141,21 +156,28 @@ def get_author(self) -> str: """ returns the author of the message implementation guide """ - return self._element_tree.getroot().attrib["Author"] + root = self._element_tree.getroot() # might be either or + return root.attrib["Author"] def get_version(self) -> str: """ returns the version of the message implementation guide """ - return self._element_tree.getroot().attrib["Versionsnummer"] + root = self._element_tree.getroot() # might be either or + return root.attrib["Versionsnummer"] def get_format(self) -> str: """returns the format of the message implementation guide, e.g. 'UTILTS'""" - root_tag: str = self._element_tree.getroot().tag - return root_tag.lstrip("M_") # converts 'M_UTILTS' to 'UTILTS' + root = self._element_tree.getroot() + if _is_uebertragungsdatei(root): + root = _get_first_tag_starting_with_m(root) + return root.tag.lstrip("M_") # converts 'M_UTILTS' to 'UTILTS' def _iter_segments_and_segment_groups(self, element: ET.Element) -> list[SegmentGroup | Segment]: """recursive function that builds a list of all segments and segment groups""" + if _is_uebertragungsdatei(element): + root = _get_first_tag_starting_with_m(element) + return self._iter_segments_and_segment_groups(root) result: list[Segment | SegmentGroup] = [] if _is_segment_group(element): result.append(_to_segment_group(element)) @@ -168,7 +190,13 @@ def read(self) -> MessageImplementationGuide: read the entire file and convert it to a MessageImplementationGuid instance """ segments_and_groups = [] - for index, element in enumerate(self._element_tree.getroot()): + root = self._element_tree.getroot() + if _is_uebertragungsdatei(root): + for elem in root.iter(): + if elem.tag.startswith("M_"): + root = elem + break + for index, element in enumerate(root): if index == 0: continue segments_and_groups.extend(self._iter_segments_and_segment_groups(element)) diff --git a/tox.ini b/tox.ini index 0c82370..0c01da8 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ deps = -r requirements.txt .[tests] setenv = PYTHONPATH = {toxinidir}/src -commands = python -m pytest --basetemp={envtmpdir} {posargs} +commands = python -m pytest --basetemp={envtmpdir} {posargs} -vv [testenv:linting] # the linting environment is called by the Github Action that runs the linter diff --git a/unittests/example_files/UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml b/unittests/example_files/UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml new file mode 100644 index 0000000..722d69d --- /dev/null +++ b/unittests/example_files/UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml @@ -0,0 +1,2028 @@ + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z36 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + 172 + + + + + + + + + 157 + + + + 303 + + + + + + + Z23 + + + + + Z33 + Z34 + Z40 + Z41 + + + + + + + + Z13 + + + 25001 + + + + + + + + Z30 + + + + Z06 + Z07 + + + + + + + + Z36 + + + + + + Z23 + + + + + + + + + Z37 + + + + + + + + + Z19 + + + + + + + + Z23 + + + + + + + + + Z86 + + + + + + + Z69 + Z70 + Z80 + Z81 + Z82 + + + + + + + + + Z87 + + + + + + + Z71 + Z72 + + + + + + + + + Z16 + + + + + + + Z28 + + + + + + + + + + ZB2 + + + + + + + Z28 + + + + + + + + + + ZG6 + + + + + + + ZH6 + + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z36 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + + E01 + + + + + + E_0218 + + + + + + ACB + + + + + + + + + + + + + + Z13 + + + 25002 + + + + + + + + + TN + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z36 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + + E01 + + + + + + E_0218 + + + + + + + + Z13 + + + 25003 + + + + + + + + + TN + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z81 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + Z09 + + + + + + + + + Z34 + + + + 303 + + + + + + + Z35 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + + Z13 + + + 25009 + + + + + + + + + AGI + + + + + + + + + Z74 + + + + + + Z45 + + + + 303 + 401 + + + + + + + + + + + + + + + + Z40 + + + + P1 + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z80 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + Z09 + + + + + + + + + Z34 + + + + 303 + + + + + + + Z35 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + + Z13 + + + 25008 + + + + + + + + + AGI + + + + + + + + + Z73 + + + + + + Z44 + + + + 303 + 401 + + + + + + + + + + + + + + + Z58 + + + + ZF4 + ZF5 + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z59 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + Z09 + + + + + + + + + Z34 + + + + 303 + + + + + + + Z35 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + + Z13 + + + 25005 + + + + + + + + + AGI + + + + + + + + + Z43 + + + + + + Z33 + + + + 303 + 401 + + + + + + + Z28 + + + + + + + + + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z79 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + + 157 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + Z36 + + + + + Z45 + Z46 + + + + + + + + Z13 + + + 25007 + + + + + + + + + AGI + + + + + + + + + Z70 + + + + + + Z53 + + + + + + + + + ZE0 + + + Z33 + Z34 + + + + + + + ZD5 + + + Z23 + Z24 + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z78 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + + 157 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + Z36 + + + + + Z45 + Z46 + + + + + + + + Z13 + + + 25006 + + + + + + + + + AGI + + + + + + + + + Z69 + + + + + + Z52 + + + + + + + + + ZE0 + + + Z33 + Z34 + + + + + + + ZD5 + + + Z23 + Z24 + + + + + + + + + + + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z60 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + + 157 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + Z36 + + + + + Z45 + Z46 + + + + + + + + Z13 + + + 25004 + + + + + + + + + AGI + + + + + + + + + Z42 + + + + + + Z39 + + + + + + + + + ZE0 + + + Z33 + Z34 + + + + + + + ZD5 + + + Z23 + Z24 + + + + + + + ZD4 + + + Z25 + Z26 + + + + + + + ZD7 + + + Z27 + Z28 + + + + + + + ZD3 + + + Z29 + Z30 + Z31 + Z32 + Z35 + + + + + + + + + + Z41 + + + + + + Z27 + + + + + + + + Z38 + + + + + + + + + + Z10 + + + + Z59 + Z60 + + + + + + + + + + + + + + + Nur MP-ID aus Sparte Strom + Wenn SG5 STS+Z23+Z34 (Formel muss beim Absender angefragt werden) in einem SG5 IDE vorhanden + Wenn SG5 STS+Z23+Z33 (Formel angefügt) vorhanden + Wenn in dieser SG5 das STS+E01++A99 (Ablehnung Sonstiges) vorhanden + Wenn das SG8 RFF+Z19 (Referenz auf eine Messlokation) in derselben SG8 SEQ+Z37 nicht vorhanden + Wenn das SG8 RFF+Z23 (Referenz auf Rechenschritt) in derselben SG8 SEQ+Z37 nicht vorhanden + Wenn in derselben SG8 SEQ+Z37 das SG8 RFF+Z19 (Referenz auf eine Messlokation) vorhanden + Rechenschrittidentifikator aus einem SG8 SEQ+Z37 (Bestandteil des Rechenschritts) DE1050 desselben SG5 IDE+24 + Der hier angegebene Rechenschrittidentifikator darf nicht identisch mit dem Rechenschrittidentifikator aus diesem SG8 SEQ+Z37 DE1050 sein + wenn vorhanden + Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z69/Z70 (Addition / Subtraktion) vorhanden, darf es in dem Vorgang beliebig viele weitere SG8 SEQ+Z37 mit identischem Rechenschrittidentifikator geben, die jedoch ausschließlich die Operatoren Z69/Z70 enthalten dürfen + Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z80/Z81 (Divisor / Dividend) vorhanden, muss in diesem Vorgang genau eine zweite SG8 SEQ+Z37 mit identischen Rechenschrittidentifikator vorhanden sein, sodass das eine SG8 SEQ+Z37 den Operator Z80 (Divisor) und das andere SG8 SEQ+Z37 den Operator Z81 (Dividend) enthält + Wenn in SG8 SEQ+Z37 SG9 CCI+++Z86 CAV+Z82 (Faktor) vorhanden, darf es in dem Vorgang beliebig viele weitere SG8 SEQ+Z37 mit identischem Rechenschrittidentifikator geben, die jedoch ausschließlich CAV+Z82 enthalten + Wenn in einem SG5 IDE+24 nur eine SEQ+Z37 mit einer SG8 RFF+Z19 (Messlokation) vorhanden ist + Der hier angegebene Code des Prüfschritt muss im EBD dem Cluster Zustimmung zugeordnet sein + Der hier angegebene Code des Prüfschritt muss im EBD dem Cluster Ablehnung zugeordnet sein + Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle LF + Wenn in dieser CAV+ZD3 der Wert im DE7110 mit Z32 (sonstiger Zählzeitdefinitionstyp) vorhanden ist + Wenn MP-ID in SG2 NAD+MS (Nachrichtenabsender) in der Rolle NB + Wenn SG5 STS+Z36+Z45 (Definitionen werden verwendet) vorhanden + Wenn MP-ID in SG2 NAD+MR (Nachrichtenempfänger) in der Rolle LF + sofern per ORDERS reklamiert + Wenn in SG9 CAV+ZD4+Z26 (keine Verwendung des Hochlastzeitfensters) vorhanden + Wenn in SG8 SEQ+Z43 DTM+Z33 (Zählzeitänderungszeitpunkt) im DE2379 der Code 303 vorhanden + Der Wert von CCYY in diesem DE muss genau um eins höher sein, als der Wert CCYY des SG5 DTM+Z34 (Gültigkeitsbeginn) DE2380 + Wenn im DE2379 dieses Segments der Code 303 vorhanden + Der Zeitpunkt in diesem DE muss ≥ dem Zeitpunkt aus dem DE2380 des Gültigkeitsbeginn der ausgerollten Definition (SG5 DTM+Z34) sein + Der Zeitpunkt in diesem DE muss ≤ dem Zeitpunkt aus dem DE2380 des Gültigkeitsende der ausgerollten Definition (SG5 DTM+Z35) sein + Wenn im DE2379 dieses Segments der Code 401 vorhanden + Wenn in SG8 SEQ+Z43 DTM+Z33 (Zählzeitänderungszeitpunkt) im DE2379 der Code 401 vorhanden + Wenn ein Gültigkeitsende bereits angegeben werden kann. + Wenn SG8 SEQ+Z42 (Zählzeitdefinition) vorhanden + Der in diesem Datenlement angegebene Code der Schaltzeitdefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein. + Der in diesem Datenlement angegebene Code der Leistungskurvendefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein. + Der in diesem Datenlement angegebene Code der Zählzeitdefinition muss innerhalb eines Vorgangs (IDE) eindeutig sein. + Wenn in SG8 SEQ+Z73 DTM+Z44 (Schaltzeitänderungszeitpunkt) im DE2379 der Code 303 vorhanden + Wenn in SG8 SEQ+Z73 DTM+Z44 (Schaltzeitänderungszeitpunkt) im DE2379 der Code 401 vorhanden + Wenn in SG8 SEQ+Z74 DTM+Z45 (Leistungskurvenänderungszeitpunkt) im DE2379 der Code 303 vorhanden + Wenn in SG8 SEQ+Z74 DTM+Z45 (Leistungskurvenänderungszeitpunkt) im DE2379 der Code 401 vorhanden + In jedem DE2379 dieses DTM-Segments innerhalb eines IDE+24 (Vorgangs) muss der gleiche Code angegeben werden + Wenn in SG5 LOC+172 DE3225 (Meldepunkt) die ID einer Marktlokation angegeben ist. + Wenn im DE3155 in demselben COM der Code EM vorhanden ist + Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist + wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.5 „Prozesszeitpunkt bei MESZ mit UTC“ ist + wenn Wert in diesem DE, an der Stelle CCYYMMDD ein Datum aus dem angegeben Zeitraum der Tabelle Kapitel 3.6 „Prozesszeitpunkt bei MEZ mit UTC“ ist + Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt. + Hinweis: Zeitpunkt, ab dem die Berechnungsformel anzuwenden ist + Hinweis: Verwendung der ID der Marktlokation + Hinweis: Verwendung der ID der Messlokation + Hinweis: Wert aus BGM+Z55 DE1004 der ORDERS mit der die Reklamation einer Definition erfolgt ist + Hinweis: Jede ausgerollte Zählzeitdefinition ist in einem eigenen IDE anzugeben + Hinweis: Zeitpunkt, ab dem die Übersicht der Zählzeitdefinitionen gültig ist + Hinweis: Es ist die Zeit nach der deutschen gesetzlichen Zeit anzugeben + Hinweis: Zeitpunkt, ab dem die Übersicht der Schaltzeitdefinitionen gültig ist + Hinweis: Zeitpunkt, ab dem die Übersicht der Leistungskurvendefinition gültig ist + Hinweis: Für jeden Zählzeitänderungszeitpunkt (SG8 DTM+Z33) ist diese Sementgruppe einmal anzugeben + Hinweis: Der Zählzeitänderungszeitpunkt (SG8DTM+Z33) dieser SG8 darf in keiner anderen SG8 „Zählzeitdefinition“ wiederholt werden + Hinweis: Wenn der Code 303 im DE2379 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) genutzt wird, muss genau ein Wert im DE2380 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) identisch mit dem Wert aus DE2380 des Gültigkeitsbeginn der ausgerollten Definition (SG5 DTM+Z34) sein + Hinweis: Wenn der Code 401 im DE2379 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) genutzt wird, muss genau ein Wert = 0000 im DE2380 des Zählzeitänderungszeitpunkt (SG8 DTM+Z33) sein + Hinweis: Für jeden Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) ist diese Sementgruppe einmal anzugeben + Hinweis: Kein Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) darf mehrfach vorkommen + Hinweis: Wenn der Code 303 im DE2379 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) genutzt wird, muss genau ein Wert im DE2380 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) identisch mit dem Wert aus DE2380 des Gültigkeitsbeginn der ausgerollten Definition (SG5 DTM+Z34) sein + Hinweis: Wenn der Code 401 im DE2379 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) genutzt wird, muss genau ein Wert = 0000 im DE2380 des Schaltzeitänderungszeitpunkt (SG8 DTM+Z44) sein + Hinweis: Für jeden Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) ist diese Sementgruppe einmal anzugeben + Hinweis: Kein Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) darf mehrfach vorkommen + Hinweis: Wenn der Code 303 im DE2379 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) genutzt wird, muss genau ein Wert im DE2380 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) identisch mit dem Wert aus DE2380 des Gültigkeitsbeginn der ausgerollten Definition (SG5 DTM+Z34) sein + Hinweis: Wenn der Code 401 im DE2379 des Leistungskurvenänderungszeitpunkt (SG8 DTM+Z45) + Hinweis: Jede ausgerollte Schaltzeitdefinition ist in einem eigenen IDE anzugeben + Hinweis: Jede ausgerollte Leistungskurvendefinition ist in einem eigenen IDE anzugeben + Hinweis: Es ist der Code einer Zählzeitdefinition anzugeben + Hinweis: Es ist der Code einer Schaltzeitdefinition anzugeben + Hinweis: Es ist der Code einer Leistungskurvendefinition anzugeben + Hinweis: Dieser Code ist anzugeben, wenn es sich um eine einmalig zu übermittelnde Definition handelt + Hinweis: Dieser Code ist anzugeben, wenn es sich um eine jährlich zu übermittelnde Definition handelt + Hinweis: Verwendung der ID der Netzlokation + Hinweis: Es darf nur eine Information im DE3148 übermittelt werden + Format: Wert kann mit maximal 6 Nachkommastellen angegeben werden + Format: Mögliche Werte: 1 bis 99999 + Format: Möglicher Wert: > 0 + Format: Möglicher Wert: ≠ 1 + Format: max. 2 Nachkommastellen + Format: ZZZ = +00 + Format: HHMM = 2200 + Format: HHMM = 2300 + Format: Die Zeichenkette muss die Zeichen @ und . enthalten + Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen + Format: MMDDHHMM = 12312300 + Format: Marktlokations-ID + Format: Zählpunktbezeichnung + Format: Netzlokations-ID + Format: Möglicher Wert: ≤ 100 + Format: HHMM ≥ 0000 + Format: HHMM ≤ 2359 + Format: Möglicher Wer: ≤ 1 + Segment bzw. Segmentgruppe ist genau einmal anzugeben + Für jeden Code der Zählzeit aus SG8 SEQ+Z42 (Zählzeitdefinition) SG9 CCI+Z39 (Code der Zählzeitdefinition) sind mindestens zwei Register anzugeben, bei denen in dieser SG8 das SG8 RFF+Z27 mit diesem Code gefüllt ist + + + ([931] ∧ [932] [490]) ⊻ ([931] ∧ [933] [491]) + + + -- + + diff --git a/unittests/example_files/UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml b/unittests/example_files/UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml new file mode 100644 index 0000000..04fbc63 --- /dev/null +++ b/unittests/example_files/UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml @@ -0,0 +1,769 @@ + + + + + + + + + + UTILTS + + + D + + + 18A + + + UN + + + 1.1d + + + + + + + Z36 + Z59 + Z60 + Z78 + Z79 + Z80 + Z81 + + + + + + + + + + 137 + + + + 303 + + + + + + + MS + + + + + + 9 + 293 + + + + + + + IC + + + + + + + + + + + EM + FX + TE + AJ + AL + + + + + + + + MR + + + + + + 9 + 293 + + + + + + + + 24 + + + + + + + + 172 + + + + + + + + Z09 + + + + + + + + + 157 + + + + 303 + + + + + + + Z34 + + + + 303 + + + + + + + Z35 + + + + 303 + + + + + + + 293 + + + + 304 + + + + + + + E01 + + + + + + + + + E_0218 + + + + + + + Z23 + + + + + Z33 + Z34 + Z40 + Z41 + + + + + + + Z36 + + + + + Z45 + Z46 + + + + + + ACB + + + + + + + + + + + + + + + + + + Z13 + + + 25001 + 25002 + 25003 + 25004 + 25005 + 25006 + 25007 + 25008 + 25009 + + + + + + + + + AGI + + + + + + + + + + TN + + + + + + + + + Z30 + + + + + + + Z06 + Z07 + + + + + + + + Z36 + + + + + + Z23 + + + + + + + + + Z37 + + + + + + + + + Z19 + + + + + + + + Z23 + + + + + + + + + + + + + Z86 + + + + + + + Z69 + Z70 + Z80 + Z81 + Z82 + Z83 + + + + + + + + + + + + + Z87 + + + + + + + Z71 + Z72 + + + + + + + + + + + + + Z16 + + + + + + + Z28 + + + + + + + + + + + + + + + + ZB2 + + + + + + + Z28 + + + + + + + + + + + + + + + + ZG6 + + + + + + + ZH6 + + + + + + + + + + + + Z42 + Z43 + + + + + + Z33 + + + + 303 + 401 + + + + + + + Z28 + + + + + + + + Z39 + + + + + + + + + + + + ZE0 + + + + + Z33 + Z34 + + + + + + + ZD5 + + + + + Z23 + Z24 + + + + + + + ZD4 + + + + + Z25 + Z26 + + + + + + + ZD7 + + + + + Z27 + Z28 + + + + + + + ZD3 + + + + + Z29 + Z30 + Z31 + Z32 + Z35 + + + + + + + + + + Z41 + + + + + + Z27 + + + + + + + + Z38 + + + + + + + + + + + + + Z10 + + + + + + + Z59 + Z60 + + + + + + + + + + Z69 + Z73 + + + + + + + Z44 + + + + 303 + 401 + + + + + + + Z52 + + + + + + + + + + + + ZE0 + + + + + Z33 + Z34 + + + + + + + ZD5 + + + + + Z23 + Z24 + + + + + + + + + Z58 + + + + + + + + ZF4 + ZF5 + + + + + + + + + + Z70 + Z74 + + + + + + + Z45 + + + + 303 + 401 + + + + + + + Z53 + + + + + + + + + + + + ZE0 + + + + + Z33 + Z34 + + + + + + + ZD5 + + + + + Z23 + Z24 + + + + + + + + + + Z40 + + + + + P1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/unittests/test_ahbreader.py b/unittests/test_ahbreader.py index 6080f61..645f7d5 100644 --- a/unittests/test_ahbreader.py +++ b/unittests/test_ahbreader.py @@ -145,6 +145,12 @@ def test_get_anwendungsfall(ahb_xml_file_path: Path, pruefidentifikator: str, ex Path(__file__).parent / "example_files" / "UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02.xml", ahb_utilts_11d, ), + pytest.param( + Path(__file__).parent + / "example_files" + / "UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml", + ahb_utilts_11d, + ), ], ) def test_get_anwendungshandbuch(ahb_xml_file_path: Path, expected: Anwendungshandbuch) -> None: @@ -152,6 +158,7 @@ def test_get_anwendungshandbuch(ahb_xml_file_path: Path, expected: Anwendungshan actual = reader.read() assert actual is not None assert isinstance(actual, Anwendungshandbuch) + assert actual.anwendungsfaelle[0].segment_groups == expected.anwendungsfaelle[0].segment_groups assert actual == expected assert len(actual.anwendungsfaelle) == 9 assert {awf.pruefidentifikator for awf in actual.anwendungsfaelle} == { diff --git a/unittests/test_migreader.py b/unittests/test_migreader.py index 940ff7d..2fa3f7b 100644 --- a/unittests/test_migreader.py +++ b/unittests/test_migreader.py @@ -82,6 +82,12 @@ def test_get_format(mig_xml_file_path: Path, expected: str) -> None: Path(__file__).parent / "example_files" / "UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02.xml", utilts_mig_11d, ), + pytest.param( + Path(__file__).parent + / "example_files" + / "UTILTS_MIG_1.1d_Konsultationsfassung_2024_04_02_with_Uebertragungsdatei.xml", + utilts_mig_11d, + ), ], ) def test_read_mig(mig_xml_file_path: Path, expected: MessageImplementationGuide) -> None: From e835e7619d32b600ed57acf712914bef81b50d68 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 5 Dec 2024 17:58:57 +0100 Subject: [PATCH 2/4] remove unnecessary check --- src/fundamend/reader/migreader.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/fundamend/reader/migreader.py b/src/fundamend/reader/migreader.py index 8c30256..9776ec8 100644 --- a/src/fundamend/reader/migreader.py +++ b/src/fundamend/reader/migreader.py @@ -175,9 +175,6 @@ def get_format(self) -> str: def _iter_segments_and_segment_groups(self, element: ET.Element) -> list[SegmentGroup | Segment]: """recursive function that builds a list of all segments and segment groups""" - if _is_uebertragungsdatei(element): - root = _get_first_tag_starting_with_m(element) - return self._iter_segments_and_segment_groups(root) result: list[Segment | SegmentGroup] = [] if _is_segment_group(element): result.append(_to_segment_group(element)) From 55668fdb2fd0ce7a06c3100305458dc49291c5bb Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 5 Dec 2024 18:01:13 +0100 Subject: [PATCH 3/4] fix mypy --- src/fundamend/reader/ahbreader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fundamend/reader/ahbreader.py b/src/fundamend/reader/ahbreader.py index 3644a72..81539bb 100644 --- a/src/fundamend/reader/ahbreader.py +++ b/src/fundamend/reader/ahbreader.py @@ -36,10 +36,13 @@ def _to_code(element: ET.Element) -> Code: assert _is_code(element) + value = element.text + if value is not None: + value = value.strip() return Code( name=element.attrib["Name"], description=element.attrib["Description"] or None, - value=element.text.strip(), + value=value, ahb_status=element.attrib["AHB_Status"], ) From 558b4324330f24ddf2f645faee2037ef4cdd6582 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 5 Dec 2024 18:01:45 +0100 Subject: [PATCH 4/4] remove unused function --- src/fundamend/reader/migreader.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/fundamend/reader/migreader.py b/src/fundamend/reader/migreader.py index 9776ec8..c02afe0 100644 --- a/src/fundamend/reader/migreader.py +++ b/src/fundamend/reader/migreader.py @@ -137,12 +137,6 @@ def __init__(self, xml_path: Path): self._xml_path = xml_path self._element_tree = ET.parse(self._xml_path) - def _get_format_root(self) -> ET.Element: - actual_root = self._element_tree.getroot() - if actual_root.tag == "Uebertragungsdatei": - return actual_root.find("//*[starts-with(name(), 'M_')][1]") - return actual_root - def get_publishing_date(self) -> date: """ returns the publishing date of the message implementation guide