# V3.0 – Infrastruktur, Transparenz und Bedienkomfort
**Status:** Freigegeben zur Implementierung
**Erstellt:** 2026-04-29
**Überarbeitet:** 2026-04-30 (nach ChatGPT-Review Runden 1, 2 und 3)
**Autor:** Marcus (mit Claude als Mentor)
---
## Ziel
V3.0 ist kein Wechsel der Kernfunktion, sondern ein gezielter Qualitätssprung
in drei Dimensionen:
1. **Infrastruktur** – saubere Versionierung, Flyway-DB-Migration,
Jenkins-Optimierung und MSI-Stabilisierung schaffen ein solides technisches
Fundament für alle weiteren Ausbaustufen.
2. **Transparenz** – der Historien-Tab macht die SQLite-Datenbank sichtbar;
Fehlerstatus werden benutzerfreundlich erklärt; ein Summary-Banner liefert
nach jedem Lauf sofort den Überblick.
3. **Bedienkomfort** – Tooltips, Statuszeile, Prompt-Editor und die Klärung der
Fehlerstatus-Semantik reduzieren den täglichen Reibungsverlust spürbar.
---
## Einordnung
V2.9 ist der abgeschlossene Ausgangspunkt. Die fachliche Kernverarbeitung
(PDF lesen → KI benennen → Zieldatei kopieren) bleibt in V3.0 **vollständig
unverändert**. Hexagonale Architektur, Modulstruktur, headless-Betrieb und
`.properties`-Konfigurationswahrheit bleiben unangetastet.
V3.0 fügt **kein neues Maven-Modul** hinzu.
---
## Scope
### In V3.0 enthalten
| # | Thema | Kategorie |
|---|---|---|
| #49 | Flyway für SQLite-DB-Migration | Infrastruktur |
| #65 | MSI-Installer Review & Optimierung | Infrastruktur |
| #67 | Konsistente Versionierung (POM / App / Jenkins) | Infrastruktur |
| #68 | Jenkins-Build-Optimierung | Infrastruktur |
| #51 | FAILED_RETRYABLE vs. FAILED_FINAL klären | Fachlich/UX |
| #7 | Historien-Tab (SQLite-DB-Ansicht) | GUI |
| #50 | Statuszeile | GUI |
| #66 | Tooltips | UX |
| #71 | Prompt-Editor in der GUI | GUI |
| #73 | Lauf-Statistik / Summary-Banner | GUI |
### Explizit nicht in V3.0
- Automatischer Scheduler / Quellordner-Überwachung (#22) → V3.x
- Token- und Kosten-Tracking (#74) → V3.x
- Excel-Export (#75) → V3.x
- Automatische Update-Prüfung (#76) → V3.x
- Dark Mode (#70) → V3.x
- Log-Viewer (#72) → V3.x
- PDF-Viewer Render-DPI (#23) → V3.x
- Zoom per Mausrad (#32) → V3.x
- Hilfe-Datei F1 (#69) → V3.x
- Änderung der fachlichen Kernverarbeitung
- Neue Maven-Module, neue KI-Provider, Architekturbrüche
---
## Unverrückbare Leitplanken (unverändert gegenüber V2.x)
- Java 21, Maven Multi-Module, hexagonale Architektur
- Shade-JAR als primäres Distributionsartefakt
- GUI ist Standardstart, `--headless` bleibt vollständig erhalten
- `.properties` bleibt die einzige Konfigurationswahrheit
- Kein Webserver, kein Applikationsserver
- GUI offiziell nur unter Windows; headless für Windows Server / Task Scheduler
- JavaFX-Threading: I/O auf Worker-Thread, UI-Updates via `Platform.runLater()`
- Kein JavaFX in Domain oder Application
- JavaDoc-Standard für alle neuen öffentlichen Ports, Use-Cases, DTOs und Adapter-Methoden
- Notwendige Code-Kommentare auf Deutsch; Logging auf Deutsch
---
## Verbindliche Status-Mapping-Tabelle
Diese Tabelle ist die einzige autoritative Quelle für Status-Darstellung in der GUI.
**Die Statusnamen wurden gegen den Domain-Enum `ProcessingStatus`
und die SQLite-Persistierung geprüft und entsprechen 1:1 dem Code.**
Persistierung via `.name()` – gespeicherter String = Enum-Konstantenname.
| Domain-Status (`ProcessingStatus`) | GUI-Icon | Farbe | GUI-Text (Tooltip) | Summary-Kategorie |
|---|---|---|---|---|
| `SUCCESS` | `✓` | Grün | „Erfolgreich verarbeitet und umbenannt." | erfolgreich |
| `FAILED_RETRYABLE` | `↻` | Orange | „Temporärer Fehler – wird beim nächsten Lauf automatisch erneut versucht." | wird wiederholt |
| `FAILED_FINAL` | `×` | Rot | „Dauerhaft nicht verarbeitbar – z. B. kein Textinhalt (Foto-PDF), Passwortschutz oder beschädigte Datei. Kein weiterer automatischer Versuch." | fehlgeschlagen |
| `SKIPPED_ALREADY_PROCESSED` | `≡` | Grau | „Übersprungen – wurde bereits in einem früheren Lauf erfolgreich verarbeitet." | übersprungen |
| `SKIPPED_FINAL_FAILURE` | `⊘` | Dunkelgrau | „Endgültig übersprungen nach wiederholten Fehlern." | endgültig übersprungen |
| `READY_FOR_AI` | `⟳` | Blau | „Wartet auf Verarbeitung." | – |
| `PROPOSAL_READY` | `◇` | Hellblau | „KI-Vorschlag liegt vor, wartet auf Bestätigung." | – |
| `PROCESSING` | `▶` | Hellgrau | „Wird gerade verarbeitet." | – |
**Wichtig:** Farbe ist niemals das einzige Unterscheidungsmerkmal.
Icon und Tooltip-Text müssen den Status allein eindeutig beschreiben.
**`PROCESSING` wird nicht als unvollständiger `processing_attempt` persistiert.**
`processing_attempt` wird erst nach vollständigem Abschluss eines Versuchs
geschrieben – `ended_at NOT NULL` bleibt daher immer erfüllbar.
`PROCESSING` tritt ausschließlich in `document_record.overall_status`
während eines laufenden Verarbeitungslaufs auf.
---
## Infrastruktur-Features
### #67 – Konsistente Versionierung
#### Problem
- Alle Maven-POMs: dauerhaft `0.0.1-SNAPSHOT`
- Anwendungsversion (`2.5.0`): manuell hardcoded als `app.version` im Packaging-POM
- Jenkins: MAJOR/MINOR als manuelle Parameter, werden nicht an Maven übergeben
#### Lösung: Maven CI-friendly Versioning
Im **Parent-POM** wird `${revision}` als zentrale Versions-Property eingeführt:
```xml
0.0.1-SNAPSHOT
${revision}
```
In allen **Kind-Modulen** (nur die ``-Referenz):
```xml
${revision}
```
**Wichtig:** Interne Modul-Abhängigkeiten zwischen Kind-Modulen verwenden
`${project.version}`, **nicht** `${revision}`:
```xml
pdf-umbenenner-domain
${project.version}
```
`flatten-maven-plugin` wird im **Parent-POM unter ``** aktiviert
(nicht nur unter `` – sonst wird es nicht ausgeführt):
```xml
org.codehaus.mojo
flatten-maven-plugin
1.6.0
true
resolveCiFriendliesOnly
flatten
process-resources
flatten
flatten.clean
clean
clean
```
#### Versionsnummer-Schema
```
MAJOR.MINOR.BUILD_NUMBER (Beispiel: 3.0.42)
```
- MAJOR/MINOR: manuell per Jenkins-Parameter (wie bisher)
- BUILD_NUMBER: automatisch durch Jenkins
- Jenkins übergibt: `-Drevision=${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}`
#### Verifikation
Nach `mvn clean install -Drevision=3.0.1` muss geprüft werden:
- Kein installiertes POM in `~/.m2` enthält noch `${revision}` unaufgelöst
- `java -jar pdf-ki-renamer-3.0.1.jar` zeigt in der Statuszeile exakt `V3.0.1`
#### MANIFEST.MF
Das Shade-Plugin im Bootstrap-Modul befüllt `Implementation-Version`.
Es muss sichergestellt werden, dass der Manifest-Transformer die Einträge
korrekt in das finale Fat-JAR überträgt:
```xml
${revision}
PDF KI Renamer
```
Laufzeit-Auslesen mit Fallback:
```java
String version = getClass().getPackage().getImplementationVersion();
if (version == null) {
version = "dev"; // Fallback für IDE-Start und ungepackten Betrieb
}
```
#### `pdf-umbenenner-packaging`
```xml
${revision}
```
---
### #68 – Jenkins-Build-Optimierung
#### Aktueller Stand
- `mvn clean verify` ohne `-Drevision` → POM bleibt `0.0.1-SNAPSHOT`
- JAR-Archivierung fragil
- MSI-Build nicht im CI (Linux-Container kann kein Windows-MSI bauen)
#### Verbesserungen
**1. Versionsübergabe an Maven:**
```groovy
stage('Maven Build') {
steps {
sh "mvn clean verify -Drevision=${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER}"
}
}
```
**2. Robuste Shade-JAR-Archivierung (exakt ein JAR erzwingen, Bash explizit):**
```groovy
stage('Archive JAR') {
steps {
sh '''#!/usr/bin/env bash
set -euo pipefail
mapfile -t JARS < <(find pdf-umbenenner-bootstrap/target \
-maxdepth 1 -name "pdf-umbenenner-bootstrap-*.jar" \
! -name "*-sources.jar" ! -name "*-javadoc.jar")
test "${#JARS[@]}" -eq 1 \
|| { echo "FEHLER: Erwartet genau 1 Shade-JAR, gefunden: ${#JARS[@]}"; exit 1; }
JAR_NAME="pdf-ki-renamer-${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}.jar"
cp "${JARS[0]}" "$JAR_NAME"
'''
archiveArtifacts artifacts: 'pdf-ki-renamer-*.jar', fingerprint: true
}
}
```
**3. MSI als manuell dokumentieren:**
Im Jenkinsfile-Kommentar und in `betrieb.md`:
> MSI-Build ist Windows-only (`jpackage + WiX Toolset 3.x`). Jenkins läuft im
> Linux-Container auf Synology NAS und kann kein MSI erzeugen. Der MSI-Build
> wird bewusst manuell auf der Windows-Entwicklungsmaschine ausgeführt:
> `.\mvnw.cmd clean package -P release -pl pdf-umbenenner-packaging --also-make -DskipTests`
#### Unverändert
MAJOR/MINOR-Persistenz in State-Datei, PIT/JaCoCo/JUnit-Berichte,
Artefakt-Ablage nach `/builds/`.
---
### #49 – Flyway für SQLite-DB-Migration
#### Motivation
Die manuelle `evolveTableColumns()`-Logik via `DatabaseMetaData.getColumns()`
ist schwer nachvollziehbar und fehleranfällig. Flyway ersetzt sie durch
versionierte, reproduzierbare Migrationsskripte.
#### Abhängigkeiten
```xml
org.flywaydb
flyway-core
```
Der SQLite-JDBC-Treiber (`org.xerial:sqlite-jdbc`) ist bereits im Projekt
vorhanden und bleibt unverändert. Flyway nutzt ihn über die DataSource.
URL-Format: `jdbc:sqlite:`.
#### Migrationsstrategie: Ein einziges Basisskript
`V1__initial_schema.sql` enthält das **vollständige Zielschema** aller Spalten
und Constraints. Es gibt keine V2 oder V3. Für neue DBs läuft V1 vollständig.
Für bestehende V2.9-DBs wird nach vollständiger Schema-Prüfung auf V1 baselined.
Ablage: `pdf-umbenenner-adapter-out/src/main/resources/db/migration/`
**`V1__initial_schema.sql`:**
```sql
CREATE TABLE document_record (
id INTEGER PRIMARY KEY AUTOINCREMENT,
fingerprint TEXT NOT NULL,
last_known_source_locator TEXT NOT NULL,
last_known_source_file_name TEXT NOT NULL,
overall_status TEXT NOT NULL,
content_error_count INTEGER NOT NULL DEFAULT 0,
transient_error_count INTEGER NOT NULL DEFAULT 0,
last_failure_instant TEXT,
last_success_instant TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
last_target_path TEXT,
last_target_file_name TEXT,
CONSTRAINT uq_document_record_fingerprint UNIQUE (fingerprint)
);
CREATE TABLE processing_attempt (
id INTEGER PRIMARY KEY AUTOINCREMENT,
fingerprint TEXT NOT NULL,
run_id TEXT NOT NULL,
attempt_number INTEGER NOT NULL,
started_at TEXT NOT NULL,
ended_at TEXT NOT NULL,
status TEXT NOT NULL,
failure_class TEXT,
failure_message TEXT,
retryable INTEGER NOT NULL DEFAULT 0,
model_name TEXT,
prompt_identifier TEXT,
processed_page_count INTEGER,
sent_character_count INTEGER,
ai_raw_response TEXT,
ai_reasoning TEXT,
resolved_date TEXT,
date_source TEXT,
validated_title TEXT,
final_target_file_name TEXT,
ai_provider TEXT,
CONSTRAINT fk_processing_attempt_fingerprint
FOREIGN KEY (fingerprint) REFERENCES document_record (fingerprint),
CONSTRAINT uq_processing_attempt_fingerprint_number
UNIQUE (fingerprint, attempt_number)
);
CREATE INDEX idx_processing_attempt_fingerprint
ON processing_attempt (fingerprint);
CREATE INDEX idx_processing_attempt_run_id
ON processing_attempt (run_id);
CREATE INDEX idx_document_record_overall_status
ON document_record (overall_status);
```
#### Differenzierte Startstrategie
Die Flyway-Initialisierung in `SqliteSchemaInitializationAdapter` unterscheidet
drei Fälle. **`baselineOnMigrate(true)` wird ausschließlich in Fall 2 gesetzt.**
```
Fall 1: DB-Datei existiert nicht, oder existiert aber enthält
keine fachlichen Tabellen und keine Flyway-History-Tabelle
(„leer" = keine Tabellen, nicht nur Dateigröße 0 Byte)
→ Flyway mit baselineOnMigrate=false
→ V1__initial_schema.sql wird ausgeführt → vollständige neue DB
Fall 2: DB existiert, enthält fachliche Tabellen, aber keine Flyway-History-Tabelle
(bestehende V2.9-DB)
→ Vollständige Schema-Prüfung gegen V1-Zielschema (siehe Checkliste unten)
→ Bei vollständig konformem Schema:
Backup erstellen (datierte Kopie der .sqlite-Datei)
Flyway mit baselineOnMigrate=true, baselineVersion="1"
migrate() → nur zukünftige Migrationen ab V2 werden ausgeführt
→ Bei nicht konformem Schema:
Start abbrechen mit klarer Fehlermeldung, kein stilles Weiterlaufen
Fall 3: DB existiert, Flyway-History-Tabelle vorhanden (regulärer Folgestart)
→ Flyway mit baselineOnMigrate=false → migrate() (idempotent)
```
#### Vollständige Schema-Prüfcheckliste für Fall 2
Da nach Baseline V1 **nicht mehr ausgeführt wird**, muss die bestehende DB
**vollständig** dem V1-Zielschema entsprechen. Geprüft wird via `DatabaseMetaData`:
**Tabellen:** `document_record` und `processing_attempt` müssen existieren.
**Alle Spalten `document_record`:**
`id`, `fingerprint`, `last_known_source_locator`, `last_known_source_file_name`,
`overall_status`, `content_error_count`, `transient_error_count`,
`last_failure_instant`, `last_success_instant`, `created_at`, `updated_at`,
`last_target_path`, `last_target_file_name`
**Alle Spalten `processing_attempt`:**
`id`, `fingerprint`, `run_id`, `attempt_number`, `started_at`, `ended_at`,
`status`, `failure_class`, `failure_message`, `retryable`,
`model_name`, `prompt_identifier`, `processed_page_count`, `sent_character_count`,
`ai_raw_response`, `ai_reasoning`, `resolved_date`, `date_source`,
`validated_title`, `final_target_file_name`, `ai_provider`
**Constraints (soweit per Metadata prüfbar):**
- `UNIQUE` auf `document_record.fingerprint`
- `UNIQUE` auf `(processing_attempt.fingerprint, processing_attempt.attempt_number)`
- Foreign Key von `processing_attempt.fingerprint` auf `document_record.fingerprint`
**Indizes:**
`idx_processing_attempt_fingerprint`, `idx_processing_attempt_run_id`,
`idx_document_record_overall_status`
Fehlt ein Element: Start mit Fehlermeldung abbrechen.
#### PRAGMA foreign_keys – Connection-sicheres Setzen
SQLite setzt `PRAGMA foreign_keys` pro Connection, nicht global.
Konfiguration via DataSource-Initializer:
```java
SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
SQLiteDataSource ds = new SQLiteDataSource(config);
```
Ein einmaliges Statement vor dem Flyway-Aufruf ist **nicht ausreichend**.
#### Gleichzeitige Starts / Concurrency
Flyway unterstützt bei SQLite keine gleichzeitigen Migrationen.
Parallele Starts werden über einen einfachen Lock-Mechanismus erkannt
(z. B. Lock-Datei neben der SQLite-Datei oder OS-Dateilock).
Bei aktivem Lock bricht der zweite Start mit einer verständlichen Meldung ab
(„Anwendung läuft bereits"). Korruption der DB ist in keinem Fall akzeptabel.
#### Architektur
- Integration in `SqliteSchemaInitializationAdapter.initializeSchema()`
- `PersistenceSchemaInitializationPort`-Vertrag bleibt unverändert
- `evolveTableColumns()` wird vollständig entfernt
---
### #65 – MSI-Installer Review & Optimierung
#### Drei kritische Baustellen (vor Release zwingend)
**1. Icon-Platzhalter ersetzen**
`src/main/packaging/icon.ico` ist ein 1×1-Pixel-Platzhalter.
Ersetzen durch echtes Anwendungsicon (`.ico`, mindestens 256×256 px).
**2. `addModules`-Liste aktualisieren**
Die Java-Modulliste ist veraltet. Neue Bibliotheken (PDFViewFX,
jai-imageio-jpeg2000, PDFBox) müssen erfasst werden:
```
jdeps --print-module-deps --ignore-missing-deps \
pdf-umbenenner-bootstrap-.jar
```
Das Ergebnis muss **manuell plausibilisiert** werden: Nach dem MSI-Build
wird die installierte Anwendung **ohne Entwicklungs-JDK** gestartet und
folgende Szenarien durchlaufen:
- GUI-Start
- PDF laden und rendern
- Verarbeitungslauf mit PDFBox-Textextraktion
- Historien-Tab öffnen
Nur eine erfolgreiche Laufzeit-Prüfung ohne JDK bestätigt die Modulliste.
**3. `app.version` ersetzen**
Nach #67: `${revision}`
#### Weitere Punkte
- `winUpgradeUuid` mit stabilem GUID einmalig generieren und dauerhaft pflegen
- Desktop-Shortcut verifizieren (`winShortcut=true` + `winShortcutPrompt=false`)
- MSI-Build bleibt bewusst manuell (Linux-CI kann kein MSI bauen)
#### Installationsstruktur nach MSI-Installation
```
\
PDF-KI-Renamer.bat (headless-Start via Task Scheduler)
PDF-KI-Renamer-GUI.bat (GUI-Start)
PDF KI Renamer\
PDF-KI-Renamer.exe
application.example.properties
(alle weiteren App-Dateien)
```
Beide BAT-Dateien sind Bestandteil des MSI-Installers und referenzieren
`%~dp0PDF KI Renamer\PDF-KI-Renamer.exe`. Sie müssen auch bei
**Installationspfaden mit Leerzeichen** korrekt funktionieren.
#### Hinweis zu Prompt- und Konfigurationspfaden unter MSI
In `betrieb.md` ist folgender Hinweis aufzunehmen:
> Für den MSI-Betrieb (Startmenü, Task Scheduler) wird **empfohlen**,
> `prompt.template.file` und `sqlite.path` als **absolute Pfade** zu konfigurieren
> oder auf `%APPDATA%`/`%ProgramData%`-Verzeichnisse zu zeigen.
> Relative Pfade beziehen sich auf das Arbeitsverzeichnis, das je nach Startart
> variiert (siehe Tabelle in #71).
#### Testmatrix
- [ ] Neuinstallation auf sauberer Windows-Umgebung ohne vorinstalliertes Java
- [ ] Installation in Pfad **mit Leerzeichen**
- [ ] Upgrade von installiertem V2.x-MSI auf V3.0-MSI (kein manuelles Deinstallieren)
- [ ] GUI-Start über Startmenü-Eintrag
- [ ] Headless-Start über `PDF-KI-Renamer.bat` im Task Scheduler
- [ ] Desktop-Shortcut vorhanden oder Einschränkung in `betrieb.md` dokumentiert
- [ ] App-Version `3.0.x` im Windows-Installer sichtbar („Programme und Features")
- [ ] Deinstallation sauber – ProgramData-Konfiguration bleibt erhalten
- [ ] SmartScreen-Warnung erwartet und in `betrieb.md` dokumentiert
- [ ] BAT-Dateien funktionieren bei Installationspfad mit Leerzeichen
- [ ] Anwendungsstart ohne Entwicklungs-JDK erfolgreich (Modullisten-Verifikation)
---
## Fachliche Features
### #51 – FAILED_RETRYABLE vs. FAILED_FINAL
Semantik und GUI-Darstellung gemäß verbindlicher Status-Mapping-Tabelle.
Die interne Status-Unterscheidung bleibt vollständig erhalten.
Kein Umbau an Domain oder Persistenz.
#### Detailbereich bei FAILED_FINAL
> „Diese Datei kann nicht verarbeitet werden. Mögliche Ursachen:
> kein lesbarer Text (z. B. gescanntes Foto ohne OCR), Passwortschutz
> oder beschädigte Datei.
> Sie können den Status manuell zurücksetzen, wenn Sie die Ursache
> behoben haben."
„Erneut verarbeiten" und „Status zurücksetzen" bleiben für beide
Fehlerstatus verfügbar.
---
## GUI-Features
### #7 – Historien-Tab
#### Ziel
Neuer Tab „Verlauf" zeigt alle jemals verarbeiteten Dokumente mit Status
und Verarbeitungsdetails aus der SQLite-Datenbank.
#### Layout
```
[ Konfiguration | Verarbeitungslauf | Verlauf | Prompt ]
[ Suchfeld / Filter ] [ Status-Filter ▾ ] [ Aktualisieren ]
[ Dokumentenliste (~55%) | Detailbereich (~45%) ]
[ | Dokument-Info ]
[ | Versuche-Tabelle ]
[ | KI-Begründung ]
[ Statuszeile ]
```
#### Dokumentenliste (linke Tabelle)
| Spalte | Quelle |
|---|---|
| Status-Icon | `document_record.overall_status` |
| Quelldateiname | `document_record.last_known_source_file_name` |
| Zieldateiname | `document_record.last_target_file_name` |
| Quellpfad | `document_record.last_known_source_locator` |
| Letzter Versuch | `document_record.updated_at` |
| Anzahl Versuche | `COUNT(processing_attempt)` |
**Sortierung:** Standard absteigend nach `updated_at`,
Tie-Breaker: `fingerprint ASC` (stabil und reproduzierbar).
**Initiales Limit (kein echtes Paging in V3.0):**
- `LIMIT 501` in der Query – 501 um „mehr vorhanden" sauber zu erkennen
- Bei mehr als 500 Treffern: „Weitere Einträge vorhanden – Filter verwenden"
- Bei leerer DB: „Noch keine Verarbeitungen vorhanden."
- Lange Dateinamen/Pfade: Ellipsis in der Tabelle, Tooltip zeigt vollen Text
**Filter (DB-seitig, kein GUI-seitiger Vollscan):**
- Freitextsuche über Quelldateiname und Zieldateiname
- Case-insensitiv (`COLLATE NOCASE` oder `LOWER()`)
- Sonderzeichen `%` und `_` in der Sucheingabe werden escaped
- Status-ComboBox: alle / einzelner Status
**`READY_FOR_AI`, `PROPOSAL_READY` und `PROCESSING` werden im Summary-Banner
nicht gezählt** – das Banner erscheint nach Laufabschluss und zeigt
ausschließlich abgeschlossene Ergebnisse.
#### Detailbereich (rechte Seite)
**Dokument-Info:** Fingerprint (12 Zeichen), Quelldateiname, Quellpfad,
Status (Icon + Text), Erstellt/Aktualisiert.
**Versuche-Tabelle:**
| Spalte | Quelle |
|---|---|
| # | `processing_attempt.attempt_number` |
| Datum | `processing_attempt.ended_at` |
| Status | `processing_attempt.status` |
| Provider | `processing_attempt.ai_provider` |
| Modell | `processing_attempt.model_name` |
| Vorgeschlagener Name | `processing_attempt.final_target_file_name` |
**KI-Begründung:** `processing_attempt.ai_reasoning` als nicht editierbare TextArea.
#### Aktionen
| Aktion | Beschreibung |
|---|---|
| **Status zurücksetzen** | Setzt definierte Felder zurück (siehe unten). **Versuche bleiben vollständig erhalten.** Bestätigungsdialog. |
| **Eintrag löschen** | Löscht Stammsatz und alle Versuche vollständig (destruktiv, nicht rückgängig). Bestätigungsdialog mit explizitem Hinweis. |
**`DefaultResetDocumentStatusUseCase` – feldgenaue Definition:**
Folgende Felder werden aktualisiert:
| Feld | Neuer Wert |
|---|---|
| `overall_status` | `READY_FOR_AI` |
| `content_error_count` | `0` |
| `transient_error_count` | `0` |
| `last_failure_instant` | `null` |
Folgende Felder werden **nicht** geändert:
| Feld | Begründung |
|---|---|
| `created_at` | Historisches Datum, immer unverändert |
| `last_success_instant` | Historische Information, bleibt erhalten |
| `last_target_path` / `last_target_file_name` | Historische Information, bleibt erhalten |
| `processing_attempt`-Einträge | Verlaufshistorie, bleibt vollständig erhalten |
Nach dem Reset muss das Dokument beim nächsten Lauf tatsächlich als
verarbeitbar gelten. `READY_FOR_AI` ist der einzige Trigger für die
Verarbeitungslogik. Alte Versuche dürfen die Wiederverarbeitung nicht blockieren.
**`DefaultDeleteDocumentHistoryUseCase` – Löschreihenfolge:**
Da der Foreign Key von `processing_attempt.fingerprint` auf
`document_record.fingerprint` **ohne `ON DELETE CASCADE`** definiert ist und
`PRAGMA foreign_keys` aktiv ist, muss innerhalb einer Transaktion
in folgender Reihenfolge gelöscht werden:
1. Alle `processing_attempt`-Einträge zum Fingerprint
2. Den `document_record`-Stammsatz zum Fingerprint
**Sperren während aktivem Verarbeitungslauf:**
Beide Aktionen sind während eines laufenden Verarbeitungslaufs **deaktiviert**.
Hinweis: „Aktion während Verarbeitungslauf nicht möglich."
#### Architektur
Das bestehende Muster des Projekts:
```
GUI-Adapter → Gui...Port (Bridge-Interface in adapter-in-gui)
↑ implementiert von Bootstrap
Default...UseCase (in application)
↓
Outbound-Ports → Adapter
```
Neue Komponenten:
| Komponente | Typ | Modul |
|---|---|---|
| `GuiHistoryOverviewPort` | Bridge-Interface | `adapter-in-gui` |
| `GuiHistoryDetailsPort` | Bridge-Interface | `adapter-in-gui` |
| `GuiResetDocumentStatusPort` | Bridge-Interface | `adapter-in-gui` |
| `GuiDeleteDocumentHistoryPort` | Bridge-Interface | `adapter-in-gui` |
| `DefaultHistoryOverviewUseCase` | Use-Case-Impl. | `application` |
| `DefaultHistoryDetailsUseCase` | Use-Case-Impl. | `application` |
| `DefaultResetDocumentStatusUseCase` | Use-Case-Impl. | `application` |
| `DefaultDeleteDocumentHistoryUseCase` | Use-Case-Impl. | `application` |
Der GUI-Adapter kennt ausschließlich `Gui...Port`-Interfaces – niemals direkt
`DocumentRecordRepository` oder `ProcessingAttemptRepository`.
**Threading:** Lesende DB-Zugriffe auf Worker-Thread, UI via `Platform.runLater()`.
Kein Echtzeit-Tailing – manueller „Aktualisieren"-Button.
---
### #50 – Statuszeile
Permanente Statuszeile am unteren Rand der Anwendung (unterhalb aller Tabs):
```
[ V3.0.42 ] [ Provider: Claude · claude-opus-4-7 (Beispiel) ] [ config/application.properties ]
```
| Segment | Inhalt | Quelle |
|---|---|---|
| Links | `V{version}` | `MANIFEST.MF` via `getImplementationVersion()`, Fallback: `dev` |
| Mitte | Aktiver Provider und Modell | Geladene Konfiguration |
| Rechts | Konfigurationsdateipfad | Aktueller Ladezustand |
- Permanent sichtbar auf allen Tabs
- Aktualisierung bei Laden/Speichern neuer Konfiguration
- Kein Profil geladen: „Kein Profil geladen"
- Version immer sichtbar, auch ohne geladene Konfiguration
- Voraussetzung: #67 muss umgesetzt sein
---
### #66 – Tooltips
JavaFX `Tooltip` auf allen Buttons, Feldern und Status-Icons.
Farbe ist niemals das einzige Unterscheidungsmerkmal.
Bei der Umsetzung wird eine UI-Checkliste pro Tab geführt.
**Konfigurationstab:**
| Element | Tooltip |
|---|---|
| Quellordner | „Ordner mit den zu verarbeitenden PDF-Dateien. Inhalt wird nicht verändert." |
| Zielordner | „Ordner für die umbenannten Kopien." |
| SQLite-Datei | „Datenbank für Verarbeitungsergebnisse und Datei-Historie." |
| Prompt-Datei | „Externe Textdatei mit den KI-Anweisungen." |
| Provider-ComboBox | „Der KI-Dienst, der die Dateinamen generiert." |
| Modell-Feld | „Das konkrete Sprachmodell des gewählten Providers." |
| max.text.characters | „Maximale Zeichenzahl aus dem PDF-Text. Höhere Werte = mehr Kontext, höhere Kosten." |
| max.pages | „Maximale Seitenzahl, die aus einem PDF gelesen wird." |
| max.title.length | „Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10–120." |
**Tab „Verarbeitungslauf":** Alle Status-Icons gemäß Status-Mapping-Tabelle.
| Element | Tooltip |
|---|---|
| „Dateiname übernehmen" | „Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen." |
| „Zurücksetzen auf KI-Vorschlag" | „Stellt den KI-generierten Namen wieder her, ohne zu speichern." |
**Toolbar:**
| Button | Tooltip |
|---|---|
| Neu | „Neue Konfiguration erstellen." |
| Öffnen | „Bestehende Konfigurationsdatei (.properties) öffnen." |
| Speichern | „Aktuelle Konfiguration speichern." |
| Speichern unter | „Konfiguration unter neuem Dateipfad speichern." |
| Validieren | „Aktuelle Eingaben auf Vollständigkeit und Korrektheit prüfen." |
| Technische Tests | „Dateipfade, Datenbankverbindung und KI-Erreichbarkeit prüfen." |
Technisch: `element.setTooltip(new Tooltip("Text"))` –
rein im Modul `pdf-umbenenner-adapter-in-gui`.
---
### #71 – Prompt-Editor in der GUI
#### Ziel
Eigener Tab „Prompt" zum Lesen, Bearbeiten und Speichern der
KI-Prompt-Datei – kein externer Editor notwendig.
#### Bestehende Infrastruktur
| Komponente | Stand |
|---|---|
| `PromptPort.loadPrompt()` | ✅ existiert |
| `ResourceCreationPort.createPromptFile()` | ✅ existiert (nur Default-Template) |
| Port zum Speichern beliebigen Inhalts | ❌ fehlt – neu erstellen |
#### Neuer Port
`PromptPort` wird um eine Schreibmethode erweitert:
```java
PromptSaveResult savePrompt(String content);
```
`savePrompt(String content)` speichert immer in die aktuell über die
geladene Konfiguration aufgelöste Prompt-Datei. Der Pfad wird im Use-Case
aus der Konfiguration bestimmt und dem Adapter intern bereitgestellt –
er ist **nicht** Teil der Port-Signatur.
Implementierung: `FilesystemPromptPortAdapter` im Modul `pdf-umbenenner-adapter-out`.
#### Architektur
| Komponente | Typ | Modul |
|---|---|---|
| `GuiPromptEditorPort` | Bridge-Interface | `adapter-in-gui` |
| `DefaultPromptEditorUseCase` | Use-Case-Impl. | `application` |
Der GUI-Adapter greift **nicht** direkt auf `PromptPort` oder das Dateisystem zu.
#### Verhalten
- **Tab-Öffnen:** Inhalt der konfigurierten Prompt-Datei laden (Worker-Thread)
- **Keine Datei konfiguriert/vorhanden:** Hinweistext +
„Standard-Prompt erstellen"-Button (ruft `createPromptFile()` auf)
- **Bearbeitung** in `TextArea`
- **Dirty State:** Tab-Titel mit Asterisk (`Prompt *`)
- **„Speichern":** schreibt atomar (temp-Datei **im selben Verzeichnis** wie
Zieldatei → `ATOMIC_MOVE`), Encoding: **UTF-8**,
Zeilenenden: unverändert übernommen (keine Normalisierung)
- **„Auf Standard zurücksetzen":** befüllt TextArea mit Default, **ohne** zu speichern
- **Tab-Wechsel/Schließen mit Dirty State:** Bestätigungsdialog „Änderungen verwerfen?"
#### Fehlerfälle
| Fehlerfall | Verhalten |
|---|---|
| Konfigurierter Ordner nicht vorhanden | Fehlermeldung im Tab, kein Absturz |
| Datei schreibgeschützt | Fehlermeldung nach Speicherversuch |
| Zielordner während Bearbeitung gelöscht | Fehlermeldung beim Speichern |
| `ATOMIC_MOVE` schlägt fehl | Speichervorgang abbrechen, Fehlermeldung – kein stiller Fallback auf Teilüberschreibung |
| Leerer Prompt beim Speichern | Bestätigungsdialog: „Wirklich leere Prompt-Datei speichern?" |
| Extern geänderte Datei | Last-write-wins (kein automatisches Reload) |
#### Pfadauflösung (Verhalten beibehalten, dokumentieren)
Der Prompt-Pfad wird relativ zum **Arbeitsverzeichnis** aufgelöst – wie bisher.
`betrieb.md` muss folgende Tabelle enthalten:
| Startart | Arbeitsverzeichnis |
|---|---|
| IDE | Projektwurzel |
| Fat-JAR per Konsole | Aktuelles Terminal-Verzeichnis |
| MSI / Startmenü | Installationsverzeichnis |
| Task Scheduler headless | Explizit gesetztes „Start in"-Verzeichnis empfohlen |
**Empfehlung für MSI-Betrieb** (in `betrieb.md`): Für MSI-Betrieb über
Startmenü oder Task Scheduler werden **absolute Pfade** für
`prompt.template.file` und `sqlite.path` empfohlen, idealerweise auf
`%APPDATA%`- oder `%ProgramData%`-Verzeichnisse.
Langfristig (V3.x): Pfadauflösung relativ zur geladenen `.properties`-Datei.
#### Threading
Laden und Speichern auf Worker-Thread, UI via `Platform.runLater()`.
---
### #73 – Lauf-Statistik / Summary-Banner
Banner erscheint nach Laufabschluss, unterhalb des Fortschrittsbalkens,
oberhalb der Ergebnistabelle:
```
✓ 14 erfolgreich · ↻ 1 wird wiederholt · × 2 fehlgeschlagen · ≡ 3 übersprungen · ⊘ 1 endgültig übersprungen
```
- Nur Kategorien mit Anzahl > 0 (inkl. `⊘ endgültig übersprungen` wenn > 0)
- `READY_FOR_AI`, `PROPOSAL_READY` und `PROCESSING` werden **nicht gezählt**
- Bei vollständig sauberem Lauf: `✓ 17 erfolgreich`
- Verschwindet beim Start des nächsten Laufs
- Kein Banner bei Anwendungsstart oder Tab-Wechsel ohne Lauf
- Icon + Text tragen die Information, nicht nur Farbe
- Reine GUI-Logik, kein neuer Port; Aggregation aus bestehender Ergebnisliste
---
## Architektur-Zusammenfassung
### Neue Ports / Use-Cases
| Komponente | Modul | Zweck |
|---|---|---|
| `PromptPort.savePrompt(String)` | `application` | Prompt-Inhalt speichern (#71) |
| `DefaultPromptEditorUseCase` | `application` | Prompt-Editor-Logik (#71) |
| `DefaultHistoryOverviewUseCase` | `application` | Dokumentenliste Historien-Tab (#7) |
| `DefaultHistoryDetailsUseCase` | `application` | Detailansicht Historien-Tab (#7) |
| `DefaultResetDocumentStatusUseCase` | `application` | Feldgenauer Status-Reset, Versuche bleiben (#7) |
| `DefaultDeleteDocumentHistoryUseCase` | `application` | Transaktionales Löschen in korrekter Reihenfolge (#7) |
### Neue Bridge-Interfaces (adapter-in-gui)
| Interface | Zweck |
|---|---|
| `GuiPromptEditorPort` | Brücke zum Prompt-Editor-Use-Case (#71) |
| `GuiHistoryOverviewPort` | Brücke zur Dokumentenliste (#7) |
| `GuiHistoryDetailsPort` | Brücke zur Detailansicht (#7) |
| `GuiResetDocumentStatusPort` | Brücke zum Status-Reset (#7) |
| `GuiDeleteDocumentHistoryPort` | Brücke zum vollständigen Löschen (#7) |
### Geänderte Adapter
| Adapter | Modul | Änderung |
|---|---|---|
| `FilesystemPromptPortAdapter` | `adapter-out` | Neue Methode `savePrompt()` (#71) |
| `SqliteSchemaInitializationAdapter` | `adapter-out` | Ersetzt durch Flyway-Integration (#49) |
### Nicht geändert
- `pdf-umbenenner-domain` – keine Änderungen
- `pdf-umbenenner-adapter-in-cli` – keine Änderungen
- `pdf-umbenenner-bootstrap` – nur MANIFEST.MF-Ergänzung (#67) + neue Bridge-Verdrahtung
- Headless-Betrieb – vollständig unberührt
---
## Definition of Done (V3.0 gesamt)
- [ ] Alle 10 Issues implementiert und einzeln getestet
- [ ] `mvn clean verify` grün (alle Module, kein `-DskipTests`)
- [ ] `mvn clean install -Drevision=3.0.1` – kein unaufgelöstes `${revision}` in installierten POMs
- [ ] `java -jar pdf-ki-renamer-3.0.1.jar` zeigt in Statuszeile exakt `V3.0.1`
- [ ] Bestehende V2.9-DB via Flyway-Baseline korrekt übernommen – kein Datenverlust
- [ ] Historien-Tab zeigt korrekte Daten aus einer V2.9-DB
- [ ] Versionsnummer konsistent: MANIFEST.MF, Statuszeile, MSI und JAR-Dateiname
- [ ] Jenkins-Build läuft mit `-Drevision`-Übergabe durch; Bash-Skript schlägt bei 0 oder >1 JAR ab
- [ ] MSI-Testmatrix vollständig abgearbeitet inkl. Anwendungsstart ohne JDK
- [ ] Tooltips vollständig auf allen spezifizierten Elementen (UI-Checkliste pro Tab)
- [ ] Prompt-Editor: alle Szenarien inkl. Fehlerfälle getestet
- [ ] `FAILED_RETRYABLE` und `FAILED_FINAL` eindeutig durch Icon, Farbe und Text unterschieden
- [ ] „Status zurücksetzen" setzt feldgenau zurück, löscht keine Versuche, Datei wird wieder verarbeitet
- [ ] „Eintrag löschen" löscht in korrekter Reihenfolge (Attempts vor Record)
- [ ] Destruktive Aktionen während Lauf gesperrt
- [ ] Notwendige Code-Kommentare auf Deutsch; Logging auf Deutsch
- [ ] JavaDoc auf allen neuen öffentlichen Ports, Use-Cases, DTOs und Adapter-Methoden
- [ ] `betrieb.md` enthält Prompt-Pfadauflösungs-Tabelle je Startart + MSI-Absolute-Pfad-Empfehlung
- [ ] `betrieb.md` und `gui-bedienanleitung.md` auf V3.0-Stand gebracht
- [ ] Freigabedokument `freigabe-v3_0.md` erstellt
- [ ] Manueller GUI-Produkttest durchgeführt (Green build ≠ fertige Software)
---
## Abnahmekriterien je Feature
### #49 Flyway
- [ ] Neue SQLite-DB vollständig per Flyway aus V1 aufgebaut
- [ ] „Leer" korrekt erkannt: keine Tabellen vorhanden (nicht nur Dateigröße 0 Byte)
- [ ] Bestehende V2.9-DB: vollständige V1-Schema-Prüfcheckliste durchlaufen
- [ ] `baselineOnMigrate=true` ausschließlich in Fall 2 gesetzt
- [ ] Start mit nicht konformem Schema bricht mit Fehlermeldung ab
- [ ] PRAGMA foreign_keys via DataSource-Initializer (nicht einmaliges Statement)
- [ ] `evolveTableColumns()` vollständig entfernt
- [ ] Lock-Mechanismus verhindert korrupte DB bei parallelem Start
### #65 MSI
- [ ] Echtes Icon (kein 1×1-Platzhalter)
- [ ] `addModules`-Liste per `jdeps` aktualisiert und per Laufzeit-Test ohne JDK verifiziert
- [ ] Version entspricht V3.0-Stand (`${revision}`)
- [ ] `winUpgradeUuid` gesetzt
- [ ] Installationsstruktur korrekt: BAT-Dateien neben App-Verzeichnis
- [ ] BAT-Dateien funktionieren bei Pfad mit Leerzeichen
- [ ] Vollständige Testmatrix abgearbeitet
- [ ] `betrieb.md` mit MSI-Hinweisen zu ProgramData, absoluten Pfaden und SmartScreen
### #67 Versionierung
- [ ] `${revision}` im Parent-POM eingeführt
- [ ] `flatten-maven-plugin` in `` des Parent-POM (nicht nur pluginManagement)
- [ ] `flatten-maven-plugin` mit `resolveCiFriendliesOnly` und `flatten:clean`
- [ ] Interne Modul-Abhängigkeiten verwenden `${project.version}`
- [ ] MANIFEST.MF enthält `Implementation-Version` korrekt im Fat-JAR
- [ ] Fallback `"dev"` bei null implementiert
- [ ] `app.version` im Packaging-Modul nutzt `${revision}`
### #68 Jenkins
- [ ] Maven-Build übergibt `-Drevision=MAJOR.MINOR.BUILD_NUMBER`
- [ ] Bash explizit erzwungen (`#!/usr/bin/env bash`, `set -euo pipefail`)
- [ ] `mapfile`-Prüfung: Build bricht ab bei 0 oder >1 JAR
- [ ] MSI-Build als manuell dokumentiert
### #51 FAILED_RETRYABLE vs. FAILED_FINAL
- [ ] Icons (↻ vs. ×), Farben (Orange vs. Rot) und Tooltips eindeutig unterschiedlich
- [ ] Detailbereich zeigt erweiterte Erklärung bei `FAILED_FINAL`
- [ ] Farbe ist nicht das einzige Unterscheidungsmerkmal
### #7 Historien-Tab
- [ ] Tab „Verlauf" sichtbar und navigierbar
- [ ] `LIMIT 501`-Technik, Hinweis bei mehr als 500 Treffern
- [ ] Hinweistext bei leerer DB
- [ ] Sortierung mit stabilem Tie-Breaker
- [ ] Suche case-insensitiv, Sonderzeichen escaped
- [ ] Detailbereich zeigt Versuche und KI-Begründung
- [ ] „Status zurücksetzen" setzt feldgenau zurück (Status, Zähler, last_failure_instant)
- [ ] „Status zurücksetzen" löscht keine Versuche; Datei wird beim nächsten Lauf verarbeitet
- [ ] „Eintrag löschen": Attempts vor Record, innerhalb Transaktion, mit Bestätigungsdialog
- [ ] Beide Aktionen während Lauf deaktiviert
### #50 Statuszeile
- [ ] Version aus MANIFEST.MF, Fallback `dev`
- [ ] Provider und Modell aus geladener Konfiguration
- [ ] Konfigurationspfad korrekt
- [ ] Aktualisierung bei Laden/Speichern neuer Konfiguration
- [ ] Kein Absturz wenn keine Konfiguration geladen
### #66 Tooltips
- [ ] UI-Checkliste pro Tab geführt und abgearbeitet
- [ ] Alle spezifizierten Elemente haben Tooltips
- [ ] Farbe ist nicht einziges Unterscheidungsmerkmal
### #71 Prompt-Editor
- [ ] Laden beim Tab-Öffnen (Worker-Thread)
- [ ] Dirty State korrekt angezeigt
- [ ] Speichern atomar (temp im selben Verzeichnis + `ATOMIC_MOVE`), Encoding UTF-8
- [ ] Kein stiller Fallback bei fehlgeschlagenem `ATOMIC_MOVE`
- [ ] „Auf Standard zurücksetzen" ohne automatisches Speichern
- [ ] Bestätigungsdialog bei Tab-Wechsel mit Dirty State
- [ ] Alle Fehlerfälle laut Spezifikation getestet
- [ ] `betrieb.md` enthält Pfadauflösungs-Tabelle + MSI-Empfehlung für absolute Pfade
### #73 Summary-Banner
- [ ] Korrekte Zählwerte nach Lauf inkl. `⊘ endgültig übersprungen`
- [ ] `READY_FOR_AI`, `PROPOSAL_READY`, `PROCESSING` werden nicht gezählt
- [ ] Nur Kategorien > 0 angezeigt
- [ ] Verschwindet beim nächsten Laufstart
- [ ] Icon + Text tragen Information, nicht nur Farbe