321dcfde64
Folgewerkzeuge (sps_skel: sepliste-Gegenprobe) erkennen daran, dass ein Separator in TrackIds, aber zu Recht nicht im Bauprotokoll der Strecke steht. CLAUDE.md beschreibt den neuen Export-Schritt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
175 lines
6.3 KiB
Python
175 lines
6.3 KiB
Python
#!/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_merkmal_einsortiert_in(self):
|
|
items = _pipeline(_mubea_vf3())
|
|
assert _by_id(items, "0037")["merkmale"].get("Einsortiert in") == "0015"
|
|
assert _by_id(items, "0038")["merkmale"].get("Einsortiert in") == "0015"
|
|
assert "Einsortiert in" not in _by_id(items, "0062")["merkmale"]
|
|
|
|
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
|