Files
dxfmakros/lib/export_sivas.py
T

1023 lines
44 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
export_sivas.py - Erzeugt Sivas-Export-CSV mit gruppierten Zeilen und Summierungszeilen.
Neues CSV-Format:
Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details
- Bezeichnung: Wert des Attributs "Bezeichnung" (Kreisel/Eckrad/VF/GF/Strecke).
Bei gruppierten Zeilen (mehrere Bloecke je Zeile) kommasepariert.
Bei Omniflo-Zeilen (Bogen/Weiche/Gerade) und der Automation-Summenzeile
bleibt das Feld leer, da es sich um Sammlungen gleichartiger Objekte handelt.
- IDs: kommaseparierte Liste der ID-Attribute aller Bloecke der Zeile
(eigene Spalte, nicht mehr Teil des details-JSON).
- Omniflo (TEILEART "Omniflo ..."): je Nummer aus dem ARTINR-Attribut
(kommagetrennt) eine Zeile, Anzahl summiert; TEF-Teile als "Omniflo TEF"
- Omniflo Gerade: gruppiert nach ARTINR-Attribut, Laenge[mm] summiert
- ILS 2.0 Kreisel: gruppiert nach Kreiselart+Abstand+Anzahl Separatoren/
Scanner/Rampen, Anzahl summiert
- ILS 2.0 Gefaellestrecke: gruppiert nach identischen Massen, Anzahl-Spalte
bleibt 1 - die reale Stueckzahl steht im Merkmal 4311 (siehe
build_gefaellestrecke_details)
- ILS 2.0 Zusatzmodul: BTMT-Beladung UND Abwurfstationen in EINER Zeile,
Anzahl bleibt 1, Stueckzahlen stehen in 3503/3504 (siehe process_blocks)
- ILS 2.0 Automation: Summierungszeile (nur wenn ILS-Elemente vorhanden)
details-JSON: Schluessel sind durchgaengig Sivas-Merkmalnummern (z.B.
"2952" fuer Kreiselhoehe) statt beschreibender Texte - siehe
cfg/Merkmalsstamm_in_Sivas.xlsx fuer die Bezeichnung je Nummer.
Aufruf:
python export_sivas.py <export_raw.json> <data_dir> <output.csv> [--mit-bezeichnung]
Argumente:
export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung
data_dir - Pfad zum data/-Verzeichnis (derzeit ungenutzt, bleibt
fuer den Aufrufer export.lsp erhalten)
output.csv - Ziel-CSV-Datei (reine Merkmalnummern, fuer den
Sivas-Import)
--mit-bezeichnung - optional. Nur dann wird zusaetzlich zu output.csv
eine zweite Datei "<output>_mit_bezeichnung.csv"
geschrieben: gleicher Inhalt, aber jeder Merkmalnummer-
Key im details-JSON wird durch die zugehoerige Klartext-
Bezeichnung aus cfg/Merkmalsstamm_in_Sivas.xlsx ersetzt
(Wert bleibt unveraendert) - reine Lesehilfe zur
Pruefung, wird von Sivas selbst nicht eingelesen.
Schalter im Dialog CSV_EXPORT_KONFIG
("Sivas Export mit Bezeichner"), Standard aus.
Zusaetzlich wird IMMER "<output>_zusammengefasst.csv" geschrieben: gleiches
Format, aber Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden, sind zu
einer Zeile zusammengefasst (Anzahl addiert, IDs vereinigt, Nr neu) - siehe
items_zusammenfassen. output.csv selbst bleibt unveraendert.
"""
import json
import sys
import os
from export_blockpatterns import (load_patterns, load_export_cfg, cfg_path_from_env,
matches_any, csv_quote, safe_float, safe_int)
from merkmalstamm import lade_merkmalstamm, bezeichnung_fuer
import sivas_mapping
BLOCKPATTERNS = load_patterns()
# Merkmalnummer fuer die Teilefarbe der Geruest-Baugruppen ([Sivas] in
# cfg/export.cfg; 108 = "Farbe" im Merkmalstamm).
MERKMAL_TEILEFARBE = load_export_cfg(cfg_path_from_env()).get(
"Sivas", "merkmal_teilefarbe", fallback="108").strip()
def load_json(path):
"""Laedt JSON, tolerant gegenueber BricsCAD-Ausgaben.
AutoLISP schreibt export_raw.json (write-line) im ANSI-Codepage des
Systems, nicht in UTF-8 - Sonderzeichen (z.B. Gradzeichen) koennen
daher nicht als UTF-8 dekodierbar sein. Fallback auf cp1252.
"""
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except UnicodeDecodeError:
with open(path, "r", encoding="cp1252") as f:
return json.load(f)
def get_id(block):
"""Liest das eindeutige ID-Attribut eines Blocks (von ssg-id-check-all vergeben)."""
return block.get("attribs", {}).get("ID", "")
# --- Sivas-Merkmalnummern: Anzeigetexte, die nicht 1:1 aus dem Attribut
# uebernommen werden koennen (Attribute bleiben ASCII, siehe CLAUDE.md-
# Konvention "Alle Texte in Dateien ohne Umlaute" - Sivas erwartet aber die
# deutsche Schreibweise mit Umlauten/Sonderzeichen). Tabelle kommt aus
# cfg/sivas.cfg [geruest_typ_anzeige], nicht ueber die uebliche ssg-text-
# Sprachdatei, da rein Sivas-seitig.
GERUEST_TYP_ANZEIGE = sivas_mapping.load_flat_mapping("geruest_typ_anzeige")
SEPARATOR_BEARBEITUNG_ANZEIGE = "ohne HARTCOAT HRC 50 ± 5"
def geruest_typ_anzeige(wert):
"""Wandelt den ASCII-Attributwert GERUEST_TYP in die Sivas-Anzeige um."""
return GERUEST_TYP_ANZEIGE.get(wert, wert)
def ja_nein(flag_1_0):
"""Wandelt ein "0"/"1"-Attribut in die Sivas-Anzeige "Ja"/"Nein" um."""
return "Ja" if flag_1_0 == "1" else "Nein"
def ist_eins(flag_1_0):
"""Wandelt ein "0"/"1"-Attribut in ein echtes bool um (True/False im
JSON, NICHT "Ja"/"Nein" wie ja_nein - siehe build_eckrad_details)."""
return flag_1_0 == "1"
# --- Transform-Namen, wie sie in cfg/sivas.cfg hinter "|" stehen koennen ---
TRANSFORMS = {
"ja_nein": ja_nein,
"geruest_typ_anzeige": geruest_typ_anzeige,
"ist_eins": ist_eins,
}
def streckengruppe_objekt_text(attribs):
"""Merkmal 4300 "Streckengruppe": "mehrere Objekte", wenn der Block ein
kombinierter Streckengruppe-Block (Gefaelle+Vario, TYP-Attribut
"Streckengruppe") ist, sonst "ein Objekt" (reine Gefaellestrecke)."""
typ = attribs.get("TYP", "Gefaellestrecke")
return "mehrere Objekte" if typ == "Streckengruppe" else "ein Objekt"
SIVAS_MAPPING_GEFAELLESTRECKE = sivas_mapping.load_section_mapping("gefaellestrecke")
def build_gefaellestrecke_details(block, anzahl=1):
"""Merkmale-Dict (Sivas-Merkmalnummern) fuer eine Gruppe gleichartiger
ILS Gefaellestrecken (GF_N-Bloecke). anzahl ist die Anzahl der in dieser
Zeile zusammengefassten, baugleichen Bloecke (4311) - anders als bei
Kreisel steht hier auf der Sivas-Hauptzeile selbst immer Anzahl=1 (siehe
process_blocks), die reale Stueckzahl steckt nur in 4311.
Die reine Attribut->Merkmal-Zuordnung kommt aus cfg/sivas.cfg
[gefaellestrecke]; 4300 (Streckengruppe-Text) und 4311 (Anzahl-Parameter)
sind berechnete/parametrisierte Werte und bleiben hier Code."""
attribs = block.get("attribs", {})
details = sivas_mapping.anwenden(SIVAS_MAPPING_GEFAELLESTRECKE, attribs, TRANSFORMS)
details["4300"] = streckengruppe_objekt_text(attribs)
details["4311"] = str(anzahl)
return details
def kreisel_signature(block):
"""
Schluessel zur Gruppierung gleichartiger Kreisel: Kreiselart, Abstand,
Anzahl der Separatoren, Anzahl der Scanner, Anzahl der Rampen sowie
Geruest fuer Einzelmodul/Geruestoption entscheiden, ob Kreisel in
derselben Sivas-Zeile summiert werden.
"""
attribs = block.get("attribs", {})
return (
attribs.get("KREISELART", "STANDARD"),
attribs.get("ABSTAND", "2300"),
attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")),
attribs.get("ANZAHL_RAMPEN", attribs.get("N_RAMPEN", "0")),
attribs.get("GERUEST_EINZELMODUL", "0"),
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
)
def eckrad_signature(block):
"""
Schluessel zur Gruppierung gleichartiger Eckraeder: Geruest fuer
Einzelmodul/Geruestoption entscheiden, ob Eckraeder in derselben
Sivas-Zeile summiert werden.
"""
attribs = block.get("attribs", {})
return (
attribs.get("GERUEST_EINZELMODUL", "0"),
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
)
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.
"""
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"),
attribs.get("GERUEST_EINZELMODUL", "0"),
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
)
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()]
SIVAS_MAPPING_VARIOFOERDERER = sivas_mapping.load_section_mapping("variofoerderer")
def build_variofoerderer_details(block):
"""
Merkmale-Dict (Sivas-Merkmalnummern) fuer eine VF_N-Streckengruppe
(VarioFoerderer, ggf. mehrsegmentig aus Gefaelle- und angetriebenen
Foerderer-Teilen). Sivas kennt je Streckengruppe nur EIN Gefaelle-
Segment-Paar (001/002) und EIN Vario-Segment (001) - ANZAHL_GF/ANZAHL_VF
ueber diese Grenze hinaus werden hier (noch) nicht abgebildet.
Die Aufteilung der Separatoren/Scanner auf Gefaelle-Anteil (4337/4339)
vs. Vario-Anteil (4381/4383) ist NOCH NICHT umgesetzt (haengt an der noch
ausstehenden Neuzuordnung von Separator/Scanner zum jeweiligen
Streckenabschnitt) - bis dahin steht 4337/4339 fest auf "0" und der volle
Attributwert steht auf 4381/4383.
Die von der Segmentanzahl UNABHAENGIGEN Felder (2950, GF_Bogen_*/
VF_Bogen_*-Zaehler, 4381-4383, 3992/3993) kommen aus cfg/sivas.cfg
[variofoerderer]. Die Gefaelle-/Vario-Segmentpaare (4312-4392, 4353/
4354/4386, abhaengig von len(gf_laengen)/len(vf_laengen)) bleiben Code.
"""
attribs = block.get("attribs", {})
gf_laengen = parse_float_list(attribs.get("L_GF_m", ""))
gf_winkel = parse_float_list(attribs.get("GF_WINKEL", ""))
vf_laengen = parse_float_list(attribs.get("L_VF_m", ""))
vf_winkel = parse_float_list(attribs.get("VF_WINKEL", ""))
vf_richtung = parse_str_list(attribs.get("ANTRIEBFAHRTRICHTUNG", ""))
motorseiten = parse_str_list(attribs.get("MOTORSEITE", ""))
details = sivas_mapping.anwenden(SIVAS_MAPPING_VARIOFOERDERER, attribs, TRANSFORMS)
details["4300"] = "mehrere Objekte"
details["4311"] = "1"
if len(gf_laengen) >= 1:
details["4312"] = "Ja"
details["4318"] = gf_laengen[0]
details["4391"] = str(int(gf_winkel[0])) if gf_winkel else ""
else:
details["4312"] = "Nein"
if len(gf_laengen) >= 2:
details["4314"] = "Ja"
details["4319"] = gf_laengen[1]
details["4392"] = str(int(gf_winkel[1])) if len(gf_winkel) >= 2 else ""
else:
details["4314"] = "Nein"
details["4337"] = "0"
details["4338"] = SEPARATOR_BEARBEITUNG_ANZEIGE
details["4339"] = "0"
details["4340"] = "Ja" if vf_laengen else "Nein"
details["4341"] = sum(1 for seite in motorseiten if seite.lower() == "links")
details["4342"] = sum(1 for seite in motorseiten if seite.lower() == "rechts")
if vf_laengen:
details["4353"] = vf_laengen[0]
details["4354"] = vf_richtung[0].lower() if vf_richtung else ""
details["4386"] = str(int(vf_winkel[0])) if vf_winkel else ""
return {k: str(v) for k, v in details.items()}
SIVAS_MAPPING_KREISEL = sivas_mapping.load_section_mapping("kreisel")
SIVAS_MAPPING_ECKRAD = sivas_mapping.load_section_mapping("eckrad")
SIVAS_MAPPING_GERUEST = sivas_mapping.load_section_mapping("geruest")
def build_kreisel_details(block):
"""Merkmale-Dict (Sivas-Merkmalnummern) fuer eine Gruppe gleichartiger
ILS Kreisel. 3511 ("Kreisel - zu fertigende Menge") steht bewusst fest
auf "1" - die reale Stueckzahl der Gruppe steht auf der Sivas-Hauptzeile
selbst (Anzahl-Spalte, siehe kreisel_groups in process_blocks).
Die reine Attribut->Merkmal-Zuordnung kommt aus cfg/sivas.cfg [kreisel];
3510 (Kreiselart-Anzeigetext) und 3512 (Abstand mm->m, gerundet) sind
berechnete Werte und bleiben hier Code."""
attribs = block.get("attribs", {})
abstand_m = str(round(safe_float(attribs.get("ABSTAND", "2300"), 2300.0) / 1000.0, 2))
kreiselart = "Kreisel mit PINBAND" if attribs.get("KREISELART", "STANDARD") == "PIN" else "Kreisel"
details = sivas_mapping.anwenden(SIVAS_MAPPING_KREISEL, attribs, TRANSFORMS)
details["3510"] = kreiselart
details["3512"] = abstand_m
return details
def build_eckrad_details(block):
"""Merkmale-Dict fuer ein ILS Eckrad. Vollstaendig aus cfg/sivas.cfg
[eckrad] (keine berechneten Werte)."""
attribs = block.get("attribs", {})
return sivas_mapping.anwenden(SIVAS_MAPPING_ECKRAD, attribs, TRANSFORMS)
# def build_geruest_details(block):
#"""Merkmale-Dict fuer eine GR_-Geruest-Baugruppe (siehe pattern_geruest in
#cfg/export.cfg, GeruestInsert.lsp *geruest-attrib-defs*). ARTINR/
#BEZEICHNUNG/TEILEART/LAENGE wandern in feste Sivas-Spalten (sivasnr/
#bezeichnung/teileart/laenge_mm), FARBE bleibt als Rest-Attribut hier
#(cfg/sivas.cfg [geruest]). LAYER ist reine BricsCAD-Zeichnungsinformation
#ohne Sivas-Bedeutung und wird bewusst NICHT mit exportiert (anders als im
#CSV-Export export_csv.py, wo sie fuer die Zuordnung in der Zeichnung
#nuetzlich bleibt)."""
#attribs = block.get("attribs", {})
#return sivas_mapping.anwenden(SIVAS_MAPPING_GERUEST, attribs, TRANSFORMS)
def build_geruest_details(farbe):
"""Merkmale-Dict fuer EINE Artikelnummer einer GR_-Geruest-Baugruppe
(siehe pattern_geruest in cfg/export.cfg, GeruestInsert.lsp
*geruest-attrib-defs*). ARTINR/BEZEICHNUNG/TEILEART/LAENGE wandern in
feste Sivas-Spalten (sivasnr/bezeichnung/teileart/laenge_mm); hier bleibt
nur die Teilefarbe dieser Artikelnummer als Merkmal MERKMAL_TEILEFARBE -
ohne Farbe (Teil darf keine tragen) ein leeres Dict. LAYER ist reine
BricsCAD-Zeichnungsinformation ohne Sivas-Bedeutung und wird bewusst
NICHT mit exportiert (anders als im CSV-Export export_csv.py, wo sie fuer
die Zuordnung in der Zeichnung nuetzlich bleibt)."""
return {MERKMAL_TEILEFARBE: farbe} if farbe else {}
def geruest_artinr_farben(attribs):
"""(artinr, farbe)-Paare eines GR_-Blocks. TEILEFARBE ist eine
kommagetrennte Liste parallel zu ARTINR (GeruestInsert.lsp,
geruest-farbliste): je Artikelnummer die Baugruppenfarbe oder "".
Passt die Laenge nicht (Altbestand mit einem Einzelwert oder von Hand
geaendertes Attribut), wird gewarnt und ohne Farbe exportiert - eine
geratene Zuordnung waere schlimmer als keine. Leere Artikelnummern
werden verworfen."""
artinr_roh = attribs.get("ARTINR", "").split(",")
farben_roh = attribs.get("TEILEFARBE", "")
farben = [f.strip() for f in farben_roh.split(",")]
if farben_roh.strip() == "":
farben = [""] * len(artinr_roh)
elif len(farben) != len(artinr_roh):
print(f"WARNUNG: Geruest-Block ID {attribs.get('ID', '?')}: TEILEFARBE "
f"({len(farben)} Eintraege) passt nicht zu ARTINR "
f"({len(artinr_roh)} Eintraege) - ohne Farbe exportiert.",
file=sys.stderr)
farben = [""] * len(artinr_roh)
return [(a.strip(), f) for a, f in zip(artinr_roh, farben) if a.strip()]
# --- Bekannte Bloecke ignorieren ---
SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"]))
# Eigenstaendige Separator_SP/S-LP- und Scanner-Bloecke werden von der Lisp-
# Sammlung (csv:collect-export-blocks) zwar mit in export_raw.json geschrieben
# (export_csv.py gibt ihnen dort eigene TeileArt-Zeilen), aber HIER bewusst
# NICHT in eine eigene Sivas-Zeile uebernommen: ihre Stueckzahl wird bereits
# ueber die ANZAHL_SEPARATOR/ANZAHL_SCANNER-Attribute des zugehoerigen
# Kreisel/GF/VF-Blocks gezaehlt (siehe kreisel_signature/build_*_details
# oben) - ein zusaetzlicher eigener Eintrag wuerde doppelt zaehlen.
#
# Dieselbe Ueberlegung gilt fuer die ausgepackten VF_n/GF_n-Sub-Komponenten
# (Boegen, gerade Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation -
# siehe csv:element-proxies-erzeugen, export.lsp; nur im Rohdaten-Modus
# *csv-export-alle-elemente*=T ueberhaupt in export_raw.json vorhanden) sowie
# fuer die gruppierten Foerdereinheit-/Gefaelleeinheit-Marker (ZZ_UNIT_MARKER,
# csv:ketten-verarbeiten, Default-Modus): ihre Stueckzahl steckt bereits
# in den GF_Bogen_*/VF_Bogen_*/ANZAHL_VF/ANZAHL_GF-Attributen des Wrappers
# (*strecke-attr-gruppe*, ssg_core.lsp) - ein eigener Sivas-Eintrag wuerde
# ebenso doppelt zaehlen. Muss VOR der pattern_strecke_module-Pruefung unten
# greifen, sonst faengt deren generisches "ILS 2.0 Strecke"-Sammeln diese
# Kopien zuerst ab.
SEP_SCANNER_PATTERNS = (
BLOCKPATTERNS.get("pattern_separator", []) + BLOCKPATTERNS.get("pattern_scanner", [])
+ BLOCKPATTERNS.get("pattern_nested_bogen", [])
+ BLOCKPATTERNS.get("pattern_nested_streckenelement", [])
+ BLOCKPATTERNS.get("pattern_nested_umlenkstation", [])
+ BLOCKPATTERNS.get("pattern_nested_motorstation", [])
+ BLOCKPATTERNS.get("pattern_unit_marker", [])
)
# Sivas-Nummern und Bezeichnungen je ILS-Elementtyp
ILS_SIVASNR = {
"kreisel": ("6200", "ILS 2.0 Kreisel"),
"eckrad": ("6210", "ILS 2.0 Eckrad"),
"zusatzmodul": ("6215", "ILS 2.0 Zusatzmodul"),
"ils_weiche": ("6216", "ILS 2.0 Weiche"),
"strecke": ("6220", "ILS 2.0 Strecke"),
"gefaellestrecke": ("6220", "ILS 2.0 Gefaellestrecke"),
"automation": ("6269", "ILS 2.0 Automation und Pneumatik - Gesamtanlage"),
}
def process_blocks(blocks):
"""
Verarbeitet alle Bloecke.
Omniflo-Elemente werden ueber das TEILEART-Attribut ("Omniflo ...")
erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - ausschliesslich
aus dem ARTINR-Attribut (kommagetrennt, TEF-Teile dort bereits
aufgeloest). Ein Katalog-Lookup ueber den Blocknamen findet nicht statt.
Rueckgabe:
items - Liste der CSV-Zeilen als Dicts
counters - Zaehler fuer ILS-Automation-Summierungszeile
"""
# Omniflo: artinr -> {teileart, anzahl, ids}
omniflo_groups = {}
# Gerade: artinr_str -> {anzahl, laenge_mm_total, ids}
gerade_groups = {}
kreisel_list = [] # Liste von blocks
eckrad_groups = {} # eckrad_signature(block) -> {"ids", "bezeichnungen", "block"}
strecke_ids = [] # IDs loser Strecke-Elemente (Vario/Staustrecke ausserhalb VF_N)
strecke_bezeichnungen = [] # Bezeichnung-Attribut loser Strecke-Elemente
vario_list = [] # Liste von VarioFoerderer-Compound-Bloecken (VF_N), je einer Zeile
gefaelle_list = [] # Liste von Gefaellestrecke-Bloecken (GF_*), je einer Zeile
btmt_beladung_ids = [] # IDs aller BTMT-Beladung*-Bloecke (eine gruppierte Zeile)
btmt_entladung_ids = [] # IDs aller SC_Entladung*-Bloecke (eine gruppierte Zeile)
geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, je einer Zeile
counters = {
"anzahl_kreisel": 0,
"anzahl_kreisel_pinband": 0,
"sep_kreisel_pin": 0,
"sep_kreisel_ohne_pin": 0,
"anzahl_verbindungskreisel": 0,
"laengen_kreisel": 0.0,
"anzahl_weichen_ils": 0,
"anzahl_eckraeder": 0,
"anzahl_gefaellestrecken": 0,
"anzahl_variofoerderer": 0,
"laengen_gefaellestrecken": 0.0,
"anzahl_motoren_vario": 0,
"laengen_variofoerderer": 0.0,
"anzahl_etf": 0,
"laengen_etf": 0.0,
"anzahl_separatoren": 0,
"anzahl_scanner": 0,
"anzahl_btbeladung": 0,
"anzahl_abwurfstationen": 0,
"anzahl_geruest": 0,
}
for block in blocks:
bname = block.get("block_name", "")
if bname in SKIP_BLOCKS:
continue
# Separator/Scanner: siehe Kommentar bei SEP_SCANNER_PATTERNS oben.
if matches_any(bname, SEP_SCANNER_PATTERNS):
continue
# --- BTMT-Beladestation ---
# Anders als Separator/Scanner wird sie von keinem Carrier-Attribut
# mitgezaehlt, darum eigene (gruppierte) Zeile UND eigener Zaehler in
# der ILS-Automation-Summierungszeile (siehe unten).
if matches_any(bname, BLOCKPATTERNS.get("pattern_btmt_beladung", [])):
btmt_beladung_ids.append(get_id(block))
counters["anzahl_btbeladung"] += 1
continue
# --- BTMT-Entladestation (Abwurfstation) ---
if matches_any(bname, BLOCKPATTERNS.get("pattern_btmt_entladung", [])):
btmt_entladung_ids.append(get_id(block))
counters["anzahl_abwurfstationen"] += 1
continue
# --- Eingangs-/Ausgangssymbol (Materialfluss-Ein-/Ausgang) ---
# Bewusst NUR im CSV-Export als eigene Zeile (siehe export_csv.py) -
# hier ohne Sivas-Zeile und ohne eigenen Zaehler ueberspringen. Ein
# Eingangs-/Ausgangssymbol ist eine reine Fluss-Markierung ohne Sivas-
# Artikel; ein eigener Sivas-Eintrag wuerde die Stueckliste verfaelschen
# (Nutzeranforderung: Eingang/Ausgang nur im CSV-Export).
if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])) or \
matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])):
continue
# --- Geruest-Baugruppe ---
# Anders als BTMT/Separator je Block eine EIGENE Zeile statt gruppiert:
# ARTINR (Sivas-IDs) und Laenge/Farbe koennen von Block zu Block
# unterschiedlich sein (freie Basis-Variante+Anbauteile-Auswahl je
# GERUEST_*-Aufruf), eine Gruppierung wuerde das verschleiern - wie
# bei VF_N/Kreisel oben, die aus demselben Grund ebenfalls einzeln
# bleiben.
if matches_any(bname, BLOCKPATTERNS.get("pattern_geruest", [])):
geruest_list.append(block)
counters["anzahl_geruest"] += 1
continue
# --- Omniflo Gerade (AP110) ---
# VOR dem allgemeinen Omniflo-Zweig: Geraden tragen ggf. ebenfalls
# TEILEART "Omniflo ...", brauchen aber die Laengensumme.
if matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])):
attribs = block.get("attribs", {})
artinr = attribs.get("ARTINR", "")
key = artinr if artinr else "AP110"
laenge_attr = attribs.get("LAENGE") or attribs.get("A")
laenge = safe_float(laenge_attr, 2000.0)
if key not in gerade_groups:
gerade_groups[key] = {
"anzahl": 0, "laenge_mm": 0.0,
"artinr": artinr, "ids": [],
}
gerade_groups[key]["anzahl"] += 1
gerade_groups[key]["laenge_mm"] += laenge
gerade_groups[key]["ids"].append(get_id(block))
continue
# --- Omniflo-Element (Bogen, Weiche, Ladeschleife, TEF, ...) ---
# Erkennung ueber TEILEART, Sivas-Nummern aus ARTINR - wie beim
# Geruest eine Zeile je Nummer, hier aber ueber ALLE Bloecke
# gruppiert. Die DWGs fuehren TEF-Teile bereits aufgeloest im ARTINR
# (z.B. "834372002, 0_B10090, 0_B10090"). TEF-Teile ("0_B*") laufen
# unter "Omniflo TEF", damit dasselbe Teil aus Bogen und Weiche EINE
# Zeile bleibt.
attribs = block.get("attribs", {})
teileart = attribs.get("TEILEART", "")
if teileart.startswith("Omniflo"):
artinr_list = [a.strip() for a in attribs.get("ARTINR", "").split(",") if a.strip()]
block_id = get_id(block)
if not artinr_list:
print(f"[export_sivas] WARNUNG: Omniflo-Block {bname} (ID {block_id}) "
f"ohne ARTINR - nicht exportiert.", file=sys.stderr)
continue
for i, artinr in enumerate(artinr_list):
comp_id = f"{block_id}.t{i+1}" if len(artinr_list) > 1 else block_id
if artinr not in omniflo_groups:
omniflo_groups[artinr] = {
"teileart": "Omniflo TEF" if artinr.startswith("0_B") else teileart,
"anzahl": 0, "ids": [],
}
omniflo_groups[artinr]["anzahl"] += 1
omniflo_groups[artinr]["ids"].append(comp_id)
continue
# --- ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst
# ECKRAD_* mit ein, pattern_eckrad ist die praezisere Teilmenge) ---
if matches_any(bname, BLOCKPATTERNS.get("pattern_eckrad", [])):
counters["anzahl_eckraeder"] += 1
sig = eckrad_signature(block)
if sig not in eckrad_groups:
eckrad_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
eckrad_groups[sig]["ids"].append(get_id(block))
eckrad_groups[sig]["bezeichnungen"].append(
block.get("attribs", {}).get("Bezeichnung", ""))
continue
# --- ILS Kreisel ---
if matches_any(bname, BLOCKPATTERNS.get("pattern_kreisel", [])):
kreisel_list.append(block)
attribs = block.get("attribs", {})
counters["anzahl_kreisel"] += 1
n_sep = safe_int(attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")) or "2", 2)
kreiselart = attribs.get("KREISELART", "STANDARD")
if kreiselart == "PIN":
counters["anzahl_kreisel_pinband"] += 1
counters["sep_kreisel_pin"] += n_sep
else:
counters["sep_kreisel_ohne_pin"] += n_sep
abstand_mm = safe_float(attribs.get("ABSTAND", "2300") or "2300", 2300.0)
counters["laengen_kreisel"] += abstand_mm / 1000.0
counters["anzahl_separatoren"] += n_sep
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")) or "0")
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
vario_list.append(block)
counters["laengen_variofoerderer"] += safe_float(attribs.get("L_VF_m", "0") or "0")
counters["anzahl_separatoren"] += safe_int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", "0") or "0")
counters["anzahl_motoren_vario"] += safe_int(attribs.get("ANZAHL_VF", "0") or "0")
continue
# --- Gefaellestrecke Compound-Block (GF_N) ---
# Eigener Typ "ILS 2.0 Gefaellestrecke" - NICHT als Variofoerderer zaehlen.
if matches_any(bname, BLOCKPATTERNS.get("pattern_gefaellestrecke", [])):
attribs = block.get("attribs", {})
counters["anzahl_gefaellestrecken"] += 1
gefaelle_list.append(block)
counters["laengen_gefaellestrecken"] += safe_float(attribs.get("L_GF_m", "0") or "0")
counters["anzahl_separatoren"] += safe_int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", "0") or "0")
continue
# --- ILS Strecke-Module (Vario/Staustrecke, einzelne Komponenten in Modelspace) ---
if matches_any(bname, BLOCKPATTERNS.get("pattern_strecke_module", [])):
counters["anzahl_variofoerderer"] += 1
strecke_ids.append(get_id(block))
strecke_bezeichnungen.append(block.get("attribs", {}).get("Bezeichnung", ""))
# --- Zeilen aufbauen ---
items = []
nr = 1
for sivasnr, g in omniflo_groups.items():
items.append({
"nr": nr,
"teileart": g["teileart"],
"sivasnr": sivasnr,
"sivasnr_quoted": False,
"bezeichnung": "",
"dispgruppe": 10,
"anzahl": g["anzahl"],
"laenge_mm": "",
"ids": g["ids"],
"details": {},
})
nr += 1
for key, g in gerade_groups.items():
items.append({
"nr": nr,
"teileart": "Omniflo Gerade",
"sivasnr": g["artinr"],
"sivasnr_quoted": True,
"bezeichnung": "",
"dispgruppe": 10,
"anzahl": g["anzahl"],
"laenge_mm": int(g["laenge_mm"]),
"ids": g["ids"],
"details": {},
})
nr += 1
# Gleichartige Kreisel (gleiche Kreiselart und gleicher Abstand) zu einer
# Zeile zusammenfassen - nur IDs/Bezeichnung bleiben je Block unterschiedlich.
kreisel_groups = {}
for block in kreisel_list:
sig = kreisel_signature(block)
if sig not in kreisel_groups:
kreisel_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
kreisel_groups[sig]["ids"].append(get_id(block))
kreisel_groups[sig]["bezeichnungen"].append(
block.get("attribs", {}).get("Bezeichnung", ""))
for group in kreisel_groups.values():
sivasnr, teileart = ILS_SIVASNR["kreisel"]
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
"dispgruppe": 20,
"anzahl": len(group["ids"]),
"laenge_mm": "",
"ids": group["ids"],
"details": build_kreisel_details(group["block"]),
})
nr += 1
# Gleichartige Eckraeder (gleiches Geruest fuer Einzelmodul/Geruestoption)
# zu einer Zeile zusammenfassen - nur IDs/Bezeichnung bleiben je Block
# unterschiedlich.
for group in eckrad_groups.values():
sivasnr, teileart = ILS_SIVASNR["eckrad"]
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
"dispgruppe": 20,
"anzahl": len(group["ids"]),
"laenge_mm": "",
"ids": group["ids"],
"details": build_eckrad_details(group["block"]),
})
nr += 1
# 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,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"bezeichnung": ", ".join(b for b in strecke_bezeichnungen if b),
"dispgruppe": 20,
"anzahl": len(strecke_ids),
"laenge_mm": "",
"ids": strecke_ids,
"details": {},
})
nr += 1
# ILS 2.0 Zusatzmodul: BTMT-Beladestationen UND BTMT-Entladestationen
# (Abwurfstationen) zusammen in EINER Zeile (Merkmale 3501-3504) statt wie
# bisher zwei separaten TeileArt-Zeilen ohne details - Anzahl-Spalte
# bleibt 1 (wie bei der Gefaellestrecke-Gruppe), die realen Stueckzahlen
# stehen in 3503/3504.
if btmt_beladung_ids or btmt_entladung_ids:
sivasnr, teileart = ILS_SIVASNR["zusatzmodul"]
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"bezeichnung": "",
"dispgruppe": 20,
"anzahl": 1,
"laenge_mm": "",
"ids": btmt_beladung_ids + btmt_entladung_ids,
"details": {
"3501": ja_nein("1" if btmt_beladung_ids else "0"),
"3503": str(len(btmt_beladung_ids)),
"3502": ja_nein("1" if btmt_entladung_ids else "0"),
"3504": str(len(btmt_entladung_ids)),
},
})
nr += 1
# Geruest-Baugruppen (GR_<id>_<timestamp>) - je Block eine eigene Zeile
# (siehe Kommentar bei geruest_list oben). Sivasnr kommt direkt aus dem
# ARTINR-Attribut - kann mehrere, kommagetrennte Sivas-IDs enthalten
# (Baugruppe aus mehreren Einzelteilen, siehe ssg-join in
# GeruestInsert.lsp) und dieselbe ID mehrfach (menge>1 je Einzelteil).
# Anders als bei den uebrigen Teilearten also KEIN fester Eintrag aus
# ILS_SIVASNR - JEDE Artikelnummer bekommt ihre EIGENE Sivas-Zeile (Sivas
# kennt nur eine Sivasnr je Zeile), gleiche Artikelnummern INNERHALB
# derselben Baugruppe werden dabei zu einer Zeile mit anzahl>1
# zusammengefasst (wie omniflo_groups oben). Die volle
# LAENGE der Baugruppe steht auf JEDER so entstandenen Zeile - Sivas hat
# kein Konzept einer "gemeinsamen" Laenge ueber mehrere Zeilen hinweg.
# Die Teilefarbe steht je Artikelnummer in TEILEFARBE (parallele Liste),
# gruppiert wird darum ueber (Artikelnummer, Farbe).
for block in geruest_list:
attribs = block.get("attribs", {})
artinr_groups = {}
for paar in geruest_artinr_farben(attribs):
artinr_groups.setdefault(paar, 0)
artinr_groups[paar] += 1
for (artinr, farbe), anzahl in artinr_groups.items():
items.append({
"nr": nr,
"teileart": attribs.get("TEILEART", "Geruest"),
"sivasnr": artinr,
"sivasnr_quoted": True,
"bezeichnung": attribs.get("BEZEICHNUNG", ""),
"dispgruppe": 20,
"anzahl": anzahl,
"laenge_mm": attribs.get("LAENGE", ""),
"ids": [get_id(block)],
"details": build_geruest_details(farbe),
})
nr += 1
# 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,
"bezeichnung": block.get("attribs", {}).get("Bezeichnung", ""),
"dispgruppe": 20,
"anzahl": 1,
"laenge_mm": "",
"ids": [get_id(block)],
"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"]
# Anders als bei Kreisel steht hier die reale Stueckzahl der Gruppe
# NICHT in der Anzahl-Spalte (die bleibt 1), sondern im Merkmal 4311 -
# siehe build_gefaellestrecke_details.
details = build_gefaellestrecke_details(group["block"], anzahl=len(group["ids"]))
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": True,
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
"dispgruppe": 20,
"anzahl": 1,
"laenge_mm": "",
"ids": group["ids"],
"details": details,
})
nr += 1
# ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden)
has_ils = (counters["anzahl_kreisel"] > 0
or counters["anzahl_eckraeder"] > 0
or counters["anzahl_variofoerderer"] > 0
or counters["anzahl_gefaellestrecken"] > 0
or counters["anzahl_btbeladung"] > 0
or counters["anzahl_abwurfstationen"] > 0)
if has_ils:
# 4407 "Anzahl der Streckengruppen" zaehlt NUR die kombinierten
# Gefaelle+Vario-Compound-Bloecke (VF_N, TYP "Streckengruppe") - das
# ist len(vario_list), NICHT counters["anzahl_variofoerderer"] (der
# zaehlt auch lose Strecke-Module ausserhalb eines VF_N mit).
# 4426 "Anzahl der einzelnen Vario-Foerderer" gibt es als eigenen
# Typ nicht mehr (nur noch Gefaellestrecke ODER Streckengruppe) und
# steht daher fest auf "0".
automation_details = {
"4000": "Ja",
"4400": str(counters["anzahl_kreisel"]),
"4413": str(counters["sep_kreisel_ohne_pin"]),
"4401": str(counters["anzahl_kreisel_pinband"]),
"4414": str(counters["sep_kreisel_pin"]),
"4402": str(counters["anzahl_verbindungskreisel"]),
"4419": f"{counters['laengen_kreisel']:.2f}",
"4403": str(counters["anzahl_weichen_ils"]),
"4404": str(counters["anzahl_eckraeder"]),
"4425": str(counters["anzahl_gefaellestrecken"]),
"4426": "0",
"4407": str(len(vario_list)),
"4418": f"{counters['laengen_gefaellestrecken']:.2f}",
"4408": str(counters["anzahl_motoren_vario"]),
"4420": str(counters["laengen_variofoerderer"]),
"4409": str(counters["anzahl_etf"]),
"4417": str(counters["laengen_etf"]),
"4410": str(counters["anzahl_separatoren"]),
"4411": str(counters["anzahl_scanner"]),
"4421": str(counters["anzahl_btbeladung"]),
"4422": str(counters["anzahl_abwurfstationen"]),
"4100": "Ja",
}
auto_sivasnr, auto_teileart = ILS_SIVASNR["automation"]
items.append({
"nr": nr,
"teileart": auto_teileart,
"sivasnr": auto_sivasnr,
"sivasnr_quoted": True,
"bezeichnung": "",
"dispgruppe": 20,
"anzahl": 1,
"laenge_mm": "",
"details": automation_details,
})
return items, counters
def format_csv_line(item, details=None):
"""Formatiert eine CSV-Zeile im neuen Sivas-Format.
details ueberschreibt item["details"] fuer die Ausgabe - genutzt von
details_mit_bezeichnung(), um dieselbe Zeile mit lesbaren Merkmal-Keys
in die zweite ("..._mit_bezeichnung.csv") Datei zu schreiben, ohne
item["details"] selbst zu veraendern.
"""
if details is None:
details = item["details"]
details_json = json.dumps(details, ensure_ascii=False)
sivasnr = f'"{csv_quote(item["sivasnr"])}"' if item["sivasnr_quoted"] else item["sivasnr"]
ids_str = ", ".join(str(i) for i in item.get("ids", []) if i)
return (
f'{item["nr"]}'
f';"{csv_quote(item["teileart"])}"'
f';{sivasnr}'
f';"{csv_quote(item["bezeichnung"])}"'
f';{item["dispgruppe"]}'
f';{item["anzahl"]}'
f';{item["laenge_mm"]}'
f';"{csv_quote(ids_str)}"'
f';{details_json}'
)
def details_mit_bezeichnung(details, merkmalstamm):
"""Kopie von details, bei der jeder Merkmalnummer-Key durch seine
Klartext-Bezeichnung aus dem Merkmalstamm ersetzt wird (Wert bleibt
unveraendert). Keys, die keine reine Zahl sind (aktuell nur beim
Eckrad, siehe build_eckrad_details - dort noch beschreibende
Klartext-Keys statt Merkmalnummern), bleiben unveraendert."""
neu = {}
for key, value in details.items():
if key.isdigit():
neu[bezeichnung_fuer(key, merkmalstamm)] = value
else:
neu[key] = value
return neu
def lesbarer_dateiname(output_csv):
"""Leitet den Dateinamen der Lesbarkeits-Kopie aus output_csv ab, z.B.
"500651_sivas.csv" -> "500651_sivas_mit_bezeichnung.csv"."""
stamm, endung = os.path.splitext(output_csv)
return f"{stamm}_mit_bezeichnung{endung}"
def zusammengefasster_dateiname(output_csv):
"""Leitet den Dateinamen der zusammengefassten Kopie aus output_csv ab,
z.B. "500651_sivas.csv" -> "500651_sivas_zusammengefasst.csv"."""
stamm, endung = os.path.splitext(output_csv)
return f"{stamm}_zusammengefasst{endung}"
def items_zusammenfassen(items):
"""Fasst Zeilen zusammen, die sich nur in Nr, Anzahl und IDs
unterscheiden: Anzahl wird addiert, die IDs werden (ohne Dubletten, in
Reihenfolge des Auftretens) vereinigt, Nr wird neu durchgezaehlt.
Reihenfolge der Gruppen = erstes Auftreten.
Betrifft in der Praxis vor allem die Geruest-Zeilen (je Block und
Artikelnummer eine Zeile, siehe process_blocks) - die uebrigen Teilearten
sind dort bereits gruppiert, und ihre Zeilen unterscheiden sich meist in
Bezeichnung/details. Die Laenge gehoert zum Schluessel: Saeulen mit
unterschiedlicher Laenge bleiben getrennte Zeilen.
Die Eingabe bleibt unveraendert (die Hauptdatei wird aus denselben
items geschrieben)."""
gruppen = {}
for item in items:
key = (
item["teileart"], str(item["sivasnr"]), item["sivasnr_quoted"],
item["bezeichnung"], item["dispgruppe"], str(item["laenge_mm"]),
json.dumps(item["details"], ensure_ascii=False, sort_keys=True),
)
g = gruppen.get(key)
if g is None:
g = dict(item, anzahl=0, ids=[])
gruppen[key] = g
g["anzahl"] += safe_int(item["anzahl"], 0)
for i in item.get("ids", []):
if i and i not in g["ids"]:
g["ids"].append(i)
ergebnis = list(gruppen.values())
for nr, g in enumerate(ergebnis, 1):
g["nr"] = nr
return ergebnis
def main():
if len(sys.argv) < 4:
print("Aufruf: python export_sivas.py <export_raw.json> <data_dir> <output.csv> [--mit-bezeichnung]")
sys.exit(1)
raw_json_path = sys.argv[1]
# sys.argv[2] (data_dir) wird derzeit nicht gebraucht - Omniflo-Nummern
# kommen aus ARTINR, nicht mehr aus dem Katalog. Argument bleibt fuer
# den Aufrufer (export.lsp) bestehen.
output_csv = sys.argv[3]
mit_bezeichnung = "--mit-bezeichnung" in sys.argv[4:]
blocks = load_json(raw_json_path)
print(f"[export_sivas] {len(blocks)} Bloecke geladen.")
items, _ = process_blocks(blocks)
header = "Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details"
with open(output_csv, "w", encoding="utf-8") as f:
f.write(header + "\n")
for item in items:
f.write(format_csv_line(item) + "\n")
print(f"[export_sivas] CSV geschrieben: {output_csv} ({len(items)} Zeilen)")
# Zusaetzliche Datei mit zusammengefassten identischen Zeilen (siehe
# items_zusammenfassen) - output_csv selbst bleibt unveraendert.
items_zus = items_zusammenfassen(items)
output_csv_zus = zusammengefasster_dateiname(output_csv)
with open(output_csv_zus, "w", encoding="utf-8") as f:
f.write(header + "\n")
for item in items_zus:
f.write(format_csv_line(item) + "\n")
print(f"[export_sivas] Zusammengefasste CSV geschrieben: {output_csv_zus} "
f"({len(items_zus)} Zeilen)")
# Zweite, lesbare Kopie fuer die manuelle Pruefung (Merkmalnummer +
# Bezeichnung aus cfg/Merkmalsstamm_in_Sivas.xlsx) - wird von Sivas
# selbst NICHT eingelesen, nur output_csv ist der eigentliche Import.
# Nur bei explizitem --mit-bezeichnung-Schalter (Dialog CSV_EXPORT_KONFIG).
if mit_bezeichnung:
merkmalstamm = lade_merkmalstamm()
output_csv_lesbar = lesbarer_dateiname(output_csv)
with open(output_csv_lesbar, "w", encoding="utf-8") as f:
f.write(header + "\n")
for item in items:
details = details_mit_bezeichnung(item["details"], merkmalstamm)
f.write(format_csv_line(item, details=details) + "\n")
print(f"[export_sivas] Lesbare Kopie geschrieben: {output_csv_lesbar}")
if __name__ == "__main__":
main()