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) <noreply@anthropic.com>
This commit is contained in:
Michael StanglandClaude Opus 4.8 committed 2026-09-23 13:23:32 +02:00
1 parent eb9237217e
commit e7aa3b4e4b
30 files changed
+2095 -1

No files matched your search

+240
View File
@@ -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.