Files
sps_skel/tests/test_kreisel_umlauf_graph.py
T
Samer AyadiandClaude Opus 5.5 c4122b4497 [FIX] Materialfluss: Kreisel als geschlossener Umlauf
Jeder Kreisel bekam nur EINE Bahnkante aus der Drehrichtung (UZS R->L,
GUZ L->R). Die Gegenbahn hatte damit keinen Abgang bzw. Zugang, und der
Sackgassen-Check meldete an fast jedem Kreisel "Sackgasse"/"nicht
erreichbar", obwohl die Anlage durchlaufbar ist.

- build_graph: immer L->R UND R->L; die Gegenkante traegt
  CIRCLE_RETURN_NOTE und wird in render_dot nicht gezeichnet
- Sackgassen-Pruefung ersetzt durch Erreichbarkeit im Gesamtgraphen
  (von Beladung erreichbar / Entladung erreichbar); unbestimmte Kanten
  zaehlen in beide Richtungen
- Weiche: Merkmal "Beruehrung" (Kreis/Kreis bzw. Tangente) hat Vorrang
  vor der Geometrie, die am Kappenuebergang per Rundung entschied
- Test test_kreisel_umlauf_graph.py mit Fixture test_system_export.csv

TRO-Graph (nach Hosts) fuer dematic/hundm_05/mubea/showroom unveraendert,
HundM05 komplett unveraendert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:40:53 +02:00

119 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
"""Tests fuer den Kreisel-Umlauf im Materialflussgraphen (lib/material_flow.py).
Ein Kreisel ist ein geschlossener Umlauf: das Ende von L laeuft in den Anfang
von R und das Ende von R in den Anfang von L. Frueher bekam jeder Kreisel nur
EINE Bahnkante (aus der Drehrichtung) - jede Bahn ohne Gegenkante galt dann
als Sackgasse bzw. unerreichbar, obwohl die Anlage vollstaendig durchlaufbar
ist. Fixture: Testanlage test_system (dxfmakros results/export.csv, 9 Kreisel,
8 Kreisel-Kreisel-Weichen, 1 Beladung, 1 Entladung).
"""
from __future__ import annotations
from dataclasses import replace
from pathlib import Path
import pytest
from material_flow import (
CIRCLE_RETURN_NOTE,
EDGE_CIRCLE,
EDGE_FLOW,
EDGE_UNRESOLVED,
Edge,
Element,
Node,
_reachability_warnings,
build_graph,
read_elements,
)
FIXTURE = Path(__file__).parent / "fixtures" / "test_system_export.csv"
@pytest.fixture(scope="module")
def elements() -> list[Element]:
return read_elements(FIXTURE, [])
def _graph(elements: list[Element]):
warnings: list[str] = []
return build_graph(elements, FIXTURE.name, warnings), warnings
def test_jeder_kreisel_hat_beide_bahnkanten(elements):
graph, _ = _graph(elements)
for el in (e for e in elements if e.is_circle):
left, right = f"{el.teile_id}-L", f"{el.teile_id}-R"
circle = [e for e in graph.edges if e.kind == EDGE_CIRCLE
and {e.src, e.dst} == {left, right}]
assert {(e.src, e.dst) for e in circle} == {(left, right), (right, left)}
# genau eine Hauptkante mit dem Drehrichtungs-Kuerzel
assert sorted(e.note for e in circle) == sorted([el.rotation, CIRCLE_RETURN_NOTE])
def test_keine_sackgassen_in_durchlaufbarer_anlage(elements):
_, warnings = _graph(elements)
bahn = [w for w in warnings if w.startswith("Bahn ")]
assert bahn == []
def test_kreis_kreis_weiche_ist_gerichtet(elements):
"""Weiche 0006<->0005 ist "Kreis/Kreis" - liegt genau auf dem Uebergang
Endkappe/Gerade, die Geometrie allein entschied dort per Rundung auf
"Laengsseite, unbestimmt". Das Merkmal "Beruehrung" hat Vorrang."""
graph, _ = _graph(elements)
weiche = [e for e in graph.edges if e.note == "Weiche"
and {e.src.split("-")[0], e.dst.split("-")[0]} == {"0005", "0006"}]
assert weiche
assert all(e.kind == EDGE_FLOW for e in weiche)
def test_ohne_weichen_meldet_unerreichbare_bahnen(elements):
"""Gegenprobe: ohne die Kreisel-Kreisel-Weichen (und ohne die rohen
Kreisel-Nachbarn, die sonst als unbestimmte Kanten verbinden) ist nur der
Kreisel der Beladung erreichbar - die Pruefung muss das melden."""
ohne = [replace(e, neighbours=[]) if e.is_circle else e
for e in elements if e.kind != "Weiche"]
_, warnings = _graph(ohne)
assert any("von keiner Beladung" in w for w in warnings)
assert any("keine Entladung" in w for w in warnings)
def _el(teile_id: str, kind: str) -> Element:
return Element(
number=teile_id, kind=kind, teile_art=kind, teile_id=teile_id,
name=f"{kind} {teile_id}", plan_square="", neighbours=[], track_ids=[],
merkmale={}, row=0, position=(0.0, 0.0, 0.0), bbox=None,
)
def test_unbestimmte_kante_in_beide_richtungen_begehbar():
"""Eine Weiche mit unbestimmter Richtung (dir=both) darf nicht nur in
src->dst-Richtung zaehlen."""
k1, k2 = _el("0001", "Kreisel"), _el("0002", "Kreisel")
nodes = {
"0001-L": Node("0001-L", "Kreisel", k1, lane="L"),
"0001-R": Node("0001-R", "Kreisel", k1, lane="R"),
"0002-L": Node("0002-L", "Kreisel", k2, lane="L"),
"0002-R": Node("0002-R", "Kreisel", k2, lane="R"),
"B": Node("B", "Beladung", _el("B", "Beladung")),
"E": Node("E", "Entladung", _el("E", "Entladung")),
}
edges = [
Edge("0001-L", "0001-R", EDGE_CIRCLE, "GUZ"),
Edge("0001-R", "0001-L", EDGE_CIRCLE, CIRCLE_RETURN_NOTE),
Edge("0002-L", "0002-R", EDGE_CIRCLE, "GUZ"),
Edge("0002-R", "0002-L", EDGE_CIRCLE, CIRCLE_RETURN_NOTE),
# Beladung an Kreisel 2, Entladung an Kreisel 1, Weiche zeigt "falsch herum"
Edge("B", "0002-L", EDGE_FLOW),
Edge("0001-L", "E", EDGE_FLOW),
Edge("0001-R", "0002-R", EDGE_UNRESOLVED, "Weiche"),
]
assert _reachability_warnings(nodes, edges) == []
# gerichtet waere Kreisel 1 von der Beladung aus nicht erreichbar
edges[-1] = Edge("0001-R", "0002-R", EDGE_FLOW, "Weiche")
assert any("0001-L" in w and "von keiner Beladung" in w
for w in _reachability_warnings(nodes, edges))