export_csv: AS/ES direkt aus ZZ_ASES_MARKER klassifizieren, Kreisel<->Strecke-Kollisionserzeugung entfernen
Stufe 2 (Python-Seite von "sepliste: Bauteil-IDs eintragen"): AS/ES-Kettenenden sind jetzt ganz normale Items, direkt aus dem dauerhaften ZZ_ASES_MARKER klassifiziert (TeileId/Position/Boundingbox vom Marker-Block selbst) - keine Kollisionsberechnung mehr, die sie erfindet. - lib/export_neighbors.py: compute_strecke_kreisel_schleus entfernt (suchte bisher per Kollisionstest den anschliessenden Kreisel und erzeugte dabei bei JEDEM Export eine neue TeileId fuer denselben physischen Punkt). AUSSCHLEUS_/ EINSCHLEUS_TEILEART sind jetzt Teil von KREISEL_UMLAUF_TEILEARTEN - ein AS/ES bringt seine eigene Bounding-Box vom Marker mit und wird dadurch, wie jeder freie Separator, ueber die normale BBox-Ueberschneidung einem Kreisel zugeordnet; die gesonderte "naechster Kreisel per sep_kette"-Suche in compute_kreisel_umlauf (dessen sep_kette-Parameter damit entfaellt) ist ueberfluessig geworden. compute_sep_kette gibt je Eintrag zusaetzlich das Feld "id" weiter (Bauteil-TeileId, siehe vorheriger Commit). - lib/export_csv.py: process_blocks klassifiziert ZZ_ASES_MARKER direkt (TYP- Attribut waehlt Ausschleus-/Einschleuselement); build_strecke_schleus_items entfaellt komplett. map_separator_kette_items -> map_sepliste_kette_items: ordnet jetzt AS/SEP/ES exakt per ID-Gleichheit zu (sepliste-Feld "id" gegen die TeileId der klassifizierten Items) statt per Koordinaten-Naehe - SEP faellt bei fehlendem "id"-Feld weiterhin auf den alten Naehe-Abgleich zurueck (Altbestand), AS/ES ohne "id" bleiben unaufgeloest. - Neu: pruefe_ases_in_beiden_containern prueft am Ende von process_blocks, ob jede AS-/ES-Zeile sowohl in den TrackIds ihrer eigenen Strecke als auch in denen mindestens eines Kreisels auftaucht, und traegt bei Abweichung eine Warnung ein (Spalte "Warnungen") - Konsistenzpruefung fuer genau die beiden "Container", zu denen ein AS/ES gehoert. - Aufgeraeumt (tote Routinen): _strecke_ends, _closest_pt_on_segment, _distinct_kreisel_ids (nur von den entfernten Funktionen genutzt), sowie der temporaere Diagnose-Filter DEBUG_HIGHLIGHT_TEILEIDS/dbg_highlight (an die jetzt behobene Koordinaten-Matching-Ambiguitaet gebunden, in seinem eigenen Kommentar bereits als Wegwerf-Code markiert). LISP-seitiger Dead-Code-Scan (export.lsp, ssg_ks_insert.lsp - die diese Woche bearbeiteten Module): keine Funde. Verifiziert an den echten HundM05-Daten (results/export_raw.json, 45 Bloecke): TeileId-Bereich luecken- und duplikatfrei 1-45 (vorher 1-55 mit Luecke 24-33 + Zusatz-IDs 46-55), alle 10 AS/ES-IDs korrekt in ihrer Strecke UND im Kreisel 06 TrackIds, keine Warnungen der neuen Pruefung. Zwei Testdateien auf die neue Architektur umgestellt (test_export_kreisel_umlauf.py: AS/ES als Item statt Kollisions-Erzeugung, neue Klasse TestAsEsImKreiselUmlauf fuer die BBox- Teilnahme; test_export_separatorliste.py: id-basierte Zuordnung, Klasse TestMarkerIdWiederverwendung durch TestMapSeplisteKetteItemsFallback ersetzt - die alte testete inzwischen entfernte Mint-Logik). Volle Suite: 224 bestanden (4 vorbestehende, unabhaengige Fixture-Failures unveraendert). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
719ffc2385
commit
ab3958e0e4
+87
-67
@@ -149,7 +149,7 @@ Konzept zur sepliste — es sind dieselben physischen Symbole, nur aus zwei
|
||||
Blickwinkeln: `csv:sep-proxies-erzeugen` liefert sie als echte, exportierbare
|
||||
INSERT-Kopie (mit Bounding-Box und ID); die sepliste liefert dieselben
|
||||
Separatoren als Eintraege mit Vorgaenger-/Nachfolger-Wissen. Python fuehrt
|
||||
beides zusammen (siehe `map_separator_kette_items` unten) statt sie doppelt
|
||||
beides zusammen (siehe `map_sepliste_kette_items` unten) statt sie doppelt
|
||||
zu zaehlen.
|
||||
|
||||
### Das Feld `id` je sepliste-Eintrag
|
||||
@@ -161,13 +161,15 @@ realen Bauteils, das ihn in der Zeichnung vertritt:
|
||||
{"typ": "AS", "lfdnr": 1, "x": 5070.1, "y": 1199.2, "id": "0031"}
|
||||
```
|
||||
|
||||
Das ist dieselbe ID, die die zugehoerige temporaere Kopie in ihrer eigenen
|
||||
JSON-Zeile traegt — die Verbindung "Kettenglied <-> reales Teil" steht damit
|
||||
schon im Export und muss in Python nicht mehr ueber Koordinaten-Naehe geraten
|
||||
werden (`map_separator_kette_items`, siehe unten; solange Python das Feld noch
|
||||
nicht liest, aendert sich dort nichts). Das Feld fehlt, wenn keine Kopie
|
||||
erzeugt werden konnte oder deren ID-Attribut leer blieb — Altbestand und
|
||||
Fehlerfaelle verhalten sich damit wie bisher.
|
||||
Das ist dieselbe ID, die die zugehoerige temporaere Kopie (SEP) bzw. der
|
||||
dauerhafte `ZZ_ASES_MARKER` (AS/ES) in ihrer eigenen JSON-Zeile traegt — die
|
||||
Verbindung "Kettenglied <-> reales Teil" steht damit schon im Export und muss
|
||||
in Python nicht mehr ueber Koordinaten-Naehe geraten werden. `map_sepliste_
|
||||
kette_items` (siehe Abschnitt 5 unten) loest genau darueber jeden sepliste-
|
||||
Eintrag exakt per ID-Gleichheit auf. Das Feld fehlt, wenn keine Kopie erzeugt
|
||||
werden konnte oder deren ID-Attribut leer blieb — fuer SEP faellt das auf den
|
||||
alten Koordinaten-Abgleich zurueck (Altbestand), fuer AS/ES bleibt der
|
||||
Eintrag dann unaufgeloest (siehe Abschnitt 3 unten).
|
||||
|
||||
### Der AS/ES-Marker (`ZZ_ASES_MARKER`)
|
||||
|
||||
@@ -358,10 +360,11 @@ Zu beachten:
|
||||
- `VF_1` (`0021`) bekommt seine eigene `TrackIds`-Spalte
|
||||
(`write_separatorliste_merkmale`) aus der `sepliste`: AS -> SEP -> SEP -> ES
|
||||
in Baureihenfolge, unabhaengig vom Kreisel-Umlauf.
|
||||
- Die AS/ES-Enden (`0021`=AS, `0024`=ES) sind hier synthetische Zeilen aus
|
||||
`build_strecke_schleus_items` (Zeilentyp "ILS Ausschleuselement"/
|
||||
"ILS Einschleuselement") — in diesem gekuerzten Beispiel nicht einzeln
|
||||
aufgefuehrt, aber ihre TeileIds tauchen in `TrackIds` beider Seiten auf.
|
||||
- Die AS/ES-Enden (`0021`=AS, `0024`=ES) sind eigene Zeilen (Zeilentyp
|
||||
"ILS Ausschleuselement"/"ILS Einschleuselement"), direkt aus dem
|
||||
dauerhaften `ZZ_ASES_MARKER` klassifiziert — in diesem gekuerzten Beispiel
|
||||
nicht einzeln aufgefuehrt, aber ihre TeileIds tauchen in `TrackIds` beider
|
||||
Seiten auf.
|
||||
- Die Separatoren 3 und 5 (`0012`/`0022`) unterscheiden sich nur durch ihre
|
||||
`Zuordnung`: `0012` haengt am Kreisel, `0022`/`0023` an der VF-Kette — beide
|
||||
Faelle laufen ueber denselben `pattern_separator`-Blocktyp.
|
||||
@@ -435,39 +438,46 @@ Partner", je nach Mindestanzahl Partner pro TeileArt).
|
||||
|
||||
### 3. Synthetische Zeilen an den Uebergangsstellen
|
||||
|
||||
Drei Arten von "Zeilen ohne eigene Zeichnungsgeometrie" werden aus der
|
||||
Geometrie der bereits klassifizierten Items abgeleitet:
|
||||
Nur noch EINE Art von "Zeile ohne eigene Zeichnungsgeometrie" wird aus der
|
||||
Geometrie bereits klassifizierter Items abgeleitet:
|
||||
|
||||
- **`compute_kreisel_touch_switches`** — praezise Kapsel-Geometrie (nicht nur
|
||||
Bounding-Box) findet jeden Punkt, an dem sich zwei echte Kreisel wirklich
|
||||
beruehren, und erzeugt dort eine `"ILS Weiche"`-Zeile
|
||||
(`build_kreisel_weiche_items`). Das ist der Verzweigungspunkt, an dem
|
||||
Material von einem Kreisel zum anderen wechseln kann.
|
||||
- **`compute_strecke_kreisel_schleus`** — an jedem Streckenende (AS/ES,
|
||||
ueber K1/K2 bzw. als Fallback die Wrapper-Enden), das einen Kreisel
|
||||
beruehrt (siehe Schritt 2), entsteht ein `"ILS Ausschleuselement"`
|
||||
(Anfang) bzw. `"ILS Einschleuselement"` (Ende), leicht zum Kreisel hin
|
||||
versetzt (`build_strecke_schleus_items`).
|
||||
- **`map_separator_kette_items`** — ordnet jedem `"SEP"`-Eintrag einer
|
||||
sepliste das dazu passende, bereits vorhandene Separator-Item zu (siehe
|
||||
naechster Abschnitt). Erzeugt bewusst KEINE eigenen Zeilen.
|
||||
|
||||
AS/ES-Kettenenden werden NICHT mehr synthetisiert (fruehere Version:
|
||||
`compute_strecke_kreisel_schleus`/`build_strecke_schleus_items` suchten den
|
||||
anschliessenden Kreisel per Kollisionstest und erfanden dabei eine eigene,
|
||||
bei jedem Export neue TeileId). Der dauerhafte `ZZ_ASES_MARKER`
|
||||
(Lisp/export.lsp, siehe oben) ist ein echter Block mit echter Geometrie -
|
||||
`"ILS Ausschleuselement"`/`"ILS Einschleuselement"` sind darum eine ganz
|
||||
normale Zeile aus Schritt 1 (Klassifikation), TeileId/Position/Boundingbox
|
||||
direkt vom Marker-Block selbst.
|
||||
|
||||
`map_sepliste_kette_items` (siehe Abschnitt 5 unten) ordnet danach jedem
|
||||
sepliste-Eintrag (AS/SEP/ES) das dazu passende, bereits vorhandene Item zu.
|
||||
Erzeugt bewusst KEINE eigenen Zeilen.
|
||||
|
||||
### 4. Der Kreisel-Umlauf — wie Nachbarn ueber einen Kreisel hinweg entstehen
|
||||
|
||||
`compute_kreisel_umlauf` ist der zentrale Baustein, der Ketten-Enden,
|
||||
freie Punkte (Separatoren UND BTMT Beladung/Entladung) und Kreisel-Kreisel-
|
||||
`compute_kreisel_umlauf` ist der zentrale Baustein, der freie Punkte
|
||||
(Separatoren, BTMT Beladung/Entladung, AS/ES-Enden) und Kreisel-Kreisel-
|
||||
Weichen zu einer widerspruchsfreien Nachbarschaft zusammenfuehrt. Fuer jeden
|
||||
echten Kreisel werden alle Punkte gesammelt, die dort geometrisch liegen:
|
||||
|
||||
- freie Punkte (`KREISEL_UMLAUF_TEILEARTEN` in `lib/export_neighbors.py`:
|
||||
Separator, BTMT Beladung, BTMT Entladung, Eingang, Ausgang — alles
|
||||
Stationen/Markierungen direkt auf der Bahn/dem Track), deren Bounding-Box
|
||||
die Kreisel-Box ueberschneidet,
|
||||
Separator, BTMT Beladung, BTMT Entladung, Eingang, Ausgang UND die AS-/ES-
|
||||
Ein-/Ausschleuselemente — alles Stationen/Markierungen direkt auf der
|
||||
Bahn/dem Track mit eigener Bounding-Box), deren Bounding-Box die Kreisel-
|
||||
Box ueberschneidet. Ein AS/ES bringt seine Box vom eigenen `ZZ_ASES_MARKER`
|
||||
mit — keine gesonderte Naeherungslogik mehr noetig (fruehere Version
|
||||
suchte hierfuer je AS/ES-sepliste-Eintrag den geometrisch naechstgelegenen
|
||||
Kreisel, unabhaengig davon ob er wirklich in Reichweite lag),
|
||||
- die Kreisel-Kreisel-Weichen aus Schritt 3 (in BEIDEN beteiligten
|
||||
Kreisel-Umlaeufen, da eine Weiche der gemeinsame Verzweigungspunkt ist —
|
||||
man kann von Kreisel A nach B und zurueck),
|
||||
- die AS/ES-Enden andockender VF_n/GF_n-Ketten (aus der `sepliste`,
|
||||
zugeordnet zum geometrisch naechstgelegenen Kreisel).
|
||||
man kann von Kreisel A nach B und zurueck).
|
||||
|
||||
Alle Punkte werden nach Winkel um die Kreiselachse sortiert (Richtung ueber
|
||||
das `DREHRICHTUNG`-Attribut, UZS/GUZ) — das ergibt eine geschlossene,
|
||||
@@ -484,32 +494,31 @@ echte Nachbarn.
|
||||
|
||||
`compute_sep_kette` liest jede `sepliste` und baut daraus einen Schluessel
|
||||
je Eintrag (`"<strecke_id>#<lfdnr>"`) mit `vorgaenger`/`nachfolger`
|
||||
(wieder solche Schluessel, `None` am Kettenende). Das ist eine reine
|
||||
Uebersetzung der LISP-Baureihenfolge — kein Rateverfahren.
|
||||
(wieder solche Schluessel, `None` am Kettenende) sowie `id` (die TeileId des
|
||||
vertretenden Items, siehe oben). Das ist eine reine Uebersetzung der
|
||||
LISP-Baureihenfolge — kein Rateverfahren.
|
||||
|
||||
**Warum die Zeilen erst NACH allen Schluesseln aufgeloest werden:** ein
|
||||
Schluessel kann auf eine Zeile zeigen, die zum Zeitpunkt seiner Vergabe noch
|
||||
gar nicht existiert (eine SEP in der Mitte einer Kette hat ihre AS als
|
||||
Vorgaenger, aber die AS-Zeile entsteht erst in `build_strecke_schleus_items`,
|
||||
das VOR der SEP-Zuordnung laeuft). Darum sammeln
|
||||
`build_strecke_schleus_items` und `map_separator_kette_items` ihre Treffer
|
||||
zunaechst nur in einem gemeinsamen `sep_kette_by_key`-dict
|
||||
(Schluessel -> Item), OHNE die `Nachbarn`-Spalte zu setzen.
|
||||
`resolve_umlauf_und_sepliste_nachbarn` loest danach in einem einzigen Schritt
|
||||
sowohl die Kreisel-Umlauf-Punkte als auch die sepliste-Schluessel gegen einen
|
||||
gemeinsamen Lookup (freie Separatoren nach TeileId, Weichen nach Schluessel,
|
||||
sepliste-Zeilen nach Schluessel) zu echten TeileIds auf.
|
||||
**`map_sepliste_kette_items`** loest jeden sepliste-Schluessel auf sein
|
||||
bereits vorhandenes Item auf — EXAKT per ID-Gleichheit (Feld `id` gegen die
|
||||
`teileid` der klassifizierten Items), nicht mehr per Koordinaten-Naehe:
|
||||
AS/ES sind eigene Items (Schritt 3 entfaellt fuer sie), SEP ein bereits
|
||||
klassifiziertes Separator-Item (der verpackte Separator liegt durch
|
||||
`csv:sep-proxies-erzeugen` bereits als echtes Item vor — eine zweite, aus
|
||||
der sepliste erzeugte Zeile waere derselbe physische Separator ein zweites
|
||||
Mal). Nur wenn das `id`-Feld fehlt (Altbestand-Export ohne Marker/Kopie-ID),
|
||||
faellt SEP auf den alten Abgleich zurueck: Wrapper-ID (Item-Merkmal
|
||||
`"Zuordnung"`) plus naechstgelegener rohen INSERT-Punkt (Item-Felder
|
||||
`_x`/`_y` vs. sepliste-Koordinate). AS/ES ohne `id` bleiben unaufgeloest -
|
||||
fuer sie existiert kein Item mehr, das sie ersatzweise vertreten koennte.
|
||||
|
||||
**`map_separator_kette_items` erzeugt keine neuen Zeilen** (Architektur-
|
||||
entscheidung, siehe Docstring in `lib/export_csv.py`): der verpackte
|
||||
Separator liegt durch `csv:sep-proxies-erzeugen` (Phase 1, Schritt 1) bereits
|
||||
als echtes Separator-Item vor. Eine zweite, aus der sepliste erzeugte Zeile
|
||||
waere derselbe physische Separator ein zweites Mal. Die Zuordnung
|
||||
sepliste-Eintrag -> vorhandenes Item laeuft ueber die Wrapper-ID (Item-
|
||||
Merkmal `"Zuordnung"`, von `csv:sep-proxies-zuordnung-setzen` fest gesetzt)
|
||||
plus den naechstgelegenen rohen INSERT-Punkt (Item-Felder `_x`/`_y` vs. die
|
||||
sepliste-Koordinate) — beide stammen aus derselben Bauphase und liegen daher
|
||||
nah beieinander, auch wenn sie nicht exakt uebereinstimmen.
|
||||
Die Treffer landen zunaechst nur in `sep_kette_by_key` (Schluessel -> Item),
|
||||
OHNE die `Nachbarn`-Spalte zu setzen: ein Schluessel kann auf eine Zeile
|
||||
zeigen, deren eigener Nachbar (Vorgaenger/Nachfolger) zu diesem Zeitpunkt
|
||||
selbst noch nicht aufgeloest ist. `resolve_umlauf_und_sepliste_nachbarn`
|
||||
loest danach in einem einzigen Schritt sowohl die Kreisel-Umlauf-Punkte als
|
||||
auch die sepliste-Schluessel gegen einen gemeinsamen Lookup (freie Punkte
|
||||
nach TeileId, Weichen nach Schluessel, sepliste-Zeilen nach Schluessel) zu
|
||||
echten TeileIds auf.
|
||||
|
||||
### Die Spalte `"TrackIds"` — Reihenfolge als eigene CSV-Spalte
|
||||
|
||||
@@ -517,17 +526,28 @@ Zusaetzlich zur `"Nachbarn"`-Spalte (Vorgaenger/Nachfolger je Zeile) traegt
|
||||
`write_separatorliste_merkmale` (`lib/export_csv.py`) jeder VF_n/GF_n-Zeile
|
||||
die eigene Spalte `"TrackIds"` (`item["trackids"]`, Top-Level-Feld wie
|
||||
`"nachbarn"` — KEIN Merkmal) ein: die TeileIds von AS-Ende, Separatoren UND
|
||||
ES-Ende in Baureihenfolge, kommagetrennt (z.B. `"0051, 0052, 0053, 0054,
|
||||
0055"` fuer AS, zwei SEP, ES). AS/ES zaehlen mit, weil sie beim Kreisel
|
||||
(siehe unten) ebenfalls Teil von dessen `TrackIds` sind — ohne sie fehlten
|
||||
hier die Kettenenden. Quelle ist die rohe `sepliste` des Items (nicht
|
||||
`sep_kette`, dessen dict-Reihenfolge nicht garantiert ist). Muss NACH
|
||||
`map_separator_kette_items`/`build_strecke_schleus_items` laufen, weil erst
|
||||
dann jeder sepliste-Schluessel (AS/SEP/ES) auf sein reales Item (TeileId)
|
||||
aufgeloest ist. Beruehrt ein Streckenende geometrisch KEINEN Kreisel,
|
||||
entsteht dort keine AS/ES-Zeile (`build_strecke_schleus_items`) und damit
|
||||
auch keine TeileId — dieses Ende fehlt dann in `TrackIds` (kein Fehler,
|
||||
sondern Folge der fehlenden Kollision).
|
||||
ES-Ende in Baureihenfolge, kommagetrennt (z.B. `"0056, 0052, 0053, 0057"`
|
||||
fuer AS, zwei SEP, ES). AS/ES zaehlen mit, weil sie beim Kreisel (siehe
|
||||
unten) ebenfalls Teil von dessen `TrackIds` sind — ohne sie fehlten hier die
|
||||
Kettenenden. Quelle ist die rohe `sepliste` des Items (nicht `sep_kette`,
|
||||
dessen dict-Reihenfolge nicht garantiert ist). Muss NACH
|
||||
`map_sepliste_kette_items` laufen, weil erst dann jeder sepliste-Schluessel
|
||||
(AS/SEP/ES) auf sein reales Item (TeileId) aufgeloest ist. Fehlt einem
|
||||
Eintrag die TeileId (kein `id`-Feld und - nur bei SEP - kein Koordinaten-
|
||||
Fallback-Treffer), fehlt dieses Kettenglied dann in `TrackIds` (kein Fehler,
|
||||
sondern Folge der fehlenden Zuordnung).
|
||||
|
||||
### Konsistenzpruefung: AS/ES in beiden Containern
|
||||
|
||||
`pruefe_ases_in_beiden_containern` (`lib/export_csv.py`, laeuft am Ende von
|
||||
`process_blocks`, NACH beiden `TrackIds`-Spalten) prueft fuer jede AS-/ES-
|
||||
Zeile, ob ihre TeileId sowohl in den `TrackIds` ihrer EIGENEN Strecke
|
||||
(ZUORDNUNG-Attribut des Markers) als auch in den `TrackIds` MINDESTENS eines
|
||||
Kreisels auftaucht — beide "Container", zu denen ein AS/ES logisch gehoert.
|
||||
Fehlt einer von beiden, ist das kein Normalfall (z.B. ZUORDNUNG leer/falsch,
|
||||
Kollisionstoleranz zu knapp, Kreisel zu weit weg, fehlende Bounding-Box am
|
||||
Marker): die Zeile bekommt eine Eintragung in der Spalte `"Warnungen"`
|
||||
(`item["warnung"]`) UND eine Konsolen-/Debuglog-Meldung.
|
||||
|
||||
`write_kreisel_separatorliste_merkmale` ueberträgt dasselbe Prinzip auf
|
||||
echte Kreisel (`"ILS 2.0 Kreisel"`): Quelle ist hier NICHT die sepliste
|
||||
@@ -544,8 +564,8 @@ separatorliste_merkmale` loest die rohen Umlauf-Schluessel (TeileId eines
|
||||
freien Punkts, sep_kette-Schluessel oder Weichen-Key) auf echte TeileIds auf
|
||||
und schreibt sie kommagetrennt in `item["trackids"]` auf den Kreisel selbst.
|
||||
Muss darum NACH `resolve_umlauf_und_sepliste_nachbarn` (bzw. nach allen
|
||||
Zeilen-Registrierungen: `build_kreisel_weiche_items`, `build_strecke_
|
||||
schleus_items`, `map_separator_kette_items`) laufen.
|
||||
Zeilen-Registrierungen: `build_kreisel_weiche_items`,
|
||||
`map_sepliste_kette_items`) laufen.
|
||||
|
||||
BTMT Beladung/Entladung sind bewusst NICHT in `SENSOR_TEILEARTEN`
|
||||
aufgenommen (die bleibt Separator/Scanner vorbehalten, siehe
|
||||
|
||||
Reference in New Issue
Block a user