Sivas-Export: Gefaellestrecke und VarioFoerderer mit Detail-Zeilen statt Sammelzeile

Gefaellestrecke (GF_N): je Zeile eine Gruppe gleichartiger Bloecke (gleiche
Hoehe/Laenge/Winkel/Separatoren/Scanner/Typ), IDs und Bezeichnung als Array
statt einer einzigen Sammelzeile mit reiner Anzahl.

VarioFoerderer (VF_N): je Compound-Block eine eigene Zeile mit vollstaendigen
Gefaelle-/Angetrieben-Teilgruppen (Laengen, Winkel, Kurven, Foerderrichtung),
da VF_N immer TYP "Streckengruppe" ist. Lose Strecke-Elemente ausserhalb
eines VF_N-Blocks bleiben weiterhin in einer gruppierten IDs-Zeile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 14:08:46 +02:00
parent 18a2172d4f
commit db75a25ae1
+154 -10
View File
@@ -103,6 +103,113 @@ def format_hoehe_m(hoehe_mm_str):
return ""
def build_gefaellestrecke_details(block):
"""Merkmale-Dict fuer eine einzelne ILS Gefaellestrecke (GF_N-Block)."""
attribs = block.get("attribs", {})
def mm_to_m(key):
try:
return str(round(float(attribs.get(key, "0")) / 1000.0, 2))
except (ValueError, TypeError):
return "0"
return {
"IDs": [get_id(block)],
"Bezeichnung": attribs.get("Bezeichnung", block.get("block_name", "")),
"Hoehe oben": mm_to_m("HOEHE_VON_mm"),
"Hoehe unten": mm_to_m("HOEHE_BIS_mm"),
"Laenge in Meter": attribs.get("L_GF_m", ""),
"Winkel": attribs.get("GF_WINKEL", ""),
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", "0"),
"Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", "0"),
"Typ": attribs.get("TYP", "Gefaellestrecke"),
}
def gefaellestrecke_signature(block):
"""
Schluessel zur Gruppierung gleichartiger Gefaellestrecken: Bloecke mit
identischen Massen (Hoehe, Laenge, Winkel, Separatoren, Scanner, Typ)
landen in derselben Sivas-Zeile - nur IDs/Bezeichnung unterscheiden sich.
"""
attribs = block.get("attribs", {})
return (
attribs.get("HOEHE_VON_mm", "0"),
attribs.get("HOEHE_BIS_mm", "0"),
attribs.get("L_GF_m", ""),
attribs.get("GF_WINKEL", ""),
attribs.get("ANZAHL_SEPARATOR", "0"),
attribs.get("ANZAHL_SCANNER", "0"),
attribs.get("TYP", "Gefaellestrecke"),
)
def parse_float_list(csv_str):
"""Kommagetrennten Attribut-String (z.B. L_GF_m) in Liste von floats."""
out = []
for part in str(csv_str or "").split(","):
part = part.strip()
if not part:
continue
try:
out.append(float(part))
except ValueError:
pass
return out
def parse_str_list(csv_str):
"""Kommagetrennten Attribut-String (z.B. MOTORSEITE) in Liste von Strings."""
return [p.strip() for p in str(csv_str or "").split(",") if p.strip()]
def build_variofoerderer_details(block):
"""
Merkmale-Dict fuer eine VF_N-Streckengruppe (VarioFoerderer, ggf.
mehrsegmentig aus Gefaelle- und angetriebenen Foerderer-Teilen).
"""
attribs = block.get("attribs", {})
def to_int(key):
try:
return int(attribs.get(key, "0") or "0")
except (ValueError, TypeError):
return 0
return {
"IDs": [get_id(block)],
"Anzahl Foerdergruppen": to_int("ANZAHL_VF"),
"Anzahl Gefaellegruppen": to_int("ANZAHL_GF"),
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
"GruppenteileGefaelle": {
"AnzahlGeraden": to_int("ANZAHL_GF"),
"Laenge in Meter": parse_float_list(attribs.get("L_GF_m", "")),
"Winkel": parse_float_list(attribs.get("GF_WINKEL", "")),
"AnzahlKurvenLinks90": to_int("GF_Bogen_L_90"),
"AnzahlKurvenLinks60": to_int("GF_Bogen_L_60"),
"AnzahlKurvenLinks30": to_int("GF_Bogen_L_30"),
"AnzahlKurvenRechts90": to_int("GF_Bogen_R_90"),
"AnzahlKurvenRechts60": to_int("GF_Bogen_R_60"),
"AnzahlKurvenRechts30": to_int("GF_Bogen_R_30"),
},
"GruppenteileAngetrieben": {
"MotorSeite": parse_str_list(attribs.get("MOTORSEITE", "")),
"AnzahlGeraden": to_int("ANZAHL_VF"),
"Laenge in Meter": parse_float_list(attribs.get("L_VF_m", "")),
"Winkel": parse_float_list(attribs.get("VF_WINKEL", "")),
"Foerderrichtung": parse_str_list(attribs.get("ANTRIEBFAHRTRICHTUNG", "")),
"AnzahlKurvenAussen90": to_int("VF_Bogen_A_90"),
"AnzahlKurvenAussen60": to_int("VF_Bogen_A_60"),
"AnzahlKurvenAussen30": to_int("VF_Bogen_A_30"),
"AnzahlKurvenInnen90": to_int("VF_Bogen_I_90"),
"AnzahlKurvenInnen60": to_int("VF_Bogen_I_60"),
"AnzahlKurvenInnen30": to_int("VF_Bogen_I_30"),
},
"Anzahl der Separatoren": to_int("ANZAHL_SEPARATOR"),
"Anzahl der Scanner": to_int("ANZAHL_SCANNER"),
}
def build_kreisel_details(block):
"""Merkmale-Dict fuer einen ILS Kreisel."""
attribs = block.get("attribs", {})
@@ -164,8 +271,9 @@ def process_blocks(blocks, lookup):
gerade_groups = {}
kreisel_list = [] # Liste von blocks
eckrad_ids = [] # IDs aller Eckrad-Bloecke
strecke_ids = [] # IDs aller Strecke-/Variofoerderer (VF_*, Vario/Staustrecke)
gefaelle_ids = [] # IDs aller Gefaellestrecken (GF_*)
strecke_ids = [] # IDs loser Strecke-Elemente (Vario/Staustrecke ausserhalb VF_N)
vario_list = [] # Liste von VarioFoerderer-Compound-Bloecken (VF_N), je einer Zeile
gefaelle_list = [] # Liste von Gefaellestrecke-Bloecken (GF_*), je einer Zeile
counters = {
"anzahl_boegen": 0,
@@ -287,10 +395,12 @@ def process_blocks(blocks, lookup):
continue
# --- VarioFoerderer Compound-Block (VF_N) ---
# Bekommt immer eine eigene Detail-Zeile (vario_list) statt der
# generischen IDs-Gruppierung - siehe build_variofoerderer_details.
if matches_any(bname, BLOCKPATTERNS.get("pattern_variofoerderer", [])):
attribs = block.get("attribs", {})
counters["anzahl_variofoerderer"] += 1
strecke_ids.append(get_id(block))
vario_list.append(block)
try:
counters["laengen_variofoerderer"] += float(attribs.get("L_VF_m", "0") or "0")
except (ValueError, TypeError):
@@ -312,7 +422,7 @@ def process_blocks(blocks, lookup):
if matches_any(bname, BLOCKPATTERNS.get("pattern_gefaellestrecke", [])):
attribs = block.get("attribs", {})
counters["anzahl_gefaellestrecken"] += 1
gefaelle_ids.append(get_id(block))
gefaelle_list.append(block)
try:
counters["laengen_gefaellestrecken"] += float(attribs.get("L_GF_m", "0") or "0")
except (ValueError, TypeError):
@@ -412,7 +522,10 @@ def process_blocks(blocks, lookup):
})
nr += 1
if counters["anzahl_variofoerderer"] > 0:
# Lose Strecke-Elemente (Vario/Staustrecke ausserhalb eines VF_N-Blocks)
# als eine gruppierte Zeile; VF_N-Compound-Bloecke bekommen unten je eine
# eigene Detail-Zeile.
if strecke_ids:
sivasnr, teileart = ILS_SIVASNR["strecke"]
items.append({
"nr": nr,
@@ -420,25 +533,56 @@ def process_blocks(blocks, lookup):
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"dispgruppe": 20,
"anzahl": counters["anzahl_variofoerderer"],
"anzahl": len(strecke_ids),
"laenge_mm": "",
"hoehe_m": "",
"details": {"IDs": strecke_ids},
})
nr += 1
if counters["anzahl_gefaellestrecken"] > 0:
sivasnr, teileart = ILS_SIVASNR["gefaellestrecke"]
# VarioFoerderer-Compound-Bloecke (VF_N) - je Block eine eigene Zeile mit
# vollstaendigen Gefaelle-/Angetrieben-Teilgruppen (immer TYP "Streckengruppe").
for block in vario_list:
sivasnr, teileart = ILS_SIVASNR["strecke"]
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"dispgruppe": 20,
"anzahl": counters["anzahl_gefaellestrecken"],
"anzahl": 1,
"laenge_mm": "",
"hoehe_m": "",
"details": {"IDs": gefaelle_ids},
"details": build_variofoerderer_details(block),
})
nr += 1
# Gleichartige Gefaellestrecken (identische Masse) zu einer Zeile
# zusammenfassen - nur IDs/Bezeichnung bleiben je Block unterschiedlich.
gefaelle_groups = {}
for block in gefaelle_list:
sig = gefaellestrecke_signature(block)
if sig not in gefaelle_groups:
gefaelle_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
gefaelle_groups[sig]["ids"].append(get_id(block))
gefaelle_groups[sig]["bezeichnungen"].append(
block.get("attribs", {}).get("Bezeichnung", block.get("block_name", "")))
for group in gefaelle_groups.values():
sivasnr, teileart = ILS_SIVASNR["gefaellestrecke"]
details = build_gefaellestrecke_details(group["block"])
details["IDs"] = group["ids"]
details["Bezeichnung"] = group["bezeichnungen"]
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"dispgruppe": 20,
"anzahl": len(group["ids"]),
"laenge_mm": "",
"hoehe_m": "",
"details": details,
})
nr += 1