- ILS 2.0 Kreisel/Strecke(VF_N)/Gefaellestrecke/Automation: details-JSON nutzt jetzt Sivas-Merkmalnummern (z.B. "2952") statt beschreibender Klartext-Keys, abgeglichen mit dem Merkmalstamm in cfg/Merkmalsstamm_in_Sivas.xlsx. - ILS 2.0 Zusatzmodul: BT-Beladung und Abwurfstationen laufen jetzt in einer gemeinsamen Zeile (Merkmale 3501-3504) statt zwei separaten Zeilen ohne details. - Gefaellestrecke-Gruppe: Anzahl-Spalte bleibt fest 1, die reale Stueckzahl der Gruppe steht im Merkmal 4311 (anders als bei Kreisel, wo die Anzahl-Spalte die Stueckzahl traegt und 3511 fix "1" ist). - Automation-Summenzeile: 4407 zaehlt jetzt echte Streckengruppen (VF_N-Bloecke) statt eines 0/1-Flags, 4408 (Motoren Vario-Foerderer) wird erstmals tatsaechlich berechnet (vorher nie inkrementiert), 4426 steht fest auf "0" (Konzept "einzelner Variofoerderer" entfaellt). - Neues Modul lib/merkmalstamm.py: liest Merkmalnummer->Bezeichnung aus der xlsx rein per Standard-Library (zipfile/xml.etree, keine neue pip-Abhaengigkeit fuer den ueber export.lsp aufgeloesten Interpreter). - export_sivas.py schreibt bei jedem Lauf zusaetzlich eine zweite Datei "<name>_mit_bezeichnung.csv" (gleicher Inhalt, aber jeder Merkmalnummer-Key um "<Nummer> | <Bezeichnung>" erweitert) - reine Lesehilfe zur Pruefung, wird von Sivas nicht eingelesen. - cfg/sivas_merkmal_zuordnung.md: Nachschlagetabelle Block-Attribut <-> Merkmalnummer je Teileart, inkl. offener Punkte (Separator-/ Scanner-Aufteilung Gefaelle-/Vario-Anteil noch Platzhalter). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
merkmalstamm.py - Liest Merkmalnummer -> Bezeichnung aus
|
|
cfg/Merkmalsstamm_in_Sivas.xlsx.
|
|
|
|
Bewusst ohne openpyxl (reine Standard-Library: zipfile + xml.etree) -
|
|
export.lsp/export:python-exe loest den Interpreter fuer EXPORTSIVAS ueber
|
|
Config/DXFM_PYTHON/Projekt-venv/PATH auf und garantiert dabei keine
|
|
zusaetzliche pip-Abhaengigkeit; ein .xlsx ist im Kern ein ZIP aus XML-
|
|
Dateien, das reicht hier ohne Zusatzpaket.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import zipfile
|
|
from functools import lru_cache
|
|
from xml.etree import ElementTree as ET
|
|
|
|
_NS = {"main": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
|
|
|
|
|
|
def merkmalstamm_pfad():
|
|
"""Pfad zu Merkmalsstamm_in_Sivas.xlsx: DXFM_CFG env var, sonst <repo>/cfg."""
|
|
dxfm_cfg = os.environ.get("DXFM_CFG")
|
|
if dxfm_cfg:
|
|
return os.path.join(dxfm_cfg, "Merkmalsstamm_in_Sivas.xlsx")
|
|
basis = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
return os.path.join(basis, "cfg", "Merkmalsstamm_in_Sivas.xlsx")
|
|
|
|
|
|
def _spalte_zu_zahl(spalte):
|
|
zahl = 0
|
|
for zeichen in spalte:
|
|
zahl = zahl * 26 + (ord(zeichen) - ord("A") + 1)
|
|
return zahl
|
|
|
|
|
|
def _spalte_aus_zellref(ref):
|
|
treffer = re.match(r"([A-Z]+)\d+", ref)
|
|
return treffer.group(1) if treffer else "A"
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def lade_merkmalstamm(pfad=None):
|
|
"""Liest Sheet 1 (Spalte A=Merkmal-Nr., Spalte C=Bezeichnung) und liefert
|
|
ein Dict Merkmalnummer (str) -> Bezeichnung (str, von Excel-
|
|
Zeilenumbruch-Artefakten wie "_x000D_" bereinigt). Bei fehlender/kaputter
|
|
Datei: leeres Dict - Aufrufer faellt dann auf die reine Merkmalnummer
|
|
zurueck (siehe bezeichnung_fuer)."""
|
|
if pfad is None:
|
|
pfad = merkmalstamm_pfad()
|
|
if not os.path.exists(pfad):
|
|
return {}
|
|
|
|
try:
|
|
with zipfile.ZipFile(pfad) as z:
|
|
shared = []
|
|
if "xl/sharedStrings.xml" in z.namelist():
|
|
baum = ET.fromstring(z.read("xl/sharedStrings.xml"))
|
|
for si in baum.findall("main:si", _NS):
|
|
texte = si.findall(".//main:t", _NS)
|
|
shared.append("".join(t.text or "" for t in texte))
|
|
|
|
sheet = ET.fromstring(z.read("xl/worksheets/sheet1.xml"))
|
|
mapping = {}
|
|
for row in sheet.findall(".//main:sheetData/main:row", _NS):
|
|
zellen = {}
|
|
for c in row.findall("main:c", _NS):
|
|
spalte = _spalte_zu_zahl(_spalte_aus_zellref(c.get("r", "A1")))
|
|
v = c.find("main:v", _NS)
|
|
wert = v.text if v is not None else None
|
|
if c.get("t") == "s" and wert is not None:
|
|
wert = shared[int(wert)]
|
|
zellen[spalte] = wert
|
|
nummer = zellen.get(1)
|
|
bezeichnung = zellen.get(3)
|
|
if nummer and str(nummer).strip().isdigit() and bezeichnung:
|
|
bezeichnung = bezeichnung.replace("_x000D_", " ")
|
|
bezeichnung = re.sub(r"\s+", " ", bezeichnung).strip()
|
|
mapping[str(nummer).strip()] = bezeichnung
|
|
return mapping
|
|
except (KeyError, zipfile.BadZipFile, ET.ParseError):
|
|
return {}
|
|
|
|
|
|
def bezeichnung_fuer(nummer, mapping=None):
|
|
"""Bezeichnung zu einer Merkmalnummer, Fallback: die Nummer selbst
|
|
(z.B. bei fehlendem Eintrag im Merkmalstamm)."""
|
|
if mapping is None:
|
|
mapping = lade_merkmalstamm()
|
|
return mapping.get(str(nummer), str(nummer))
|