From dd955999a9cae5e51a2bab3331a64bc0f7df448b Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 28 Oct 2026 23:13:01 +0100 Subject: [PATCH] tro_flow: sepliste-Gegenprobe zaehlt nach Lage einsortierte Separatoren nicht als Abweichung Der dxfmakros-Export nimmt freie Separatoren nach Lage in die Kette ihrer Strecke auf (Merkmal "Einsortiert in"). In der sepliste, dem Bauprotokoll, fehlen sie zu Recht - die Gegenprobe meldete bei Mubea VF_3 sonst 4 statt 2. Co-Authored-By: Claude Opus 5.5 --- lib/tro_flow.py | 16 ++++++- tests/test_sepliste_gegenprobe.py | 73 +++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/test_sepliste_gegenprobe.py diff --git a/lib/tro_flow.py b/lib/tro_flow.py index 02fdd49..4819495 100644 --- a/lib/tro_flow.py +++ b/lib/tro_flow.py @@ -1084,18 +1084,30 @@ def derive_tros( # Sepliste-Gegenprobe (siehe load_sepliste_counts/Docstring oben): reine # Beobachtung, veraendert weder sep_host noch die spaeter abgeleiteten # TROs - die CSV bleibt die massgebliche Quelle. + # Die sepliste ist das Bauprotokoll der Strecke. Freie Separatoren, die der + # dxfmakros-Export nach Lage in deren Kette aufgenommen hat, tragen das + # Merkmal "Einsortiert in" = Strecke - sie fehlen dort zu Recht und zaehlen + # hier nicht als Abweichung. if sepliste_counts: + merkmale_by_id = {e.teile_id: e.merkmale for e in elements} for node_id, node in sorted(graph.nodes.items()): if node.kind not in ("Strecke", "Gefaellestrecke"): continue expected = sepliste_counts.get(node_id) if expected is None: continue - actual = len(seps_on([node_id])) + auf_strecke = seps_on([node_id]) + einsortiert = [ + s for s in auf_strecke + if _merkmal(merkmale_by_id.get(s, {}), "Einsortiert in") == node_id + ] + actual = len(auf_strecke) - len(einsortiert) if actual != expected: + ohne = (f" (ohne {len(einsortiert)} per Lage einsortierte)" + if einsortiert else "") findings.append( f"{node.element.name} ({node_id}): {actual} Separator(en) aus " - f"der CSV zugeordnet, aber {expected} 'SEP'-Eintraege in " + f"der CSV zugeordnet{ohne}, aber {expected} 'SEP'-Eintraege in " f"sepliste (Roh-JSON) - Gegenprobe weicht ab, bitte pruefen" ) diff --git a/tests/test_sepliste_gegenprobe.py b/tests/test_sepliste_gegenprobe.py new file mode 100644 index 0000000..cd3e4ec --- /dev/null +++ b/tests/test_sepliste_gegenprobe.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +"""Tests fuer die sepliste-Gegenprobe in tro_flow.derive_tros. + +Die sepliste im Roh-JSON ist das Bauprotokoll einer Strecke. Freie +Separatoren, die der dxfmakros-Export nach Lage in deren Kette aufnimmt, +stehen in der CSV mit dem Merkmal "Einsortiert in" = Strecke - sie fehlen in +der sepliste zu Recht und duerfen keine Abweichung melden. + +Szenario nach Mubea VF_3 (0015), fest im Test eingetragen: gebaut sind +0062/0063, einsortiert 0037/0038. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +from material_flow import build_graph, read_elements +from tro_flow import derive_tros + +HEADER = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;" + "Boundingbox;Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale") + + +def _zeile(nr, teileart, teileid, name, x, y, nachbarn="", trackids="", merkmale=None): + m = json.dumps(merkmale or {}, ensure_ascii=False) + return (f'{nr};"{teileart}";"{teileid}";"{name}";"";1;"{x}, {y}, 1550.00";' + f'"150.00, 210.00, 3.00";"";"";"";"";"";"{nachbarn}";"{trackids}";"";"";{m}') + + +def _csv(tmp_path: Path, extra_sep: bool) -> Path: + zeilen = [ + HEADER, + _zeile(1, "ILS 2.0 Strecke", "0015", "VF_3", -631.0, 6543.0, + trackids="0061, 0062, 0038, 0037, 0063, 0064", merkmale={"Typ": "Streckengruppe"}), + _zeile(2, "ILS Ausschleuselement", "0061", "AS", -400.0, 270.0, nachbarn="0062", + merkmale={"Förderer": "0015"}), + _zeile(3, "ILS 2.0 Separator", "0062", "Separator :6", -899.0, 1135.0, + nachbarn="0061, 0038", merkmale={"Zuordnung": "0015"}), + _zeile(4, "ILS 2.0 Separator", "0038", "Separator :32", -1026.0, 5250.0, + nachbarn="0062, 0037", merkmale={"Zuordnung": "0015", "Einsortiert in": "0015"}), + _zeile(5, "ILS 2.0 Separator", "0037", "Separator :33", -1026.0, 10723.0, + nachbarn="0038, 0063", merkmale={"Zuordnung": "0015", "Einsortiert in": "0015"}), + _zeile(6, "ILS 2.0 Separator", "0063", "Separator :5", -703.0, 12308.0, + nachbarn="0037, 0064", merkmale={"Zuordnung": "0015"}), + _zeile(7, "ILS Einschleuselement", "0064", "ES", -400.0, 12833.0, nachbarn="0063", + merkmale={"Förderer": "0015"}), + ] + if extra_sep: + zeilen.append(_zeile(8, "ILS 2.0 Separator", "0070", "Separator :40", -900.0, 8000.0, + merkmale={"Zuordnung": "0015"})) + path = tmp_path / "gegenprobe_export.csv" + path.write_text("\n".join(zeilen) + "\n", encoding="utf-8") + return path + + +def _gegenprobe(path: Path) -> list[str]: + elements = read_elements(path, []) + graph = build_graph(elements, path.name, []) + findings: list[str] = [] + derive_tros(graph, elements, findings, sepliste_counts={"0015": 2}) + return [f for f in findings if "Gegenprobe" in f] + + +def test_einsortierte_separatoren_zaehlen_nicht_als_abweichung(tmp_path): + assert _gegenprobe(_csv(tmp_path, extra_sep=False)) == [] + + +def test_echte_abweichung_wird_weiter_gemeldet(tmp_path): + meldungen = _gegenprobe(_csv(tmp_path, extra_sep=True)) + assert len(meldungen) == 1 + assert "3 Separator(en)" in meldungen[0] + assert "ohne 2 per Lage einsortierte" in meldungen[0]