From e7aa3b4e4b789301815f083a82b133a181a89220 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 23 Sep 2026 13:23:32 +0200 Subject: [PATCH] feat: Fortna-Extraktion nach chapters/ + Sphinx-PDF-Build Skripte zum Befuellen der Kapitelstruktur aus dem Fortna-Rohmaterial und Aufbau eines vollstaendigen Sphinx/LaTeX-Builds (nur Code/Config/Doku, generierte RST/Bilder/PDFs und Rohmaterial via .gitignore ausgeschlossen). - lib/fortna_inventory.py: Rohmaterial scannen, Sprache+Zielkapitel+Aktion zuordnen, results/fortna_inventory.json schreiben - lib/fortna_convert_chapters.py: 5 Hauptanleitungen volltextig (Text+Bilder) konvertieren, Verzeichnislisten (index.de.rst) fuer uebrige Kapitel - lib/fortna_init_project.py: projekte/fortna/ (anlage.json, overrides, images) - lib/build_index.py: index.rst mit toctrees aus chapters/**/*.de.rst generieren - lib/extract_products.py: Symbolfont-Zeichen (PUA) auf Unicode mappen (clean_pua) - conf.py: Sphinx-Build (lualatex, babel statt polyglossia, iconpackage=none) - bin/: .bat/.sh-Wrapper fuer alle Skripte + build_pdf - doc/: Konzept-Plan (plan_fortna_extraktion.md) und lib-Skript-Uebersicht - requirements.txt: Sphinx + myst-parser ergaenzt Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 19 + bin/build_pdf.bat | 38 ++ bin/build_pdf.sh | 34 ++ bin/extractids.bat | 26 + bin/fortna_convert_chapters.bat | 22 + bin/fortna_convert_chapters.sh | 22 + bin/fortna_init_project.bat | 19 + bin/fortna_init_project.sh | 19 + bin/fortna_inventory.bat | 21 + bin/fortna_inventory.sh | 19 + bin/projectfromExcel.bat | 20 + .../einbauerklaerungen/.gitkeep | 0 .../.gitkeep | 0 .../gemeinsame_spezifikationen/.gitkeep | 0 .../normen/sonstige_spezifikationen/.gitkeep | 0 .../risikobewertung/restrisiken/.gitkeep | 0 conf.py | 102 ++++ doc/Fortna-size.txt | 504 ++++++++++++++++++ doc/lib_skripte.md | 99 ++++ doc/plan_fortna_extraktion.md | 240 +++++++++ lib/build_index.py | 95 ++++ lib/extract_products.py | 32 +- lib/fortna_convert_chapters.py | 281 ++++++++++ lib/fortna_init_project.py | 117 ++++ lib/fortna_inventory.py | 317 +++++++++++ projekte/fortna/README.md | 23 + projekte/fortna/anlage.json | 25 + projekte/fortna/images/.gitkeep | 0 projekte/fortna/overrides/.gitkeep | 0 requirements.txt | 2 + 30 files changed, 2095 insertions(+), 1 deletion(-) create mode 100644 bin/build_pdf.bat create mode 100644 bin/build_pdf.sh create mode 100644 bin/extractids.bat create mode 100644 bin/fortna_convert_chapters.bat create mode 100644 bin/fortna_convert_chapters.sh create mode 100644 bin/fortna_init_project.bat create mode 100644 bin/fortna_init_project.sh create mode 100644 bin/fortna_inventory.bat create mode 100644 bin/fortna_inventory.sh create mode 100644 bin/projectfromExcel.bat create mode 100644 chapters/01_zertifikate/einbauerklaerungen/.gitkeep create mode 100644 chapters/01_zertifikate/konformitaetserklaerungen_komponenten/.gitkeep create mode 100644 chapters/02_sicherheit/normen/gemeinsame_spezifikationen/.gitkeep create mode 100644 chapters/02_sicherheit/normen/sonstige_spezifikationen/.gitkeep create mode 100644 chapters/02_sicherheit/risikobewertung/restrisiken/.gitkeep create mode 100644 conf.py create mode 100644 doc/Fortna-size.txt create mode 100644 doc/lib_skripte.md create mode 100644 doc/plan_fortna_extraktion.md create mode 100644 lib/build_index.py create mode 100644 lib/fortna_convert_chapters.py create mode 100644 lib/fortna_init_project.py create mode 100644 lib/fortna_inventory.py create mode 100644 projekte/fortna/README.md create mode 100644 projekte/fortna/anlage.json create mode 100644 projekte/fortna/images/.gitkeep create mode 100644 projekte/fortna/overrides/.gitkeep diff --git a/.gitignore b/.gitignore index c323ef1..ddb8cd8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,11 @@ cover/ .env .venv env/ +# Sphinx-Build-Ausgabe (generiert, nicht versionieren) +/_build/ +# generierter Build-Index (aus lib/build_index.py) +/index.rst +env/ venv/ ENV/ env.bak/ @@ -69,3 +74,17 @@ ipython_config.py /log /results /_output + +# IDE- / Tool-Ordner +/.vscode +/.iis +/.claude + +# Fremd-/Rohmaterial und grosse Binaerdaten (nicht versionieren) +/examples +doc/*.pdf + +# Generierte Dokumentations-Bausteine (reproduzierbar via lib/fortna_convert_chapters.py) +# und extrahierte Bilder. Nur Code/Config/Doku wird versioniert. +chapters/**/*.rst +chapters/**/images/ diff --git a/bin/build_pdf.bat b/bin/build_pdf.bat new file mode 100644 index 0000000..7ec7d09 --- /dev/null +++ b/bin/build_pdf.bat @@ -0,0 +1,38 @@ +@echo off +REM ================================================================ +REM Vollstaendiger PDF-Build der Fortna-Dokumentation (DE) +REM +REM Ablauf: +REM 1. index.rst aus vorhandenen chapters/**/*.de.rst generieren +REM 2. sphinx-build -b latex -> _build/latex/*.tex +REM 3. lualatex (via latexmk) -> _build/latex/Fortna_Dokumentation_DE.pdf +REM +REM Voraussetzung: chapters/ ist befuellt (fortna_convert_chapters.bat --apply) +REM ================================================================ + +call "%~dp0setenv.bat" + +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" + +echo. +echo [1/3] index.rst generieren ... +python "%PV_LIB%\build_index.py" + +echo. +echo [2/3] LaTeX-Quellen erzeugen (sphinx-build -b latex) ... +python -m sphinx -b latex "%PROJECT%" "%PROJECT%\_build\latex" + +echo. +echo [3/3] PDF erzeugen (lualatex) ... +pushd "%PROJECT%\_build\latex" +latexmk -lualatex -interaction=nonstopmode Fortna_Dokumentation_DE.tex +popd + +echo. +echo Fertig. PDF: %PROJECT%\_build\latex\Fortna_Dokumentation_DE.pdf +deactivate diff --git a/bin/build_pdf.sh b/bin/build_pdf.sh new file mode 100644 index 0000000..24d3a02 --- /dev/null +++ b/bin/build_pdf.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# ================================================================ +# Vollstaendiger PDF-Build der Fortna-Dokumentation (DE) +# 1. index.rst generieren +# 2. sphinx-build -b latex +# 3. lualatex (via latexmk) +# Voraussetzung: chapters/ ist befuellt (fortna_convert_chapters.sh --apply) +# ================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setenv.sh" + +if [ ! -d "$PROJECT/.venv" ]; then + echo "FEHLER: .venv nicht gefunden. Bitte zuerst install_py.sh ausfuehren." + exit 1 +fi + +source "$PROJECT/.venv/bin/activate" 2>/dev/null || source "$PROJECT/.venv/Scripts/activate" + +echo "" +echo "[1/3] index.rst generieren ..." +python "$PV_LIB/build_index.py" + +echo "" +echo "[2/3] LaTeX-Quellen erzeugen (sphinx-build -b latex) ..." +python -m sphinx -b latex "$PROJECT" "$PROJECT/_build/latex" + +echo "" +echo "[3/3] PDF erzeugen (lualatex) ..." +( cd "$PROJECT/_build/latex" && latexmk -lualatex -interaction=nonstopmode Fortna_Dokumentation_DE.tex ) + +echo "" +echo "Fertig. PDF: $PROJECT/_build/latex/Fortna_Dokumentation_DE.pdf" +deactivate diff --git a/bin/extractids.bat b/bin/extractids.bat new file mode 100644 index 0000000..8136736 --- /dev/null +++ b/bin/extractids.bat @@ -0,0 +1,26 @@ +@echo off +REM ================================================================ +REM Sivas-IDs aus JSON-Baeumen extrahieren +REM +REM Liest JSON-Dateien aus results/ und extrahiert 9-stellige +REM Sivas-Nummern eine Ebene ueber den Blaettern. +REM +REM Verwendung: +REM extractids.bat --filename KA135776 +REM extractids.bat --filename KA135776 --tab soll +REM extractids.bat --all +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%\extract_ids.py" %* + +deactivate diff --git a/bin/fortna_convert_chapters.bat b/bin/fortna_convert_chapters.bat new file mode 100644 index 0000000..2cd4022 --- /dev/null +++ b/bin/fortna_convert_chapters.bat @@ -0,0 +1,22 @@ +@echo off +REM ================================================================ +REM chapters/ aus dem Fortna-Inventar befuellen +REM (5 Hauptanleitungen Volltext + Verzeichnislisten) +REM +REM Verwendung: +REM fortna_convert_chapters.bat (Dry-Run) +REM fortna_convert_chapters.bat --apply +REM ================================================================ + +call "%~dp0setenv.bat" + +if not exist "%PROJECT%\.venv" ( + echo FEHLER: .venv nicht gefunden. Bitte zuerst install_py.bat ausfuehren. + exit /b 1 +) + +if "%PV_FORTNA_SOURCE%"=="" set "PV_FORTNA_SOURCE=C:\10-Develop\projekte\Fortna" + +call "%PROJECT%\.venv\Scripts\activate.bat" +python "%PV_LIB%\fortna_convert_chapters.py" %* +deactivate diff --git a/bin/fortna_convert_chapters.sh b/bin/fortna_convert_chapters.sh new file mode 100644 index 0000000..23a5f7a --- /dev/null +++ b/bin/fortna_convert_chapters.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# ================================================================ +# chapters/ aus dem Fortna-Inventar befuellen +# (5 Hauptanleitungen Volltext + Verzeichnislisten) +# Verwendung: +# bash bin/fortna_convert_chapters.sh (Dry-Run) +# bash bin/fortna_convert_chapters.sh --apply +# ================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setenv.sh" + +if [ ! -d "$PROJECT/.venv" ]; then + echo "FEHLER: .venv nicht gefunden. Bitte zuerst install_py.sh ausfuehren." + exit 1 +fi + +export PV_FORTNA_SOURCE="${PV_FORTNA_SOURCE:-C:/10-Develop/projekte/Fortna}" + +source "$PROJECT/.venv/bin/activate" 2>/dev/null || source "$PROJECT/.venv/Scripts/activate" +python "$PV_LIB/fortna_convert_chapters.py" "$@" +deactivate diff --git a/bin/fortna_init_project.bat b/bin/fortna_init_project.bat new file mode 100644 index 0000000..ffb626c --- /dev/null +++ b/bin/fortna_init_project.bat @@ -0,0 +1,19 @@ +@echo off +REM ================================================================ +REM Projektspezifischen Ordner projekte/fortna/ anlegen +REM +REM Verwendung: +REM fortna_init_project.bat +REM fortna_init_project.bat --force +REM ================================================================ + +call "%~dp0setenv.bat" + +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%\fortna_init_project.py" %* +deactivate diff --git a/bin/fortna_init_project.sh b/bin/fortna_init_project.sh new file mode 100644 index 0000000..045f97e --- /dev/null +++ b/bin/fortna_init_project.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# ================================================================ +# Projektspezifischen Ordner projekte/fortna/ anlegen +# Verwendung: +# bash bin/fortna_init_project.sh +# bash bin/fortna_init_project.sh --force +# ================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setenv.sh" + +if [ ! -d "$PROJECT/.venv" ]; then + echo "FEHLER: .venv nicht gefunden. Bitte zuerst install_py.sh ausfuehren." + exit 1 +fi + +source "$PROJECT/.venv/bin/activate" 2>/dev/null || source "$PROJECT/.venv/Scripts/activate" +python "$PV_LIB/fortna_init_project.py" "$@" +deactivate diff --git a/bin/fortna_inventory.bat b/bin/fortna_inventory.bat new file mode 100644 index 0000000..8823b91 --- /dev/null +++ b/bin/fortna_inventory.bat @@ -0,0 +1,21 @@ +@echo off +REM ================================================================ +REM Fortna-Rohmaterial inventarisieren + Kapitel-Mapping pruefen +REM +REM Verwendung: +REM fortna_inventory.bat +REM fortna_inventory.bat --lang de +REM ================================================================ + +call "%~dp0setenv.bat" + +if not exist "%PROJECT%\.venv" ( + echo FEHLER: .venv nicht gefunden. Bitte zuerst install_py.bat ausfuehren. + exit /b 1 +) + +if "%PV_FORTNA_SOURCE%"=="" set "PV_FORTNA_SOURCE=C:\10-Develop\projekte\Fortna" + +call "%PROJECT%\.venv\Scripts\activate.bat" +python "%PV_LIB%\fortna_inventory.py" %* +deactivate diff --git a/bin/fortna_inventory.sh b/bin/fortna_inventory.sh new file mode 100644 index 0000000..6bc9f19 --- /dev/null +++ b/bin/fortna_inventory.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# ================================================================ +# Fortna-Rohmaterial inventarisieren + Kapitel-Mapping pruefen +# Verwendung: bash bin/fortna_inventory.sh [--lang de] +# ================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setenv.sh" + +if [ ! -d "$PROJECT/.venv" ]; then + echo "FEHLER: .venv nicht gefunden. Bitte zuerst install_py.sh ausfuehren." + exit 1 +fi + +export PV_FORTNA_SOURCE="${PV_FORTNA_SOURCE:-C:/10-Develop/projekte/Fortna}" + +source "$PROJECT/.venv/bin/activate" 2>/dev/null || source "$PROJECT/.venv/Scripts/activate" +python "$PV_LIB/fortna_inventory.py" "$@" +deactivate diff --git a/bin/projectfromExcel.bat b/bin/projectfromExcel.bat new file mode 100644 index 0000000..2db24bf --- /dev/null +++ b/bin/projectfromExcel.bat @@ -0,0 +1,20 @@ +@echo off +REM ================================================================ +REM DOCU_BUILD - Projektdaten aus Excel-Stuecklisten 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 Projektdaten aus Excel-Stuecklisten ... +echo. + +python "%PV_LIB%\project_from_xlsx.py" %* diff --git a/chapters/01_zertifikate/einbauerklaerungen/.gitkeep b/chapters/01_zertifikate/einbauerklaerungen/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/chapters/01_zertifikate/konformitaetserklaerungen_komponenten/.gitkeep b/chapters/01_zertifikate/konformitaetserklaerungen_komponenten/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/chapters/02_sicherheit/normen/gemeinsame_spezifikationen/.gitkeep b/chapters/02_sicherheit/normen/gemeinsame_spezifikationen/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/chapters/02_sicherheit/normen/sonstige_spezifikationen/.gitkeep b/chapters/02_sicherheit/normen/sonstige_spezifikationen/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/chapters/02_sicherheit/risikobewertung/restrisiken/.gitkeep b/chapters/02_sicherheit/risikobewertung/restrisiken/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..f1d042a --- /dev/null +++ b/conf.py @@ -0,0 +1,102 @@ +"""Sphinx-Konfiguration fuer den Fortna-Dokumentations-Build (DE). + +Vorlaeufige Konfiguration fuer den ersten vollstaendigen Build aus dem Base-Repo +heraus. Die spaetere Zwei-Repo-Architektur (conf_base.py + Projekt-conf.py, siehe +doc/Kernkonzepte.md) ist hier noch nicht umgesetzt. +""" + +from __future__ import annotations + +# -- Projekt ---------------------------------------------------------------- +project = "Anlagendokumentation Fortna (500592 / KA 135776)" +author = "Schoenenberger Systeme GmbH / Mista GmbH" +copyright = "2026, Schoenenberger Systeme GmbH" +release = "0.1" +language = "de" + +# -- Allgemein -------------------------------------------------------------- +extensions = [ + "myst_parser", # erlaubt .md gleichwertig zu .rst (Kernkonzepte.md) +] + +# Master-Dokument +root_doc = "index" + +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} + +# Nur die deutschsprachigen Bausteine in den Build aufnehmen; fremdsprachige +# {name}.en.rst / {name}.cz.rst (aktuell keine im Baum) werden ignoriert. +# Sphinx sammelt per Default ALLE .rst/.md unter dem Wurzelverzeichnis ein. +# Alles ausserhalb von chapters/ ausschliessen, damit nur die Doku-Bausteine +# in den Build gelangen (CLAUDE.md, README.md, doc/, Git-Worktrees etc. raus). +exclude_patterns = [ + "_build", + "**/*.en.rst", + "**/*.cz.rst", + "*.md", + ".claude", + ".venv", + ".git", + "doc", + "examples", + "results", + "data", + "lib", + "bin", + "cfg", + "log", + "tests", + "projekte", + "_ext", + "_templates", + "assets", + "modules", + "glossary", + "styles", + "scripts", + # Defekte Altbestaende aus fruehen Testlaeufen (nicht von den fortna_*-Skripten + # erzeugt): unvollstaendiger Bildbestand bzw. fehlende RST-Ueberschrift. + # TODO: mit fortna_convert_chapters.py sauber neu erzeugen, dann Exclude entfernen. + "chapters/02_sicherheit/risikobewertung/rb_fortna_h_m_500592_ev12_20260311.de.rst", + "chapters/00_inhaltsverzeichnis/contens_obsah_500592_135776_v2_1_en_cz.de.rst", +] + +# -- HTML ------------------------------------------------------------------- +html_theme = "alabaster" + +# -- LaTeX / PDF ------------------------------------------------------------ +# lualatex verkraftet die Unicode-Zeichen (tschechische Diakritik, ue/oe/ae, +# Sonderzeichen aus der PDF-Extraktion) deutlich besser als pdflatex. +latex_engine = "lualatex" + +latex_documents = [ + (root_doc, + "Fortna_Dokumentation_DE.tex", + "Anlagendokumentation Fortna\\\\500592 / KA 135776", + author, + "manual"), +] + +# iconpackage=none: FontAwesome nicht laden. fontawesome7 (MiKTeX) bricht unter +# lualatex beim Font-Analyze ab (\faDice undefined). Ohne Icons werden Admonitions +# (.. note:: in den Verzeichnislisten) einfach ohne Symbol gesetzt. +latex_elements = { + "sphinxsetup": "iconpackage=none", + "papersize": "a4paper", + "pointsize": "11pt", + # babel statt polyglossia erzwingen: polyglossia 2.13 (MiKTeX 09/2026) wirft + # unter lualatex einen bidi-Initialisierungsfehler (\g__xpg_unibidi_str), der + # fuer rein deutschen Text irrelevant ist. babel vermeidet ihn. + "polyglossia": "", + "babel": r"\usepackage[ngerman]{babel}", + # Robustheit gegen ueberlange Zeilen aus der PDF-Textextraktion: + "preamble": r""" +\usepackage{microtype} +\emergencystretch=3em +\setcounter{tocdepth}{2} +\sloppy +""", +} diff --git a/doc/Fortna-size.txt b/doc/Fortna-size.txt new file mode 100644 index 0000000..81df082 --- /dev/null +++ b/doc/Fortna-size.txt @@ -0,0 +1,504 @@ +[ 512] . +├── [ 512] 00_CONTENTS_NOTES-OBSAH_POZNÁMKY +│   ├── [355K] Contens-Obsah_500592_135776_V2.1_EN-CZ.pdf +│   └── [348K] Contens_Obsah_500592_135776_V2.1_20260311_Rev_2.pdf +├── [ 512] 01_CERTIFICATES-CERTIFIKÁTY +│   ├── [ 512] 01_SCHÖNENBERGER +│   │   ├── [ 512] 01_EN_original +│   │   │   └── [ 74K] 00_EC Declaration of Conformity_V2_EN.pdf +│   │   └── [ 512] 02_CZ +│   │   └── [ 76K] 00_Prohlášení o shodě ES_V2_CZ.pdf +│   └── [ 512] 02_KONFIGURATIONSBESCHREIBUNG_CONFIG-DESCRIPTION_POPIS KONFIG +│   ├── [ 512] 01_DE +│   │   └── [252K] Konfigurationsbeschreibung_für_CE_V2.pdf +│   ├── [ 512] 02_EN +│   │   └── [249K] Configuration Description_CE_V2_EN.pdf +│   └── [ 512] 03_CZ +│   └── [248K] Popis konfigurace_für_CE_V2_CZ.pdf +├── [ 512] 02_INSTRUCTIONS-NÁVOD_K_POUŽITÍ +│   ├── [ 512] 01_DE_original +│   │   ├── [5.5M] 01_BETRIEBSANLEITUNG_500592_V2.3.pdf +│   │   ├── [5.5M] 02_WARTUNGSANLEITUNG_500592_V2.2.pdf +│   │   ├── [7.4M] 03_FUNKTION_STEUERUNG_ILS_500592_V2.1.pdf +│   │   ├── [4.3M] 04_FUNKTION_STEUERUNG_OMNIFLO_500592_V1.0.pdf +│   │   └── [2.5M] 05_FUNKTION_STEUERUNG_LoadingLoop_500592_V2.2.pdf +│   ├── [ 512] 02_EN +│   │   └── [2.5M] 05_FUNKTION_CONTROL_LoadingLoop_500592_V2.2_EN.pdf +│   └── [ 512] 03_CZ +│   ├── [5.4M] 01_NÁVOD_K_POUŽÍVÁNÍ_500592_135776_V2.3_CZ.pdf +│   ├── [5.5M] 02_NÁVOD_K_ÚDRŽBĚ_500592_135776_V2.2_CZ.pdf +│   ├── [7.5M] 03_FUNKCE_ŘÍZENÍ_ILS_500592_V2.1_CZ.pdf +│   └── [4.8M] 04_ŘÍZENÍ_FUNKCÍ_OMNIFLO_500592_V1.0._CZ.pdf +├── [ 512] 03_SPARE_PARTS-NÁHRADNÍ_DÍLY +│   └── [3.3M] 01_SPARE_PARTS_500592_EN_V2.1.pdf +├── [ 512] 04_TECHNICAL_DOC-TECHNICKÉ_DOKUMENTY +│   ├── [ 512] 01_LAYOUT-DISPOZICE +│   │   ├── [ 512] 01_MECHANICAL-MECHANICKÝ +│   │   │   ├── [ 512] 01_EN_original +│   │   │   │   ├── [196M] 01_500592_10_5.14_Layout_V2.1_EN.pdf +│   │   │   │   └── [ 37M] 02_500592_10_5.14_Bereiche_V2.1_EN.pdf +│   │   │   └── [ 512] 02_CZ +│   │   │   ├── [196M] 01_500592_10_5.14_dispozice_V2.1_CZ.pdf +│   │   │   └── [ 36M] 02_500592_10_5.14_oblasti_V2.1_CZ.pdf +│   │   └── [ 512] 02_ELECTRICAL-ELEKTRIKA +│   │   ├── [ 512] 01_EN_original +│   │   │   ├── [3.0M] 01_ST500592_10_5.14_Omniflo-AMR+Highway-(ST6)-[EN].pdf +│   │   │   ├── [2.4M] 02_ST500592_10_5.14_Omniflo-Outbound+Highway-(ST7)-[EN].pdf +│   │   │   ├── [2.1M] 03_ST500592_10_5-14_ILS-AMR-ILS (ST4)-[EN].pdf +│   │   │   ├── [2.3M] 04_ST500592_10_5-14_ILS-F2H-Workstation (ST5)-[EN].pdf +│   │   │   ├── [1.8M] 05_ST500592_10_5-14_ILS-Highway (ST1-ST3)-[EN].pdf +│   │   │   ├── [4.6M] 06_ST500592_10_5-14_ILS-Inbound level 0 (ST1)-[EN].pdf +│   │   │   ├── [5.6M] 07_ST500592_10_5-14_ILS-Storage Level 1 (ST2)-[EN].pdf +│   │   │   └── [5.4M] 08_ST500592_10_5-14_ILS-Storage level 2 (ST3)-[EN].pdf +│   │   └── [ 512] 02_CZ +│   │   ├── [3.7M] 01_ST500592_10_5.14_Omniflo_CZ-AMR+Highway(ST6)-[CZ].pdf +│   │   ├── [2.4M] 02_ST500592_10_5.14_Omniflo_CZ-Outbound+Highway (ST7)-[CZ].pdf +│   │   ├── [5.4M] 03_ST500592_10_5-14_ILS_CZ-Storage level 2 (ST3)-[CZ].pdf +│   │   ├── [1.8M] 04_ST500592_10_5-14_ILS_CZ-AMR-ILS (ST4)-[CZ].pdf +│   │   ├── [1.9M] 05_ST500592_10_5-14_ILS_CZ-F2H-Workstation (ST5)-[CZ].pdf +│   │   ├── [1.4M] 06_ST500592_10_5-14_ILS_CZ-Highway (ST1-ST3)-[CZ].pdf +│   │   ├── [2.7M] 07_ST500592_10_5-14_ILS_CZ-Inbound level 0 (ST1)-[CZ].pdf +│   │   └── [6.0M] 08_ST500592_10_5-14_ILS_CZ-Storage Level 1 (ST2)-[CZ].pdf +│   ├── [ 512] 02_AREA_STOP_ZASTAVENI_OBLASTI +│   │   ├── [ 512] 02_CZ +│   │   │   └── [ 22M] 01_ES500592_10_5-14_ILS-OF_Zastavení oblasti_cpl-[CZ].pdf +│   │   └── [ 512] 1_EN_original +│   │   └── [ 23M] 01_ES500592_10_5-14_ILS-OF-Area-Stop-cpl.-[EN].pdf +│   ├── [ 512] 03_EMERGENCY_STOP-NOUZOVÉ_ZASTAVENÍ +│   │   ├── [ 512] 01_EMERGENCY_STOP_NOUZOVE_ZASTAVENI +│   │   │   ├── [ 512] 01_EN_original +│   │   │   │   └── [ 30M] 01_ES500592_10_5-14_ILS-OF_cpl.-E-Stop-[EN].pdf +│   │   │   └── [ 512] 02_CZ +│   │   │   └── [ 26M] 01_ES500592_10_5-14_ILS-OF-cpl._NV-[CZ].pdf +│   │   └── [ 512] 02_EMERGENCY_STOP_MATRIX +│   │   └── [837K] 01_Emergency_Stop_Matrix_V1.5_20260213-[EN].pdf +│   ├── [ 512] 04_DIAGRAMS-SCHÉMATA +│   │   ├── [ 512] 01_EN_original +│   │   │   ├── [ 32M] 01_Circuit diagrams_Controlarea 1_EN_Update_20260416.pdf +│   │   │   ├── [ 94M] 02_Circuit diagrams_Controlarea 2_EN_Update_20260416.pdf +│   │   │   ├── [ 98M] 03_Circuit diagrams_Controlarea 3_EN_Update_20260416.pdf +│   │   │   ├── [ 17M] 04_Circuit diagrams_Controlarea 4_EN_Update_20260416.pdf +│   │   │   ├── [ 18M] 05_Circuit diagrams_Controlarea 5_EN_Update_20260416.pdf +│   │   │   ├── [ 39M] 06_Circuit diagrams_Controlarea 6_EN_Update_20260416.pdf +│   │   │   └── [ 58M] 07_Circuit diagrams_Controlarea 7_EN_Update_20260416.pdf +│   │   └── [ 512] 02_CZ +│   │   ├── [ 32M] 01_Schéma zapojení_Ovládací oblast 1_CZ_Aktualizace_20260416.pdf +│   │   ├── [ 93M] 02_Schéma zapojení_Ovládací oblast 2_CZ_Aktualizace_20260416.pdf +│   │   ├── [ 96M] 03_Schéma zapojení_Ovládací oblast 3_CZ_Aktualizace_20260416.pdf +│   │   ├── [ 17M] 04_Schéma zapojení_Ovládací oblast 4_CZ_Aktualizace_20260416.pdf +│   │   ├── [ 18M] 05_Schéma zapojení_Ovládací oblast 5_CZ_Aktualizace_20260416.pdf +│   │   ├── [ 39M] 06_Schéma zapojení_Ovládací oblast 6_CZ_Aktualizace_20260416.pdf +│   │   └── [ 58M] 07_Schéma zapojení_Ovládací oblast 7_CZ_Aktualizace_20260416.pdf +│   └── [ 512] 05_REPORTS-ZPRÁVY +│   ├── [ 512] 01_Protokoll_Wiederanlauf_20260227 +│   │   └── [149K] 01_Schutz_gegen_automatischen_Wiederanlauf_V1.1.pdf +│   ├── [ 512] 02_Prüfung_Drehrichtung_Asyncronmotoren_20260227 +│   │   └── [153K] 01_Drehrichtung_Asynchronmotoren_20260420.pdf +│   └── [ 512] 03_Erprobung_Anzeige_Bediengeräte_20260227 +│   └── [161K] 01_Erprobung_Anzeige_Bediengeräte_20260420.pdf +├── [ 512] 05_SUPPLIER_DOC-DOKUMENTY_DODAVATELE +│   ├── [ 512] 01_MiTek_steel_structure_ocelová konstrukce +│   │   ├── [ 512] 01_As Built_drawing +│   │   │   └── [ 85M] MMS 2307-025 (As Built).pdf +│   │   ├── [ 512] 02_Structural_Calcs +│   │   │   └── [ 16M] Design_Calculation_Report_Rev_002_23.01.25_EN.pdf +│   │   ├── [ 512] 03_Instructions_Pokyny +│   │   │   ├── [ 512] 01_EN_original +│   │   │   │   ├── [ 90K] 01_Sestavení dat_CZ-Data_Assembly_EN.pdf +│   │   │   │   ├── [168K] 02_Operations and Maintenance_rev.1_EN.pdf +│   │   │   │   └── [241K] 03_marathon-prime-finish-tds_EN.pdf +│   │   │   └── [ 512] 02_CZ +│   │   │   └── [106K] 02_Provozní a údržbové postupy_rev.1_CZ.pdf +│   │   ├── [ 512] 04_MiTek DOP +│   │   │   ├── [240K] Declaration_Performance_Certificate_MMS-2307-025_EN.pdf +│   │   │   └── [204K] MMS-2307-025_Design_Compliance_Statement_EN.pdf +│   │   ├── [ 512] 05_Fabricator DOP +│   │   │   └── [228K] BRW0C96E613B534_000616.pdf +│   │   ├── [ 512] 06_Steel Test Cert +│   │   │   ├── [ 512] Lot 1 +│   │   │   │   ├── [4.4M] k093444 - Test Certs.pdf +│   │   │   │   ├── [1.2M] k093445 - Test Certs.pdf +│   │   │   │   ├── [718K] k093446 - Test Certs.pdf +│   │   │   │   ├── [220K] k093452 - Test Certs.pdf +│   │   │   │   ├── [192K] k093713 - Test Certs.pdf +│   │   │   │   └── [147K] k093771 - Test Certs.pdf +│   │   │   ├── [ 512] Lot 10 +│   │   │   │   ├── [1.2M] TC_7545925_001_2668830_001_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_002_2668830_002_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_003_2668830_003_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_004_2668830_004_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_005_2668830_005_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_006_2668830_006_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_007_2668830_007_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_008_2668830_008_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_009_2668830_009_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545925_010_2668830_010_0188358.pdf +│   │   │   │   └── [1.2M] TC_7545925_011_2668830_011_0188358.pdf +│   │   │   ├── [ 512] Lot 11 +│   │   │   │   ├── [1.2M] TC_7545926_001_2668831_001_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_002_2668831_002_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_003_2668831_003_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_004_2668831_004_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_005_2668831_005_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_006_2668831_006_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_007_2668831_007_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_008_2668831_008_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_009_2668831_009_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_010_2668831_010_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_011_2668831_011_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_012_2668831_012_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_013_2668831_013_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_014_2668831_014_0188358.pdf +│   │   │   │   ├── [1.2M] TC_7545926_015_2668831_015_0188358.pdf +│   │   │   │   └── [1.2M] TC_7545926_016_2668831_016_0188358.pdf +│   │   │   ├── [ 512] Lot 2 +│   │   │   │   ├── [2.1M] k093448 - Test Certs.pdf +│   │   │   │   ├── [1.5M] k093449 - Test Certs.pdf +│   │   │   │   ├── [2.0M] k093450 - Test Certs.pdf +│   │   │   │   ├── [220K] k093452 - Test Certs.pdf +│   │   │   │   ├── [189K] k093605 - Test Certs.pdf +│   │   │   │   ├── [192K] k093714 - Test Certs.pdf +│   │   │   │   └── [147K] k093771 - Test Certs.pdf +│   │   │   ├── [ 512] Lot 3 +│   │   │   │   ├── [ 29M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (10).zip +│   │   │   │   ├── [ 29M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (11).zip +│   │   │   │   ├── [ 31M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (12).zip +│   │   │   │   ├── [ 31M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (13).zip +│   │   │   │   ├── [ 29M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (14).zip +│   │   │   │   ├── [ 29M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (15).zip +│   │   │   │   ├── [ 28M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (16).zip +│   │   │   │   ├── [ 18M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (17).zip +│   │   │   │   └── [ 28M] SO-7537535-Test-Certs-03-12-2025-15-41-46 (9).zip +│   │   │   ├── [251M] Lot 3.zip +│   │   │   ├── [ 512] Lot 4 +│   │   │   │   ├── [ 30M] SO-7537547-Test-Certs-03-12-2025-16-04-28 (1).zip +│   │   │   │   ├── [ 30M] SO-7537547-Test-Certs-03-12-2025-16-04-28 (2).zip +│   │   │   │   ├── [ 30M] SO-7537547-Test-Certs-03-12-2025-16-04-28 (3).zip +│   │   │   │   ├── [ 29M] SO-7537547-Test-Certs-03-12-2025-16-04-28 (4).zip +│   │   │   │   ├── [ 30M] SO-7537547-Test-Certs-03-12-2025-16-04-28 (5).zip +│   │   │   │   └── [ 29M] SO-7537547-Test-Certs-03-12-2025-16-04-28.zip +│   │   │   ├── [ 512] Lot 5 +│   │   │   │   ├── [ 30M] SO-7538540-Test-Certs-10-12-2025-11-55-15 (1).zip +│   │   │   │   ├── [ 30M] SO-7538540-Test-Certs-10-12-2025-11-55-15 (2).zip +│   │   │   │   ├── [ 27M] SO-7538540-Test-Certs-10-12-2025-11-55-15 (3).zip +│   │   │   │   ├── [ 30M] SO-7538540-Test-Certs-10-12-2025-11-55-15 (4).zip +│   │   │   │   ├── [ 28M] SO-7538540-Test-Certs-10-12-2025-11-55-15 (5).zip +│   │   │   │   └── [ 29M] SO-7538540-Test-Certs-10-12-2025-11-55-15.zip +│   │   │   ├── [ 512] Lot 6 +│   │   │   │   ├── [ 29M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (1).zip +│   │   │   │   ├── [ 29M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (2).zip +│   │   │   │   ├── [ 28M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (3).zip +│   │   │   │   ├── [ 29M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (4).zip +│   │   │   │   ├── [ 30M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (5).zip +│   │   │   │   ├── [ 27M] SO-7539198-Test-Certs-10-12-2025-12-54-38 (6).zip +│   │   │   │   └── [ 28M] SO-7539198-Test-Certs-10-12-2025-12-54-38.zip +│   │   │   ├── [ 512] Lot 7 +│   │   │   │   ├── [ 29M] SO-7540495-Test-Certs-10-12-2025-14-22-20 (1).zip +│   │   │   │   ├── [ 28M] SO-7540495-Test-Certs-10-12-2025-14-22-20 (2).zip +│   │   │   │   ├── [ 30M] SO-7540495-Test-Certs-10-12-2025-14-22-20 (3).zip +│   │   │   │   ├── [ 18M] SO-7540495-Test-Certs-10-12-2025-14-22-20 (4).zip +│   │   │   │   └── [ 29M] SO-7540495-Test-Certs-10-12-2025-14-22-20.zip +│   │   │   ├── [ 512] Lot 8 +│   │   │   └── [ 512] Lot 9 +│   │   │   ├── [1.2M] TC_7545924_001_2668829_001_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_002_2668829_002_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_003_2668829_003_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_004_2668829_004_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_005_2668829_005_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_006_2668829_006_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_007_2668829_007_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_008_2668829_008_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_009_2668829_009_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_010_2668829_010_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_011_2668829_011_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_012_2668829_012_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_013_2668829_013_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_014_2668829_014_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_015_2668829_015_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_016_2668829_016_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_017_2668829_017_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_018_2668829_018_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_019_2668829_019_0188358.pdf +│   │   │   ├── [1.2M] TC_7545924_020_2668829_020_0188358.pdf +│   │   │   └── [1.2M] TC_7545924_021_2668829_021_0188358.pdf +│   │   ├── [ 512] 07_Joist Test Cert +│   │   │   ├── [ 512] Lot 13 +│   │   │   │   ├── [332K] Mitek 96119739_1 H3.309.pdf +│   │   │   │   ├── [ 21K] Mitek 96119739_2 H4.87.pdf +│   │   │   │   └── [ 21K] Mitek 96119739_3 H4.134.pdf +│   │   │   ├── [ 512] Lot 14 +│   │   │   │   ├── [ 89K] Mitek 96120095_1 H3.309.pdf +│   │   │   │   ├── [ 21K] Mitek 96120095_2 H4.87.pdf +│   │   │   │   └── [ 21K] Mitek 96120095_3 H4.134.pdf +│   │   │   ├── [ 512] Lot 15 +│   │   │   │   ├── [238K] Mitek 96120721_1 H3.309.pdf +│   │   │   │   ├── [ 21K] Mitek 96120721_2 H4.87.pdf +│   │   │   │   └── [238K] Mitek 96120721_3 H4.79.pdf +│   │   │   ├── [ 512] Lot 16 +│   │   │   │   ├── [237K] Mitek 96120781_1 H3.309.pdf +│   │   │   │   ├── [ 21K] Mitek 96120781_2 H4.87.pdf +│   │   │   │   └── [ 21K] Mitek 96120781_3 H4.80.pdf +│   │   │   └── [ 512] Lot 17 +│   │   │   ├── [237K] Mitek 96121082_1 H3.309.pdf +│   │   │   ├── [ 21K] Mitek 96121082_2 H4.87.pdf +│   │   │   └── [322K] Mitek 96121082_3 H4.134.pdf +│   │   ├── [ 512] 08_Bolt Test Cert +│   │   │   ├── [ 512] BAPP +│   │   │   │   ├── [201K] 35801 LOC.pdf +│   │   │   │   ├── [201K] 35935 LOC.pdf +│   │   │   │   ├── [ 98K] FPC Hollo-Bolt 2812-CPR-0208 18-12-2023.pdf +│   │   │   │   ├── [805K] M12X100 EN 15048 8.8SB HDG - Locked.pdf +│   │   │   │   ├── [1.2M] M12X40 EN15048 8.8SB HDG - Locked.pdf +│   │   │   │   ├── [1.2M] M12X45 EN15048 8.8SB HDG.pdf +│   │   │   │   ├── [182K] M12X50 EN 15048 8.8SB HDG.pdf +│   │   │   │   ├── [2.5M] M16X50 EN15048 8.8SB HDG.pdf +│   │   │   │   └── [804K] M20X50 EN 15048 8.8SB HDG.pdf +│   │   │   └── [ 512] Senco +│   │   │   └── [ 77K] RSM562BFL.pdf +│   │   ├── [ 512] 09_Paint Cert +│   │   │   └── [268K] Marathon Prime Finish- SDS.pdf +│   │   ├── [ 512] 10_Decking Data Sheet +│   │   │   └── [ 512] Berghoef +│   │   │   └── [513K] DOP_Mezzanine U7 Deluxe TG_EN.pdf +│   │   ├── [ 512] 11_RAMS +│   │   │   ├── [279K] MMS-2307-025-HS-1055 Method Statement Schonenberger Cheb.pdf +│   │   │   ├── [648K] MMS-HS-1495 Paint Touch Up Risk Assessment_V2.pdf +│   │   │   ├── [580K] MMS-HS-815 Decking Risk Assessment_V4.pdf +│   │   │   ├── [582K] MMS-HS-820 Mobilisation & De-Mobilisation Risk Assessment_V3.pdf +│   │   │   ├── [595K] MMS-HS-825 Setting Out Risk Assessment_V3.pdf +│   │   │   ├── [602K] MMS-HS-830 Site Deliveries Risk Assessment_V3.pdf +│   │   │   └── [619K] MMS-HS-835 Steel Erection Risk Assessment_V4.pdf +│   │   └── [ 512] 12_Handover +│   │   ├── [1.5M] 1_LOT 1-3 GOH FLOOR HANDOVER.pdf +│   │   ├── [1.5M] 2_LOT 4-5 GOH FLOOR HANDOVER.pdf +│   │   ├── [1.5M] 3_LOTS 6-9 GOH FLOOR HANDOVER..pdf +│   │   ├── [2.7M] 4_LOT 1-11 COMPLETION & FINAL HANDOVER ALL LOTS..pdf +│   │   └── [244K] MMS-2307-025_PM_Handover Check Sheet.pdf +│   ├── [ 512] 02_ABS_safety_line-bezpečnostní_lano +│   │   ├── [ 512] 01_Instructions_pokyny +│   │   │   ├── [ 512] 01_DE_original +│   │   │   │   ├── [4.2M] 01_ABS-Safety-Sicherheitshinweise-DE-EN.pdf +│   │   │   │   ├── [ 29M] 02_Montagedokumentation_DE.pdf +│   │   │   │   ├── [292K] 03_ABS_Basisinformationen_DE.pdf +│   │   │   │   └── [1006K] 04_Pruefbuch_InspectionLog_DE_EN.pdf +│   │   │   └── [ 512] 02_CZ +│   │   │   ├── [1.3M] 01_ABS-Safety-Bezpečnostní pokyny_CZ.pdf +│   │   │   ├── [7.7M] 02_Montážní dokumentace_CZ.pdf +│   │   │   ├── [214K] 03_ABS_Základní informace_CZ.pdf +│   │   │   └── [1.1M] 04_Kontrolní kniha_InspectionLog_CZ.pdf +│   │   ├── [ 512] 02_Drawing +│   │   │   └── [2.0M] 24007341P-25012501Z-3-Plan 4_DE.pdf +│   │   └── [ 512] 03_Certificates +│   │   ├── [584K] Acceptance_report_rev.2_DE.pdf +│   │   ├── [973K] Certification_construction-product_DE.pdf +│   │   └── [289K] Certification_installation_DE.pdf +│   ├── [ 512] 03_DTS_Loading_Loop +│   │   └── [966K] Zertifikat_3834_DE.pdf +│   ├── [ 512] 04_Leantechnik_Loading_Loop +│   │   ├── [ 512] 01_DE_original +│   │   │   ├── [4.8M] 01_Leantechnik_Anleitung_DE.pdf +│   │   │   └── [ 11M] 02_Leantechnik_Gesamtkatalog_DE.pdf +│   │   ├── [ 512] 02_EN +│   │   │   ├── [1.2M] 01_Leantechnik_instructions_EN.pdf +│   │   │   └── [8.7M] 02_Leantechnik-complete-catalogue_EN.pdf +│   │   └── [ 512] 03_CZ +│   │   └── [1.3M] 01_Leantechnik_pokyny_CZ.pdf +│   ├── [ 512] 06_Electrics_elektrika +│   │   ├── [ 512] 01_Instructions_pokyny +│   │   │   └── [ 512] 01_MOTORS_MOTORY +│   │   │   ├── [ 512] 01_SEW +│   │   │   │   ├── [ 512] 01_DE_für_Gesamtanlage +│   │   │   │   │   ├── [ 34M] 01_Motor_25957066_DE.pdf +│   │   │   │   │   ├── [3.5M] 01_Motor_info_30589452_DE.pdf +│   │   │   │   │   ├── [ 10M] 01_Motor_korr_26857987_DE.pdf +│   │   │   │   │   └── [ 21M] 02_Getriebe_31978061_DE.pdf +│   │   │   │   ├── [ 512] 02_EN_for_entire_system +│   │   │   │   │   ├── [ 34M] 01_Motor_25957074_EN.pdf +│   │   │   │   │   ├── [3.6M] 01_Motor_info_30589460_EN.pdf +│   │   │   │   │   ├── [10.0M] 01_Motor_rev_26861313_EN.pdf +│   │   │   │   │   └── [ 20M] 02_Gear_31978088_EN.pdf +│   │   │   │   ├── [ 512] 03_CZ_pro_celé_zařízení +│   │   │   │   │   ├── [ 34M] 01_Motory_25957228_CZ.pdf +│   │   │   │   │   ├── [5.6M] 01_Motory_info_30589606_CZ.pdf +│   │   │   │   │   ├── [8.0M] 01_Motory_oprava_26861453_CZ.pdf +│   │   │   │   │   └── [ 24M] 02_Převody_31988741_CZ.pdf +│   │   │   │   └── [ 512] 04_Movimot_Loading_Loop +│   │   │   │   ├── [ 512] 01_DE_original +│   │   │   │   │   ├── [4.5M] 03_Movi_26865513_DE.pdf +│   │   │   │   │   └── [9.6M] 03_Movimot_DBC_31985297_DE.pdf +│   │   │   │   ├── [ 512] 02_EN_original +│   │   │   │   │   ├── [6.6M] 03_Movi_C_info_26870770_EN.pdf +│   │   │   │   │   └── [9.7M] 03_Movimot_DBC_31985300_EN.pdf +│   │   │   │   └── [ 512] 03_CZ +│   │   │   │   ├── [2.2M] 03_Movi_C_info_26870770_CZ.pdf +│   │   │   │   └── [ 13M] 03_Movimot_DBC_31985300_CZ.pdf +│   │   │   └── [ 512] 02_NORD +│   │   │   ├── [ 512] 01_DE_original +│   │   │   │   ├── [3.9M] 01_Getriebe_LoadingLoopAntr_DE.pdf +│   │   │   │   └── [2.3M] 02_Motor_LoadingLoopAntr_DE.pdf +│   │   │   ├── [ 512] 02_EN_original +│   │   │   │   ├── [3.8M] 01_Gear_LoadingLoopDrive_EN.pdf +│   │   │   │   └── [2.5M] 02_Motor_LoadingLoopDrive_EN.pdf +│   │   │   └── [ 512] 03_CZ +│   │   │   ├── [3.9M] 01_Převod_LoadingLoopPohon_CZ.pdf +│   │   │   └── [2.4M] 02_Motory_LoadingLoopPohon_CZ.pdf +│   │   └── [ 512] 02_Data-sheets +│   │   ├── [ 512] 01_Siemens +│   │   │   ├── [436K] 3LD22031TL53_datasheet_en.pdf +│   │   │   ├── [1.1M] 3LD23051TL13_datasheet_en.pdf +│   │   │   ├── [407K] 3LD28040TK53_datasheet_en.pdf +│   │   │   ├── [539K] 3RA21101AA151BB4_datasheet_en.pdf +│   │   │   ├── [539K] 3RA21101DA151BB4_datasheet_en.pdf +│   │   │   ├── [393K] 3RH29112FA22_datasheet_en.pdf +│   │   │   ├── [484K] 3RT20151BB41_datasheet_en.pdf +│   │   │   ├── [460K] 3RT20162BB42_datasheet_en.pdf +│   │   │   ├── [485K] 3RT20272BB40_datasheet_en.pdf +│   │   │   ├── [278K] 3RT29261BB00_datasheet_en.pdf +│   │   │   ├── [311K] 3RV29255AB_datasheet_en.pdf +│   │   │   ├── [301K] 3SU10000AB100AA0_datasheet_en.pdf +│   │   │   ├── [304K] 3SU10000BB600AA0_datasheet_en.pdf +│   │   │   ├── [320K] 3SU10001GB200AA0_datasheet_en.pdf +│   │   │   ├── [312K] 3SU10010AB500AA0_datasheet_en.pdf +│   │   │   ├── [305K] 3SU10011BA300AA0_datasheet_en.pdf +│   │   │   ├── [312K] 3SU10016AA400AA0_datasheet_en.pdf +│   │   │   ├── [320K] 3SU10022BF100AA0_datasheet_en.pdf +│   │   │   ├── [316K] 3SU10022BM100AA0_datasheet_en.pdf +│   │   │   ├── [357K] 3SU11000AB103CA0_datasheet_en.pdf +│   │   │   ├── [370K] 3SU11001HB203CH0_datasheet_en.pdf +│   │   │   ├── [376K] 3SU11002BF603BA0_datasheet_en.pdf +│   │   │   ├── [389K] 3SU11004BF113BA0_datasheet_en.pdf +│   │   │   ├── [367K] 3SU11020AB403BA0_datasheet_en.pdf +│   │   │   ├── [366K] 3SU11020AB503BA0_datasheet_en.pdf +│   │   │   ├── [367K] 3SU11020AB703BA0_datasheet_en.pdf +│   │   │   ├── [351K] 3SU11026AA203AA0_datasheet_en.pdf +│   │   │   ├── [352K] 3SU11026AA303AA0_datasheet_en.pdf +│   │   │   ├── [320K] 3SU14002AA103BA0_datasheet_en.pdf +│   │   │   ├── [320K] 3SU14002AA103CA0_datasheet_en.pdf +│   │   │   ├── [322K] 3SU14012BB303AA0_datasheet_en.pdf +│   │   │   ├── [322K] 3SU14012BB403AA0_datasheet_en.pdf +│   │   │   ├── [323K] 3SU14012BB503AA0_datasheet_en.pdf +│   │   │   ├── [271K] 3UG46152CR20_datasheet_en.pdf +│   │   │   ├── [416K] 5ST3010_datasheet_en.pdf +│   │   │   ├── [647K] 5SU13246FA10_datasheet_en.pdf +│   │   │   ├── [424K] 5SY42107_datasheet_en.pdf +│   │   │   ├── [447K] 5SY43027_datasheet_en.pdf +│   │   │   ├── [447K] 5SY43067_datasheet_en.pdf +│   │   │   ├── [447K] 5SY43107_datasheet_en.pdf +│   │   │   ├── [446K] 5SY43167_datasheet_en.pdf +│   │   │   ├── [447K] 5SY43327_datasheet_en.pdf +│   │   │   ├── [420K] 5SY62107_datasheet_en.pdf +│   │   │   ├── [239K] 5SY63027_datasheet_en.pdf +│   │   │   ├── [443K] 5SY63067_datasheet_en.pdf +│   │   │   ├── [239K] 5SY63107_datasheet_en.pdf +│   │   │   ├── [239K] 5SY63167_datasheet_en.pdf +│   │   │   ├── [444K] 5SY63257_datasheet_en.pdf +│   │   │   ├── [443K] 5SY63327_datasheet_en.pdf +│   │   │   ├── [369K] 6AV21283UB060AX1_datasheet_en.pdf +│   │   │   ├── [289K] 6EP34368SB000AY0_datasheet_en.pdf +│   │   │   ├── [281K] 6EP34378SB000AY0_datasheet_en.pdf +│   │   │   ├── [306K] 6ES71316BF010AA0_datasheet_en.pdf +│   │   │   ├── [306K] 6ES71316BH010BA0_datasheet_en.pdf +│   │   │   ├── [332K] 6ES71326BH010BA0_datasheet_en.pdf +│   │   │   ├── [285K] 6ES71366BA010CA0_datasheet_en.pdf +│   │   │   ├── [295K] 6ES71366DB000CA0_datasheet_en.pdf +│   │   │   ├── [190K] 6ES71376BD000BA0_datasheet_en.pdf +│   │   │   ├── [303K] 6ES71556AU010BN0_datasheet_en.pdf +│   │   │   ├── [208K] 6ES71936BP200BA0_datasheet_en.pdf +│   │   │   ├── [289K] 6ES71936BP200DA0_datasheet_en.pdf +│   │   │   ├── [327K] 6ES75184FP000AB0_datasheet_en.pdf +│   │   │   ├── [ 94K] 6ES79548LL040AA0_datasheet_en.pdf +│   │   │   ├── [338K] 6GK51160BA002AC2_datasheet_en.pdf +│   │   │   └── [502K] Siemens_3SU_3275_05_202107032151368800.pdf +│   │   ├── [ 512] 02_Leuze +│   │   │   ├── [452K] Datasheet_HT25CI-4P-M12_50134231_en.pdf +│   │   │   ├── [388K] Datasheet_IS-218MM-4NO-8E0-S12_50109701_en.pdf +│   │   │   ├── [393K] Datasheet_IS-218MM-4NO-8N0-S12_50109697_en.pdf +│   │   │   ├── [1.2M] Datasheet_PAL_HRT25B_2500_50134386_en.pdf +│   │   │   ├── [ 74K] Leuze_50109697_CER_CE_IS218(x)_M_de_en_fr.pdf +│   │   │   ├── [ 74K] Leuze_50109701_CER_CE_IS218(x)_M_de_en_fr.pdf +│   │   │   ├── [ 54K] Leuze_50134231_CER_CE_SR25C_de_en_fr.pdf +│   │   │   └── [ 54K] Leuze_50142302_CER_CE_HRT(x)25B_de_en_fr.pdf +│   │   ├── [ 512] 03_Cognex +│   │   │   ├── [ 512] 01_EN_original +│   │   │   │   ├── [670K] Cognex_DM280_Scanner.pdf +│   │   │   │   ├── [6.8M] DM280_Reference_Manual_EN.pdf +│   │   │   │   ├── [8.7M] DM370_Series_Reference_Manual_EN.pdf +│   │   │   │   ├── [3.6M] Datasheet_DM280_en.pdf +│   │   │   │   └── [1.8M] Datasheet_DM370_en.pdf +│   │   │   └── [ 512] 02_CZ +│   │   │   ├── [670K] Cognex_DM280_Scanner.pdf +│   │   │   ├── [8.1M] DM280_Referenční příručka_CZ.pdf +│   │   │   └── [10.0M] DM370_Series_Referenční příručka_CZ.pdf +│   │   ├── [ 512] 04_Helukabel +│   │   │   ├── [1.3M] Datasheet_HELUKABEL_51796_EN.pdf +│   │   │   └── [198K] Datasheet_HELUKABEL_EN.pdf +│   │   ├── [ 512] 05_Werma +│   │   │   ├── [270K] Datasheet_26131070_en.pdf +│   │   │   ├── [419K] Datasheet_64081000_en.pdf +│   │   │   ├── [316K] Datasheet_64580075_en.pdf +│   │   │   ├── [258K] Datasheet_64711075_en.pdf +│   │   │   ├── [240K] Datasheet_64721075_en.pdf +│   │   │   ├── [275K] Datasheet_64731075_en.pdf +│   │   │   └── [277K] Datasheet_64751075_en.pdf +│   │   ├── [ 512] 06_Phoenix +│   │   │   ├── [363K] Datasheet_0804027_en.pdf +│   │   │   ├── [186K] Datasheet_2900299_en.pdf +│   │   │   └── [101K] Datasheet_2908215_en.pdf +│   │   ├── [ 512] 07_Murrelektronik +│   │   │   ├── [223K] Datasheet_58902_en.pdf +│   │   │   ├── [230K] Datasheet_58904_en.pdf +│   │   │   ├── [190K] Datasheet_8000-88590-0000000_en.pdf +│   │   │   └── [233K] Datasheet_9000-41034-0401000_en.pdf +│   │   ├── [ 512] 08_SEW +│   │   │   ├── [ 93K] Datasheet_RF57_DRN80MK4_ASE1_Z_en.pdf +│   │   │   ├── [ 88K] Datasheet_SA57_DRN80MK4_BE1_en.pdf +│   │   │   ├── [ 93K] Datasheet_SA57_DRN80MK4_en.pdf +│   │   │   ├── [ 88K] Datasheet_SA57_DRN80MK4_li_en.pdf +│   │   │   ├── [ 93K] Datasheet_SA57_DRN90S4BE2_en.pdf +│   │   │   ├── [ 88K] Datasheet_SF67_DRN80MK4_Z_en.pdf +│   │   │   ├── [ 90K] Datasheet_WAF59_R37_DRN71M4_BE05_en.pdf +│   │   │   └── [140K] SEW_DRNxx_23114118_Conformity_CE.pdf +│   │   ├── [ 512] 09_Rittal +│   │   │   ├── [102K] Datasheet_2500210-LED system light_en.pdf +│   │   │   ├── [ 75K] Datasheet_2500470-Door-Operated Switch_en.pdf +│   │   │   ├── [357K] Rittal_KX_declaration_of_conformity_20_1852.pdf +│   │   │   └── [621K] Rittal_VX_20_2659.pdf +│   │   ├── [ 512] 10_Nord +│   │   │   └── [ 99K] Datasheet_Motor_SK1SI40-IEC71-71RP_6 TF_IE3_en.pdf +│   │   ├── [ 512] 11_Pepperl+Fuchs +│   │   │   ├── [661K] Datasheet_NBN12-18GM35-E2-0_en.pdf +│   │   │   └── [1.2M] Pepperl_326163-0134_doc4844a.pdf +│   │   ├── [ 512] 12_Bailey +│   │   │   └── [577K] Datasheet_145502_en.pdf +│   │   ├── [ 512] 13_Sick +│   │   │   ├── [544K] Datasheet_WLL180T-F232_6039098_en.pdf +│   │   │   ├── [482K] Datasheet_WLL180T-M432_6039097_en.pdf +│   │   │   └── [156K] Sick_WLL180_Konformität.pdf +│   │   ├── [ 512] 14_Schmersal +│   │   │   └── [1.1M] Datasheet_Z4V7-336-11z-M20_en.pdf +│   │   ├── [ 512] 15_ifm electronic +│   │   │   └── [352K] Datasheet_JN2201-02_en.pdf +│   │   ├── [ 512] 16_Balluff +│   │   │   └── [214K] Balluff_BES M08MH1-PSC30B-S04G-101_CE_BES02PW_PV116429_sig.pdf +│   │   └── [ 512] 17_TRelectronic +│   │   └── [ 61K] TR-Electronic_CES582M-00004_TR-ECE-KE-DGB-0374-06.pdf +│   └── [ 512] 07_Pneumatic_pneumatika +│   ├── [ 512] 01_Aventics +│   │   ├── [ 512] 01_DE-EN_original +│   │   │   ├── [1.0M] 01_IU_Filter_DE_EN.pdf +│   │   │   ├── [999K] 02_IU_Pressure_regulator_valve_DE_EN.pdf +│   │   │   ├── [891K] 03_IU-3-2-shut_off_valve_DE_EN.pdf +│   │   │   └── [562K] 04_IU-Distributor_DE_EN.pdf +│   │   └── [ 512] 02_CZ +│   │   ├── [1.8M] 01_IU_Filter_filtr_CZ.pdf +│   │   ├── [2.1M] 02_IU_Press_reg_valve_Regulační ventil tlaku_CZ.pdf +│   │   ├── [2.0M] 03_IU-3-2-Uzavírací ventil_CZ.pdf +│   │   └── [1.3M] 04_IU-Distrib_Rozdělovač_CZ.pdf +│   └── [ 512] 02_ITS +│   └── [151K] KC-0167-00-01-4 Air distributor.pdf +├── [ 11M] FUNKTION_CONTROL_LoadingLoop_500592_V2.2_EN.docx +├── [ 11M] FUNKTION_STEUERUNG_LoadingLoop_500592_V2.2.docx +├── [ 35M] RB_Fortna_H+M_500592_EV12_20260311.docx +└── [2.7M] RB_Fortna_H+M_500592_V12_20260311.docx + +123 directories, 378 files diff --git a/doc/lib_skripte.md b/doc/lib_skripte.md new file mode 100644 index 0000000..fbbf3f1 --- /dev/null +++ b/doc/lib_skripte.md @@ -0,0 +1,99 @@ +# Skripte in lib/ + +Übersicht über die Python-Hilfsskripte zur Datenaufbereitung (Excel/PDF/Word -> JSON/CSV/RST). +Alle Skripte werden über `bin/*.bat` bzw. `bin/*.sh` aufgerufen und nutzen die +Umgebungsvariablen aus `bin/setenv.*` (`PROJECT`, `PV_EXAMPLES`, `PV_DATA`, ...). + +## jsonFromXlsx.py + +Baut aus der Positionsspalte einer Excel-Stückliste (`Stk.-Liste SOLL` / `Stk.-Liste IST`) +eine hierarchische JSON-Baumstruktur. Positionspfade wie `10/5/1` bestimmen die Schachtelung, +als Schlüssel dient die Teilenummer (bei Duplikaten ergänzt um Bezeichnung). Sonderfälle: + +- **Längenabhängige Teile** (`LAENGENABHAENGIG`-Liste, z. B. Kettenprofile): Menge wird als + `laenge` aufsummiert statt gezählt. +- **9-stellige Sivas-Nummern**: doppelte Vorkommen werden zu `{"anzahl": n, "beschreibung": ...}` + aggregiert. + +``` +python jsonFromXlsx.py --filename Fortna --tab soll +python jsonFromXlsx.py --all +``` + +Eingabe: `examples/*.xlsx` · Ausgabe: `results/_.json` + +## extract_ids.py + +Liest die von `jsonFromXlsx.py` erzeugten JSON-Bäume und sammelt 9-stellige Sivas-Nummern, +die genau eine Ebene über den Blättern liegen (d. h. Baugruppen, deren Kinder alle Blätter sind). +Kann Ergebnisse mehrerer Projekte zu einer Summary zusammenfassen. + +``` +python extract_ids.py --filename KA135776 +python extract_ids.py --all --sum2json +python extract_ids.py --all --sum2txt +``` + +Eingabe: `results/*_.json` · Ausgabe: `results/_ids_.txt` bzw. +`results/summary_ids_.{json,txt}` + +## project_from_xlsx.py + +Durchsucht rekursiv Unterordner in `PV_EXAMPLES`, liest je Ordner die erste `.xlsx` aus dem +Reiter `Stk.-Liste IST` (Position/Teilenummer/Bezeichnung/Menge) und filtert auf Positionspfade +mit 3 oder 4 Hierarchieebenen. Optional werden pro Ordner JSON/CSV/XLSX-Ergebnisdateien erzeugt; +die XLSX-Variante aggregiert Mengen je Teilenummer und markiert bekannte Produktnummern aus +`cfg/product_numbers.cfg`. + +``` +python project_from_xlsx.py --example-dir KA135776 --2csv +python project_from_xlsx.py --2json --2xlsx +``` + +## extract_products.py + +Extrahiert Bilder und Fließtext aus Produktprogramm-PDFs (`data/Produktprogramm/*.pdf`) mittels +PyMuPDF. Bilder werden nach Zielordner (Omniflo/ILS/CPC/Gerüst/Trolley, siehe `PDF_TARGETS`) +sortiert und per MD5 dedupliziert. Text wird anhand von Schriftgröße, Positions- und +Nummerierungsheuristiken (inkl. TOC-Abgleich) in `.de.rst`-Dateien mit Überschriftenhierarchie +umgewandelt. + +``` +python extract_products.py --data-dir data +``` + +Ausgabe: `data/Produktprogramm/images//` und `data/Produktprogramm/.de.rst` + +## collect_partnumbers.py + +Sammelt 9-stellige Teilenummern aus den von `extract_products.py` erzeugten RST-Dateien, +inklusive Bereichsnotation (`834372007-011`) und OCR-bedingt verklebter Zahlen. Gleicht die +Nummern gegen eine Excel-Stückliste (`data/KA*.xlsx`, Reiter `Stk.-Liste SOLL`) ab und schreibt +eine CSV mit Fundstelle, Kapitel und Bezeichnung. + +``` +python collect_partnumbers.py --data-dir data +``` + +Ausgabe: `data/Produktprogramm/teilenummern.csv` + +## convert_to_rst.py + +Konvertiert Word-Dokumente (`.docx`, via `mammoth`) und Word-erstellte PDFs (via `pdfminer`, +erkannt über PDF-Metadaten Creator/Producer) nach `.rst`. Ordnet Quelldateien anhand des +Dateinamens automatisch einem Zielkapitel unter `chapters/` zu (`CHAPTER_MAPPING`), erkennt +benutzerdefinierte Word-Formatvorlagen (`Ü1`-`Ü4` etc.) als Überschriftenebenen und speichert +eingebettete Bilder neben der Ziel-RST. + +``` +python convert_to_rst.py chapters --dry-run +python convert_to_rst.py chapters --lang de +``` + +## Typischer Ablauf + +1. `jsonFromXlsx.py --all` – Stücklisten als JSON-Bäume aufbereiten +2. `extract_ids.py --all --sum2json` – Sivas-Nummern projektübergreifend sammeln +3. `extract_products.py` – Produktprogramm-PDFs in Bilder + RST zerlegen +4. `collect_partnumbers.py` – Teilenummern aus den RSTs mit der Stückliste abgleichen +5. `convert_to_rst.py` – vorhandene Word/PDF-Dokumentation in die Kapitelstruktur überführen diff --git a/doc/plan_fortna_extraktion.md b/doc/plan_fortna_extraktion.md new file mode 100644 index 0000000..c9d21d1 --- /dev/null +++ b/doc/plan_fortna_extraktion.md @@ -0,0 +1,240 @@ +# Konzept: Fortna-Extraktion nach chapters/ + Projektordner fortna/ + +Status: **Umgesetzt** (erster vollständiger Durchlauf inkl. PDF-Build). Antworten aus der +Rückfragerunde sind eingearbeitet (siehe Abschnitt "Entscheidungen"). Der tatsächliche +Ist-Stand ist unten unter "Umsetzung / Ist-Stand" dokumentiert. + +## Umsetzung / Ist-Stand + +Implementiert wurden vier Python-Skripte in `lib/` mit `bin/`-Wrappern (`.bat` + `.sh`): + +| Skript | Zweck | Wrapper | +|---|---|---| +| `lib/fortna_inventory.py` | Scannt das Rohmaterial, ordnet jede Datei Sprache + Zielkapitel + Aktion zu, schreibt `results/fortna_inventory.json`. | `bin/fortna_inventory.*` | +| `lib/fortna_convert_chapters.py` | Volltext+Bilder für die 5 DE-Hauptanleitungen, Verzeichnislisten (`index.de.rst`) für alle übrigen Kapitel. `--dry-run` (Default) / `--apply`. | `bin/fortna_convert_chapters.*` | +| `lib/fortna_init_project.py` | Legt `projekte/fortna/` an (`anlage.json`, `overrides/`, `images/`, `README.md`). | `bin/fortna_init_project.*` | +| `lib/build_index.py` | Generiert `index.rst` mit toctrees aus allen `chapters/**/*.de.rst`. | (in `build_pdf.*`) | +| `bin/build_pdf.*` | Kompletter PDF-Build: index → `sphinx-build -b latex` → `latexmk -lualatex`. | — | + +Ergebnis des ersten Laufs: + +- 5 Hauptanleitungen volltextig konvertiert (Betriebs-/Wartungsanleitung, Funktionsbeschreibung + ILS/OMNIFLO/LoadingLoop), zusammen ~13.500 Textblöcke + mehrere Hundert entduplizierte Bilder. +- 9 Verzeichnislisten erzeugt (Zertifikate, Lieferantendoku nach Gewerk, Notstopp, Prüfberichte). +- `projekte/fortna/` mit `anlage.json`-Platzhalter angelegt. +- **Vollständiger PDF-Build erfolgreich**: `_build/latex/Fortna_Dokumentation_DE.pdf`, ~670 Seiten, + `latexmk`-Exit 0, keine LaTeX-Fehler. + +### Umgebung / Build-Voraussetzungen (bereits erfüllt) + +- Sphinx 9.1.0 + myst-parser 5.1.0 im `.venv` (ergänzt in `requirements.txt`). +- LaTeX: MiKTeX unter `C:\Program Files\MiKTeX\` (lualatex/latexmk vorhanden). +- `conf.py` erzeugt: LaTeX-Engine **lualatex**, **babel** statt polyglossia + (polyglossia 2.13 wirft unter lualatex einen bidi-Fehler), **`iconpackage=none`** + (FontAwesome7 in MiKTeX bricht unter lualatex ab). Beides in `conf.py` kommentiert. + +### Behobene Extraktionsprobleme + +- **Symbolfont-Zeichen** (Wingdings/Symbol Private-Use-Area, z. B. Aufzählungs-Bullet U+F0B7): + in `extract_products.py` via `clean_pua()` auf echte Unicode-Zeichen (•, →, ✓) gemappt, + restliche PUA-Zeichen entfernt. Vorher erschienen leere Kästchen im PDF. + +### Bekannte Restpunkte (nicht Teil des Auftrags-Scopes) + +- Zwei **Fremd-Altdateien** aus früheren `convert_to_rst.py`-Testläufen sind noch im Baum und + vom Build ausgeschlossen (`conf.py` + `build_index.py` SKIP-Liste): + `02_sicherheit/risikobewertung/rb_fortna_...de.rst` (unvollständiger Bildbestand) und + `00_inhaltsverzeichnis/contens_obsah_...de.rst` (keine RST-Überschrift). Zwei weitere + Altdateien in `10_anhang/` (Motor/Getriebe) sind noch im toctree und zeigen typische + Extraktionsartefakte – bei Bedarf mit `fortna_convert_chapters.py` sauber neu erzeugen. +- **Tabellen** (z. B. Änderungshistorie) werden weiterhin als Einzelzeilen extrahiert, nicht als + RST-Tabelle rekonstruiert (wie im Konzept angekündigt). + +--- + + +## Ausgangslage + +- Reales Fortna-Rohmaterial liegt in `c:\10-Develop\projekte\Fortna\` (zusätzliches + Arbeitsverzeichnis dieser Session, **nicht** im Repo). Struktur wie in `doc/Fortna.txt` + dokumentiert (00_CONTENTS_NOTES … 05_SUPPLIER_DOC), Versionsstände dort sind aktueller als in + `doc/Fortna.txt` (z. B. Betriebsanleitung V2.4 statt V2.3, Wartungsanleitung V2.3, ILS V2.2, + OMNIFLO V1.1, LoadingLoop V2.3). `doc/Fortna.txt`/`doc/Fortna-size.txt` sind nur noch als + Struktur-Referenz zu verstehen, nicht als Quelle für den Extraktionslauf. +- `chapters/` (Base-Repo-Gerüst) existiert bereits vollständig mit `.gitkeep`-Platzhaltern gemäß + `doc/kapitelstruktur.md`. `06_steuerung/loading_loop/` wurde testweise mit `convert_to_rst.py` + aus der LoadingLoop-Word-Datei befüllt (2 RST + 74 Bilder). Qualität ist noch unzureichend: + Kopf-/Fußzeilen (Firmenadresse, Kontaktdaten) landen als normale Absätze im Fließtext, + Tabellen (Änderungshistorie) werden zu einzelnen Zeilen ohne Tabellenstruktur zerlegt. Das ist + ein bekanntes Problem der aktuellen `_html_to_blocks`-Logik in `convert_to_rst.py` und wird im + Zuge dieser Arbeit mit adressiert (siehe Abschnitt "Bekannte Schwachstellen"). +- Vorhandene Skripte in `lib/` (siehe `doc/lib_skripte.md`): + - `convert_to_rst.py`: `.docx`/Word-PDF → RST, erkennt Zielkapitel nur anhand Dateiname-Pattern + im Root eines Ordners, kein Verständnis für den verschachtelten Fortna-Sprachordner-Baum. + - `extract_products.py`: Produktprogramm-PDFs → Bilder + RST (bereits fertig genutzt für + `data/Produktprogramm`). + - `jsonFromXlsx.py`, `extract_ids.py`, `project_from_xlsx.py`: Stücklisten-Auswertung, für + diese Aufgabe nicht direkt relevant. +- Kein Skript kennt bisher die komplette Fortna-Ordnerstruktur (Lieferantendoku-Baum, Layouts, + Schaltpläne, Prüfberichte) und keines erzeugt den projektspezifischen Ordner (`anlage.json`, + `overrides/`, `images/`) gemäß `doc/Kernkonzepte.md`. + +## Entscheidungen (aus Rückfragen) + +| Frage | Entscheidung | +|---|---| +| Lieferantendoku (~250 PDFs: SEW, Siemens, Leuze, Cognex, MiTek, Aventics, …) | **Nur Verzeichnisliste**, keine Kopie. RST-Referenztabelle mit Pfaden auf das Originalverzeichnis. | +| Sonstige PDFs (Zertifikate, Konfigurationsbeschreibung, Layouts, Schaltpläne, Notstopp/Bereichsstopp, Prüfberichte, Ersatzteilliste) | Gleiche Behandlung wie Lieferantendoku: **nur Verzeichnisliste**, keine Kopie nach `chapters/`. | +| Volltext-Konvertierung (Text + Bilder → RST) | **Nur die 5 Hauptanleitungen**: Betriebsanleitung, Wartungsanleitung, Funktionsbeschreibung ILS, Funktionsbeschreibung OMNIFLO, Funktionsbeschreibung LoadingLoop. | +| Projektordner-Ort | `projekte/fortna/` **innerhalb dieses Repos** (docu_build), pragmatisch für jetzt; spätere Auslagerung in eigenes Repo bleibt möglich. | +| Sprachumfang | **Nur Deutsch** in diesem Durchlauf. Namenskonvention `{name}.de.rst` einhalten, EN/CZ als späterer zweiter Durchlauf vorgesehen. | +| Quelle | `c:\10-Develop\projekte\Fortna\` (aktueller Stand), nicht `doc/Fortna.txt`. | +| Umgang mit bestehendem `chapters/06_steuerung/loading_loop/` | **Überschreiben/neu erzeugen** – konsistent aus einem Guss statt Altbestand stehen lassen. | + +## Zielstruktur (Ergebnis nach Lauf) + +```text +chapters/ + 01_zertifikate/ + konformitaetserklaerung/index.de.rst # Verzeichnisliste -> Originalpfade + konfigurationsbeschreibung/index.de.rst + 02_sicherheit/ # unverändert (kein Fortna-Rohmaterial 1:1 zuordenbar) + 03_anlagenbeschreibung/ # unverändert (Modul-Mechanismus, nicht Teil dieser Aufgabe) + 04_betrieb/betriebsanleitung/ + betriebsanleitung_500592_135776_v2_4.de.rst # Volltext + Bilder + images/img_001.png ... + 05_wartung/wartungsanleitung/ + wartungsanleitung_500592_v2_3.de.rst # Volltext + Bilder + images/... + 06_steuerung/ + ils/funktion_steuerung_ils_500592_v2_2.de.rst # Volltext + Bilder + omniflo/funktion_steuerung_omniflo_500592_v1_1.de.rst + loading_loop/funktion_steuerung_loadingloop_500592_v2_3.de.rst # neu erzeugt, ersetzt Altbestand + 07_ersatzteile/index.de.rst # Verzeichnisliste -> Originalpfad SPARE_PARTS PDF + 08_lieferantendoku/ + antriebe/index.de.rst # Verzeichnisliste SEW, NORD, Leantechnik, DTS + elektrik/index.de.rst # Siemens, Leuze, Cognex, Werma, ... + pneumatik/index.de.rst # Aventics, ITS + stahlbau/index.de.rst # MiTek (As-Built, Statik, Zertifikate, ...) + sicherheitseinrichtungen/index.de.rst # ABS Sicherheitslinie + 09_technische_unterlagen/ + layouts/mechanisch/index.de.rst + layouts/elektrisch/index.de.rst + schaltplaene/index.de.rst + notstopp/index.de.rst + bereichsstopp/index.de.rst + 10_anhang/ + pruefberichte/index.de.rst + +projekte/ + fortna/ + anlage.json # Projektmetadaten (Platzhalter/Vorschlag, manuell nachzuschärfen) + overrides/ # leer angelegt, Struktur gemäß Kernkonzepte.md + images/ # leer angelegt (projektspezifische Bilder, Kundenlogo etc.) + README.md # kurze Erläuterung was hier reinkommt +``` + +Jede `index.de.rst`-Verzeichnisliste enthält eine Tabelle: Dateiname, Hersteller/Kategorie +(aus Ordnername abgeleitet), Originalpfad (relativ zu einer konfigurierbaren Basis, z. B. +`PV_FORTNA_SOURCE`), Dateigröße. Kein PDF wird kopiert oder eingebettet – das hält den +Kapitelbaum schlank und vermeidet Redundanz zum Rohmaterial, das ohnehin an anderer Stelle +(Nextcloud/Projektordner) verwaltet wird. + +## Geplante Skripte (in `lib/`) + +### 1. `fortna_inventory.py` (neu) + +Scannt `c:\10-Develop\projekte\Fortna\` rekursiv und baut ein Inventar (JSON) aller Dateien mit: +Pfad, Größe, erkannte Sprache (aus Ordnername: `*_DE*`/`*_EN*`/`*_CZ*`/`*original*` → de), +vermutetem Fortna-Kapitel (oberste zwei Ebenen) und vermutetem Ziel-Kapitel gemäß Mapping-Tabelle +aus `doc/kapitelstruktur.md`. Dient als Kontrollartefakt (`results/fortna_inventory.json`), auf +dessen Basis sich beide folgenden Skripte konfigurieren bzw. dessen Output sich manuell prüfen +lässt, bevor tatsächlich geschrieben wird (`--dry-run` Default, `--apply` zum Ausführen). + +Grund für ein separates Inventarskript: 387 Dateien über 6 Top-Level-Ordner mit inkonsistenter +Sprachordner-Benennung (`01_DE_original`, `01_EN_original`, `1_EN_original` [Tippfehler im +Quellbaum], `02_CZ`, `03_CZ_pro_celé_zařízení`, …) – ein Trockenlauf mit Report ist nötig, um +Mapping-Lücken zu erkennen, bevor Dateien verschoben/verarbeitet werden. + +### 2. `fortna_convert_chapters.py` (neu) + +Nutzt das Inventar aus Schritt 1 (oder scannt bei Bedarf direkt) und führt zwei Aktionsarten aus: + +- **Volltext-Konvertierung** für die 5 Hauptanleitungen: ruft die vorhandene Konvertierungslogik + aus `convert_to_rst.py` wieder, aber mit expliziter Quelldatei-Liste (kein Verzeichnis-Scan) + und fest zugeordnetem Zielkapitel (aus der Tabelle unten), statt der generischen + `CHAPTER_MAPPING`-Heuristik. Überschreibt bestehende Ziel-RSTs/Bilder vollständig + (Ordnerinhalt vorher leeren, dann neu schreiben). +- **Verzeichnislisten-Generierung** für alle übrigen Kapitel (Zertifikate, Layouts, Schaltpläne, + Notstopp/Bereichsstopp, Prüfberichte, Ersatzteile, Lieferantendoku): läuft über die im + Inventar erkannten Dateien je Zielordner und rendert eine `index.de.rst` mit + RST-list-table (Dateiname, Pfad, Größe). + +Fest zugeordnete Hauptanleitungen (Quelle → Ziel): + +| Quelldatei (DE, aktuellster Stand in `projekte/Fortna`) | Zielkapitel | +|---|---| +| `02_INSTRUCTIONS.../01_DE_original/01_BETRIEBSANLEITUNG_500592_135776_V2.4.pdf` | `04_betrieb/betriebsanleitung/` | +| `02_INSTRUCTIONS.../01_DE_original/02_WARTUNGSANLEITUNG_500592_V2.3.pdf` | `05_wartung/wartungsanleitung/` | +| `02_INSTRUCTIONS.../01_DE_original/03_FUNKTION_STEUERUNG_ILS_500592_V2.2.pdf` | `06_steuerung/ils/` | +| `02_INSTRUCTIONS.../01_DE_original/04_FUNKTION_STEUERUNG_OMNIFLO_500592_V1.1.pdf` | `06_steuerung/omniflo/` | +| `02_INSTRUCTIONS.../01_DE_original/05_FUNKTION_STEUERUNG_LOADING_LOOP_500592_V2.3.pdf` | `06_steuerung/loading_loop/` (ersetzt Altbestand) | + +Alle fünf sind PDFs (keine `.docx` im Quellordner mehr vorhanden), daher wird der +`is_word_pdf()`-Pfad aus `convert_to_rst.py` verwendet (pdfminer-Textextraktion). Die extra +`extract_products.py`-Textheuristik (Schriftgrößen-basierte Überschriftenerkennung, +Kopf-/Fußzeilen-Ausschluss nach Y-Position) ist für Word-erstellte PDFs wie diese *robuster* als +die aktuelle `convert_to_rst.py`-Textheuristik (siehe Bekannte Schwachstellen) – Vorschlag: die +Extraktionsroutine aus `extract_products.py` (`extract_text_to_rst`, `_is_noise`, +`extract_images`) wiederverwenden/verallgemeinern statt der schwächeren `pdfminer`-Variante aus +`convert_to_rst.py`. + +### 3. `fortna_init_project.py` (neu) + +Legt `projekte/fortna/` an: `anlage.json` (Platzhalter mit Projektnummer 500592/KA135776, +Sprache `["de"]`, `normen_region` offen als TODO), leere `overrides/`- und `images/`-Ordner mit +`.gitkeep`, sowie ein kurzes `README.md`. Einmaliger Lauf, kein Datei-Scan nötig – im +Wesentlichen ein Gerüst-Generator. + +## Bekannte Schwachstellen der bisherigen Konvertierung (zu beheben) + +Der Altbestand in `chapters/06_steuerung/loading_loop/` zeigt zwei konkrete Probleme der +aktuellen `convert_to_rst.py`-Logik, die für die 5 Hauptanleitungen behoben werden sollten: + +1. **Kopf-/Fußzeilen als Fließtext**: Firmenadresse, Kontaktdaten, Projektnummer erscheinen als + normale Absätze mitten im Dokument (weil `pdfminer.high_level.extract_text` keine + Positionsinformation liefert, mit der man Kopf-/Fußzeilen herausfiltern könnte). + `extract_products.py` löst das bereits über `page.get_text("dict")` mit Y-Positions-Filter + (`bbox[1] < 100` / `bbox[3] > page_height - 50`) – diese Technik übernehmen. +2. **Tabellen zerfallen in Einzelzeilen**: Die Änderungshistorie-Tabelle wird zu einer Folge + einzelner Absätze ohne Tabellenstruktur. Vorschlag für ersten Wurf: Tabellen nicht als + RST-Table rekonstruieren (zu fehleranfällig aus PDF-Text), sondern als eingerückten + `.. code-block::` oder einfache Aufzählung kennzeichnen, mit `.. todo:: Tabelle manuell + nachbearbeiten`-Marker – ehrlich statt falsch-schön. + +## Offene Punkte / Annahmen, die ich getroffen habe + +- Ich gehe davon aus, dass `PV_FORTNA_SOURCE` (oder ein `--source-dir`-Argument) als neue + Umgebungsvariable/Parameter eingeführt wird, die auf `c:\10-Develop\projekte\Fortna\` zeigt, + da dieser Pfad außerhalb des Repos liegt und nicht über `PV_DATA`/`PV_EXAMPLES` erreichbar ist. +- `02_sicherheit/` und `03_anlagenbeschreibung/` bleiben in diesem Durchlauf unangetastet – es + gibt keine 1:1-Quelle dafür in `projekte/Fortna/` (Risikobewertung liegt als separate + `.docx`-Dateien direkt im Repo-Root von `chapters/`, siehe Git-Status, nicht Teil dieser + Aufgabe). +- Die generierten `index.de.rst`-Verzeichnislisten verweisen auf absolute oder + repo-relative-konfigurierbare Pfade zum Rohmaterial – das bedeutet, das Zielsystem (Sphinx) + bekommt keine eigenständig lauffähige Doku ohne Zugriff auf `projekte/Fortna/`. Das ist so + gewollt gemäß der Entscheidung "nur Verzeichnisliste", sollte aber vor dem produktiven + PDF-Build nochmal bewusst bestätigt werden (spätestens beim ersten `sphinx-build`-Versuch + fehlen dann die Downloads/Links). + +## Nächste Schritte + +1. Dieses Konzept abstimmen (insb. Umgang mit `02_sicherheit`/Risikobewertung-Docx im Root von + `chapters/`, sowie die Pfad-Referenzierung in den Verzeichnislisten). +2. `fortna_inventory.py` implementieren und Trockenlauf-Report gegen `projekte/Fortna/` prüfen. +3. `fortna_convert_chapters.py` implementieren (inkl. Wiederverwendung der + `extract_products.py`-Textextraktion), auf 5 Hauptanleitungen anwenden, Ergebnis stichprobenhaft + gegenlesen. +4. `fortna_init_project.py` implementieren, `projekte/fortna/` anlegen. +5. `bin/`-Wrapper-Skripte (`.bat`/`.sh`) für alle drei ergänzen, analog bestehender Konvention. diff --git a/lib/build_index.py b/lib/build_index.py new file mode 100644 index 0000000..c8906d8 --- /dev/null +++ b/lib/build_index.py @@ -0,0 +1,95 @@ +"""Generiert index.rst mit toctrees aus allen vorhandenen chapters/**/*.de.rst. + +Baut pro Top-Level-Kapitel (00..10) einen Abschnitt mit einer toctree-Direktive, +die alle deutschsprachigen RST-Bausteine dieses Kapitels einbindet. So bleibt der +Build robust gegenueber neuen/entfernten Bausteinen (kein Handpflegen der toctrees). + +Verwendung: + python build_index.py +""" + +from __future__ import annotations + +import logging +import os +import re +from pathlib import Path + +log = logging.getLogger(__name__) + +# Defekte Altbestaende aus fruehen Testlaeufen, die nicht in die toctree +# aufgenommen werden (siehe exclude_patterns in conf.py). Relativ zu chapters/. +SKIP = { + "02_sicherheit/risikobewertung/rb_fortna_h_m_500592_ev12_20260311.de.rst", + "00_inhaltsverzeichnis/contens_obsah_500592_135776_v2_1_en_cz.de.rst", +} + +# Lesbare Titel je Top-Level-Kapitelordner +CHAPTER_TITLES = { + "00_inhaltsverzeichnis": "Inhaltsverzeichnis", + "01_zertifikate": "Zertifikate", + "02_sicherheit": "Sicherheit", + "03_anlagenbeschreibung": "Anlagenbeschreibung", + "04_betrieb": "Betrieb", + "05_wartung": "Wartung", + "06_steuerung": "Steuerung", + "07_ersatzteile": "Ersatzteile", + "08_lieferantendoku": "Lieferantendokumentation", + "09_technische_unterlagen": "Technische Unterlagen", + "10_anhang": "Anhang", +} + + +def rst_underline(text: str, char: str) -> str: + return f"{text}\n{char * max(len(text), 3)}\n" + + +def main() -> None: + logging.basicConfig(level=logging.INFO, format="%(levelname)-7s %(message)s") + + project_root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent)) + chapters = project_root / "chapters" + + # RST-Dokumentnamen (ohne .rst-Endung, posix, relativ zum project_root) + # je Top-Level-Kapitel einsammeln. + by_chapter: dict[str, list[str]] = {} + for rst in sorted(chapters.rglob("*.de.rst")): + rel = rst.relative_to(project_root) + if rst.relative_to(chapters).as_posix() in SKIP: + continue + top = rel.parts[1] # chapters//... + docname = rel.as_posix()[: -len(".rst")] # Sphinx-Docname ohne Endung + by_chapter.setdefault(top, []).append(docname) + + lines: list[str] = [] + lines.append(".. Automatisch generiert von lib/build_index.py - nicht manuell editieren.") + lines.append("") + title = "Anlagendokumentation Fortna" + lines.append("#" * len(title)) + lines.append(title) + lines.append("#" * len(title)) + lines.append("") + lines.append("Projekt 500592 / KA 135776 - H&M Cheb (Eger), CZ") + lines.append("") + + total = 0 + for top in sorted(by_chapter): + docs = by_chapter[top] + total += len(docs) + heading = CHAPTER_TITLES.get(top, top) + lines.append("") + lines.append(rst_underline(heading, "*")) + lines.append(".. toctree::") + lines.append(" :maxdepth: 2") + lines.append("") + for doc in sorted(docs): + lines.append(f" {doc}") + lines.append("") + + index_path = project_root / "index.rst" + index_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + log.info("index.rst geschrieben: %d Kapitel, %d Bausteine", len(by_chapter), total) + + +if __name__ == "__main__": + main() diff --git a/lib/extract_products.py b/lib/extract_products.py index dbd719c..e2b754c 100644 --- a/lib/extract_products.py +++ b/lib/extract_products.py @@ -29,6 +29,36 @@ PDF_TARGETS: list[tuple[str, str]] = [ # rST Ueberschriften-Zeichen nach Level RST_HEADING_CHARS = {1: "=", 2: "-", 3: "~", 4: "^"} +# Private-Use-Area-Zeichen aus Word-Symbolfonts (Wingdings/Symbol) auf sinnvolle +# Unicode-Aequivalente abbilden. Diese Zeichen haben in Standard-LaTeX-Fonts keine +# Glyphe (erscheinen sonst als leere Kaestchen im PDF). +PUA_REPLACEMENTS = { + "": "•", # bullet -> • + "": "•", # square bullet -> • + "": "•", # bullet variante + "": "→", # rightwards arrow -> → + "": "→", # rightwards arrow -> → + "": "←", # leftwards arrow -> ← + "": "✓", # checkmark -> ✓ + "": " ", # space +} + +_PUA_RE = re.compile("|".join(re.escape(k) for k in PUA_REPLACEMENTS)) +# Restliche Private-Use-Area-Zeichen (U+E000..U+F8FF), die keine bekannte +# Entsprechung haben, werden entfernt - sie haben in LaTeX-Fonts keine Glyphe. +_PUA_REST_RE = re.compile(r"[-]") + + +def clean_pua(text: str) -> str: + """Ersetzt Symbolfont-Zeichen (Private Use Area) durch Unicode-Aequivalente. + + Bekannte Symbole (Bullet, Pfeile, Haken) werden gemappt, alle uebrigen + PUA-Zeichen entfernt, damit im PDF keine leeren Kaestchen erscheinen. + """ + text = _PUA_RE.sub(lambda m: PUA_REPLACEMENTS[m.group(0)], text) + text = _PUA_REST_RE.sub("", text) + return text + def find_target(pdf_name: str) -> str | None: """Findet den Zielordner-Namen fuer eine PDF-Datei.""" @@ -202,7 +232,7 @@ def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int: text = "" max_size = 0.0 for span in line.get("spans", []): - span_text = span.get("text", "").strip() + span_text = clean_pua(span.get("text", "")).strip() if span_text: text += span_text + " " size = span.get("size", 0) diff --git a/lib/fortna_convert_chapters.py b/lib/fortna_convert_chapters.py new file mode 100644 index 0000000..eade31a --- /dev/null +++ b/lib/fortna_convert_chapters.py @@ -0,0 +1,281 @@ +"""Befuellt chapters/ aus dem Fortna-Rohmaterial anhand des Inventars. + +Zwei Aktionsarten (siehe doc/plan_fortna_extraktion.md): + +- action="convert": Die 5 Hauptanleitungen (Betriebs-/Wartungsanleitung, + Funktionsbeschreibung ILS/OMNIFLO/LoadingLoop) werden volltextig mit Bildern + nach .rst konvertiert. Genutzt wird die positionsbasierte Extraktion aus + extract_products.py (Kopf-/Fusszeilen-Filter, Schriftgroessen-Heuristik), + die fuer Word-erstellte PDFs robuster ist als die pdfminer-Variante. + +- action="list": Alle uebrigen Kapitel (Zertifikate, Layouts, Schaltplaene, + Notstopp/Bereichsstopp, Pruefberichte, Ersatzteile, Lieferantendoku) erhalten + eine generierte index.de.rst mit einer RST-list-table (Dateiname, Kategorie, + Groesse, Originalpfad). Es werden KEINE PDFs kopiert. + +Nur deutschsprachige Quellen (lang == "de") werden verarbeitet. + +Verwendung: + python fortna_convert_chapters.py --dry-run + python fortna_convert_chapters.py --apply + python fortna_convert_chapters.py --apply --only convert + python fortna_convert_chapters.py --apply --only list +""" + +from __future__ import annotations + +import argparse +import json +import logging +import os +import re +import shutil +import sys +from collections import defaultdict +from pathlib import Path + +# extract_products stellt die robuste PDF-Extraktion bereit (gleiches lib/-Verzeichnis) +import extract_products as ep + +log = logging.getLogger(__name__) + +RST_H = {1: "=", 2: "-", 3: "~", 4: "^"} + + +# --------------------------------------------------------------------------- +# Hilfen +# --------------------------------------------------------------------------- + +def slugify(name: str) -> str: + """Erzeugt einen sauberen Dateinamen-Stamm aus einem PDF-Namen.""" + stem = Path(name).stem.lower() + stem = re.sub(r"[^a-z0-9]+", "_", stem) + stem = re.sub(r"_+", "_", stem).strip("_") + return stem + + +def rst_heading(text: str, level: int = 1) -> list[str]: + """Erzeugt eine RST-Ueberschrift als Zeilenliste.""" + char = RST_H[level] + lines = [] + if level == 1: + lines.append(char * max(len(text), 3)) + lines.append(text) + lines.append(char * max(len(text), 3)) + lines.append("") + return lines + + +def load_inventory(path: Path) -> dict: + with open(path, encoding="utf-8") as f: + return json.load(f) + + +# --------------------------------------------------------------------------- +# action="convert": Volltext + Bilder +# --------------------------------------------------------------------------- + +def convert_manual(item: dict, source_root: Path, chapters_dir: Path, + dry_run: bool) -> None: + """Konvertiert eine Hauptanleitung nach chapters//.""" + src_pdf = source_root / item["rel_path"] + chapter_dir = chapters_dir / item["chapter"] + stem = slugify(item["filename"]) + rst_path = chapter_dir / f"{stem}.de.rst" + img_dir = chapter_dir / "images" + + log.info("CONVERT %s", item["chapter"]) + log.info(" Quelle: %s (%d MB)", item["rel_path"], + item["size_bytes"] // (1024 * 1024)) + log.info(" -> %s", rst_path.relative_to(chapters_dir)) + + if dry_run: + return + + if not src_pdf.exists(): + log.error(" Quelle fehlt: %s", src_pdf) + return + + # Zielordner vorbereiten: vorhandene RST/Bilder ersetzen (aus einem Guss) + chapter_dir.mkdir(parents=True, exist_ok=True) + if img_dir.exists(): + shutil.rmtree(img_dir) + # Alte .de.rst im Zielordner entfernen (Testbestand) + for old in chapter_dir.glob("*.de.rst"): + old.unlink() + + # Bilder extrahieren (extract_products entfernt kleine Icons < 5KB automatisch) + img_count = ep.extract_images(src_pdf, img_dir) + dup = ep.dedup_images(img_dir) + log.info(" %d Bilder (%d Duplikate entfernt)", img_count, dup) + + # Text nach RST extrahieren (positionsbasiert, Kopf-/Fusszeilen gefiltert) + blocks = ep.extract_text_to_rst(src_pdf, rst_path) + log.info(" %d Textbloecke -> %s", blocks, rst_path.name) + + +# --------------------------------------------------------------------------- +# action="list": Verzeichnisliste index.de.rst +# --------------------------------------------------------------------------- + +def human_size(n: int) -> str: + if n >= 1024 * 1024: + return f"{n / (1024 * 1024):.1f} MB" + if n >= 1024: + return f"{n / 1024:.0f} KB" + return f"{n} B" + + +def category_from_path(rel_path: str, chapter: str) -> str: + """Leitet eine Kategorie (z.B. Hersteller) aus dem Quellpfad ab. + + Nimmt das Ordnersegment direkt unterhalb des Fortna-Top-Level bzw. ein + aussagekraeftiges Zwischensegment (Herstellername bei Lieferantendoku). + """ + parts = Path(rel_path).parts + # Lieferantendoku: Herstellername steckt meist tiefer im Pfad + # Heuristik: letztes Segment vor der Datei, das keine Sprach-/Nummernkennung ist + lang_dir = re.compile(r"^\d*_?(DE|EN|CZ)(_|$)", re.I) + skip = re.compile(r"^\d+_", ) + candidates = [p for p in parts[1:-1] if not lang_dir.match(p)] + if candidates: + # bevorzugt ein Segment mit Buchstaben (Herstellername) + for c in reversed(candidates): + cleaned = skip.sub("", c) + if re.search(r"[A-Za-z]{3,}", cleaned): + return cleaned.split("-")[0].strip() + return "" + + +def generate_index(chapter: str, items: list[dict], source_root: Path, + chapters_dir: Path, dry_run: bool) -> None: + """Erzeugt chapters//index.de.rst mit RST-list-table.""" + chapter_dir = chapters_dir / chapter + rst_path = chapter_dir / "index.de.rst" + + title_map = { + "00_inhaltsverzeichnis": "Inhaltsverzeichnis / Dokumentennotizen", + "01_zertifikate/konformitaetserklaerung": "Konformitaetserklaerungen", + "01_zertifikate/konfigurationsbeschreibung": "Konfigurationsbeschreibung", + "07_ersatzteile": "Ersatzteillisten", + "08_lieferantendoku/antriebe": "Lieferantendoku - Antriebe", + "08_lieferantendoku/elektrik": "Lieferantendoku - Elektrik", + "08_lieferantendoku/pneumatik": "Lieferantendoku - Pneumatik", + "08_lieferantendoku/stahlbau": "Lieferantendoku - Stahlbau", + "08_lieferantendoku/sicherheitseinrichtungen": "Lieferantendoku - Sicherheitseinrichtungen", + "09_technische_unterlagen/layouts/mechanisch": "Layout mechanisch", + "09_technische_unterlagen/layouts/elektrisch": "Layout elektrisch", + "09_technische_unterlagen/schaltplaene": "Schaltplaene", + "09_technische_unterlagen/notstopp": "Not-Stopp", + "09_technische_unterlagen/bereichsstopp": "Bereichsstopp", + "10_anhang/pruefberichte": "Pruefberichte", + } + title = title_map.get(chapter, chapter) + + log.info("LIST %-45s (%d Dateien) -> %s", + chapter, len(items), rst_path.relative_to(chapters_dir)) + if dry_run: + return + + chapter_dir.mkdir(parents=True, exist_ok=True) + + lines: list[str] = [] + lines += rst_heading(title, 1) + lines.append(".. note::") + lines.append("") + lines.append(" Automatisch generierte Verzeichnisliste aus dem Fortna-Quellmaterial.") + lines.append(" Die referenzierten Dokumente liegen im Projekt-Rohmaterial und werden") + lines.append(" hier nicht dupliziert.") + lines.append("") + lines.append(".. list-table::") + lines.append(" :header-rows: 1") + lines.append(" :widths: 34 18 12 36") + lines.append("") + lines.append(" * - Dokument") + lines.append(" - Kategorie") + lines.append(" - Groesse") + lines.append(" - Quellpfad") + for it in sorted(items, key=lambda x: x["rel_path"]): + cat = category_from_path(it["rel_path"], chapter) + lines.append(f" * - {it['filename']}") + lines.append(f" - {cat}") + lines.append(f" - {human_size(it['size_bytes'])}") + lines.append(f" - ``{it['rel_path']}``") + lines.append("") + + rst_path.write_text("\n".join(lines), encoding="utf-8") + + +# --------------------------------------------------------------------------- +# Hauptlogik +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser( + description="Befuellt chapters/ aus dem Fortna-Inventar (Volltext + Verzeichnislisten)." + ) + parser.add_argument("--inventory", default=None, + help="Pfad zum Inventar-JSON (default: results/fortna_inventory.json)") + parser.add_argument("--source-dir", default=None, + help="Fortna-Quellverzeichnis (default: aus Inventar-JSON)") + parser.add_argument("--chapters-dir", default=None, + help="Ziel chapters/-Verzeichnis (default: PROJECT/chapters)") + parser.add_argument("--lang", default="de", choices=["de", "en", "cz"], + help="Zu verarbeitende Sprache (default: de)") + parser.add_argument("--only", choices=["convert", "list"], default=None, + help="Nur eine Aktionsart ausfuehren") + grp = parser.add_mutually_exclusive_group() + grp.add_argument("--dry-run", action="store_true", default=True, + help="Nur anzeigen (Default)") + grp.add_argument("--apply", action="store_true", + help="Aenderungen tatsaechlich schreiben") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(levelname)-7s %(message)s", + ) + dry_run = not args.apply + + project_root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent)) + inv_path = Path(args.inventory) if args.inventory else project_root / "results" / "fortna_inventory.json" + if not inv_path.exists(): + log.error("Inventar nicht gefunden: %s (erst fortna_inventory.py laufen lassen)", inv_path) + sys.exit(1) + + inv = load_inventory(inv_path) + source_root = Path(args.source_dir) if args.source_dir else Path(inv["source"]) + chapters_dir = Path(args.chapters_dir) if args.chapters_dir else project_root / "chapters" + + log.info("Quelle: %s", source_root) + log.info("Ziel: %s", chapters_dir) + log.info("Sprache: %s | Modus: %s", args.lang, "APPLY" if args.apply else "DRY-RUN") + + items = [i for i in inv["items"] if i["lang"] == args.lang and i["chapter"]] + + # 1. Volltext-Konvertierung + if args.only in (None, "convert"): + conv = [i for i in items if i["action"] == "convert"] + log.info("--- %d Hauptanleitungen konvertieren ---", len(conv)) + for it in conv: + convert_manual(it, source_root, chapters_dir, dry_run) + + # 2. Verzeichnislisten + if args.only in (None, "list"): + list_items = [i for i in items if i["action"] == "list"] + by_chapter: dict[str, list[dict]] = defaultdict(list) + for it in list_items: + by_chapter[it["chapter"]].append(it) + log.info("--- %d Verzeichnislisten erzeugen ---", len(by_chapter)) + for chapter in sorted(by_chapter): + generate_index(chapter, by_chapter[chapter], source_root, chapters_dir, dry_run) + + if dry_run: + log.info("DRY-RUN abgeschlossen. Mit --apply tatsaechlich schreiben.") + else: + log.info("Fertig.") + + +if __name__ == "__main__": + main() diff --git a/lib/fortna_init_project.py b/lib/fortna_init_project.py new file mode 100644 index 0000000..d760c8f --- /dev/null +++ b/lib/fortna_init_project.py @@ -0,0 +1,117 @@ +"""Legt den projektspezifischen Ordner projekte/fortna/ an. + +Erzeugt das Projekt-Repo-Geruest gemaess doc/Kernkonzepte.md: + projekte/fortna/ + anlage.json # Projektmetadaten (Platzhalter, manuell zu schaerfen) + overrides/.gitkeep # projektspezifische Ueberschreibungen (leer) + images/.gitkeep # projektspezifische Bilder (leer) + README.md # kurze Erlaeuterung + +Einmaliger Generator, kein Datei-Scan. Bestehende Dateien werden per Default NICHT +ueberschrieben (--force zum Erzwingen). + +Verwendung: + python fortna_init_project.py + python fortna_init_project.py --force +""" + +from __future__ import annotations + +import argparse +import json +import logging +import os +from pathlib import Path + +log = logging.getLogger(__name__) + + +ANLAGE_JSON = { + "projekt": { + "nummer": "500592", + "ka_nummer": "KA 135776", + "kunde": "Fortna / H&M", + "standort": "Cheb (Eger), CZ", + "sprachen": ["de"], + "_kommentar_sprachen": "EN/CZ vorhanden im Rohmaterial, aktuell nur DE gebaut", + "normen_region": "EU", + "_todo_rechtsgrundlage": "MRL 2006/42/EG (Altprojekt) vs. VO (EU) 2023/1230 - klaeren", + "base_version": ">=0.1.0", + }, + "quelle": { + "_kommentar": "Rohmaterial-Verzeichnis (ausserhalb des Repos), Basis der Verzeichnislisten", + "rohmaterial": "c:/10-Develop/projekte/Fortna", + }, + "maschinen": [ + {"_kommentar": "Modul-Liste noch zu befuellen (03_anlagenbeschreibung via meta.yaml)"}, + ], + "zusatz_kapitel": [], +} + +README = """\ +# Projekt Fortna (500592 / KA 135776) + +Projektspezifischer Ordner fuer die Fortna/H&M-Anlage in Cheb (CZ). + +Struktur gemaess `doc/Kernkonzepte.md` (Zwei-Repo-Architektur): + +- `anlage.json` - Projektmetadaten, Sprachen, Normenregion, Maschinenliste (ERP-Export-Platzhalter). +- `overrides/` - projektspezifische Ueberschreibungen von Base-Modulen/Kapiteln. +- `images/` - projektspezifische Bilder (Aufstellplan, Abnahmefotos, Kundenlogo). + +## Herkunft der Inhalte + +Die eigentliche Dokumentation wird aus dem Fortna-Rohmaterial +(`c:/10-Develop/projekte/Fortna`) erzeugt: + +1. `lib/fortna_inventory.py` - inventarisiert das Rohmaterial (`results/fortna_inventory.json`). +2. `lib/fortna_convert_chapters.py --apply` - befuellt `chapters/`: + - Volltext + Bilder fuer die 5 Hauptanleitungen (Betriebs-/Wartungsanleitung, Funktionsbeschreibung ILS/OMNIFLO/LoadingLoop). + - Verzeichnislisten (`index.de.rst`) fuer alle uebrigen Kapitel (Zertifikate, Layouts, Schaltplaene, Lieferantendoku, Ersatzteile, Pruefberichte). + +Hinweis: Die Verzeichnislisten referenzieren Pfade im Rohmaterial und kopieren keine PDFs. +Fuer einen produktiven PDF-Build mit eingebundenen Downloads muss das Rohmaterial zugaenglich +gemacht bzw. die referenzierten Dokumente ins Projekt uebernommen werden. +""" + + +def write_if_absent(path: Path, content: str, force: bool) -> None: + if path.exists() and not force: + log.info(" vorhanden (uebersprungen): %s", path.name) + return + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + log.info(" geschrieben: %s", path.name) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Legt projekte/fortna/ an.") + parser.add_argument("--project-dir", default=None, + help="Zielordner (default: PROJECT/projekte/fortna)") + parser.add_argument("--force", action="store_true", + help="Bestehende Dateien ueberschreiben") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(levelname)-7s %(message)s", + ) + + project_root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent)) + target = Path(args.project_dir) if args.project_dir else project_root / "projekte" / "fortna" + + log.info("Projektordner: %s", target) + target.mkdir(parents=True, exist_ok=True) + + write_if_absent(target / "anlage.json", + json.dumps(ANLAGE_JSON, ensure_ascii=False, indent=2) + "\n", args.force) + write_if_absent(target / "README.md", README, args.force) + write_if_absent(target / "overrides" / ".gitkeep", "", args.force) + write_if_absent(target / "images" / ".gitkeep", "", args.force) + + log.info("Fertig.") + + +if __name__ == "__main__": + main() diff --git a/lib/fortna_inventory.py b/lib/fortna_inventory.py new file mode 100644 index 0000000..5f1e767 --- /dev/null +++ b/lib/fortna_inventory.py @@ -0,0 +1,317 @@ +"""Inventarisiert das Fortna-Rohmaterial und ordnet jede Datei einem Zielkapitel zu. + +Scannt das Fortna-Quellverzeichnis (Standard: PV_FORTNA_SOURCE oder --source-dir) +rekursiv, erkennt fuer jede PDF/DOCX-Datei die Sprache (aus dem Sprachordner) und +das Ziel-Kapitel unter chapters/ (anhand einer praefix-basierten Mapping-Tabelle, +abgeleitet aus doc/kapitelstruktur.md). + +Ergebnis wird als results/fortna_inventory.json geschrieben und tabellarisch auf +stdout ausgegeben. Reines Lese-/Analyseskript - schreibt nichts nach chapters/. + +Verwendung: + python fortna_inventory.py + python fortna_inventory.py --source-dir "c:/10-Develop/projekte/Fortna" + python fortna_inventory.py --lang de # nur deutsche Dateien im Report +""" + +from __future__ import annotations + +import argparse +import json +import logging +import os +import re +import sys +from collections import Counter +from dataclasses import dataclass, asdict +from pathlib import Path + +log = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Sprach-Erkennung aus Ordnernamen +# --------------------------------------------------------------------------- + +# Reihenfolge wichtig: spezifische Muster zuerst. Ein Segment des relativen +# Pfades wird gegen diese Muster geprueft. +LANG_PATTERNS: list[tuple[re.Pattern[str], str]] = [ + (re.compile(r"_DE(_|$)|_DE_original|DE_f[uü]r|01_DE\b", re.I), "de"), + (re.compile(r"_EN(_|$)|_EN_original|EN_for|1_EN_original", re.I), "en"), + (re.compile(r"_CZ(_|$)|02_CZ|03_CZ|CZ_pro", re.I), "cz"), +] + +# Sprach-Suffixe im Dateinamen (Fallback, wenn Ordner nichts hergibt) +FILENAME_LANG = [ + (re.compile(r"[_\-\[ ]DE[_\-\.\] ]|_DE\b", re.I), "de"), + (re.compile(r"[_\-\[ ]EN[_\-\.\] ]|_EN\b", re.I), "en"), + (re.compile(r"[_\-\[ ]CZ[_\-\.\] ]|_CZ\b", re.I), "cz"), +] + + +def detect_lang(rel_path: Path) -> str: + """Bestimmt die Sprache einer Datei anhand Pfadsegmenten und Dateiname. + + Fallback auf 'de', wenn nichts eindeutig erkennbar (z.B. sprachneutrale + Datenblaetter oder gemischte DE-EN-Dokumente). + """ + # Zuerst Ordnersegmente pruefen (aussagekraeftiger als Dateiname) + for part in rel_path.parts[:-1]: + for pattern, lang in LANG_PATTERNS: + if pattern.search(part): + return lang + # Dann Dateiname + name = rel_path.name + for pattern, lang in FILENAME_LANG: + if pattern.search(name): + return lang + return "de" + + +# --------------------------------------------------------------------------- +# Kapitel-Mapping: Fortna-Quellpfad-Praefix -> (Ziel-Kapitel, Aktion) +# --------------------------------------------------------------------------- +# Aktion: "convert" = Volltext+Bilder nach RST, "list" = nur Verzeichnisliste. +# Regeln werden der Reihe nach geprueft; erste passende gewinnt (praefix-Match +# gegen den mit '/' normalisierten relativen Pfad, case-insensitive). + +@dataclass +class MapRule: + pattern: re.Pattern[str] + chapter: str + action: str # "convert" | "list" + + +MAPPING: list[MapRule] = [ + # --- Volltext-Konvertierung: die 5 Hauptanleitungen (nur DE_original) --- + MapRule(re.compile(r"02_INSTRUCTIONS.*/01_BETRIEBSANLEITUNG", re.I), + "04_betrieb/betriebsanleitung", "convert"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/02_WARTUNGSANLEITUNG", re.I), + "05_wartung/wartungsanleitung", "convert"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/03_FUNKTION_STEUERUNG_ILS", re.I), + "06_steuerung/ils", "convert"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/04_FUNKTION_STEUERUNG_OMNIFLO", re.I), + "06_steuerung/omniflo", "convert"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/05_FUNKTION_STEUERUNG_LOADING_LOOP", re.I), + "06_steuerung/loading_loop", "convert"), + + # Fremdsprach-Varianten der Anleitungen (EN: USER_MANUAL / MAINTENANCE / + # FUNKTION_CONTROL, CZ: NAVOD / RIZENI). In diesem Durchlauf (nur DE) werden + # sie NICHT konvertiert (action="list"), aber sie sollen nicht als UNMAPPED + # erscheinen. Zuordnung nach Anleitungstyp anhand der fuehrenden Nummer. + MapRule(re.compile(r"02_INSTRUCTIONS.*/01_", re.I), + "04_betrieb/betriebsanleitung", "list"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/02_", re.I), + "05_wartung/wartungsanleitung", "list"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/03_", re.I), + "06_steuerung/ils", "list"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/04_", re.I), + "06_steuerung/omniflo", "list"), + MapRule(re.compile(r"02_INSTRUCTIONS.*/05_", re.I), + "06_steuerung/loading_loop", "list"), + + # --- Verzeichnislisten: alles uebrige nach Kapitel --- + # 00 Inhaltsverzeichnis / Notes + MapRule(re.compile(r"^00_CONTENTS", re.I), + "00_inhaltsverzeichnis", "list"), + # 01 Zertifikate + MapRule(re.compile(r"^01_CERTIFICATES.*/01_SCH[OÖ]NENBERGER", re.I), + "01_zertifikate/konformitaetserklaerung", "list"), + MapRule(re.compile(r"^01_CERTIFICATES.*/(02_)?CONFIG", re.I), + "01_zertifikate/konfigurationsbeschreibung", "list"), + MapRule(re.compile(r"^01_CERTIFICATES", re.I), + "01_zertifikate/konformitaetserklaerung", "list"), + # 03 Ersatzteile (Fortna 03_SPARE_PARTS) + MapRule(re.compile(r"^03_SPARE_PARTS", re.I), + "07_ersatzteile", "list"), + # 04 Technical Doc -> 09 / 10 + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/01_LAYOUT.*/01_MECHANICAL", re.I), + "09_technische_unterlagen/layouts/mechanisch", "list"), + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/01_LAYOUT.*/02_ELECTRICAL", re.I), + "09_technische_unterlagen/layouts/elektrisch", "list"), + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/02_AREA_STOP", re.I), + "09_technische_unterlagen/bereichsstopp", "list"), + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/03_EMERGENCY_STOP", re.I), + "09_technische_unterlagen/notstopp", "list"), + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/04_DIAGRAMS", re.I), + "09_technische_unterlagen/schaltplaene", "list"), + MapRule(re.compile(r"^04_TECHNICAL_DOC.*/05_REPORTS", re.I), + "10_anhang/pruefberichte", "list"), + # 05 Supplier Doc -> 08 Lieferantendoku (nach Gewerk) + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/01_MiTek", re.I), + "08_lieferantendoku/stahlbau", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/02_ABS", re.I), + "08_lieferantendoku/sicherheitseinrichtungen", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/(03_DTS|04_Leantechnik)", re.I), + "08_lieferantendoku/antriebe", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/05_Electrics.*/01_Instructions.*/01_MOTORS", re.I), + "08_lieferantendoku/antriebe", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/05_Electrics", re.I), + "08_lieferantendoku/elektrik", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC.*/(06_|07_)?Pneumatic", re.I), + "08_lieferantendoku/pneumatik", "list"), + MapRule(re.compile(r"^05_SUPPLIER_DOC", re.I), + "08_lieferantendoku", "list"), +] + + +def classify(rel_path: Path) -> tuple[str | None, str | None]: + """Ordnet einen relativen Pfad Kapitel + Aktion zu. + + Returns (chapter, action) oder (None, None), wenn keine Regel greift. + """ + norm = rel_path.as_posix() + for rule in MAPPING: + if rule.pattern.search(norm): + return rule.chapter, rule.action + return None, None + + +# --------------------------------------------------------------------------- +# Inventar-Aufbau +# --------------------------------------------------------------------------- + +@dataclass +class InvItem: + rel_path: str + filename: str + ext: str + size_bytes: int + lang: str + fortna_top: str # oberster Fortna-Ordner + chapter: str | None # Ziel-Kapitel unter chapters/ + action: str | None # "convert" | "list" | None + + +def resolve_source(arg_dir: str | None) -> Path: + """Bestimmt das Fortna-Quellverzeichnis.""" + if arg_dir: + src = Path(arg_dir).expanduser().resolve() + elif os.environ.get("PV_FORTNA_SOURCE"): + src = Path(os.environ["PV_FORTNA_SOURCE"]).expanduser().resolve() + else: + # Sinnvoller Default fuer diese Umgebung + src = Path("c:/10-Develop/projekte/Fortna").resolve() + return src + + +def build_inventory(source: Path) -> list[InvItem]: + """Scannt das Quellverzeichnis und baut die Inventarliste.""" + items: list[InvItem] = [] + exts = {".pdf", ".docx"} + + for path in sorted(source.rglob("*")): + if not path.is_file(): + continue + if path.suffix.lower() not in exts: + continue + rel = path.relative_to(source) + chapter, action = classify(rel) + items.append(InvItem( + rel_path=rel.as_posix(), + filename=path.name, + ext=path.suffix.lower(), + size_bytes=path.stat().st_size, + lang=detect_lang(rel), + fortna_top=rel.parts[0] if rel.parts else "", + chapter=chapter, + action=action, + )) + return items + + +def human_size(n: int) -> str: + """Formatiert Bytes menschenlesbar (KB/MB).""" + if n >= 1024 * 1024: + return f"{n / (1024 * 1024):.1f}M" + if n >= 1024: + return f"{n / 1024:.0f}K" + return f"{n}B" + + +def print_summary(items: list[InvItem], lang_filter: str | None) -> None: + """Gibt eine tabellarische Zusammenfassung auf stdout aus.""" + shown = [i for i in items if lang_filter is None or i.lang == lang_filter] + + # Uebersicht nach Kapitel + Aktion + by_chapter: Counter = Counter() + for it in shown: + key = f"{it.chapter or '(UNMAPPED)'} [{it.action or '-'}]" + by_chapter[key] += 1 + + print("\n=== Dateien je Ziel-Kapitel ===") + for key in sorted(by_chapter): + print(f" {by_chapter[key]:4d} {key}") + + # Sprachverteilung + by_lang: Counter = Counter(i.lang for i in items) + print("\n=== Sprachverteilung (alle) ===") + for lang in sorted(by_lang): + print(f" {by_lang[lang]:4d} {lang}") + + # Nicht zugeordnete Dateien explizit auflisten + unmapped = [i for i in shown if i.chapter is None] + if unmapped: + print(f"\n=== NICHT ZUGEORDNET ({len(unmapped)}) ===") + for it in unmapped: + print(f" {it.rel_path}") + + # Konvertierungs-Kandidaten (Volltext) explizit auflisten + conv = [i for i in items if i.action == "convert"] + print(f"\n=== Volltext-Konvertierung ({len(conv)} Kandidaten, alle Sprachen) ===") + for it in sorted(conv, key=lambda x: (x.chapter or "", x.lang)): + print(f" [{it.lang}] {it.chapter:32s} <- {it.rel_path} ({human_size(it.size_bytes)})") + + print(f"\nGesamt: {len(items)} Dateien, davon {len(shown)} in Filter '{lang_filter or 'alle'}'.") + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Inventarisiert Fortna-Rohmaterial und ordnet Zielkapitel zu." + ) + parser.add_argument("--source-dir", default=None, + help="Fortna-Quellverzeichnis (default: PV_FORTNA_SOURCE " + "oder c:/10-Develop/projekte/Fortna)") + parser.add_argument("--lang", default=None, choices=["de", "en", "cz"], + help="Report auf eine Sprache filtern (JSON enthaelt immer alles)") + parser.add_argument("--output", default=None, + help="Ausgabe-JSON (default: results/fortna_inventory.json)") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(levelname)-7s %(message)s", + ) + + source = resolve_source(args.source_dir) + if not source.exists() or not source.is_dir(): + log.error("Quellverzeichnis nicht gefunden: %s", source) + sys.exit(1) + + log.info("Scanne: %s", source) + items = build_inventory(source) + log.info("Gefunden: %d Dateien (.pdf/.docx)", len(items)) + + # JSON schreiben + project_root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent)) + results_dir = project_root / "results" + results_dir.mkdir(exist_ok=True) + out_path = Path(args.output) if args.output else results_dir / "fortna_inventory.json" + if not out_path.is_absolute(): + out_path = results_dir / out_path + + payload = { + "source": str(source), + "count": len(items), + "items": [asdict(i) for i in items], + } + with open(out_path, "w", encoding="utf-8") as f: + json.dump(payload, f, ensure_ascii=False, indent=2) + log.info("Inventar geschrieben: %s", out_path) + + print_summary(items, args.lang) + + +if __name__ == "__main__": + main() diff --git a/projekte/fortna/README.md b/projekte/fortna/README.md new file mode 100644 index 0000000..90d5ddd --- /dev/null +++ b/projekte/fortna/README.md @@ -0,0 +1,23 @@ +# Projekt Fortna (500592 / KA 135776) + +Projektspezifischer Ordner fuer die Fortna/H&M-Anlage in Cheb (CZ). + +Struktur gemaess `doc/Kernkonzepte.md` (Zwei-Repo-Architektur): + +- `anlage.json` - Projektmetadaten, Sprachen, Normenregion, Maschinenliste (ERP-Export-Platzhalter). +- `overrides/` - projektspezifische Ueberschreibungen von Base-Modulen/Kapiteln. +- `images/` - projektspezifische Bilder (Aufstellplan, Abnahmefotos, Kundenlogo). + +## Herkunft der Inhalte + +Die eigentliche Dokumentation wird aus dem Fortna-Rohmaterial +(`c:/10-Develop/projekte/Fortna`) erzeugt: + +1. `lib/fortna_inventory.py` - inventarisiert das Rohmaterial (`results/fortna_inventory.json`). +2. `lib/fortna_convert_chapters.py --apply` - befuellt `chapters/`: + - Volltext + Bilder fuer die 5 Hauptanleitungen (Betriebs-/Wartungsanleitung, Funktionsbeschreibung ILS/OMNIFLO/LoadingLoop). + - Verzeichnislisten (`index.de.rst`) fuer alle uebrigen Kapitel (Zertifikate, Layouts, Schaltplaene, Lieferantendoku, Ersatzteile, Pruefberichte). + +Hinweis: Die Verzeichnislisten referenzieren Pfade im Rohmaterial und kopieren keine PDFs. +Fuer einen produktiven PDF-Build mit eingebundenen Downloads muss das Rohmaterial zugaenglich +gemacht bzw. die referenzierten Dokumente ins Projekt uebernommen werden. diff --git a/projekte/fortna/anlage.json b/projekte/fortna/anlage.json new file mode 100644 index 0000000..a5ea6d1 --- /dev/null +++ b/projekte/fortna/anlage.json @@ -0,0 +1,25 @@ +{ + "projekt": { + "nummer": "500592", + "ka_nummer": "KA 135776", + "kunde": "Fortna / H&M", + "standort": "Cheb (Eger), CZ", + "sprachen": [ + "de" + ], + "_kommentar_sprachen": "EN/CZ vorhanden im Rohmaterial, aktuell nur DE gebaut", + "normen_region": "EU", + "_todo_rechtsgrundlage": "MRL 2006/42/EG (Altprojekt) vs. VO (EU) 2023/1230 - klaeren", + "base_version": ">=0.1.0" + }, + "quelle": { + "_kommentar": "Rohmaterial-Verzeichnis (ausserhalb des Repos), Basis der Verzeichnislisten", + "rohmaterial": "c:/10-Develop/projekte/Fortna" + }, + "maschinen": [ + { + "_kommentar": "Modul-Liste noch zu befuellen (03_anlagenbeschreibung via meta.yaml)" + } + ], + "zusatz_kapitel": [] +} diff --git a/projekte/fortna/images/.gitkeep b/projekte/fortna/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/projekte/fortna/overrides/.gitkeep b/projekte/fortna/overrides/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 4d953d0..39f6d78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,8 @@ rstcheck==6.2.5 rstcheck-core==1.2.2 shellingham==1.5.4 six==1.17.0 +Sphinx==9.1.0 +myst-parser==5.1.0 stevedore==5.7.0 typer==0.25.0 typing-inspection==0.4.2