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

39 KiB
Raw Blame History

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:

<properties>
    <revision>0.0.1-SNAPSHOT</revision>
</properties>
<version>${revision}</version>

In allen Kind-Modulen (nur die <parent>-Referenz):

<parent>
    <version>${revision}</version>
</parent>

Wichtig: Interne Modul-Abhängigkeiten zwischen Kind-Modulen verwenden ${project.version}, nicht ${revision}:

<!-- 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):

<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:

<manifestEntries>
    <Implementation-Version>${revision}</Implementation-Version>
    <Implementation-Title>PDF KI Renamer</Implementation-Title>
</manifestEntries>

Laufzeit-Auslesen mit Fallback:

String version = getClass().getPackage().getImplementationVersion();
if (version == null) {
    version = "dev"; // Fallback für IDE-Start und ungepackten Betrieb
}

pdf-umbenenner-packaging

<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:

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):

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

<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:

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:

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:

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