Weichenerkennung: Kriterium ist das Uebergabeelement, nicht der Scanner

Korrektur zu 72d0a21: Regel 2 in _find_switch_separators() pruefte bisher,
ob ein Separator einen eigenen Scanner traegt, bevor sie ihn (ohne feste
mm-Grenze) einem Aus-/Einschleuselement zuordnete. Der Scanner ist aber nur
ein zusaetzliches Bauteil des TROs, kein Kennzeichen einer Weiche - viele
Separatoren tragen einen Scanner, ohne eine Weiche zu sein (siehe Docstring
und Hinweis in tro_flow.py selbst: "Ein Scanner am Separator aendert den Typ
NICHT").

Regel 2 ist jetzt reine naechster-freier-Nachbar-Zuordnung: jedes noch
unbelegte Aus-/Einschleuselement bekommt den naechstgelegenen freien
Separator derselben Rolle/Bahn, kleinste Distanz zuerst, jedes Paar nur
einmal - ohne Scanner-Kriterium und ohne feste mm-Grenze. Ein gewoehnlicher
Zwischenseparator ohne eigenes Uebergabeelement bleibt dadurch weiterhin
unbelegt, weil ihm kein Element mehr uebrig bleibt.

Ergebnis fuer HundM05.csv unveraendert (13 TROs, 6x 1Sep1Swi, keine Fehler) -
mit der zwischenzeitlich aktualisierten CSV liegen die echten Weichen jetzt
ohnehin wieder innerhalb der 500mm-Grenze aus Regel 1; Regel 2 bleibt als
Absicherung gegen erneute Kalibrierungs-Drift bestehen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-18 16:19:50 +02:00
co-authored by Claude Sonnet 5
parent 29d466f696
commit c7886477ff
+39 -34
View File
@@ -275,7 +275,15 @@ def _find_switch_separators(
) -> dict[str, tuple[Element, float]]: ) -> dict[str, tuple[Element, float]]:
"""Separator-Id -> (Aus-/Einschleuselement der Weiche, Abstand mm). """Separator-Id -> (Aus-/Einschleuselement der Weiche, Abstand mm).
Zwei Regeln, angewandt in dieser Reihenfolge: Ein Separator ist eine Weiche, wenn ihm ein ILS Aus- oder Einschleuselement
zugeordnet werden kann - das Element ist die Weiche (siehe unten), der
Separator nur der Steuerpunkt davor. Ein eventueller Scanner am selben
Separator ist dabei kein Kriterium, sondern nur ein zusaetzliches Bauteil
des TROs (siehe derive_tros/scanners_for) - viele Separatoren tragen einen
Scanner, ohne eine Weiche zu sein.
Zwei Durchlaeufe, um trotzdem jedem Aus-/Einschleuselement genau einen
Separator zuzuordnen:
1. Geometrische Naehe (Distanz <= tol_mm). HundM05 hat an seinen 1. Geometrische Naehe (Distanz <= tol_mm). HundM05 hat an seinen
Kreisel<->Strecke-Uebergaengen kein eigenes "ILS Weiche"-Bauteil Kreisel<->Strecke-Uebergaengen kein eigenes "ILS Weiche"-Bauteil
@@ -286,21 +294,20 @@ def _find_switch_separators(
Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig
davon, ob seine CSV-Spalte "Zuordnung" auf die Kreisel-Bahn oder auf davon, ob seine CSV-Spalte "Zuordnung" auf die Kreisel-Bahn oder auf
die Strecke zeigt (in den Daten kommt beides vor, siehe derive_tros). die Strecke zeigt (in den Daten kommt beides vor, siehe derive_tros).
2. Separator mit eigenem Scanner (Scanner-Merkmal "Naechster Separator"), 2. Naechstgelegener freier Separator je noch unbelegtem Aus-/
auf noch unbelegten Uebergabestellen der SELBEN Rolle (Aus- oder Einschleuselement, jedes Paar nur einmal vergeben (kleinste Distanz
Einschleusung) und dem SELBEN Kreisel-Anschluss wie ein bereits durch zuerst), auf derselben Bahn und mit derselben Rolle (Aus- oder
Regel 1 bestaetigter Treffer auf dieser Bahn - keine feste mm-Grenze, Einschleusung) wie ein bereits durch Regel 1 bestaetigter Treffer -
stattdessen die naechstgelegene freie Uebergabestelle dieser Rolle. verhindert, dass ein Separator versehentlich mit dem naeher liegenden
Grund (HundM05, Review 2026-09-18): an einem Kreisel mit 5 Abzweigen Einschleuselement einer anderen Strecke verkoppelt wird. Grund fuer den
liegen die tatsaechlichen Schalt-Separatoren pro Abzweig unterschiedlich zweiten Durchlauf (HundM05, Review 2026-09-18): an einem Kreisel mit 5
weit vom Uebergabeelement entfernt (418-1139mm in der aktuellen Abzweigen lagen die tatsaechlichen Schalt-Separatoren pro Abzweig
Registrierung) - eine einzelne mm-Schwelle (Regel 1) trifft nicht alle. unterschiedlich weit vom Uebergabeelement entfernt (418-1139mm in der
Ein Scanner direkt am Separator ist hier aber der CAD-belegte aktuellen Registrierung) - eine einzelne mm-Schwelle (Regel 1) trifft
Lesepunkt vor der Weichenentscheidung (Barcode -> Richtung), also ein nicht alle. Durch die eindeutige Vergabe (jedes Element nur einmal)
verlaesslicherer Marker als der reine Abstand. Nach Rolle (Aus- vs. bleibt ein Separator ohne eigenes Uebergabeelement (z. B. ein
Einschleusung) gefiltert, damit z. B. ein Separator auf der gewoehnlicher Zwischenseparator auf der Bahn) trotzdem unbelegt, auch
Ausschleus-Bahn nicht versehentlich mit dem naeher liegenden ohne feste mm-Grenze.
Einschleuselement einer anderen Strecke verkoppelt wird.
""" """
transfers = [e for e in elements if e.kind in ("Ausschleus", "Einschleus")] transfers = [e for e in elements if e.kind in ("Ausschleus", "Einschleus")]
result: dict[str, tuple[Element, float]] = {} result: dict[str, tuple[Element, float]] = {}
@@ -326,31 +333,29 @@ def _find_switch_separators(
if hit and sep.assignment: if hit and sep.assignment:
bahn_role.setdefault(sep.assignment, hit[0].kind) bahn_role.setdefault(sep.assignment, hit[0].kind)
scanner_targets = {
str(_merkmal(e.merkmale, "Naechster Separator") or "").strip()
for e in elements if e.kind == "Scanner"
}
used_transfers = {hit[0].teile_id for hit in result.values()} used_transfers = {hit[0].teile_id for hit in result.values()}
remaining_seps = [s for s in separators if s.teile_id not in result
and s.x is not None and s.y is not None
and bahn_role.get(s.assignment or "") is not None]
for sep in separators: candidates: list[tuple[float, Element, Element]] = []
if sep.teile_id in result or sep.teile_id not in scanner_targets: for sep in remaining_seps:
continue role = bahn_role[sep.assignment or ""]
if sep.x is None or sep.y is None:
continue
role = bahn_role.get(sep.assignment or "")
if role is None:
continue
best: tuple[float, Element] | None = None
for transfer in transfers: for transfer in transfers:
if (transfer.kind != role or transfer.teile_id in used_transfers if (transfer.kind != role or transfer.teile_id in used_transfers
or transfer.x is None or transfer.y is None): or transfer.x is None or transfer.y is None):
continue continue
d = math.hypot(transfer.x - sep.x, transfer.y - sep.y) d = math.hypot(transfer.x - sep.x, transfer.y - sep.y)
if best is None or d < best[0]: candidates.append((d, sep, transfer))
best = (d, transfer) candidates.sort(key=lambda c: c[0])
if best is not None:
result[sep.teile_id] = (best[1], best[0]) claimed_seps: set[str] = set()
used_transfers.add(best[1].teile_id) for d, sep, transfer in candidates:
if sep.teile_id in claimed_seps or transfer.teile_id in used_transfers:
continue
result[sep.teile_id] = (transfer, d)
claimed_seps.add(sep.teile_id)
used_transfers.add(transfer.teile_id)
return result return result