feat(export): Merkmal "Einsortiert in" an nach Lage einsortierten Separatoren

Folgewerkzeuge (sps_skel: sepliste-Gegenprobe) erkennen daran, dass ein
Separator in TrackIds, aber zu Recht nicht im Bauprotokoll der Strecke
steht. CLAUDE.md beschreibt den neuen Export-Schritt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 23:12:50 +01:00
parent 5368f5b447
commit 321dcfde64
3 changed files with 12 additions and 2 deletions
+1 -1
View File
@@ -208,7 +208,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM
| `set_einfuegepkt.py` | Setzt `$INSBASE` (Einfuegepunkt) in Omniflo-DXF-Dateien. Schalter je Typ: `--boegen`, `--weichen45`, `--weichen90`, `--weichenkoerper`, `--weichen-parallel`, `--delta`, `--dreifachweiche`, `--sternweiche`, `--show-omniflo` | | `set_einfuegepkt.py` | Setzt `$INSBASE` (Einfuegepunkt) in Omniflo-DXF-Dateien. Schalter je Typ: `--boegen`, `--weichen45`, `--weichen90`, `--weichenkoerper`, `--weichen-parallel`, `--delta`, `--dreifachweiche`, `--sternweiche`, `--show-omniflo` |
| `set_koords.py` | Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo-DXF-Dateien. Schalter: `--k1set` bis `--k4set`, `--set-all`, `--force`, `--show-omniflo`, `--test`, `--number` | | `set_koords.py` | Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo-DXF-Dateien. Schalter: `--k1set` bis `--k4set`, `--set-all`, `--force`, `--show-omniflo`, `--test`, `--number` |
| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Omniflo-Elemente werden ueber das TEILEART-Attribut (`Omniflo *`) erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - NUR aus dem kommagetrennten `ARTINR`-Attribut (eine Zeile je Nummer, TEF-Teile `0_B*` als `Omniflo TEF`); KEIN Katalog-Lookup mehr (den nutzt nur noch `export_csv.py` fuer die Merkmale). Schreibt zusaetzlich immer `<output>_zusammengefasst.csv`: Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden (v.a. Geruest, je Block eine Zeile), zu einer Zeile zusammengefasst (`items_zusammenfassen`, Anzahl addiert, IDs vereinigt); die Hauptdatei bleibt unveraendert. **ILS 2.0 Weiche (6216)**: eine Summenzeile (Anzahl 1), sobald `kreisel_weichen.json` (LISP-Export, siehe KreiselInsert.lsp) Beruehrpunkte enthaelt - je Beruehrpunkt eine Weichenkombination aus 1x aktiv (3371) + 1x passiv (3372), Zuordnung in `cfg/sivas.cfg` `[weichen]` ueber das synthetische Attribut `ANZAHL_WEICHEN`; dieselbe Zahl geht in Merkmal 4403 der Automation-Zeile. Aufruf: `python export_sivas.py <export_raw.json> <data_dir> <output.csv>` | | `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Omniflo-Elemente werden ueber das TEILEART-Attribut (`Omniflo *`) erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - NUR aus dem kommagetrennten `ARTINR`-Attribut (eine Zeile je Nummer, TEF-Teile `0_B*` als `Omniflo TEF`); KEIN Katalog-Lookup mehr (den nutzt nur noch `export_csv.py` fuer die Merkmale). Schreibt zusaetzlich immer `<output>_zusammengefasst.csv`: Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden (v.a. Geruest, je Block eine Zeile), zu einer Zeile zusammengefasst (`items_zusammenfassen`, Anzahl addiert, IDs vereinigt); die Hauptdatei bleibt unveraendert. **ILS 2.0 Weiche (6216)**: eine Summenzeile (Anzahl 1), sobald `kreisel_weichen.json` (LISP-Export, siehe KreiselInsert.lsp) Beruehrpunkte enthaelt - je Beruehrpunkt eine Weichenkombination aus 1x aktiv (3371) + 1x passiv (3372), Zuordnung in `cfg/sivas.cfg` `[weichen]` ueber das synthetische Attribut `ANZAHL_WEICHEN`; dieselbe Zahl geht in Merkmal 4403 der Automation-Zeile. Aufruf: `python export_sivas.py <export_raw.json> <data_dir> <output.csv>` |
| `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>` | | `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Spalten `Nachbarn`/`TrackIds` einer VF_n/GF_n kommen aus deren `sepliste` (Bauprotokoll, nur beim Bau per `entnext` gesammelt). **Freie Separatoren** (eigenstaendige `Separator_SP`-Bloecke, deren Zuordnung die Strecke nennt, die aber nicht beim Bau entstanden - realer Fall Mubea VF_3: 0037/0038) nimmt `einsortieren_freie_separatoren` (export_neighbors.py) vor `compute_sep_kette` nach Lage in die Kette auf: nur wenn Mittelpunkt in der Strecken-BBox UND laengs des Linienzugs der Kettenglieder zwischen AS und ES (reiner Kern `sepliste_einsortier_positionen`); der Separator bekommt das Merkmal `"Einsortiert in": <Strecke>` (sps_skel zieht ihn darueber von der sepliste-Gegenprobe ab) und einen Hinweis in `Warnungen`. **Bewusst nur im Speicher des Exportlaufs, NICHT in die sepliste-XDATA zurueckgeschrieben**: Lisp paart die sepliste-Eintraege ueber die Anzahl je Typ mit den eingebauten Bausteinen (`csv:sepliste-kette-paare`) und fuehrt reservierte AS/ES-IDs unter der `lfdnr` (`csv:asmark-res-key`) - ein fremder Eintrag verschoebe beides; ausserdem schreibt jeder Neuaufbau/Edit der Strecke die XDATA neu. Test `tests/test_export_freie_separatoren.py`. Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>` |
| `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ | | `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ |
| `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv <sivas_export.csv>` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py <dxf> <output.json> [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) | | `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv <sivas_export.csv>` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py <dxf> <output.json> [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) |
| `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` | | `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` |
+5 -1
View File
@@ -1352,7 +1352,8 @@ def einsortieren_freie_separatoren(items, tolerance_mm, dbg=None):
SEP-Eintraege (lfdnr "frei<TeileId>", "id" = TeileId des Separators, SEP-Eintraege (lfdnr "frei<TeileId>", "id" = TeileId des Separators,
"frei": True); der Separator bekommt "_in_strecke_kette" (Strecken- "frei": True); der Separator bekommt "_in_strecke_kette" (Strecken-
TeileId, damit compute_kreisel_umlauf ihn nicht zusaetzlich einem TeileId, damit compute_kreisel_umlauf ihn nicht zusaetzlich einem
Kreisel zuschlaegt) und einen Hinweis in "warnung". Kreisel zuschlaegt), das Merkmal "Einsortiert in" (Strecken-TeileId,
in der CSV-Spalte Merkmale) und einen Hinweis in "warnung".
Muss NACH der Separator-Zuordnung und VOR compute_sep_kette laufen. Muss NACH der Separator-Zuordnung und VOR compute_sep_kette laufen.
""" """
half_tol = tolerance_mm / 2.0 half_tol = tolerance_mm / 2.0
@@ -1428,6 +1429,9 @@ def einsortieren_freie_separatoren(items, tolerance_mm, dbg=None):
neu.append({"typ": "SEP", "lfdnr": f"frei{tid}", "id": tid, "frei": True}) neu.append({"typ": "SEP", "lfdnr": f"frei{tid}", "id": tid, "frei": True})
sep = by_teileid[tid] sep = by_teileid[tid]
sep["_in_strecke_kette"] = sid sep["_in_strecke_kette"] = sid
# Fuer Folgewerkzeuge (sps_skel: sepliste-Gegenprobe): dieser
# Separator steht in TrackIds, aber nicht im Bauprotokoll.
sep.setdefault("merkmale", {})["Einsortiert in"] = sid
hinweis = (f"nicht im Bau von {sid} erzeugt - per Lage in dessen " hinweis = (f"nicht im Bau von {sid} erzeugt - per Lage in dessen "
f"Kette einsortiert") f"Kette einsortiert")
bisherige = sep.get("warnung", "") bisherige = sep.get("warnung", "")
+6
View File
@@ -146,6 +146,12 @@ class TestMubeaVf3:
assert "0015" in _by_id(items, "0037").get("warnung", "") assert "0015" in _by_id(items, "0037").get("warnung", "")
assert _by_id(items, "0062").get("warnung", "") == "" assert _by_id(items, "0062").get("warnung", "") == ""
def test_merkmal_einsortiert_in(self):
items = _pipeline(_mubea_vf3())
assert _by_id(items, "0037")["merkmale"].get("Einsortiert in") == "0015"
assert _by_id(items, "0038")["merkmale"].get("Einsortiert in") == "0015"
assert "Einsortiert in" not in _by_id(items, "0062")["merkmale"]
def test_fremde_zuordnung_bleibt_draussen(self): def test_fremde_zuordnung_bleibt_draussen(self):
items = _mubea_vf3() items = _mubea_vf3()
_by_id(items, "0037")["merkmale"]["Zuordnung"] = "0002-L" _by_id(items, "0037")["merkmale"]["Zuordnung"] = "0002-L"