feat: Sivas-Merkmale 4427-4432 (AS-/ES-Element) im Export befuellen

Die Streckenbloecke GF_N und VF_N tragen Winkel und Seite ihres AS-/ES-
Elements bereits als Attribute des gemeinsamen Schemas (WINKEL_AS/SEITE_AS/
WINKEL_ES/SEITE_ES, *strecke-attr-front* in ssg_core.lsp). Diese Information
landet jetzt auch im details-JSON des Sivas-Exports.

Zuordnung (Sivas-Hierarchie: 4431/4432 schalten Winkel und Seite frei):
  4431/4432  AS-/ES-Element vorhanden   Ja/Nein
  4427/4429  Winkel                     "30 °"/"90 °"
  4428/4430  Seite                      links/rechts

Die Zuordnung steht vollstaendig in cfg/sivas.cfg - mit denselben sechs
Zeilen in [gefaellestrecke] und [variofoerderer], jeweils direkt hinter 2950,
was auch die Reihenfolge im details-JSON bestimmt.

Ob ein Element vorhanden ist, entscheidet ausschliesslich WINKEL_AS/WINKEL_ES:
das Schema laesst den Winkel leer, wenn keines gewaehlt wurde, waehrend
SEITE_AS/SEITE_ES mit "rechts" immer einen Vorgabewert tragen. Ohne Element
bleiben Winkel UND Seite leer, damit in Sivas keine Seite unter einem
"Nein"-Knoten haengt.

Weitere Punkte:
- sivas_mapping: neuer Dekorator @braucht_attribs. Ein so markierter
  Transform wird als fn(wert, attribs) statt fn(wert) aufgerufen und sieht
  damit die uebrigen Attribute des Blocks - noetig, weil die Seite vom
  Winkel abhaengt. Ohne diese Klammer muessten bedingte Merkmale wieder als
  Python-Code aus der Config herauswandern.
- Die Winkel-Schreibweise der Sivas-Auswahlliste ("30 °" mit Leerzeichen vor
  dem Gradzeichen) steht als Tabelle [as_es_winkel_anzeige] in sivas.cfg,
  Bauart wie [geruest_typ_anzeige] - weicht Sivas davon ab, ist das die
  einzige Stelle zum Nachziehen.
- gefaellestrecke_signature beruecksichtigt AS/ES: zwei sonst baugleiche
  Gefaellestrecken mit AS links bzw. rechts bleiben getrennte Zeilen, sonst
  zeigte die gemeinsame Zeile nur die Seite des ersten Blocks.
- Altbestand ohne diese Attribute meldet "Nein" statt abzubrechen.
- Merkmalsstamm_in_Sivas.xlsx auf den aktuellen Sivas-Stand gebracht
  (enthaelt neben 4427-4432 auch 4002, 7010-7013 und Klartext-Korrekturen;
  4339/4383 heissen jetzt eindeutig "... Gefaellestrecke"/"... Variofoerderer").
- 12 neue Tests in tests/test_export_sivas_as_es.py, ohne BricsCAD lauffaehig.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 10:28:35 +02:00
parent 4fdeaf831f
commit 4ce2a02205
6 changed files with 396 additions and 12 deletions
Binary file not shown.
+51 -4
View File
@@ -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).
+50 -1
View File
@@ -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
+89 -2
View File
@@ -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),
)
+25 -5
View File
@@ -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
+181
View File
@@ -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": ""}