Kompletter Umbau in einem Rutsch auf PDF als template Grundlage

This commit is contained in:
trnq1ll0 committed 2026-06-16 20:46:45 +02:00
1 parent dbe325d305
commit 169d2167e0
12 files changed
+421 -287

No files matched your search

+68 -65
View File
@@ -1,24 +1,37 @@
# Ziel
Eine Python + Streamlit App für Windows, die aus einer Input-Excel-Datei (ein Sheet pro Monat mit Mitarbeiterdaten) und einem Excel-Template pro Mitarbeiter eine Excel-Datei generiert (ein Sheet pro Monat, befüllt mit dessen Daten).
Eine Python + Streamlit App für Windows, die aus einer Input-Excel-Datei (ein Sheet pro Monat mit Mitarbeiterdaten) und einem **PDF-Template** pro Mitarbeiter eine **PDF-Datei** generiert (eine Seite pro Monat, befüllt mit dessen Daten).
**Ansatz**: Verwendung von openpyxl für Single-Input-File + Streamlit-UI.
**Ansatz**: openpyxl für Input-Parsing + PyMuPDF (fitz) für PDF-Generierung + Streamlit-UI.
> **Hintergrund**: Das ursprüngliche Excel-Template war teilweise schreibgeschützt und konnte nicht mit openpyxl befüllt werden.
> Daher wurde das Template als PDF exportiert. Im PDF wurden alle zu befüllenden Textstellen mit einem Platzhalter
> im Format `{Feldname}_Field` markiert (z. B. `Antragsnummer_Field`, `Name_Field`). Die App sucht nach diesen
> `_Field`-Markern und ersetzt sie durch die echten Daten.
# Aufgabe
Erstelle eine Windows App in dessen Oberfläche aus folgenden Bestandteilen aufgebaut ist:
- mittels einem DropDown-Menü kann eine Datei aus dem inputfolder gewählt werden
- mittels einem DropDown-Menü kann ein Template aus dem Templatefolder gewählt werden
- mittels einem DropDown-Menü kann ein PDF-Template aus dem Templatefolder gewählt werden
- ein Button um den Vorgang zu starten
Die Funktion der App besteht darin, das Templatefile mit Daten aus dem Input-File zu befüllen.
Im Inputfile ist für alle Monate eines Jahres eine Liste aller Mitarbeitenden und in den Spalten alle Daten zur Befüllung des Template files.
Die Funktion der App besteht darin, das Template-PDF mit Daten aus dem Input-File zu befüllen.
Im Inputfile ist für alle Monate eines Jahres eine Liste aller Mitarbeitenden und in den Spalten alle Daten zur Befüllung des Templates.
# Aufbau Input Datei
- In der Input Datei ist für jeden Monat eines Jahres ein Sheet gespeichert.
- In jedem Sheet befindet sich eine Tabelle (felder speichern in Input Schema json) mit Mitarbeitenden.
- Für jeden Mitarbeitenden soll aus dem Template eine neue ExcelDatei erzeugt werden, welche auch wieder für jeden Monat ein Sheet des Templates enthält und alle Daten des Mitarbeiters für dieses Monat aus der Inputliste enthält.
- Für jeden Mitarbeitenden wird ein PDF erzeugt, das für jeden Monat eine Seite enthält, befüllt mit den Daten des Mitarbeiters für diesen Monat.
# Aufbau PDF-Template
- Das PDF-Template ist eine einseitige PDF-Datei, generiert aus der ursprünglichen XLSX-Vorlage.
- Jede zu befüllende Textstelle enthält einen Platzhalter: den Feldnamen gefolgt von `_Field`
(z. B. `Antragsnummer_Field`, `Name_Field`, `Stellenanteil in %_Field`, …).
- Die App sucht im PDF nach allen `_Field`-Texten, ermittelt deren Position und Schriftgröße,
und ersetzt sie durch die tatsächlichen Datenwerte.
# README
@@ -30,15 +43,14 @@ In der README.md ist ein kleine Doku wie das Programm zu benutzen ist und welche
### Phase 1: Projekt-Grundgerüst & Konfiguration
1. **`requirements.txt` befüllen** — `streamlit`, `openpyxl`
1. **`requirements.txt` befüllen** — `streamlit`, `openpyxl`, `PyMuPDF`
2. **`config/settings.ini` befüllen** — Pfade, Standardwerte
- Abschnitte: `[paths]` (input_dir, template_dir, output_dir), `[output]` (filename_pattern)
- Abschnitte: `[paths]` (input_dir, template_dir, output_dir), `[output]` (filename_pattern → `.pdf`)
3. **`config/input-fields.json` Schema definieren** — Spalten der Input-Tabelle mit ihren exakten Überschriften
- Wird durch Analyse der Datei `input/2025 JM.xlsx` ermittelt: Spaltenposition (A, B, C…) und die exakte Spaltenüberschrift aus Zeile 1 der jeweiligen Sheets
- JSON-Array mit `column` (Spaltenbuchstabe), `field_name` (exakte Überschrift aus der Excel-Tabelle), `type` (string|number|date), `required` (true|false)
4. **`config/output-fields.json` Mapping definieren** — Welche Input-Felder (exakte Spaltenüberschrift) in welche Template-Zellen geschrieben werden
- JSON-Array mit `source_field` (exakte Spaltenüberschrift aus der Input-Tabelle, wie in input-fields.json definiert), `target_cell` (Zelladresse im Template, z.B. "A17"), `format` (optional, z.B. "dd.mm.yyyy")
- Muss durch Analyse des Templates `template/Tätigkeitsnachweise Stellenförderung.xlsx` ermittelt werden (welche Zellen sind zu befüllen?)
4. **`config/output-fields.json` Mapping definieren** — Welche Input-Felder in das PDF geschrieben werden
- JSON-Array mit `source_field` (exakte Spaltenüberschrift aus der Input-Tabelle), `format` (optional, z. B. "dd.mm.yyyy")
- Der Platzhalter im PDF ist immer `{source_field}_Field` – kein explizites `target_cell` mehr nötig
### Phase 2: Core-Module (`src/`)
@@ -50,62 +62,52 @@ In der README.md ist ein kleine Doku wie das Programm zu benutzen ist und welche
- `load_input_schema()`: liest `config/input-fields.json`
- `load_output_mapping()`: liest `config/output-fields.json`
- `get_available_input_files()`: listet `.xlsx`-Dateien in `input/`
- `get_available_templates()`: listet `.xlsx`-Dateien in `template/`
7. **`src/parser.py`** — Input-Datei einlesen
- `get_available_templates()`: listet `.pdf`-Dateien in `template/`
7. **`src/parser.py`** — Input-Datei einlesen (unverändert: openpyxl für XLSX-Input)
- `parse_input_file(filepath, schema)`: öffnet Workbook, iteriert über alle Sheets (Monate)
- Extrahiert Monat/Jahr aus Sheet-Namen oder Sheet-Inhalt
- Für jedes Sheet: liest Zeilen anhand des Schemas, erzeugt `EmployeeRecord`-Liste
- Rückgabe: `dict[str, list[EmployeeRecord]]` (key = Mitarbeiter-Name)
- Rückgabe: `list[EmployeeRecord]`
### Phase 3: Writer & Validierung
### Phase 3: Writer & Validierung (PDF-basiert)
8. **`src/writer.py`** — Output generieren
8. **`src/writer.py`** — PDF-Output generieren (NEU: PyMuPDF)
- `generate_activity_reports(employee_records, template_path, mapping, config)`:
- **Pre-Scan**: Öffnet das Template-PDF einmal und sucht nach allen `_Field`-Platzhaltern,
ermittelt deren Position (bounding box) und Schriftgröße
- Gruppiert Records nach Mitarbeiter
- Für jeden Mitarbeiter: lädt Template, erstellt für jeden Monat ein Sheet (kopiert Template-Sheet)
- Befüllt Zielzellen gemäß output-fields.json Mapping
- Benennt Sheets nach Monat (z.B. "01_Januar", "02_Februar", …)
- Speichert als `{nachname}_{vorname}_{YYYY}_Tätigkeitsnachweis.xlsx` in `output/`
9. **`src/validator.py`** — Eingabevalidierung
- `validate_input_file(filepath, schema)`: prüft Datei-Existenz, Sheet-Anzahl, Spalten laut Schema
- `validate_template(template_path, mapping)`: prüft ob alle target_cells im Template existieren
- Für jeden Mitarbeiter:
- Erstellt ein neues PDF
- Für jeden Monats-Record: fügt eine Kopie der Template-Seite ein
- Ersetzt alle `_Field`-Platzhalter durch die tatsächlichen Daten (redact + insert_text)
- Speichert als `{nachname}_{vorname}_Tätigkeitsnachweis.pdf` in `output/`
9. **`src/validator.py`** — Eingabevalidierung (angepasst für PDF)
- `validate_input_file(filepath, schema)`: unverändert (prüft XLSX-Input)
- `validate_template(template_path, mapping)`: prüft ob alle `_Field`-Platzhalter im PDF gefunden werden
### Phase 4: Streamlit UI
10. **`app.py`** — Streamlit-Oberfläche
10. **`app.py`** — Streamlit-Oberfläche (angepasst für PDF-Templates)
- Layout:
- Titel + Beschreibung
- `st.selectbox` für Input-Datei (aus `input/`-Folder)
- **Sofort bei Auswahl**: Validierung + Parsing der Datei
- Anzeige: Anzahl gefundener Mitarbeiter, Auflistung aller enthaltenen Antragsnummern
- Bei Fehlern: `st.error` mit Details
- `st.selectbox` für Template (aus `template/`-Folder)
- **Sofort bei Auswahl**: Validierung des Templates gegen `output-fields.json`
- Anzeige: Anzahl gefundener Zielfelder (target_cells), fehlende/fehlerhafte Zellen
- Bei Fehlern: `st.warning` mit Details
- `st.selectbox` für PDF-Template (aus `template/`-Folder)
- **Sofort bei Auswahl**: Validierung des PDF-Templates (Prüfung aller `_Field`-Marker)
- Anzeige: Anzahl gefundener Platzhalter-Felder
- **Mitarbeiter-Liste** (erscheint nach erfolgreicher Input-Validierung)
- `st.multiselect` mit allen gefundenen Mitarbeiternamen, standardmäßig alle vorselektiert
- Nicht selektierte Mitarbeiter werden bei der Generierung übersprungen
- Zeigt: „X von Y Mitarbeitern ausgewählt"
- `st.button("Tätigkeitsnachweise generieren")`
- Bei Button-Click:
1. Filtere Employee Records auf selektierte Mitarbeiter
2. Generiere Activity Reports nur für selektierte Mitarbeiter und speichere in `output/`
3. Zeige Erfolgsmeldung mit Liste der erstellten Dateien via `st.success`
- Status-Anzeige mit `st.spinner` und `st.progress`
2. Generiere PDFs nur für selektierte Mitarbeiter und speichere in `output/`
3. Zeige Erfolgsmeldung mit Liste der erstellten PDF-Dateien
### Phase 5: Start-Skript & Doku
11. **`start.bat`** — Windows Batch-Startdatei
- Prüft ob Python verfügbar ist
- Installiert Requirements (falls nötig)
- Startet `streamlit run app.py`
11. **`start.bat`** — Windows Batch-Startdatei (unverändert)
12. **`README.md` aktualisieren** — Vollständige Doku
- Setup-Anleitung (Python, venv, pip install)
- Konfiguration (settings.ini, input-fields.json, output-fields.json)
- Bedienung (Datei auswählen, Template wählen, Button klicken)
- Erwartetes Input-Format (Spalten, monatliche Sheets)
- Output-Beschreibung (Dateiname, Struktur)
---
@@ -113,19 +115,17 @@ In der README.md ist ein kleine Doku wie das Programm zu benutzen ist und welche
| Datei | Aktion |
|-------|--------|
| `requirements.txt` | Neu befüllen |
| `config/settings.ini` | Neu befüllen |
| `config/input-fields.json` | Neu befüllen (nach Analyse Input-Datei) |
| `config/output-fields.json` | Neu befüllen (nach Analyse Template) |
| `src/__init__.py` | Neu (leer) |
| `src/models.py` | Neu |
| `src/config.py` | Neu |
| `src/parser.py` | Neu |
| `src/writer.py` | Neu |
| `src/validator.py` | Neu |
| `app.py` | Neu |
| `start.bat` | Neu |
| `README.md` | Überschreiben mit vollständiger Doku |
| `requirements.txt` | `PyMuPDF` hinzufügen |
| `config/settings.ini` | filename_pattern auf `.pdf` ändern |
| `config/output-fields.json` | `target_cell` entfernen (wird durch `_Field`-Marker ersetzt) |
| `src/writer.py` | Komplett neu: PDF-basierte Generierung mit PyMuPDF |
| `src/validator.py` | `validate_template` für PDF anpassen |
| `src/config.py` | `get_available_templates` auf `.pdf` umstellen |
| `app.py` | Template-Validierung für PDF anpassen |
| `coldrun.py` | An PDF-Template anpassen |
| `tests/test_e2e.py` | Tests für PDF-Output anpassen |
| `.copilot/PLAN.md` | Dieser Plan – aktualisiert |
| `.copilot/TASKS.md` | Aufgabenliste – aktualisiert |
## Projektstruktur
@@ -139,25 +139,28 @@ In der README.md ist ein kleine Doku wie das Programm zu benutzen ist und welche
├── config/
│ ├── settings.ini ← Einstellungen der App
│ ├── input-fields.json ← Schema der Inputdaten-Tabelle
│ └── output-fields.json ← Schema der Outputdaten-Tabelle
├── output/ ← Zielordner für generierte Dateien
│ └── output-fields.json ← Mapping: Input-Feld → PDF-Platzhalter
├── output/ ← Zielordner für generierte PDF-Dateien
├── template/
│ └── *.pdf ← PDF-Template mit _Field-Platzhaltern
├── src/
│ ├── __init__.py
│ ├── models.py
│ ├── config.py
│ ├── parser.py
│ ├── writer.py
│ ├── writer.py ← PDF-basierte Generierung (PyMuPDF)
│ └── validator.py
├── app.py ← Streamlit-App
├── coldrun.py ← CLI Cold-Run
├── start.bat ← Windows batch zum Start der App
├── README.md ← README für Programmbenutzer
└── requirements.txt ← streamlit, openpyxl
└── requirements.txt ← streamlit, openpyxl, PyMuPDF
```
## Verifikation
1. **Input-Datei-Analyse**: `2025 JM.xlsx` mit openpyxl öffnen, Sheet-Namen prüfen, exakte Spaltenüberschriften aus Zeile 1 jedes Sheets auslesen → `input-fields.json` ableiten (Spaltenbuchstabe + original Feldname)
2. **Template-Analyse**: `Tätigkeitsnachweise Stellenförderung.xlsx` öffnen, belegte/freie Zellen inspizieren, die befüllt werden sollen → `output-fields.json` ableiten (Mapping: Input-Spaltenüberschrift → Template-Zelladresse)
3. **Unit-Test**: App mit vorhandener Input-Datei und Template ausführen, prüfen ob Output-Dateien korrekt befüllt sind
4. **Edge Cases**: Leere Input-Zeilen, fehlende Monate, Mitarbeiter ohne Vorname, Sonderzeichen in Namen
1. **Input-Datei-Analyse**: `2025 JM.xlsx` mit openpyxl öffnen, Sheet-Namen prüfen, Spaltenüberschriften validieren
2. **PDF-Template-Analyse**: `Tätigkeitsnachweise Stellenförderung.pdf` mit PyMuPDF öffnen, alle `_Field`-Platzhalter suchen → mit `output-fields.json` abgleichen
3. **Unit-Test**: App mit vorhandener Input-Datei und PDF-Template ausführen, prüfen ob Output-PDFs korrekt befüllt sind
4. **Edge Cases**: Leere Input-Zeilen, fehlende Monate, Mitarbeiter ohne Vorname, Sonderzeichen in Namen, Umlaute im PDF
```
+42 -22
View File
@@ -1,27 +1,47 @@
# Aufgaben: Tätigkeitsnachweis-Generator (Streamlit App)
# Aufgaben: Tätigkeitsnachweis-Generator (Streamlit App) – PDF-Migration
## 🔍 Analyse ✅
- [x] **A1**: Input-Datei `input/2025 JM.xlsx` analysieren → Sheet-Namen, exakte Spaltenüberschriften (Zeile 1), Spaltenpositionen (A, B, C…), Beispiel-Daten
- [x] **A2**: Template `template/Tätigkeitsnachweise Stellenförderung.xlsx` analysieren → zu befüllende Zielzellen identifizieren (Zelladresse + erwarteter Inhalt)
> **Migration**: Das XLSX-Template war schreibgeschützt und wurde durch ein PDF-Template ersetzt.
> Alle `_Field`-Platzhalter im PDF werden durch echte Daten ersetzt.
## ⚙️ Konfiguration ✅
- [x] **K1**: `config/input-fields.json` mit Spaltenschema befüllen — `column` (A, B…), `field_name` (exakte Überschrift aus Excel), `type`, `required`
- [x] **K2**: `config/output-fields.json` mit Zell-Mappings befüllen — `source_field` (exakte Spaltenüberschrift aus input-fields.json), `target_cell`, `format`
- [x] **K3**: `config/settings.ini` mit Pfaden und Einstellungen befüllen
## 🔄 Migration: XLSX → PDF Template ✅
## 🧱 Core-Module ✅
- [x] **C1**: `requirements.txt` befüllen (streamlit, openpyxl)
- [x] **C2**: `src/__init__.py` + `src/models.py` — Datenklassen
- [x] **C3**: `src/config.py` — Konfigurations-Loader
- [x] **C4**: `src/parser.py` — Input-Datei parsen
- [x] **C5**: `src/validator.py` — Eingabevalidierung
- [x] **C6**: `src/writer.py` — Output generieren
- [x] **M1**: `requirements.txt` → `PyMuPDF` hinzufügen
- [x] **M2**: `config/settings.ini` → `filename_pattern` auf `.pdf`-Endung ändern
- [x] **M3**: `config/output-fields.json` → `target_cell` entfernen, `pdf_field` hinzufügen (Mapping source_field → PDF-Platzhalter-Name)
- [x] **M4**: `src/config.py` → `get_available_templates()` auf `.pdf`-Dateien umstellen
- [x] **M5**: `src/validator.py` → `validate_template()` für PDF angepasst (prüft `_Field`-Platzhalter via PyMuPDF)
- [x] **M6**: `src/writer.py` → Komplett neu: PDF-basierte Generierung mit PyMuPDF
- Pre-Scan: Alle `_Field`-Platzhalter im Template-PDF finden (Position, Schriftgröße)
- Für jeden Mitarbeiter: neues PDF, pro Monat eine Seite (Template-Kopie), `_Field`-Texte redacten + ersetzen
- Dateiname: `{nachname}_{vorname}_Tätigkeitsnachweis.pdf`
- [x] **M7**: `app.py` → Template-Validierung für PDF angepasst (Dropdown zeigt `.pdf`, Validierung prüft `_Field`-Marker)
- [x] **M8**: `coldrun.py` → An PDF-Template angepasst
- [x] **M9**: `tests/test_e2e.py` → Tests für PDF-Output umgeschrieben (PyMuPDF statt openpyxl zur Output-Validierung)
- [x] **M10**: `.copilot/PLAN.md` und `.copilot/TASKS.md` aktualisiert
## 🖥️ UI & Start ✅
- [x] **U1**: `app.py` — Streamlit-Oberfläche (Input-Selektion: validieren & Mitarbeiteranzahl + Antragsnummern anzeigen; Template-Selektion: validieren & Zielfelder anzeigen; Mitarbeiter-Multiselect zum Ein-/Ausschließen)
- [x] **U2**: `start.bat` — Windows Batch-Startdatei
- [x] **U3**: `README.md` — vollständige Dokumentation
## 🔍 Analyse (abgeschlossen)
- [x] **A1**: Input-Datei `input/2025 JM.xlsx` analysiert
- [x] **A2**: PDF-Template `template/Tätigkeitsnachweise Stellenförderung.pdf` liegt vor (mit `_Field`-Platzhaltern)
## ✅ Test ✅
- [x] **T1**: App mit vorhandener Input-Datei und Template testen — 19 Tätigkeitsnachweise erfolgreich generiert
- [x] **T2**: Edge Cases prüfen (leere Zeilen, Sonderzeichen, fehlende Monate) — Umlaute, Sonderzeichen in Namen, "Alle Rollen"-Sheet korrekt behandelt
## ⚙️ Konfiguration (migriert)
- [x] **K1**: `config/input-fields.json` — unverändert gültig
- [x] **K2**: `config/output-fields.json` — `target_cell` entfernt, `pdf_field` hinzugefügt
- [x] **K3**: `config/settings.ini` — filename_pattern auf `.pdf`
## 🧱 Core-Module
- [x] **C1**: `requirements.txt` — streamlit, openpyxl, PyMuPDF
- [x] **C2**: `src/__init__.py` + `src/models.py` — `OutputMapping` um `pdf_field` erweitert
- [x] **C3**: `src/config.py` — Template-Suche auf `.pdf` umgestellt
- [x] **C4**: `src/parser.py` — unverändert
- [x] **C5**: `src/validator.py` — `validate_template()` für PDF via PyMuPDF
- [x] **C6**: `src/writer.py` — Komplett neu mit PyMuPDF (redact + insert_text)
## 🖥️ UI & Start
- [x] **U1**: `app.py` — PDF-Template-Validierung angepasst
- [x] **U2**: `start.bat` — unverändert
- [x] **U3**: `coldrun.py` — An PDF angepasst, funktioniert
- [x] **U4**: `README.md` — vorhanden
## ✅ Test
- [x] **T1**: E2E-Test mit realer Input-Datei und PDF-Template → 5/5 Tests bestanden
- [x] **T2**: Edge Cases — Umlaute korrekt im PDF, fehlende Marker (Ort/Datum) werden übersprungen