Files
pdf-umbenenner/docs/specs/V3_0_-_Spezifikation.md
T

1031 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<properties>
<revision>0.0.1-SNAPSHOT</revision>
</properties>
<version>${revision}</version>
```
In allen **Kind-Modulen** (nur die `<parent>`-Referenz):
```xml
<parent>
<version>${revision}</version>
</parent>
```
**Wichtig:** Interne Modul-Abhängigkeiten zwischen Kind-Modulen verwenden
`${project.version}`, **nicht** `${revision}`:
```xml
<!-- korrekt für interne Modul-Abhängigkeiten: -->
<dependency>
<artifactId>pdf-umbenenner-domain</artifactId>
<version>${project.version}</version>
</dependency>
```
`flatten-maven-plugin` wird im **Parent-POM unter `<build><plugins>`** aktiviert
(nicht nur unter `<pluginManagement>` sonst wird es nicht ausgeführt):
```xml
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals><goal>flatten</goal></goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals><goal>clean</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
#### 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
<manifestEntries>
<Implementation-Version>${revision}</Implementation-Version>
<Implementation-Title>PDF KI Renamer</Implementation-Title>
</manifestEntries>
```
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
<appVersion>${revision}</appVersion>
```
---
### #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
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<!-- Version zentral im Parent-POM pinnen -->
</dependency>
```
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:<pfad-zur-datei>`.
#### 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-<version>.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: `<appVersion>${revision}</appVersion>`
#### 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
```
<InstallDir>\
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: 10120." |
**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 `<build><plugins>` 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