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:
co-authored by
Claude Sonnet 5
parent
618ff3d86c
commit
719ffc2385
+42
-3
@@ -592,8 +592,6 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
|
|||||||
zaehler = {}
|
zaehler = {}
|
||||||
for sp in schleus:
|
for sp in schleus:
|
||||||
elem_nr += 1
|
elem_nr += 1
|
||||||
naechste_id += 1
|
|
||||||
teileid = f"{naechste_id:04d}"
|
|
||||||
teileart = sp["teileart"]
|
teileart = sp["teileart"]
|
||||||
zaehler[teileart] = zaehler.get(teileart, 0) + 1
|
zaehler[teileart] = zaehler.get(teileart, 0) + 1
|
||||||
planquadrat = (compute_planquadrat(sp["x"], sp["y"], planquadrat_cfg)
|
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
|
sepliste = strecke.get("sepliste") if strecke else None
|
||||||
merkmale = {"Erzeugt aus": "Kreisel-Strecke-Kollision"}
|
merkmale = {"Erzeugt aus": "Kreisel-Strecke-Kollision"}
|
||||||
sep_kette_key = None
|
sep_kette_key = None
|
||||||
|
marker_id = ""
|
||||||
if sepliste:
|
if sepliste:
|
||||||
# Neue XDATA vorhanden: Nachbarn nur noch aus der Ketten-internen
|
# Neue XDATA vorhanden: Nachbarn nur noch aus der Ketten-internen
|
||||||
# Vorgaenger/Nachfolger-Beziehung (AS/ES ist immer das erste bzw.
|
# 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
|
# TeileIds - erst nach dem Bau ALLER SEP/AS/ES-Zeilen dieser Kette
|
||||||
# aufloesbar (siehe process_blocks, Abschnitt nach beiden Aufrufen,
|
# aufloesbar (siehe process_blocks, Abschnitt nach beiden Aufrufen,
|
||||||
# analog zum Kreisel-Umlauf/Weichen-Muster).
|
# 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}"
|
sep_kette_key = f"{sp['strecke_id']}#{lfdnr}"
|
||||||
nachbarn = [] # in process_blocks nachgetragen
|
nachbarn = [] # in process_blocks nachgetragen
|
||||||
merkmale["Kreisel"] = sp["kreisel_id"]
|
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["Strecke"] = sp["strecke_id"]
|
||||||
merkmale["Streckenende"] = sp["ende_label"]
|
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 = {
|
item = {
|
||||||
"nr": elem_nr,
|
"nr": elem_nr,
|
||||||
"teileart": teileart,
|
"teileart": teileart,
|
||||||
@@ -997,6 +1023,19 @@ def process_blocks(blocks, lookup, dbg=None):
|
|||||||
if bname in SKIP_BLOCKS:
|
if bname in SKIP_BLOCKS:
|
||||||
continue
|
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
|
# Omniflo Bogen oder Weiche
|
||||||
if bname in lookup:
|
if bname in lookup:
|
||||||
typ, eintrag = lookup[bname]
|
typ, eintrag = lookup[bname]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from export_neighbors import compute_sep_kette, compute_kreisel_umlauf # noqa:
|
|||||||
from export_csv import ( # noqa: E402
|
from export_csv import ( # noqa: E402
|
||||||
build_strecke_schleus_items,
|
build_strecke_schleus_items,
|
||||||
map_separator_kette_items,
|
map_separator_kette_items,
|
||||||
|
max_numeric_teileid,
|
||||||
write_separatorliste_merkmale,
|
write_separatorliste_merkmale,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,3 +146,59 @@ class TestSeparatorlisteMerkmal:
|
|||||||
write_separatorliste_merkmale([strecke], {})
|
write_separatorliste_merkmale([strecke], {})
|
||||||
assert "trackids" not in strecke
|
assert "trackids" not in strecke
|
||||||
assert "Separatorlist" not in strecke["merkmale"]
|
assert "Separatorlist" not in strecke["merkmale"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestMarkerIdWiederverwendung:
|
||||||
|
"""build_strecke_schleus_items darf die ID eines dauerhaften
|
||||||
|
ZZ_ASES_MARKER (siehe csv:asmark-aktualisieren, Lisp/export.lsp; im
|
||||||
|
sepliste-Eintrag als Feld "id" gespiegelt) nicht ignorieren und
|
||||||
|
stattdessen eine neue erfinden - genau das erzeugte die vom Nutzer
|
||||||
|
gemeldete TeileId-Luecke (Marker-ID blieb ungenutzt, gleichzeitig
|
||||||
|
entstand fuer denselben Punkt eine zusaetzliche, hoehere ID)."""
|
||||||
|
|
||||||
|
def test_marker_id_wiederverwendet_statt_neu_gemintet(self):
|
||||||
|
items = _strecke_mit_zwei_separatoren()
|
||||||
|
strecke = next(it for it in items if it.get("teileid") == "0010")
|
||||||
|
# AS = sepliste[0] (lfdnr=1), ES = sepliste[-1] (lfdnr=4) - siehe
|
||||||
|
# ssg-sepliste-sammeln (Lisp/ssg_ks_insert.lsp): AS immer zuerst,
|
||||||
|
# ES immer zuletzt. Bewusst NIEDRIGER als die Proxy-Separator-IDs
|
||||||
|
# 0052/0053 gewaehlt - genau wie im real beobachteten HundM05-Export
|
||||||
|
# (Marker 0024-0033, Separator-Proxies 0034-0045).
|
||||||
|
strecke["sepliste"][0]["id"] = "0024"
|
||||||
|
strecke["sepliste"][-1]["id"] = "0033"
|
||||||
|
|
||||||
|
sep_kette = compute_sep_kette(items)
|
||||||
|
sep_kette_by_key = {}
|
||||||
|
build_strecke_schleus_items(
|
||||||
|
items, None, 100, sep_kette=sep_kette,
|
||||||
|
sep_kette_by_key=sep_kette_by_key)
|
||||||
|
|
||||||
|
as_item = sep_kette_by_key["0010#1"]
|
||||||
|
es_item = sep_kette_by_key["0010#4"]
|
||||||
|
assert as_item["teileid"] == "0024", (
|
||||||
|
f'AS-Zeile hat teileid={as_item["teileid"]!r}, erwartet die '
|
||||||
|
f'wiederverwendete Marker-ID "0024"')
|
||||||
|
assert es_item["teileid"] == "0033", (
|
||||||
|
f'ES-Zeile hat teileid={es_item["teileid"]!r}, erwartet die '
|
||||||
|
f'wiederverwendete Marker-ID "0033"')
|
||||||
|
|
||||||
|
# Kein spaeterer Mint in DIESEM Lauf darf eine wiederverwendete
|
||||||
|
# Marker-ID erneut vergeben - der laufende Zaehler muss mindestens
|
||||||
|
# auf die hoechste wiederverwendete ID hochgezogen worden sein.
|
||||||
|
assert max_numeric_teileid(items) >= 33
|
||||||
|
|
||||||
|
def test_strecke_ohne_marker_id_mintet_wie_bisher(self):
|
||||||
|
"""Fehlt das "id"-Feld (Altbestand-Wrapper vor der Marker-Funktion,
|
||||||
|
oder Marker-Zuweisung in Lisp fehlgeschlagen), bleibt das Mint-
|
||||||
|
Verhalten unveraendert - dieselben TeileIds wie vor diesem Fix."""
|
||||||
|
items = _strecke_mit_zwei_separatoren()
|
||||||
|
sep_kette = compute_sep_kette(items)
|
||||||
|
sep_kette_by_key = {}
|
||||||
|
build_strecke_schleus_items(
|
||||||
|
items, None, 100, sep_kette=sep_kette,
|
||||||
|
sep_kette_by_key=sep_kette_by_key)
|
||||||
|
|
||||||
|
as_item = sep_kette_by_key["0010#1"]
|
||||||
|
es_item = sep_kette_by_key["0010#4"]
|
||||||
|
assert as_item["teileid"] == "0054"
|
||||||
|
assert es_item["teileid"] == "0055"
|
||||||
|
|||||||
Reference in New Issue
Block a user