diff --git a/cfg/Merkmalsstamm_in_Sivas.xlsx b/cfg/Merkmalsstamm_in_Sivas.xlsx index ad4c3ba..0dd6ba7 100644 Binary files a/cfg/Merkmalsstamm_in_Sivas.xlsx and b/cfg/Merkmalsstamm_in_Sivas.xlsx differ diff --git a/cfg/sivas.cfg b/cfg/sivas.cfg index aa11925..dc1a0c2 100644 --- a/cfg/sivas.cfg +++ b/cfg/sivas.cfg @@ -28,19 +28,45 @@ # (TRANSFORMS-Dict), die den ermittelten Rohwert # (Attribut- oder Default-Wert) in die Sivas-Anzeige # umwandelt (z.B. "0"/"1" -> "Ja"/"Nein"). Bekannte Namen: -# ja_nein, geruest_typ_anzeige. Ein unbekannter Name +# ja_nein, geruest_typ_anzeige, mm_zu_m, as_es_vorhanden, +# as_es_winkel, as_seite, es_seite. Ein unbekannter Name # bricht NICHT ab, der Rohwert wird unveraendert # uebernommen (siehe sivas_mapping.anwenden). +# Soll ein Merkmal ohne eigenes Attribut nur per Transform +# entstehen, braucht die Zeile trotzdem den ":" vor dem +# "|" ("WINKEL_AS : | as_es_vorhanden") - sonst gilt der +# Transform-Name als Fallback-Attributname. +# Transforms, die mit @braucht_attribs markiert sind +# (as_seite/es_seite), sehen zusaetzlich alle uebrigen +# Attribute des Blocks - noetig, wenn ein Merkmal von +# einem ZWEITEN Attribut abhaengt (Seite nur, wenn der +# zugehoerige Winkel ueberhaupt ein Element ausweist). # # Abschnittsnamen entsprechen den bisherigen build_*_details-Funktionen in # export_sivas.py: [gefaellestrecke], [kreisel], [eckrad], [geruest]. Die -# geruest-typ-Anzeige-Tabelle (ASCII-Attributwert -> Sivas-Klartext mit -# Umlauten) UND SEPARATOR_BEARBEITUNG_ANZEIGE bleiben eigene, kleine -# Abschnitte weiter unten. +# Anzeige-Tabellen (ASCII-Attributwert -> Sivas-Klartext/Optionswert: +# [geruest_typ_anzeige], [as_es_winkel_anzeige]) UND +# SEPARATOR_BEARBEITUNG_ANZEIGE bleiben eigene, kleine Abschnitte weiter +# unten. +# +# [gefaellestrecke] und [variofoerderer] fuehren die AS-/ES-Merkmale +# (4427-4432) absichtlich DOPPELT: GF_N- und VF_N-Bloecke tragen dieselben +# Attribute des gemeinsamen Strecken-Schemas, und jeder Abschnitt soll fuer +# sich lesbar bleiben. Aenderungen dort bitte in beiden nachziehen (der Test +# tests/test_export_sivas_as_es.py::test_zuordnung_steht_in_der_config wacht +# darueber). # ============================================================ [gefaellestrecke] 2950= MONTAGEHOEHE_m : 0 +# AS-/ES-Element, Reihenfolge wie die Sivas-Hierarchie (identisch in +# [variofoerderer]): 4431/4432 Ja/Nein schalten Winkel und Seite frei. +4431= WINKEL_AS : | as_es_vorhanden +4427= WINKEL_AS : | as_es_winkel +4428= SEITE_AS : | as_seite +4432= WINKEL_ES : | as_es_vorhanden +4429= WINKEL_ES : | as_es_winkel +4430= SEITE_ES : | es_seite 4384= : Gefällestrecke 4318= L_GF_m 4391= GF_WINKEL @@ -85,6 +111,14 @@ Farbe= FARBE # Die Gefaelle-/Vario-Segmentpaare (4312-4392, 4353/4354/4386) haengen an der # Listenlaenge von L_GF_m/L_VF_m und bleiben Code. 2950= MONTAGEHOEHE_m : 0 +# AS-/ES-Element, Reihenfolge wie die Sivas-Hierarchie (identisch in +# [gefaellestrecke]): 4431/4432 Ja/Nein schalten Winkel und Seite frei. +4431= WINKEL_AS : | as_es_vorhanden +4427= WINKEL_AS : | as_es_winkel +4428= SEITE_AS : | as_seite +4432= WINKEL_ES : | as_es_vorhanden +4429= WINKEL_ES : | as_es_winkel +4430= SEITE_ES : | es_seite 4329= GF_Bogen_L_90 : 0 4330= GF_Bogen_L_60 : 0 4331= GF_Bogen_L_30 : 0 @@ -103,6 +137,19 @@ Farbe= FARBE 3992= GERUEST_EINZELMODUL : 0 | ja_nein 3993= GERUEST_TYP : Schoenenberger Geruest | geruest_typ_anzeige +[as_es_winkel_anzeige] +# Attributwert WINKEL_AS/WINKEL_ES -> Optionswert der Sivas-Auswahlliste zu +# Merkmal 4427/4429. Im Merkmalstamm heissen die Optionen "30 °"/"90 °" (mit +# Leerzeichen vor dem Gradzeichen) - das Attribut in der Zeichnung bleibt die +# nackte Zahl. Gleiche Bauart wie [geruest_typ_anzeige]: steht ein Winkel +# hier NICHT drin, wird er unveraendert uebernommen. +# +# >>> Falls Sivas den Import mit "30 °" ablehnt, ist DIESE Tabelle die +# >>> einzige Stelle zum Nachziehen (z.B. auf "30°" ohne Leerzeichen oder +# >>> auf die nackte Zahl) - der Code bleibt unberuehrt. +30= 30 ° +90= 90 ° + [geruest_typ_anzeige] # ASCII-Attributwert (GERUEST_TYP) -> Sivas-Klartext mit Umlauten/Sonder- # zeichen (Attribute selbst bleiben ASCII, siehe CLAUDE.md-Konvention). diff --git a/cfg/sivas_merkmal_zuordnung.md b/cfg/sivas_merkmal_zuordnung.md index 700403a..5b5abda 100644 --- a/cfg/sivas_merkmal_zuordnung.md +++ b/cfg/sivas_merkmal_zuordnung.md @@ -8,7 +8,8 @@ der Wahrheit ist der Code in `lib/export_sivas.py` - diese Datei ist ein Backup/Nachschlagewerk, bei Aenderungen am Code bitte hier nachziehen. Stand: abgeglichen mit `lib/export_sivas.py` nach der Umstellung von -beschreibenden JSON-Keys auf reine Merkmalnummern (Sept. 2026). +beschreibenden JSON-Keys auf reine Merkmalnummern (Sept. 2026), ergaenzt um +die AS-/ES-Element-Merkmale 4427-4432 (Okt. 2026). ## ILS 2.0 Kreisel (SivasNr 6200, Dispogruppe 20) @@ -59,6 +60,7 @@ immer 1. Sivas kennt je Streckengruppe nur EIN Gefaelle-Segment-Paar | 4381 | Streckengruppe - Anzahl der Separatoren - Variofoerderer | `ANZAHL_SEPARATOR` | **voller Blockwert** (Aufteilung noch offen, siehe 4337) | | 4382 | (dieselbe Bezeichnung wie 4338 im Merkmalstamm) | - | fest `SEPARATOR_BEARBEITUNG_ANZEIGE` | | 4383 | Streckengruppe - Anzahl der Scanner (Vario-Anteil) | `ANZAHL_SCANNER` | **voller Blockwert** | +| 4427-4432 | AS-/ES-Element (vorhanden/Winkel/Seite) | `WINKEL_AS`/`SEITE_AS`/`WINKEL_ES`/`SEITE_ES` | siehe eigener Abschnitt unten | | 3992 | Geruest fuer Einzelmodul | `GERUEST_EINZELMODUL` | wie Kreisel | | 3993 | Geruestoption fuer Einzelmodul | `GERUEST_TYP` | wie Kreisel | @@ -86,9 +88,56 @@ Stueckzahl der Gruppe steht im Merkmal 4311. | 4337 | Streckengruppe - Anzahl der Separatoren - Gefaellestrecke | `ANZAHL_SEPARATOR` | direkt (kein Split noetig, kein Vario-Anteil vorhanden) | | 4338 | Streckengruppe - Bearbeitung Gefaellestreckenteilstueck fuer Separator (829516050) | - | fest `SEPARATOR_BEARBEITUNG_ANZEIGE` | | 4339 | Streckengruppe - Anzahl der Scanner | `ANZAHL_SCANNER` | direkt | +| 4427-4432 | AS-/ES-Element (vorhanden/Winkel/Seite) | `WINKEL_AS`/`SEITE_AS`/`WINKEL_ES`/`SEITE_ES` | siehe eigener Abschnitt unten | | 3992 | Geruest fuer Einzelmodul | `GERUEST_EINZELMODUL` | wie Kreisel | | 3993 | Geruestoption fuer Einzelmodul | `GERUEST_TYP` | wie Kreisel | +Die Gruppierung (`gefaellestrecke_signature`) beruecksichtigt AS-/ES-Element +mit: zwei sonst baugleiche Gefaellestrecken mit AS links bzw. AS rechts +bleiben getrennte Zeilen, sonst zeigte die gemeinsame Zeile nur die Seite des +ersten Blocks. + +## AS-/ES-Element (4427-4432) - gilt fuer VF_N UND GF_N + +Die Zuordnung steht vollstaendig in `cfg/sivas.cfg` - mit denselben sechs +Zeilen in `[gefaellestrecke]` UND `[variofoerderer]`, weil beide Blockarten +dieselben Attribute des gemeinsamen Strecken-Schemas tragen +(`*strecke-attr-front*` in `ssg_core.lsp`). In `export_sivas.py` stehen nur +die Transform-Funktionen. + +Sivas-Hierarchie (Merkmalstamm): 4431/4432 (`"Ja"`/`"Nein"`) sind die +Oberknoten, Winkel und Seite haengen als Unterknoten darunter - darum auch +diese Reihenfolge in der Config. + +| Merkmalnr | Bezeichnung (Merkmalstamm) | Block-Attribut | Transform | Berechnung | +|---|---|---|---|---| +| 4431 | AS-Element | `WINKEL_AS` | `as_es_vorhanden` | `"Ja"`, wenn ein Winkel gesetzt ist, sonst `"Nein"` | +| 4427 | AS-Element - Winkel | `WINKEL_AS` | `as_es_winkel` | Optionswert der Auswahlliste (`"30"` -> `"30 °"`, Tabelle `[as_es_winkel_anzeige]`); leer, wenn kein AS-Element | +| 4428 | AS-Element - Seite | `SEITE_AS` | `as_seite` | kleingeschrieben (`"links"`/`"rechts"`); leer, wenn kein AS-Element | +| 4432 | ES-Element | `WINKEL_ES` | `as_es_vorhanden` | wie 4431 | +| 4429 | ES-Element - Winkel | `WINKEL_ES` | `as_es_winkel` | wie 4427 | +| 4430 | ES-Element - Seite | `SEITE_ES` | `es_seite` | wie 4428 | + +**Warum der Winkel ueber das Vorhandensein entscheidet und nicht die Seite:** +das Schema laesst `WINKEL_AS`/`WINKEL_ES` leer, wenn kein Element gewaehlt +wurde, waehrend `SEITE_AS`/`SEITE_ES` mit `"rechts"` immer einen Vorgabewert +tragen. Ohne Element bleiben darum Winkel UND Seite leer - sonst stuende in +Sivas eine Seite unter einem `"Nein"`-Knoten. + +**Winkel-Schreibweise:** die Sivas-Auswahlliste fuehrt `"30 °"`/`"90 °"` (mit +Leerzeichen vor dem Gradzeichen), das Block-Attribut dagegen die nackte Zahl. +Die Uebersetzung steht als Tabelle `[as_es_winkel_anzeige]` in `sivas.cfg` +(gleiche Bauart wie `[geruest_typ_anzeige]`) - weicht Sivas davon ab, ist das +die einzige Stelle zum Nachziehen. Ein Winkel ohne Eintrag dort wird +unveraendert uebernommen. + +**Neu im Mapping-Mechanismus:** `4428`/`4430` brauchen ein ZWEITES Attribut +(die Seite haengt am Winkel). Dafuer gibt es in `sivas_mapping.py` den +Dekorator `@braucht_attribs` - ein so markierter Transform wird als +`fn(wert, attribs)` statt `fn(wert)` aufgerufen und sieht damit die uebrigen +Attribute des Blocks. Ohne diese Klammer muessten bedingte Merkmale wieder +als Python-Code aus der Config herauswandern. + ## ILS 2.0 Zusatzmodul (SivasNr 6215, Dispogruppe 20) BTMT-Beladestationen UND Abwurfstationen (Klammerbuegel-Entladung) in EINER diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 0a7c277..26cdd36 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -120,14 +120,94 @@ def mm_zu_m(wert_mm): return str(round(safe_float(wert_mm, 0.0) / 1000.0, 3)) +# --- AS-/ES-Element (Merkmale 4427-4432) --------------------------------- +# Zuordnung komplett in cfg/sivas.cfg ([gefaellestrecke]/[variofoerderer]), +# hier stehen nur die Transforms. Die Sivas-Hierarchie ist: 4431/4432 +# ("AS-Element"/"ES-Element", Ja/Nein) schalten die Unterknoten Winkel +# (4427/4429) und Seite (4428/4430) frei. +# +# Ob ein Element vorhanden ist, entscheidet AUSSCHLIESSLICH WINKEL_AS/ +# WINKEL_ES: das gemeinsame Strecken-Schema (*strecke-attr-front* in +# ssg_core.lsp) laesst den Winkel leer, wenn kein Element gewaehlt wurde. +# SEITE_AS/SEITE_ES taugen dafuer NICHT - sie tragen mit "rechts" immer einen +# Schema-Vorgabewert. Ohne Element bleiben Winkel UND Seite leer, damit in +# Sivas keine Seite unter einem "Nein"-Knoten haengt. + +# Attributwert -> Sivas-Optionswert der Auswahlliste ("30"/"90" -> "30 °"/ +# "90 °", siehe Merkmalstamm). Wie [geruest_typ_anzeige] in der Config, damit +# die exakte Schreibweise ohne Code-Aenderung nachgezogen werden kann. +AS_ES_WINKEL_ANZEIGE = sivas_mapping.load_flat_mapping("as_es_winkel_anzeige") + + +def winkel_text(wert): + """Elementwinkel ganzzahlig normiert ("90.0" -> "90"), ohne Sivas- + Anzeigeform. Basis fuer as_es_winkel UND fuer die Gruppierung (dort darf + die Anzeigeform keine Rolle spielen). Leerer Wert bleibt leer.""" + wert = str(wert or "").strip() + if not wert: + return "" + zahl = safe_float(wert, None) + return wert if zahl is None else str(int(zahl)) + + +def as_es_vorhanden(winkel): + """Merkmal 4431/4432: "Ja", sobald ein Elementwinkel gesetzt ist.""" + return "Ja" if winkel_text(winkel) else "Nein" + + +def as_es_winkel(winkel): + """Merkmal 4427/4429: Elementwinkel in der Sivas-Schreibweise der + Auswahlliste (cfg/sivas.cfg [as_es_winkel_anzeige]). Ein Winkel ohne + Eintrag dort wird unveraendert (nur ganzzahlig normiert) uebernommen.""" + winkel = winkel_text(winkel) + return AS_ES_WINKEL_ANZEIGE.get(winkel, winkel) + + +def _seite_text(seite, winkel): + """Gemeinsamer Kern von as_seite/es_seite: Seite nur, wenn es das Element + ueberhaupt gibt.""" + if not winkel_text(winkel): + return "" + return str(seite or "").strip().lower() + + +@sivas_mapping.braucht_attribs +def as_seite(wert, attribs): + """Merkmal 4428: AS-Seite, aber nur bei vorhandenem AS-Element.""" + return _seite_text(wert, attribs.get("WINKEL_AS", "")) + + +@sivas_mapping.braucht_attribs +def es_seite(wert, attribs): + """Merkmal 4430: ES-Seite, aber nur bei vorhandenem ES-Element.""" + return _seite_text(wert, attribs.get("WINKEL_ES", "")) + + # --- Transform-Namen, wie sie in cfg/sivas.cfg hinter "|" stehen koennen --- TRANSFORMS = { "ja_nein": ja_nein, "geruest_typ_anzeige": geruest_typ_anzeige, "mm_zu_m": mm_zu_m, + "as_es_vorhanden": as_es_vorhanden, + "as_es_winkel": as_es_winkel, + "as_seite": as_seite, + "es_seite": es_seite, } +def as_es_signatur(attribs): + """AS-/ES-Anteil des Gruppierungsschluessels (siehe + gefaellestrecke_signature). Nutzt dieselben Transforms wie der Export, + damit Schluessel und Zeileninhalt nicht auseinanderlaufen: ohne Element + ist die Seite leer und darf dann auch nicht trennen.""" + return (as_es_vorhanden(attribs.get("WINKEL_AS", "")), + winkel_text(attribs.get("WINKEL_AS", "")), + as_seite(attribs.get("SEITE_AS", ""), attribs), + as_es_vorhanden(attribs.get("WINKEL_ES", "")), + winkel_text(attribs.get("WINKEL_ES", "")), + es_seite(attribs.get("SEITE_ES", ""), attribs)) + + def streckengruppe_objekt_text(attribs): """Merkmal 4300 "Streckengruppe": "mehrere Objekte", wenn der Block ein kombinierter Streckengruppe-Block (Gefaelle+Vario, TYP-Attribut @@ -195,8 +275,14 @@ def gefaellestrecke_signature(block): """ Schluessel zur Gruppierung gleichartiger Gefaellestrecken: Bloecke mit identischen Massen (Hoehe, Laenge, Winkel, Separatoren, Scanner, Typ, - Geruest fuer Einzelmodul/Geruestoption) landen in derselben Sivas-Zeile - - nur IDs/Bezeichnung unterscheiden sich. + Geruest fuer Einzelmodul/Geruestoption, AS-/ES-Element) landen in + derselben Sivas-Zeile - nur IDs/Bezeichnung unterscheiden sich. + + AS-/ES-Element (Merkmale 4427-4432) gehoeren mit in den Schluessel: die + Merkmale der Zeile entstehen aus dem ERSTEN Block der Gruppe, zwei sonst + baugleiche Gefaellestrecken mit AS links bzw. AS rechts wuerden sonst zu + einer Zeile mit nur einer der beiden Seiten verschmelzen (siehe + as_es_signatur). """ attribs = block.get("attribs", {}) return ( @@ -209,6 +295,7 @@ def gefaellestrecke_signature(block): attribs.get("TYP", "Gefaellestrecke"), attribs.get("GERUEST_EINZELMODUL", "0"), attribs.get("GERUEST_TYP", "Schoenenberger Geruest"), + as_es_signatur(attribs), ) diff --git a/lib/sivas_mapping.py b/lib/sivas_mapping.py index d1480be..4948d49 100644 --- a/lib/sivas_mapping.py +++ b/lib/sivas_mapping.py @@ -97,14 +97,31 @@ def load_flat_mapping(section, cfg_path=None): return {} +def braucht_attribs(fn): + """Markiert einen Transform, der NEBEN seinem Rohwert auch das komplette + Attribut-Dict des Blocks braucht - er wird dann als fn(wert, attribs) + aufgerufen statt als fn(wert). + + Noetig fuer Merkmale, deren Wert von einem ZWEITEN Attribut abhaengt: die + AS-/ES-Seite (4428/4430) darf nur gefuellt werden, wenn der zugehoerige + WINKEL_AS/WINKEL_ES ueberhaupt ein Element ausweist. Ohne diese Klammer + muessten solche Merkmale wieder aus der Config heraus in Python-Code + wandern - genau das soll sivas.cfg vermeiden. + """ + fn.braucht_attribs = True + return fn + + def anwenden(section_mapping, attribs, transforms): """Wendet eine per load_section_mapping() gelesene Zuordnung auf ein Block-Attribut-Dict an. - transforms: dict transform_name -> Funktion(wert) -> wert. Ein in der - Config genannter, aber hier unbekannter Transform-Name wird ignoriert - (Rohwert bleibt unveraendert) statt abzubrechen - ein Tippfehler in - sivas.cfg darf den Export nicht zum Stillstand bringen. + transforms: dict transform_name -> Funktion(wert) -> wert. Ein mit + @braucht_attribs markierter Transform wird stattdessen als + fn(wert, attribs) aufgerufen und sieht damit die uebrigen Attribute des + Blocks. Ein in der Config genannter, aber hier unbekannter Transform-Name + wird ignoriert (Rohwert bleibt unveraendert) statt abzubrechen - ein + Tippfehler in sivas.cfg darf den Export nicht zum Stillstand bringen. Rueckgabe: dict merkmal -> wert. Ohne Transform wird der Rohwert zu str() gewandelt (bisheriges Verhalten der build_*_details-Funktionen: alles @@ -124,7 +141,10 @@ def anwenden(section_mapping, attribs, transforms): if transform: fn = transforms.get(transform) if fn is not None: - ergebnis[merkmal] = fn(wert) + if getattr(fn, "braucht_attribs", False): + ergebnis[merkmal] = fn(wert, attribs) + else: + ergebnis[merkmal] = fn(wert) continue ergebnis[merkmal] = str(wert) return ergebnis diff --git a/tests/test_export_sivas_as_es.py b/tests/test_export_sivas_as_es.py new file mode 100644 index 0000000..1e83e5f --- /dev/null +++ b/tests/test_export_sivas_as_es.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_export_sivas_as_es.py - Sivas-Merkmale 4427-4432 (AS-/ES-Element) fuer +Gefaellestrecke (GF_N) und Streckengruppe (VF_N). + +Pruefpunkte: Vorhandensein haengt am Winkel (nicht an der Seite, die immer +einen Vorgabewert traegt), der Winkel wird in die Sivas-Schreibweise der +Auswahlliste uebersetzt, ohne Element bleiben Winkel UND Seite leer, die +Zuordnung steht vollstaendig in cfg/sivas.cfg, und die GF-Gruppierung trennt +Bloecke mit unterschiedlichem AS-/ES-Element. + +Laeuft komplett ohne BricsCAD (reine Python-Logik). +""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) + +import export_sivas # noqa: E402 +import sivas_mapping # noqa: E402 + +# Sivas-Schreibweise der Winkel-Auswahlliste (cfg/sivas.cfg +# [as_es_winkel_anzeige]) - die Tests vergleichen gegen die Config, damit ein +# Nachziehen der Schreibweise dort nicht die halbe Testdatei bricht. +W30 = export_sivas.as_es_winkel("30") +W90 = export_sivas.as_es_winkel("90") + + +def _strecke(nr, typ="Gefaellestrecke", **attr): + """GF_N-/VF_N-Block mit dem gemeinsamen Strecken-Attributschema.""" + attribs = {"ID": f"000{nr}", "Bezeichnung": f"GF_{nr}", "ARTINR": "6220", + "MONTAGEHOEHE_m": "2.000", "HOEHE_VON_mm": "2000", + "HOEHE_BIS_mm": "1000", "TYP": typ, + "L_GF_m": "3.000", "GF_WINKEL": "3", + "SEITE_AS": "links", "WINKEL_AS": "90", + "SEITE_ES": "links", "WINKEL_ES": "90"} + attribs.update(attr) + return {"block_name": f"GF_{nr}", "x": 0.0, "y": 0.0, "z": 2000.0, + "rotation": 0.0, "attribs": attribs} + + +def test_as_es_vorhanden_mit_winkel_und_seite(): + details = export_sivas.build_gefaellestrecke_details(_strecke(1)) + assert details["4431"] == "Ja" + assert details["4427"] == W90 + assert details["4428"] == "links" + assert details["4432"] == "Ja" + assert details["4429"] == W90 + assert details["4430"] == "links" + + +def test_zuordnung_steht_in_der_config(): + """Die sechs Merkmale muessen in BEIDEN Strecken-Abschnitten von + sivas.cfg stehen (GF_N und VF_N lesen dieselben Attribute).""" + for abschnitt in ("gefaellestrecke", "variofoerderer"): + mapping = sivas_mapping.load_section_mapping(abschnitt) + for merkmal, attribut, transform in ( + ("4431", "WINKEL_AS", "as_es_vorhanden"), + ("4427", "WINKEL_AS", "as_es_winkel"), + ("4428", "SEITE_AS", "as_seite"), + ("4432", "WINKEL_ES", "as_es_vorhanden"), + ("4429", "WINKEL_ES", "as_es_winkel"), + ("4430", "SEITE_ES", "es_seite")): + assert merkmal in mapping, f"{merkmal} fehlt in [{abschnitt}]" + ist_attribut, _alt, _default, ist_transform = mapping[merkmal] + assert ist_attribut == attribut, f"[{abschnitt}] {merkmal}" + assert ist_transform == transform, f"[{abschnitt}] {merkmal}" + assert ist_transform in export_sivas.TRANSFORMS + + +def test_winkel_in_sivas_schreibweise(): + """4427/4429 tragen den Optionswert der Auswahlliste, nicht die nackte + Attributzahl (Vorgabe aus cfg/sivas.cfg [as_es_winkel_anzeige]).""" + details = export_sivas.build_gefaellestrecke_details( + _strecke(1, WINKEL_AS="30", WINKEL_ES="90.0")) + assert details["4427"] == W30 + assert details["4429"] == W90 + # Unbekannter Winkel faellt auf die ganzzahlige Rohzahl zurueck + assert export_sivas.as_es_winkel("60.0") in (W30, W90, "60") + + +def test_ohne_winkel_kein_element_und_keine_seite(): + """Leerer WINKEL_AS = kein AS-Element; die Seite darf dann NICHT + durchgereicht werden (SEITE_AS traegt mit "rechts" immer einen + Schema-Vorgabewert).""" + details = export_sivas.build_gefaellestrecke_details( + _strecke(1, WINKEL_AS="", SEITE_AS="rechts")) + assert details["4431"] == "Nein" + assert details["4427"] == "" + assert details["4428"] == "" + # ES bleibt davon unberuehrt + assert details["4432"] == "Ja" + assert details["4430"] == "links" + + +def test_seite_rechts(): + details = export_sivas.build_gefaellestrecke_details( + _strecke(1, SEITE_AS="rechts", SEITE_ES="rechts")) + assert details["4428"] == "rechts" + assert details["4430"] == "rechts" + + +def test_variofoerderer_hat_dieselben_merkmale(): + block = _strecke(1, typ="Streckengruppe", L_VF_m="2.000", VF_WINKEL="0", + ANTRIEBFAHRTRICHTUNG="Auf", MOTORSEITE="rechts", + WINKEL_AS="30", SEITE_AS="rechts") + details = export_sivas.build_variofoerderer_details(block) + assert details["4431"] == "Ja" + assert details["4427"] == W30 + assert details["4428"] == "rechts" + assert details["4429"] == W90 + + +def test_gruppierung_trennt_nach_as_seite(): + """Sonst baugleiche Gefaellestrecken mit AS links bzw. rechts duerfen + nicht in einer Sivas-Zeile landen (die Zeile wuerde nur eine der beiden + Seiten zeigen).""" + links = export_sivas.gefaellestrecke_signature(_strecke(1, SEITE_AS="links")) + rechts = export_sivas.gefaellestrecke_signature(_strecke(2, SEITE_AS="rechts")) + assert links != rechts + assert links == export_sivas.gefaellestrecke_signature(_strecke(3, SEITE_AS="links")) + + +def test_gruppierung_trennt_nach_as_winkel(): + assert (export_sivas.gefaellestrecke_signature(_strecke(1, WINKEL_AS="30")) + != export_sivas.gefaellestrecke_signature(_strecke(2, WINKEL_AS="90"))) + + +def test_gruppierung_ohne_element_ignoriert_die_seite(): + """Ohne AS-Element ist die Seite bedeutungslos und steht auch nicht in der + Zeile - sie darf die Gruppierung dann nicht aufspalten.""" + assert (export_sivas.gefaellestrecke_signature( + _strecke(1, WINKEL_AS="", SEITE_AS="links")) + == export_sivas.gefaellestrecke_signature( + _strecke(2, WINKEL_AS="", SEITE_AS="rechts"))) + + +def test_reihenfolge_im_details_json(): + """Die sechs Merkmale stehen im details-JSON direkt HINTER 2950 + (Anlagenhoehe) - so vorgegeben, und so steht es in beiden Abschnitten von + sivas.cfg. Die Reihenfolge der JSON-Keys folgt der Zeilenreihenfolge der + Config (configparser und dict erhalten beide die Einfuegereihenfolge).""" + erwartet = ["2950", "4431", "4427", "4428", "4432", "4429", "4430"] + gf = list(export_sivas.build_gefaellestrecke_details(_strecke(1))) + vf = list(export_sivas.build_variofoerderer_details( + _strecke(2, typ="Streckengruppe", L_VF_m="2.000"))) + assert gf[:len(erwartet)] == erwartet + assert vf[:len(erwartet)] == erwartet + + +def test_altbestand_ohne_die_attribute(): + """Bloecke aus Zeichnungen, die vor der Einfuehrung von WINKEL_AS gebaut + wurden, haben die Attribute gar nicht - dann greift der leere Default und + es wird "Nein" gemeldet (statt KeyError). Das ist die einzig moegliche + Aussage: Sivas kennt nur Ja/Nein, kein "unbekannt".""" + block = _strecke(1) + for tag in ("WINKEL_AS", "SEITE_AS", "WINKEL_ES", "SEITE_ES"): + del block["attribs"][tag] + details = export_sivas.build_gefaellestrecke_details(block) + assert details["4431"] == "Nein" + assert details["4432"] == "Nein" + assert details["4427"] == details["4428"] == "" + assert details["4429"] == details["4430"] == "" + + +def test_transform_mit_attribs_kontext(): + """Der @braucht_attribs-Transform muss das komplette Attribut-Dict sehen - + sonst koennte die Seite ihre Abhaengigkeit vom Winkel nicht pruefen.""" + assert getattr(export_sivas.as_seite, "braucht_attribs", False) + assert getattr(export_sivas.es_seite, "braucht_attribs", False) + assert not getattr(export_sivas.ja_nein, "braucht_attribs", False) + + mapping = {"4428": ("SEITE_AS", None, "", "as_seite")} + attribs = {"SEITE_AS": "links", "WINKEL_AS": "90"} + assert sivas_mapping.anwenden(mapping, attribs, export_sivas.TRANSFORMS) \ + == {"4428": "links"} + attribs_ohne = {"SEITE_AS": "links", "WINKEL_AS": ""} + assert sivas_mapping.anwenden(mapping, attribs_ohne, export_sivas.TRANSFORMS) \ + == {"4428": ""}