export_csv: AS/ES-Marker-ID wiederverwenden statt neu zu minten

build_strecke_schleus_items erfand bisher fuer jede AS/ES-Zeile eine neue
TeileId, obwohl der zugehoerige ZZ_ASES_MARKER (Lisp/export.lsp) laengst
eine stabile ID traegt (im sepliste-Eintrag als Feld "id" gespiegelt).
Ergebnis in der Praxis (HundM05): TeileIds 24-33 blieben ungenutzt
(Marker bekommt bis heute keine eigene CSV-Zeile), waehrend fuer dieselben
Punkte bei jedem Export zusaetzlich frische, hoehere IDs (46-55) minted
wurden - eine sichtbare Luecke plus unnoetige ID-Inflation.

Jetzt: liest die Marker-ID aus sepliste[0]["id"] (AS) bzw. sepliste[-1]["id"]
(ES) und uebernimmt sie unveraendert; fehlt sie (Altbestand ohne Marker),
bleibt das bisherige Mint-Verhalten unangetastet. Der laufende Zaehler wird
auf die wiederverwendete ID hochgezogen, damit ein spaeterer Mint in
derselben Kette sie nicht erneut vergibt. ZZ_ASES_MARKER selbst bekommt in
process_blocks jetzt einen expliziten, dokumentierten Skip (statt stillem
Durchfallen durch alle Muster).

Verifiziert an den echten HundM05-Daten: export.csv lief vorher 1-55 mit
Luecke 24-33, jetzt luecken- und duplikatfrei 1-45 (results/export.csv neu
erzeugt). 2 neue Tests in tests/test_export_separatorliste.py (Wiederver-
wendung + unveraenderter Fallback), volle Suite unveraendert bis auf 4
vorbestehende, unabhaengige Fixture-Failures (stale tests/output/*.dxf).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-22 13:41:35 +02:00
co-authored by Claude Sonnet 5
parent 618ff3d86c
commit 719ffc2385
2 changed files with 99 additions and 3 deletions
+42 -3
View File
@@ -592,8 +592,6 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
zaehler = {}
for sp in schleus:
elem_nr += 1
naechste_id += 1
teileid = f"{naechste_id:04d}"
teileart = sp["teileart"]
zaehler[teileart] = zaehler.get(teileart, 0) + 1
planquadrat = (compute_planquadrat(sp["x"], sp["y"], planquadrat_cfg)
@@ -603,6 +601,7 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
sepliste = strecke.get("sepliste") if strecke else None
merkmale = {"Erzeugt aus": "Kreisel-Strecke-Kollision"}
sep_kette_key = None
marker_id = ""
if sepliste:
# Neue XDATA vorhanden: Nachbarn nur noch aus der Ketten-internen
# Vorgaenger/Nachfolger-Beziehung (AS/ES ist immer das erste bzw.
@@ -611,7 +610,18 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
# TeileIds - erst nach dem Bau ALLER SEP/AS/ES-Zeilen dieser Kette
# aufloesbar (siehe process_blocks, Abschnitt nach beiden Aufrufen,
# analog zum Kreisel-Umlauf/Weichen-Muster).
lfdnr = sepliste[0]["lfdnr"] if sp["ende_label"] == "Anfang" else sepliste[-1]["lfdnr"]
sepliste_ende = sepliste[0] if sp["ende_label"] == "Anfang" else sepliste[-1]
lfdnr = sepliste_ende["lfdnr"]
# ZZ_ASES_MARKER (csv:asmark-aktualisieren, Lisp/export.lsp) traegt
# bereits eine STABILE, ueber Exportlaeufe hinweg gleichbleibende
# ID fuer genau dieses Kettenende (schon 4-stellig formatiert,
# ssg-id-format) - im sepliste-Eintrag als Feld "id" gespiegelt.
# Diese ID unten WIEDERVERWENDEN statt eine neue zu minten: der
# Marker bekommt in process_blocks bewusst KEINE eigene CSV-Zeile
# (siehe pattern_ases_marker), seine ID bliebe sonst fuer immer
# ungenutzt, waehrend hier bei jedem Exportlauf zusaetzlich eine
# neue, hoehere ID fuer denselben physischen Punkt erfunden wuerde.
marker_id = str(sepliste_ende.get("id", "")).strip()
sep_kette_key = f"{sp['strecke_id']}#{lfdnr}"
nachbarn = [] # in process_blocks nachgetragen
merkmale["Kreisel"] = sp["kreisel_id"]
@@ -622,6 +632,22 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
merkmale["Strecke"] = sp["strecke_id"]
merkmale["Streckenende"] = sp["ende_label"]
if marker_id:
# Marker-ID uebernehmen (schon 4-stellig formatiert - NICHT
# erneut formatieren) und den laufenden Zaehler ggf. hochziehen,
# damit ein SPAETERER Mint in dieser Schleife (Altbestand-Wrapper
# ohne Marker/sepliste) nie mit einer bereits wiederverwendeten
# Marker-ID kollidiert - unabhaengig davon, in welcher Reihenfolge
# Lisp Marker- vs. Separator-Proxy-IDs vergeben hat.
teileid = marker_id
if marker_id.isdigit():
naechste_id = max(naechste_id, int(marker_id))
else:
# Altbestand-Wrapper ohne Marker-ID (oder Marker-ID-Zuweisung in
# Lisp fehlgeschlagen) - unveraendertes bisheriges Verhalten.
naechste_id += 1
teileid = f"{naechste_id:04d}"
item = {
"nr": elem_nr,
"teileart": teileart,
@@ -997,6 +1023,19 @@ def process_blocks(blocks, lookup, dbg=None):
if bname in SKIP_BLOCKS:
continue
# ZZ_ASES_MARKER (siehe cfg/export.cfg pattern_ases_marker sowie
# csv:asmark-aktualisieren in Lisp/export.lsp): der persistente
# Marker, den Lisp an jedes AS-/ES-Kettenende setzt, damit dessen ID
# ueber Exportlaeufe hinweg stabil bleibt. Bekommt hier bewusst KEINE
# eigene CSV-Zeile - die sichtbare AS/ES-Zeile fuer denselben
# physischen Punkt liefert weiterhin build_strecke_schleus_items
# (reine Geometrie); die Marker-ID wird dort ueber das sepliste-Feld
# "id" wiederverwendet (siehe dort). Eine eigene Zeile hier waere
# doppelt gezaehlt und haette ohnehin keine sinnvolle Geometrie/
# Boundingbox aus dem kleinen Marker-Rechteck selbst.
if matches_any(bname, BLOCKPATTERNS.get("pattern_ases_marker", [])):
continue
# Omniflo Bogen oder Weiche
if bname in lookup:
typ, eintrag = lookup[bname]