diff --git a/CLAUDE.md b/CLAUDE.md index 3811359..294294a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,7 +33,17 @@ Dieses Repo (`docu_build`) dient als Entwicklungs- und Planungsumgebung. Das Zie ### Technologie-Stack -Sphinx + reStructuredText + Jinja2-Templating → LaTeX → PDF. Konfiguration via JSON (ERP-Export) + YAML (Modul-Metadaten). Bildkonvertierung: ezdxf → SVG, Pillow. CI: Gitea Actions oder n8n auf Hetzner-Server. +| Komponente | Technologie | +|------------------|--------------------------------------------------| +| Build-System | Sphinx (Python) | +| Textformat | reStructuredText (rST) | +| Templating | Jinja2 (Rahmenkapitel, Titelseite) | +| PDF-Erzeugung | LaTeX (pdflatex/lualatex) | +| Versionierung | Git (Gitea auf Hetzner) | +| Konfiguration | JSON (ERP-Export) + YAML (Modul-Metadaten) | +| CI/Automatisierung | Gitea Actions oder n8n | +| Bildkonvertierung | ezdxf → SVG, Pillow (Optimierung) | + ### Build-Pipeline (zu implementieren) @@ -49,6 +59,8 @@ Sphinx + reStructuredText + Jinja2-Templating → LaTeX → PDF. Konfiguration v **meta.yaml** (pro Modul): Definiert `display_name` (mehrsprachig), `chapters` (Kapitelzuordnung mit Sections), `images` (shared/localized/module_local), `requires` (Abhängigkeiten). +Details siehe `doc/Kernkonzepte.md`. + ### Kapitelstruktur (`chapters/`) ```text diff --git a/bin/create_examples.bat b/bin/create_examples.bat deleted file mode 100644 index dc02733..0000000 --- a/bin/create_examples.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -REM ================================================================ -REM DOCU_BUILD - Bilder und Text aus Produktprogramm-PDFs extrahieren -REM ================================================================ - -call "%~dp0setenv.bat" - -if not exist "%PROJECT%\.venv" ( - echo FEHLER: Virtual environment nicht gefunden. - echo Bitte zuerst bin\install_py.bat ausfuehren. - exit /b 1 -) - -call "%PROJECT%\.venv\Scripts\activate.bat" - -echo. -echo Extrahiere Bilder und Text aus Produktprogramm-PDFs ... -echo. - -python "%PV_LIB%\create_examples.py" --example-dir fortna %* diff --git a/bin/jsonFromXlsx.bat b/bin/jsonFromXlsx.bat new file mode 100644 index 0000000..5d694d0 --- /dev/null +++ b/bin/jsonFromXlsx.bat @@ -0,0 +1,25 @@ +@echo off +REM ================================================================ +REM JSON-Baumstruktur aus Excel-Stueckliste erzeugen +REM +REM Liest eine Excel-Datei aus examples/ und baut aus der +REM Positionsspalte eine hierarchische JSON-Datei. +REM +REM Verwendung: +REM jsonFromXlsx.bat --filename Fortna --tab soll +REM jsonFromXlsx.bat --filename Fortna --tab ist +REM ================================================================ + +call "%~dp0setenv.bat" + +REM Pruefe ob venv existiert +if not exist "%PROJECT%\.venv" ( + echo FEHLER: .venv nicht gefunden. Bitte zuerst install_py.bat ausfuehren. + exit /b 1 +) + +call "%PROJECT%\.venv\Scripts\activate.bat" + +python "%PV_LIB%\jsonFromXlsx.py" %* + +deactivate diff --git a/doc/Kernkonzepte.md b/doc/Kernkonzepte.md index df6b310..4cb9354 100644 --- a/doc/Kernkonzepte.md +++ b/doc/Kernkonzepte.md @@ -386,14 +386,112 @@ Ausgabe: ## Umsetzungsphasen -| Phase | Beschreibung | Aufwand | -|-------|---------------------------------------------------------------|-----------| -| 1 | PoC: Ein Modul, ein Kapitel, zwei Sprachen, PDF | 2–3 Tage | -| 2 | Base-Repo aufsetzen: Sphinx, Extensions, LaTeX-CI | 3–5 Tage | -| 3 | Erste 5–10 Module migrieren, Glossar aufbauen | 1–2 Wochen| -| 4 | JSON-Export aus ERP definieren, build.py fertigstellen | 2–3 Tage | -| 5 | Pilotprojekt: Erste vollständige Anlagendokumentation | 1 Woche | -| 6 | CI/CD: Automatischer Build via Gitea Actions oder n8n | 1–2 Tage | +### Methodik (nach Alistair Cockburn) + +Jeder Use Case durchläuft sieben Schritte bis zur Umsetzungsreife: + +| Schritt | Aktivität | Verantwortung | +|---------|--------------------------------------------------------|---------------| +| 1 | Titel / Teaser, Prosa-Story | C (Kunde/PO) | +| 2 | Stakeholder & Interessen / Anwender-Ziele identifizieren | T (Dev Team) | +| 3 | Standard-Ablauf entwerfen (primäres Erfolgsszenario) | M (Member) | +| 4 | Ausnahmen und Erweiterungen sammeln | T (Dev Team) | +| 5 | Ausnahmen detaillieren, Schneiden in User Stories | M (Member) | +| 6 | Business Value schätzen / Aufwand schätzen | T (Dev Team) | +| 7 | Review: Prüfe alle Interessen, Definition of Ready | C (Kunde/PO) | + +**Legende Verantwortung:** +- **C** = Kunde / Product Owner (N.Sch, M.Ro) +- **T** = Dev Team (F.Ve, A.Sch, M.St, D.Fe) +- **M** = Einzelnes Dev Team Member + +### Personen + +| Kürzel | Rolle | Schwerpunkt | +|--------|------------------------|-----------------------| +| N.Sch | Stakeholder / PO | Fachliche Anforderungen | +| M.Ro | Stakeholder / PO | Fachliche Anforderungen | +| F.Ve | Dev Team | IT und KI | +| A.Sch | Dev Team | Redaktion / Inhalte | +| M.St | Dev Team | IT und KI | +| D.Fe | Dev Team | Redaktion / Inhalte | + +### Produktprogramm-Umfang + +Basis für die Aufwandsschätzung: **1388 eindeutige Produktnummern** in 70 Produktgruppen über 5 Produktfamilien (CPC, GERÜST, ILS 2100, OMNIFLO, Trolleyprogramm). 129 Nummern erscheinen in mehreren Kapiteln. + +### Projektplan + +| Phase | Beschreibung | Aufwand | Beteiligte | +|-------|--------------|---------|------------| +| 0 | **Use-Case-Definition & Anforderungen** | | | +| 0.1 | Story & Teaser: Zieldokumentation beschreiben, Prosa-Story je Produktfamilie | 2 Tage | D.Fe, A.Sch, M.St | +| 0.2 | Stakeholder-Analyse: Alle Akteure und deren Interessen/Ziele auflisten | 1 Tag | F.Ve, A.Sch, M.St, D.Fe | +| 0.3 | Standard-Ablauf entwerfen: Build-Pipeline, Redaktionsworkflow, Freigabeprozess | 2 Tage | M.St | +| 0.4 | Ausnahmen sammeln: Sonderfälle (Mehrfachverwendung der 129 Nummern, Varianten, fehlende Übersetzungen) | 1 Tag | F.Ve, A.Sch, M.St, D.Fe | +| 0.5 | Ausnahmen detaillieren, in User Stories schneiden | 2 Tage | F.Ve, M.St | +| 0.6 | Aufwandsschätzung & Business Value für alle Stories | 1 Tag | F.Ve, A.Sch, M.St, D.Fe | +| 0.7 | Review & DOR: Prüfung aller Interessen, Freigabe für Umsetzung | 1 Tag | N.Sch, M.Ro | +| | | | | +| 1 | **PoC: Proof of Concept** | | | +| 1.1 | Ein Modul (z.B. OMNIFLO Weiche), ein Kapitel, zwei Sprachen (DE/EN), PDF-Ausgabe | 3 Tage | M.St, F.Ve | +| 1.2 | LaTeX-Template mit Firmen-CI erstellen und validieren | 2 Tage | M.St, F.Ve, A.Sch | +| 1.3 | PoC-Review mit Stakeholdern | 0,5 Tage | N.Sch, M.Ro, A.Sch | +| | | | | +| 2 | **Base-Repo & Infrastruktur** | | | +| 2.1 | Repo-Struktur anlegen (transport-docs-base), Sphinx-Projekt initialisieren | 1 Tag | M.St | +| 2.2 | Sphinx-Extensions implementieren (anlagen_builder, localized_figure, glossary_loader) | 4 Tage | M.St | +| 2.3 | build.py: JSON-Parsing, Abhängigkeitsauflösung, Override-Mechanismus | 3 Tage | F.Ve, M.St | +| 2.4 | CI/CD: Gitea Actions für automatischen Build | 2 Tage | M.St | +| | | | | +| 3 | **Produktprogramm-Strukturierung** | | | +| 3.1 | Modulstruktur & meta.yaml-Schema je Produktfamilie definieren (5 Familien) | 3 Tage | A.Sch, M.St, D.Fe | +| 3.2 | Nummernkreis-Mapping: 1388 Produkte auf Module abbilden, Gruppierung festlegen | 3 Tage | A.Sch, F.Ve, M.St | +| 3.3 | Vorlage-Templates pro Kapiteltyp erstellen (Beschreibung, Wartung, Ersatzteile) | 2 Tage | A.Sch, M.St | +| | | | | +| 4 | **Content-Migration (Hauptaufwand)** | | | +| 4.1 | CPC-Familie: 126 Produkte in 11 Gruppen – rST-Bausteine erstellen | 2 Wochen | A.Sch, F.Ve | +| 4.2 | GERÜST-Familie: 263 Produkte in 14 Gruppen – rST-Bausteine erstellen | 1 Wochen | A.Sch, F.Ve | +| 4.3 | ILS 2100-Familie: 396 Produkte in 27 Gruppen – rST-Bausteine erstellen | 3 Wochen | A.Sch, F.Ve | +| 4.4 | OMNIFLO-Familie: 453 Produkte in 14 Gruppen – rST-Bausteine erstellen | 4 Wochen | A.Sch, F.Ve | +| 4.5 | Trolleyprogramm: 191 Produkte in 4 Gruppen – rST-Bausteine erstellen | 2 Wochen | A.Sch, F.Ve | +| 4.6 | KI-gestützte Textgenerierung: Vorlagen automatisiert befüllen (Beschreibungen, techn. Daten, Logische Schalter) | parallel | F.Ve, M.St | +| 4.7 | Bildmaterial: Fotos/Zeichnungen zuordnen, optimieren, Pfadstruktur aufbauen | parallel | A.Sch, F.Ve | +| | | | | +| 5 | **Glossar & Mehrsprachigkeit** | | | +| 5.1 | Glossar DE aufbauen (Fachbegriffe aus allen 5 Produktfamilien) | 1 Woche | A.Sch, D.Fe, M.St | +| 5.2 | Glossar EN übersetzen | 1 Woche | A.Sch, D.Fe, M.St, V.Fe | +| 5.3 | Stichproben-Übersetzung: 10% der Module auf EN migrieren | 2 Wochen | A.Sch, D.Fe, F.Ve | +| | | | | +| 6 | **ERP-Integration & Automatisierung** | | | +| 6.1 | JSON-Export-Format mit ERP abstimmen (anlage.json-Schema) | 2 Tage | M.St, A.Jakob | +| 6.2 | Validierungsskripte: meta.yaml-Konsistenz, fehlende Dateien/Übersetzungen | 2 Tage | M.St | +| 6.3 | Hilfsskripte: dxf2svg, optimize_images | 2 Tage | F.Ve | +| | | | | +| 7 | **Pilotprojekt** | | | +| 7.1 | Erste vollständige Anlagendokumentation (reales Kundenprojekt) generieren | 1 Woche | A.Sch, M.St, F.Ve | +| 7.2 | Review mit Stakeholdern, Korrekturrunde | 3 Tage | N.Sch, M.Ro, A.Sch | +| 7.3 | Lessons Learned, Backlog-Pflege | 1 Tag | F.Ve, A.Sch, M.St, D.Fe | + +### Aufwandszusammenfassung + +| Phase | Dauer (geschätzt) | +|-------|-------------------| +| 0 – Use-Case-Definition | ~2 Wochen | +| 1 – PoC | ~1 Woche | +| 2 – Base-Repo & Infrastruktur | ~2 Wochen | +| 3 – Produktprogramm-Strukturierung | ~2 Wochen | +| 4 – Content-Migration | ~10-12 Wochen (2 Redakteure parallel + KI-Unterstützung) | +| 5 – Glossar & Mehrsprachigkeit | ~4 Wochen | +| 6 – ERP-Integration | ~1 Woche | +| 7 – Pilotprojekt | ~2 Wochen | +| **Gesamt** | **~24 Wochen (6 Monate)** | + +**Anmerkungen:** +- Phase 4 ist der Hauptaufwand. Bei 1388 Produkten und 2 Redakteurinnen (A.Sch, F.Ve) parallel ergibt sich ca. 10 Wochen netto für die Content-Erstellung. +- KI-gestützte Textgenerierung (F.Ve, M.St) kann den Aufwand in Phase 4 um geschätzt 30–50% reduzieren. +- Phasen 4, 5 und 6 können teilweise parallelisiert werden, wodurch die Gesamtlaufzeit auf ca. 5 Monate verkürzt werden kann. +- Die Schätzung basiert auf ~20 Produkte/Woche pro Redakteur bei mittlerer Komplexität (Beschreibung, techn. Daten, Wartungshinweise als rST-Bausteine). ## Coding-Konventionen diff --git a/doc/kapitelstruktur.md b/doc/kapitelstruktur.md index f397c32..de24a1d 100644 --- a/doc/kapitelstruktur.md +++ b/doc/kapitelstruktur.md @@ -148,3 +148,109 @@ Neues Konzept: Pro Sprache ein kompletter Dokumentenbaum auf Top-Level: - Zukuenftig: `chapters_en/`, `chapters_cz/` etc. als eigenstaendige Kopien Fallback-Mechanismus: Fehlt ein Dokument in einer Fremdsprache, wird die deutsche Version verwendet. + + +## Heutige Checkliste für die nötigen Dokumente aus der Automation + +### 1. Layout Elektrik + +- Elektrolayout +- Layout Kabelpritsche und Erdung + +### 2. Schaltplan & Pneumatikplan + +- Schaltplan (WSCAD Elektrisch) +- Pneumatikplan (WSCAD Elektrisch) +- PDF-Ausgabe +- Projektsicherung im WSCAD-Format + +### 3. Layout Pneumatik + +- Verschlauchungsplan + +### 4. Ersatzteilliste Elektrik + +- Ersatzteilliste für elektrische Komponenten als PDF-Dokument + +### 5. Dokumentationen Elektrolieferanten + +- Ablage der Datenblätter +- Dokumentationen der Elektrolieferanten + +### 6. Netzwerkstrukturplanung + +- Netzwerkstrukturplanung (ProNetPlan / WSCAD) + +### 7. Betriebsanleitung Steuerung + +- Bedienhandbuch +- Steuerungsanleitung + +### 8. Software-Daten SPS + +- SPS-Projektdaten mit Sicherung +- PDF-Ausdruck des SPS-Programms + +### 9. Sicherheitsausdruck SPS + +- Sicherheitsausdruck der SPS-Software + +### 10. Prüfprotokoll externe Gewerke + +- Prüfprotokolle für externe Gewerke im eigenen Lieferumfang (z. B. Brandschutztüre) + +### 11. Sistema Sicherheitsbetrachtung + +- Sicherheitsbetrachtung gemäß Sistema als PDF-Dokument + +### 12. Prüfprotokolle Schaltschränke + +- Prüfprotokolle der Schaltschrankprüfung als PDF-Dokument + +### 13. Lizenzen + +- Softwarelizenzen (z. B. Sinamics, TIA Portal, OPC UA) + +### 14. Abschaltmatrix + +- Emergency-Stop-Matrix (Abschaltmatrix) als PDF-Dokument + +### 15. Layout Sicherheitsbereiche + +- Layout der Sicherheitsbereiche zur Abschaltmatrix + +### 16. Validierungsprotokolle + +Unterschriebene Validierungsprotokolle, bestehend aus: + +#### 16.1 Prüfprotokoll ortsfester Arbeitsmittel +- Elektrische Prüfung ortsfester Arbeitsmittel + +#### 16.2 Erprobung Wiederanlauf +- Schutz gegen automatischen Wiederanlauf + +#### 16.3 Erprobung Drehrichtung Motoren +- Test der Drehrichtung der Asynchronmotoren + +#### 16.4 Berechnung Fehlerschleifenimpedanz / Validierung Erdung +- Berechnung der Fehlerschleifenimpedanz +- Erdungsvalidierung + +#### 16.5 Erprobung Anzeige- und Bediengeräte +- Test der Anzeige- und Bediengeräte + + +# Fragen und TODOs + +- [ ] Wie ist der Standardablauf der Dokumentation bisher? +- [ ] Wer ist wann daran bei einem einfach Projekt daran beteiligt? +- [ ] Was wäre bei einer Automatisierung der Dokumentations- Erstellung für die wünschenswert? +- [ ] Welche Ausnahmen sind üblich, fehlt oft oder ist nicht immer dabei? +- [ ] für wen ist die Dokumentation gedacht, bzw. was erwarten diese Leute zu finden? +- [ ] Welche Detailierungstiefe bei den Produkten ist nötig oder gewollt? +- [ ] soll die aktuelle Kapitelstruktur mit nur 4 Kapiteln beibehalten werden oder könnten wir dies auf ca. 7 - 10 Kapitel erweitern (siehe Vorschlag)? +- [ ] in welchem Kapitel sollen die jeweiligen Dokumente aus der Automation landen? +- [ ] Wie läuft die Ersatzteilfindung nach der Dokumentation statt? +- [ ] Wie die Behandlung einer Anlage bei Fehler oder Störungen? +- [ ] Welche Daten des Produktprogrammes sollen in die Dokumentation eingang finden? +- [ ] Welche Abteilung liefert normalerweise was in der Dokumentation (Liste Automation ist vorhanden, Was passiert bei Sonderkonstruktionen)? \ No newline at end of file diff --git a/lib/jsonFromXlsx.py b/lib/jsonFromXlsx.py new file mode 100644 index 0000000..63b8baa --- /dev/null +++ b/lib/jsonFromXlsx.py @@ -0,0 +1,343 @@ +"""Builds a JSON hierarchy tree from the Position column of an Excel Stk.-Liste. + +Usage: + python jsonFromXlsx.py --filename Fortna --tab soll + python jsonFromXlsx.py --filename Fortna --tab ist --output result.json + python jsonFromXlsx.py --filename KA135776 --extract-ids + python jsonFromXlsx.py --from-results --extract-ids +""" + +import argparse +import json +import os +import re +import sys +from collections import Counter +from pathlib import Path + +import openpyxl + + +SHEET_NAMES = { + "soll": "Stk.-Liste SOLL", + "ist": "Stk.-Liste IST", +} + +# Column indices (0-based) in the header row +COL_POSITION = 0 +COL_TEILENUMMER = 1 +COL_BEZEICHNUNG = 2 +HEADER_ROW = 3 # 1-based row number of the header + + +def find_xlsx(examples_dir: str, pattern: str) -> Path: + """Find an .xlsx file in examples/ whose name contains the given pattern.""" + matches = [] + for f in Path(examples_dir).glob("*.xlsx"): + if pattern.lower() in f.name.lower(): + matches.append(f) + if not matches: + print(f"FEHLER: Keine .xlsx-Datei mit Muster '{pattern}' in {examples_dir} gefunden.") + sys.exit(1) + if len(matches) > 1: + print(f"Mehrere Dateien gefunden fuer Muster '{pattern}':") + for m in sorted(matches): + print(f" {m.name}") + print(f"Verwende: {matches[0].name}") + return sorted(matches)[0] + + +def make_key(teilenummer: str, bezeichnung: str, is_unique: bool) -> str: + """Build a node key from Teilenummer, adding Bezeichnung if not unique.""" + if is_unique: + return teilenummer + combined = f"{teilenummer}-{bezeichnung}" + return combined.replace(" ", "_") + + +def unique_key(parent: dict, key: str) -> str: + """Return key with _1, _2, ... suffix if it already exists in parent.""" + if key not in parent: + return key + suffix = 1 + while f"{key}_{suffix}" in parent: + suffix += 1 + return f"{key}_{suffix}" + + +def build_tree(ws, sheet_type: str) -> dict: + """Read the worksheet and build a nested dict from Position paths. + + Keys are Teilenummer-based (with Bezeichnung if non-unique). + Position paths are used only for hierarchy, not as keys. + """ + # First pass: collect all Teilenummern to determine uniqueness + teil_counter: Counter = Counter() + rows_data = [] + for row in ws.iter_rows(min_row=HEADER_ROW + 1, max_row=ws.max_row, values_only=True): + pos = row[COL_POSITION] + teil = row[COL_TEILENUMMER] + bez = row[COL_BEZEICHNUNG] + if pos is None or teil is None: + continue + pos_str = str(pos).strip() + if pos_str.lower() == "position": + continue # skip header row + teil_str = str(teil).strip() + bez_str = str(bez).strip() if bez else "" + teil_counter[teil_str] += 1 + rows_data.append((pos_str, teil_str, bez_str)) + + unique_teile = {t for t, count in teil_counter.items() if count == 1} + + # Second pass: build tree with Teilenummer-based keys + tree: dict = {} + pos_to_node: dict[str, dict] = {} # maps position path -> dict node + # Track metadata per node for leaf post-processing + node_meta: dict[int, tuple[str, str]] = {} # id(child) -> (teil_str, bez_str) + + for pos_str, teil_str, bez_str in rows_data: + key = make_key(teil_str, bez_str, teil_str in unique_teile) + segments = pos_str.split("/") + + # Find parent dict + if len(segments) == 1: + parent = tree + else: + parent_pos = "/".join(segments[:-1]) + if parent_pos not in pos_to_node: + # Parent row missing in data, create placeholder + pos_to_node[parent_pos] = {} + parent = pos_to_node[parent_pos] + + # Insert with unique key + final_key = unique_key(parent, key) + child: dict = {} + parent[final_key] = child + pos_to_node[pos_str] = child + node_meta[id(child)] = (teil_str, bez_str) + + is_sivas = re.compile(r"^\d{9}$").match + + # Post-process: leaves (empty dicts) -> key=Teilenummer, value=Bezeichnung + def simplify_leaves(node: dict) -> dict: + result: dict = {} + for key, value in node.items(): + if isinstance(value, dict) and len(value) == 0: + # Leaf node: use only Teilenummer as key, Bezeichnung as value + teil, bez = node_meta.get(id(value), ("", "")) + if teil in result and is_sivas(teil): + # Aggregate duplicate 9-digit Sivas numbers + existing = result[teil] + if isinstance(existing, str): + result[teil] = {"anzahl": 2, "beschreibung": existing} + else: + existing["anzahl"] += 1 + else: + leaf_key = unique_key(result, teil) + result[leaf_key] = bez + elif isinstance(value, dict): + result[key] = simplify_leaves(value) + else: + result[key] = value + return result + + return simplify_leaves(tree) + + +RE_SIVAS = re.compile(r"^(\d{9})") + + +def is_leaf_value(v) -> bool: + """True if v is a leaf entry (string or anzahl-dict).""" + if isinstance(v, str): + return True + if isinstance(v, dict) and "anzahl" in v: + return True + return False + + +def extract_ids_from_tree(tree: dict) -> dict[str, str]: + """Collect 9-digit Sivas numbers one level above leaves. + + Returns dict of nummer -> bezeichnung. + """ + ids: dict[str, str] = {} + + def walk(node: dict): + for key, value in node.items(): + if not isinstance(value, dict) or "anzahl" in value: + continue # leaf, skip + # Check if all children are leaves + all_leaves = all(is_leaf_value(v) for v in value.values()) + m = RE_SIVAS.match(key) + if all_leaves and value and m: + nummer = m.group(1) + # Bezeichnung from key: part after "nummer-", underscores back to spaces + if "-" in key[9:]: + bez = key[10:].replace("_", " ") + elif len(key) > 9: + bez = key[10:].replace("_", " ") + else: + bez = nummer + ids[nummer] = bez + else: + walk(value) + + walk(tree) + return ids + + +def process_file(xlsx_path: Path, sheet_name: str, tab: str, results_dir: Path, + out_path: Path | None = None) -> bool: + """Process a single xlsx file. Returns True on success.""" + print(f"Lade: {xlsx_path.name}") + print(f"Sheet: {sheet_name}") + + wb = openpyxl.load_workbook(str(xlsx_path), data_only=True) + if sheet_name not in wb.sheetnames: + print(f" SKIP: Sheet '{sheet_name}' nicht vorhanden.") + return False + + ws = wb[sheet_name] + tree = build_tree(ws, tab) + + if out_path is None: + stem = xlsx_path.stem.replace(" ", "_") + out_path = results_dir / f"{stem}_{tab}.json" + + with open(out_path, "w", encoding="utf-8") as f: + json.dump(tree, f, indent=2, ensure_ascii=False) + + print(f" Ausgabe: {out_path}") + return True + + +def main(): + parser = argparse.ArgumentParser( + description="Erzeugt eine JSON-Baumstruktur aus der Positionsspalte einer Excel-Stueckliste." + ) + parser.add_argument( + "--filename", + default=None, + help="Suchmuster fuer die .xlsx-Datei im examples/-Ordner (z.B. 'Fortna')", + ) + parser.add_argument( + "--tab", + choices=["soll", "ist"], + default="soll", + help="Welches Tabellenblatt gelesen wird: 'soll' oder 'ist' (default: soll)", + ) + parser.add_argument( + "--output", + default=None, + help="Ausgabedatei (default: _.json im results/-Ordner)", + ) + parser.add_argument( + "--all", + action="store_true", + dest="process_all", + help="Alle .xlsx-Dateien im examples/-Ordner in _ist und _soll JSON konvertieren", + ) + parser.add_argument( + "--extract-ids", + action="store_true", + dest="extract_ids", + help="9-stellige Sivas-Nummern eine Ebene ueber den Blaettern extrahieren", + ) + parser.add_argument( + "--from-results", + action="store_true", + dest="from_results", + help="JSON-Dateien aus results/ lesen statt aus .xlsx zu erzeugen", + ) + args = parser.parse_args() + + if not args.process_all and not args.filename and not args.from_results: + parser.error("Entweder --filename, --all oder --from-results muss angegeben werden.") + + # Resolve paths relative to project root + project_root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent)) + examples_dir = project_root / "examples" + results_dir = project_root / "results" + results_dir.mkdir(exist_ok=True) + + # Default tab to "ist" when --extract-ids without explicit --tab + tab = args.tab + if args.extract_ids and args.tab == "soll" and "--tab" not in sys.argv: + tab = "ist" + + if args.from_results: + # Read existing JSON files from results/ + pattern = f"*_{tab}.json" + json_files = sorted(results_dir.glob(pattern)) + if args.filename: + json_files = [f for f in json_files if args.filename.lower() in f.name.lower()] + if not json_files: + print(f"FEHLER: Keine JSON-Dateien mit Muster '{pattern}' in {results_dir} gefunden.") + sys.exit(1) + print(f"Lese {len(json_files)} JSON-Dateien aus {results_dir}\n") + for json_path in json_files: + with open(json_path, encoding="utf-8") as f: + tree = json.load(f) + # Derive project name from filename: strip _ist/_soll.json suffix + project_name = json_path.stem + for suffix in ("_ist", "_soll"): + if project_name.endswith(suffix): + project_name = project_name[: -len(suffix)] + break + if args.extract_ids: + ids = extract_ids_from_tree(tree) + ids_path = results_dir / f"{project_name}_ids_{tab}.txt" + with open(ids_path, "w", encoding="utf-8") as f: + for nummer, bez in sorted(ids.items()): + f.write(f"{nummer}: {bez}\n") + print(f" {json_path.name} -> {ids_path.name} ({len(ids)} IDs)") + elif args.process_all: + xlsx_files = sorted(f for f in Path(examples_dir).glob("*.xlsx") + if not f.name.startswith("~$")) + print(f"Verarbeite {len(xlsx_files)} Dateien aus {examples_dir}\n") + ok, skip = 0, 0 + for xlsx_path in xlsx_files: + for t in ("soll", "ist"): + sheet_name = SHEET_NAMES[t] + if process_file(xlsx_path, sheet_name, t, results_dir): + ok += 1 + else: + skip += 1 + print() + print(f"Fertig: {ok} JSON erzeugt, {skip} uebersprungen.") + else: + xlsx_path = find_xlsx(str(examples_dir), args.filename) + sheet_name = SHEET_NAMES[tab] + + out_path = None + if args.output: + out_path = Path(args.output) + if not out_path.is_absolute(): + out_path = results_dir / out_path + + json_out = None + if args.extract_ids: + stem = xlsx_path.stem.replace(" ", "_") + json_out = results_dir / f"{stem}_{tab}.json" + + if not process_file(xlsx_path, sheet_name, tab, results_dir, out_path): + sys.exit(1) + + if args.extract_ids: + # Read back the generated JSON + read_path = out_path or json_out or results_dir / f"{xlsx_path.stem.replace(' ', '_')}_{tab}.json" + with open(read_path, encoding="utf-8") as f: + tree = json.load(f) + ids = extract_ids_from_tree(tree) + project_name = xlsx_path.stem.replace(" ", "_") + ids_path = results_dir / f"{project_name}_ids_{tab}.txt" + with open(ids_path, "w", encoding="utf-8") as f: + for nummer, bez in sorted(ids.items()): + f.write(f"{nummer}: {bez}\n") + print(f" IDs: {ids_path} ({len(ids)} IDs)") + + +if __name__ == "__main__": + main() diff --git a/lib/create_examples.py b/lib/project_from_xlsx.py similarity index 100% rename from lib/create_examples.py rename to lib/project_from_xlsx.py