#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_export_freie_separatoren.py - freie Separatoren in der Kette einer Strecke. Laeuft komplett ohne BricsCAD (reine Python-Logik, siehe lib/export_neighbors.py). Szenario nach einem realen Mubea-Befund (VF_3 = 0015): die sepliste aus dem Bau kennt nur AS 0061, SEP 0062, SEP 0063, ES 0064. Die eigenstaendigen Separatoren 0037/0038 liegen auf der Strecke, ihre Zuordnung nennt 0015 - sie fehlten aber in TrackIds und Nachbarn. einsortieren_freie_separatoren nimmt sie nach Lage auf. Alle Daten sind hier fest eingetragen (Stand des Mubea-Exports 2026-10-28) und werden aus keiner Datei gelesen - aendert sich die Mubea-Zeichnung, bleibt dieser Test unberuehrt. Er prueft den Algorithmus, nicht die Szene. """ import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from export_neighbors import ( # noqa: E402 compute_sep_kette, einsortieren_freie_separatoren, sepliste_einsortier_positionen, ) from export_csv import ( # noqa: E402 map_sepliste_kette_items, resolve_umlauf_und_sepliste_nachbarn, write_separatorliste_merkmale, ) TOL_MM = 100.0 def _sep(teileid, x, y, zuordnung): return { "teileart": "ILS 2.0 Separator", "teileid": teileid, "merkmale": {"Zuordnung": zuordnung}, "nachbarn": "", "_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 150.0, "dy": 210.0}, } def _ases(teileart, teileid, x, y, zuordnung): return { "teileart": teileart, "teileid": teileid, "_zuordnung": zuordnung, "merkmale": {}, "nachbarn": "", "_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 200.0}, } def _mubea_vf3(): strecke = { "teileart": "ILS 2.0 Strecke", "teileid": "0015", "nachbarn": "0002-L", "merkmale": {}, "k1": "", "k2": "", "_bbox": {"cx": -631.28, "cy": 6543.11, "cz": 1624.70, "dx": 1018.09, "dy": 12791.0}, "sepliste": [ {"typ": "AS", "lfdnr": 1, "id": "0061"}, {"typ": "SEP", "lfdnr": 2, "id": "0062"}, {"typ": "SEP", "lfdnr": 3, "id": "0063"}, {"typ": "ES", "lfdnr": 4, "id": "0064"}, ], } return [ strecke, _ases("ILS Ausschleuselement", "0061", -400.00, 270.54, "0015"), _sep("0062", -898.87, 1134.60, "0015"), _sep("0063", -702.98, 12307.81, "0015"), _ases("ILS Einschleuselement", "0064", -400.00, 12832.58, "0015"), _sep("0037", -1026.00, 10723.05, "0015"), _sep("0038", -1026.00, 5250.05, "0015"), ] def _by_id(items, teileid): return next(it for it in items if it.get("teileid") == teileid) def _pipeline(items): einsortieren_freie_separatoren(items, TOL_MM) sep_kette = compute_sep_kette(items) sep_kette_by_key = map_sepliste_kette_items(items, sep_kette) write_separatorliste_merkmale(items, sep_kette_by_key) resolve_umlauf_und_sepliste_nachbarn(items, {}, sep_kette, sep_kette_by_key, {}) return items class TestEinsortierPositionen: """Reiner Rechenkern, ohne Items.""" KETTE = [(0.0, 0.0), (0.0, 1000.0), (0.0, 5000.0), (0.0, 6000.0)] def test_zwei_kandidaten_im_selben_abschnitt_sortiert(self): einfuegen, verworfen = sepliste_einsortier_positionen( self.KETTE, [("B", 50.0, 4000.0), ("A", -50.0, 2000.0)]) assert einfuegen == {1: ["A", "B"]} assert verworfen == [] def test_vor_as_und_hinter_es_verworfen(self): einfuegen, verworfen = sepliste_einsortier_positionen( self.KETTE, [("vor", 0.0, -500.0), ("hinter", 0.0, 6500.0)]) assert einfuegen == {} assert sorted(verworfen) == ["hinter", "vor"] def test_kette_um_eine_kurve(self): """Polygonzug statt Gerade AS->ES: ein Separator auf dem zweiten Schenkel einer L-Kette gehoert in DEN Abschnitt, nicht nach seiner Projektion auf die Diagonale.""" kette = [(0.0, 0.0), (0.0, 3000.0), (3000.0, 3000.0)] einfuegen, _ = sepliste_einsortier_positionen(kette, [("S", 1500.0, 3050.0)]) assert einfuegen == {1: ["S"]} def test_entartete_kette(self): einfuegen, verworfen = sepliste_einsortier_positionen( [(0.0, 0.0), (0.0, 0.0)], [("S", 10.0, 10.0)]) assert einfuegen == {} assert verworfen == ["S"] class TestMubeaVf3: def test_trackids_mit_freien_separatoren(self): items = _pipeline(_mubea_vf3()) assert _by_id(items, "0015").get("trackids") == \ "0061, 0062, 0038, 0037, 0063, 0064" def test_nachbarn_der_kette(self): items = _pipeline(_mubea_vf3()) assert _by_id(items, "0062")["nachbarn"] == "0061, 0038" assert _by_id(items, "0038")["nachbarn"] == "0062, 0037" assert _by_id(items, "0037")["nachbarn"] == "0038, 0063" assert _by_id(items, "0063")["nachbarn"] == "0037, 0064" def test_hinweis_am_freien_separator(self): items = _pipeline(_mubea_vf3()) assert "0015" in _by_id(items, "0037").get("warnung", "") assert _by_id(items, "0062").get("warnung", "") == "" def test_fremde_zuordnung_bleibt_draussen(self): items = _mubea_vf3() _by_id(items, "0037")["merkmale"]["Zuordnung"] = "0002-L" _pipeline(items) assert _by_id(items, "0015").get("trackids") == "0061, 0062, 0038, 0063, 0064" def test_ausserhalb_der_strecken_bbox_bleibt_draussen(self): items = _mubea_vf3() _by_id(items, "0038")["_bbox"]["cx"] = -3000.0 _pipeline(items) assert _by_id(items, "0015").get("trackids") == "0061, 0062, 0037, 0063, 0064" def test_ohne_ids_kein_einsortieren(self): """Altbestand ohne aufgeloeste IDs: Glieder nicht lokalisierbar -> sepliste bleibt wie gebaut.""" items = _mubea_vf3() for e in _by_id(items, "0015")["sepliste"]: e["id"] = "" einsortieren_freie_separatoren(items, TOL_MM) assert len(_by_id(items, "0015")["sepliste"]) == 4