From 01e97848a768b18c0d31523ec914c3da354ff7d9 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 30 Apr 2026 10:30:50 +0200 Subject: [PATCH] =?UTF-8?q?Spezifikation=20f=C3=BCr=20V3.0=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/specs/V3_0_-_Spezifikation.md | 1030 ++++++++++++++++++++++++++++ 1 file changed, 1030 insertions(+) create mode 100644 docs/specs/V3_0_-_Spezifikation.md diff --git a/docs/specs/V3_0_-_Spezifikation.md b/docs/specs/V3_0_-_Spezifikation.md new file mode 100644 index 0000000..6bee5c6 --- /dev/null +++ b/docs/specs/V3_0_-_Spezifikation.md @@ -0,0 +1,1030 @@ +# 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