Diagnose-Verbesserungen rund um die KI-Verarbeitung:
- Verlauf-Tab zeigt die vollständige KI-Rohantwort des ausgewählten Versuchs an
(neues Feld "KI-Rohantwort"). Die technische Original-Fehlermeldung ist zusätzlich
als Tooltip der Fehlerursache verfügbar.
- AiFailureMessageTranslator: eigene, freundliche Meldung für strukturell nicht
verwertbare KI-Antworten (NOT_JSON_OBJECT/INVALID_JSON u.a.); der bisher generische
Fallback zeigt jetzt den echten technischen Grund an, statt ihn zu verbergen.
- Log-Verzeichnis wird im GUI-Modus zur Laufzeit auf das in der geladenen Konfiguration
konfigurierte Verzeichnis umgestellt (RuntimeLogDirectoryReconfigurer, Best-Effort,
Log4j2-Reconfigure). Bisher landeten GUI-Logs im user.home-Fallback, weil die GUI
ohne --config startet. Der headless Pfad bleibt unverändert.
- Standard-Prompt: explizit keine Markdown-Codeblöcke/Zäune, nur reines JSON;
Unsicherheiten gehören ins reasoning-Feld.
Tests fuer alle vier Punkte ergaenzt/aktualisiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AiResponseParser lehnte jede Antwort ab, die nach dem Trimmen nicht mit '{'
begann und mit '}' endete (NOT_JSON_OBJECT). claude-sonnet-5 liefert das
JSON gelegentlich mit Einleitungstext oder in ```json ...```-Codeblöcken; solche
Antworten scheiterten dann als transienter Fehler und wurden endlos wiederholt
(betroffen u.a. Scan260701074355.pdf, sporadisch weitere Dokumente).
Der Parser löst nun das erste ausbalancierte JSON-Objekt aus der Antwort heraus
(Klammerzählung mit Beachtung von String-Literalen und Escapes) und ignoriert
umgebenden Fließtext sowie Markdown-Codeblöcke. Reine JSON-Objekt-Antworten
verhalten sich unverändert; ein Top-Level-Array (oder sonstige Nicht-Objekt-
Struktur) wird weiterhin als NOT_JSON_OBJECT abgelehnt. Regressionstests ergänzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SqliteHistoryQueryAdapter.findAttemptsByFingerprint las die Token-/Preis-Spalten,
die die SELECT-Liste nicht projizierte -> "no such column: 'input_tokens'" bei
jedem Dokument mit Versuchen. Spalten in die SELECT-Liste aufgenommen (konsistent
mit dem Schreibpfad in SqliteProcessingAttemptRepositoryAdapter.save).
- EarlyLogDirectoryInitializer las die .properties ohne die Backslash-Absicherung
des regulaeren Konfig-Lesers; Windows-Pfade wie C:\Users\...\logs wurden von
java.util.Properties verstuemmelt, das konfigurierte Log-Verzeichnis nie angelegt
und der Fehler blieb unsichtbar. Backslashes vor dem Laden verdoppeln, Pfad
normalisieren, Verzeichnis best-effort anlegen; bei Fehlschlag sichtbar auf das
Standard-Log-Verzeichnis ausweichen (WARN nach Log4j2-Init).
- Regressionstests fuer beide Faelle ergaenzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ohne Override und ohne geladene Konfiguration lieferte der aktive-DB-Supplier
faelschlich den Zielpfad selbst zurueck, wodurch die Selbst-Ueberschreib-Pruefung
immer ansprach und jede Anlage mit "Zielpfad ist die aktuell aktive
Datenbankdatei" abgelehnt wurde.
Der Supplier signalisiert "keine aktive Datenbank" nun mit null; der Use-Case
ueberspringt in diesem Fall die Pruefung und laesst die Anlage zu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Erste Stufe der V3.3-Spezifikation: Token- und Kosten-Tracking-Fundament.
Schema und Persistenz:
- Neue Flyway-Migration V2__token_tracking.sql mit sechs Token-/Preis-Snapshot-
Spalten in processing_attempt, neuer model_price-Tabelle (Composite-Key
provider+model_name) und Default-Preisen fuer beide Provider-Familien.
- SqliteModelPriceRepositoryAdapter mit UPSERT, transaktionalem Batch und
invalidUpdatedAt-Mapping.
- Zentrale SqliteConnectionFactory; alle direkten DriverManager.getConnection-
Stellen in den Repository-Adaptern (Document, Attempt, History, UnitOfWork)
auf die Factory umgezogen, damit WAL und busy_timeout pro Connection greifen.
Application und Domain:
- Neue DTOs AiUsageMetadata, ModelPriceEntry/View/Key/ChangeSet, CostResult.
- AiInvocationSuccess um usageMetadata erweitert; AiAttemptContext um vier
nullable Token-Felder.
- ProcessingAttempt um sechs Token-/Preis-Snapshot-Felder erweitert
(Convenience-Konstruktor und withoutAiFields-Factory unveraendert).
- ModelPriceRepository-Port mit Schreib-/Lese-Trennung.
- DefaultManageModelPricesUseCase mit ChangeSet-Konfliktvalidierung,
Provider-Whitelist und Clock-Stempel.
- CostCalculator (formatRow + calculateAttempt; formatTotal als Stub fuer AP-B).
KI-Adapter:
- AnthropicClaudeHttpAdapter und OpenAiHttpAdapter extrahieren Token-Daten
aus den Response-Bodies inklusive Validierung (negativ, > 10 Mio., nicht
numerisch -> NULL + WARN-Log).
BatchRunProcessingUseCase-Hook:
- DocumentProcessingCoordinator erhaelt optional ModelPriceRepository und ein
Headless-Flag. Beim Bau eines KI-Versuchs wird der Snapshot-Preis fuer
(Provider, Modell) geladen und mit den Token-Daten am ProcessingAttempt
persistiert. Lookup-Fehler verlieren keinen Attempt.
GUI:
- Neuer Tab "Modell-Preise" (TableView mit Editierfeldern, Add-Dialog,
Loesch-Bestaetigung, Konvertierung Nano-USD <-> $/1M Tokens).
- History-Tab um drei Spalten erweitert: Input-Tokens, Output-Tokens, Kosten.
- Summary-Banner um Token-, Kosten- und Cache-only-Zeile erweitert
(Default-Werte; AP-B liefert spaeter die echten Aggregate).
- Konfigurations-Tab warnt beim Speichern, wenn das aktive Modell keinen
Preis-Eintrag hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schliesst die durch die SonarQube-Reliability-Fixes (Commit 32e32a9)
neu eingefuehrten Quellzeilen testseitig ab, damit das new-coverage
Quality Gate von 80% wieder erreicht wird.
PdfPreviewPaneRenderingTest:
- erzeugt mit PDFBox echte ein- bzw. mehrseitige PDFs und ruft
loadSource() auf, sodass loadAndRenderFirstPageOnWorker (inkl.
AtomicReference-Setter fuer currentDocument/currentRenderer) und
renderPageOnWorker (AtomicReference-Getter) tatsaechlich ausgefuehrt
werden.
BootstrapRunnerGuiContextInitFailureTest:
- deckt die catch-Zweige fuer InvalidStartConfigurationException,
DocumentPersistenceException und unspezifische RuntimeException in
initializeApplicationRunContext ab. Damit werden die Pfade ausgefuehrt,
in denen guiApplicationRunContext via AtomicReference.set(Optional.empty())
zurueckgesetzt wird.
Keine Aenderungen an Produktivcode oder bestehenden Tests.
S2789 (32 Stellen): null-Checks auf Optional-Feldern entfernt bzw. durch
Objects.requireNonNullElse(field, Optional.empty()) ersetzt. Die zuvor
defensive Behandlung von null-Optionals erfolgt jetzt ueber den
Bibliotheksaufruf, sodass das Verhalten unveraendert bleibt, aber die
direkte Null-Pruefung gegen Optional entfaellt.
S3077 (5 Stellen): volatile-Felder mit Objekt-Referenzen durch
AtomicReference ersetzt (ScheduledExecutorServiceSchedulerAdapter,
BootstrapRunner.guiApplicationRunContext, PdfPreviewPane.currentDocument/
currentRenderer/currentSourceFile, SingleInstanceGuard.socket). Die
PdfPreviewPane-Felder werden auf JavaFX- bzw. Worker-Thread genutzt;
AtomicReference bietet hier konsistente atomare Publikation ohne
Verhaltensaenderung.
S2184 (3 Stellen): Integer-Division SECONDARY_SPACING / 2 durch
SECONDARY_SPACING / 2.0 ersetzt, damit das Insets-Argument als double
ohne implizite Truncierung berechnet wird.
Der Scheduler-Tab meldete nach erfolgreicher Verarbeitung faelschlich
"keine neuen Dokumente". Ursache war ein hartkodiertes RunSummary.noOp()
im BatchRunTrigger der Bootstrap; der echte Lauf-Summary wurde nie
gelesen.
- Bootstrap: BatchRunProgressObserver erfasst RunSummary aus onRunEnded
und uebersetzt ihn in den ausgehenden RunSummary fuer das Tick-Ergebnis
- Neuer Wert-Typ SchedulerSessionTotals (success/failed) plus
Optional-Feld in SchedulerStatus
- DefaultSchedulerControlUseCase setzt die Totale beim start() auf null
zurueck, summiert pro Started-Tick auf, friert sie beim stop() ein
- GuiSchedulerTab zeigt pro Tick "X verarbeitet, Y Fehler" oder
"keine neuen Dokumente" sowie ein zusaetzliches Label
"Seit Scheduler-Start: X verarbeitet, Y Fehler"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Die GUI-Methoden fuer Historie und manuelle Datei-Aktionen haben Konfig
und Schema bei jedem Aufruf neu geladen. Bei aktivem Scheduler-Lock
schlug der Properties-Lesezugriff in loadAndValidateConfiguration mit
einer IOException fehl.
Zwei neue Helper bevorzugen den bereits stehenden ApplicationRunContext
und fallen nur ohne Kontext auf das alte Migrate-Load-Validate-
Schema-Init-Schema zurueck:
- resolveJdbcUrlForGui (nur JDBC-URL benoetigt)
- resolveStartConfigurationForGui (volle StartConfiguration benoetigt)
Refactoring betrifft:
- resolveHistoricalDocumentContextForGui
- loadHistoryOverviewForGui
- loadHistoryDetailsForGui
- resetHistoryDocumentStatusForGui
- deleteDocumentHistoryForGui
- performGuiManualFileRename
- performGuiManualFileCopy
Der kurzlebige Helper migrateConfigurationIfNeededForGui wurde durch
die beiden neuen Helper ueberfluessig und entfernt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Konsequente Anwendung des Fix-Musters auf alle GUI-Methoden, die nach
dem Aufbau des ApplicationRunContext eine erneute, redundante
Legacy-Migration ausgeloest haetten. Bei aktivem Scheduler-Lock ist der
Lese-/Schreibzugriff auf die Konfigurationsdatei sonst blockiert.
Neue Helper-Methode migrateConfigurationIfNeededForGui kapselt den Check
auf den Run-Kontext und ersetzt den unbedingten Aufruf in:
- performGuiManualFileRename
- performGuiManualFileCopy
- resolveHistoricalDocumentContextForGui
- loadHistoryOverviewForGui (vorhandene inline-Variante zentralisiert)
- loadHistoryDetailsForGui
- resetHistoryDocumentStatusForGui
- deleteDocumentHistoryForGui
Die uebrigen Aufrufstellen bleiben unveraendert: der headless-Pfad,
initializeApplicationRunContext (die einzige zustaendige Stelle), die
GUI-Launch-Methoden mit bestehendem Early-Return bei vorhandenem Kontext
sowie die Stellen, die vor Aufbau des Kontexts laufen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
loadHistoryOverviewForGui rief migrateConfigurationIfNeeded unbedingt
auf. Bei aktivem Scheduler haelt der Use Case den OS-Lock auf der
Konfigurationsdatei; der Migrations-Lesezugriff lief dadurch in eine
IOException.
Die Migration ist nur einmal noetig; sie wurde bereits beim Aufbau des
ApplicationRunContext durchgefuehrt. Der erneute Aufruf wird daher
uebersprungen, sobald der Kontext steht.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
installSchedulerCloseGuard hat den Scheduler-Use-Case bisher nur einmalig
aus dem unveraenderlichen GuiStartupContext gelesen. Bei normalem
GUI-Start ohne --config war dieser Optional leer; der nach dem Auto-Load
verdrahtete Use Case wurde nicht erfasst und der Close-Guard griff nie.
Der Close-Handler wird jetzt unabhaengig vom Startup-Context installiert
und liest den Aktiv-Status zur Laufzeit ueber den Workspace, der den im
GuiSchedulerTab live verdrahteten Use Case kennt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Die zentrale Status-Refresh-Timeline las den Scheduler-Use-Case aus dem
unveraenderlichen GuiStartupContext. Beim regulaeren GUI-Start ohne
--config ist dieser Optional leer; der via Auto-Load nachtraeglich
verdrahtete Use Case wurde dadurch nie sichtbar, updateStatus wurde nie
aufgerufen und der Stop-Button blieb dauerhaft deaktiviert.
Die Timeline liest den Status jetzt ueber den Workspace, der den live im
GuiSchedulerTab verdrahteten Use Case kennt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Der Scheduler startet niemals automatisch beim Programmstart. Der Nutzer
startet ihn ausschliesslich bewusst ueber den Start-Button im
Scheduler-Tab. scheduler.enabled wird nicht mehr gelesen oder geschrieben;
das Property ist obsolet.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BUG 1: RunLockPort-JavaDoc dokumentierte den Scheduler-Tick faelschlicherweise als
nicht-blockierenden Pfad mit tryAcquire(). Da execute() intern acquire() aufruft,
wuerde tryAcquire() vor execute() einen Double-Lock erzeugen. JavaDoc korrigiert:
Scheduler-Tick nutzt denselben blockierenden acquire()-Pfad wie der manuelle Lauf.
BUG 2: GuiConfigurationPropertiesWriter.copyFile() faengt jetzt AccessDeniedException
separat ab und liefert den klaren Hinweis "Konfiguration kann nicht gespeichert
werden - Scheduler laeuft." statt einer generischen Fehlermeldung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bisher wurde der ApplicationRunContext nur beim --config-Startpfad
erzeugt. Der auto-load-Pfad (letzte Konfiguration aus Preferences)
baute keinen Kontext auf, was Scheduler und Batch-Vorinitialisierung
blockierte.
Neu: GuiApplicationContextInitializer-Callback, den Bootstrap für
jeden GUI-Startpfad bereitstellt. openConfigurationFile() ruft ihn
im Hintergrund-Thread auf; das Scheduler-Ergebnis wird via
Platform.runLater() an GuiSchedulerTab.onSchedulerAvailable()
übergeben.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: Der Aggregate-Report enthält Klassen aller Module. SonarQube
analysiert jedes Modul isoliert und findet für Klassen anderer Module
keine Quellen → "File not found" für alle Einträge. Das Coverage-Modul
(kein Java-Code) lehnt beim Import alle Einträge ab.
Lösung:
- jacoco:report-Goal (verify-Phase) im Root-POM ergänzt → jedes Modul
erzeugt target/site/jacoco/jacoco.xml nur für seine eigenen Klassen
- sonar.coverage.jacoco.xmlReportPaths auf relativen Pfad target/site/jacoco/jacoco.xml
umgestellt → SonarQube löst pro Modul auf, liest ausschließlich dessen
eigene Klassen, keine Cross-Modul-Kollisionen mehr
- sonar.skip=true in pdf-umbenenner-coverage und pdf-umbenenner-packaging
gesetzt → Aggregator-/Packaging-Module ohne Java-Quellen werden von
SonarQube nicht mehr analysiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schritt 11: updateLockState() implementiert in GuiConfigurationEditorWorkspace
- schedulerLockActive-Feld eingeführt
- applyBatchRunLockState() delegiert an neues applyConfigTabLockState()
- applyConfigTabLockState() vereint Batch-Run- und Scheduler-Sperre:
Banner, sectionsBox, Neu/Öffnen/Speichern/Speichern-unter werden gesperrt
wenn Scheduler aktiv oder Lauf aktiv
Schritt 12: updateSchedulerState() implementiert in GuiBatchRunTab
- schedulerActive-Feld eingeführt
- Starten-Button wird deaktiviert + Tooltip gesetzt wenn Scheduler läuft
- updateButtonStates() berücksichtigt schedulerActive damit Sperre beim
Laufende nicht verloren geht
Schritt 13: Scheduler-Close-Guard in PdfUmbenennerGuiApplication
- installSchedulerCloseGuard() als äußerste Schicht des Close-Handlers
- Zeigt Informationsdialog und verhindert Beenden wenn Scheduler aktiv
- Bestehender Workspace-/Tray-Handler bleibt erhalten wenn Scheduler gestoppt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der Glob **/jacoco-aggregate/jacoco.xml wird von SonarQube pro Modul relativ
zu dessen basedir aufgelöst und findet den Aggregate-Report im Geschwistermodul
pdf-umbenenner-coverage nicht. Maven löst ${project.basedir} hingegen vor der
Übergabe an das Sonar-Plugin zu einem absoluten Pfad auf, sodass
../pdf-umbenenner-coverage/... für alle Kind-Module korrekt zeigt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sonar.coverage.jacoco.xmlReportPaths wird jetzt als Property im Root-POM
gepflegt statt als -D-Parameter im mvn sonar:sonar-Befehl des Jenkinsfile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PdfUmbenennerGuiApplication startet nach dem Anzeigen des Hauptfensters
eine GuiStatusRefreshTimeline, die im Sekundentakt refreshAllTabStates()
aufruft. Die Methode liest schedulerControlUseCase.getStatus() (falls
present) und delegiert an workspace.onSchedulerStatusRefresh(status).
GuiConfigurationEditorWorkspace.onSchedulerStatusRefresh() leitet den
Status an batchRunTab.updateSchedulerState() und updateLockState() weiter.
Beide Methoden sind vorerst leere Stubs; die Implementierung folgt in
späteren Schritten. Ebenso bleibt der zukünftige GuiSchedulerTab-Aufruf
ausgespart bis Schritt 10.
GuiStatusRefreshTimeline ist eine eigenständige Klasse im gui-Paket,
konsistent mit den bestehenden Coordinator-Klassen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GuiBatchRunCoordinator erwirbt vor jedem Verarbeitungslauf (regulär und
Mini-Lauf) einen exklusiven OS-Lock auf die Konfigurationsdatei via
ConfigurationFileLockPort. Bei ConfigurationFileLockException wird ein
deutscher Fehlerdialog angezeigt und der Lauf abgebrochen. In finally
wird der Lock immer freigegeben.
GuiStartupContext erhält das 27. Feld configurationFileLockPort;
BootstrapRunner befüllt es mit einem FileChannelConfigurationAccessAdapter
wenn eine Konfigurationsdatei geladen wurde. GuiBatchRunTab und
GuiConfigurationEditorWorkspace reichen den Port durch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implementiert SchedulerControlUseCase als zentralen Orchestrator:
- start()-Sequenz mit STARTING → RUNNING_IDLE und vollständigem Rollback
- stop()-Sequenz mit CAS-gesichertem STOPPING_BATCH_ACTIVE für laufende Batches
- executeWrappedTick() (package-private) setzt RUNNING_BATCH_ACTIVE vor dem Trigger
und leitet Folgezustand aus BatchRunTriggerResult-Variante ab
- AtomicReference<SchedulerStatus> für threadsichere Zustandsverwaltung
- Intervall wird beim Start aus SchedulerSettingsPort geladen, Minimum 30 s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single-Thread-Executor mit scheduleWithFixedDelay (Initial Delay 0).
Thread-Name: pdf-umbenenner-scheduler, Non-Daemon, UncaughtExceptionHandler
auf ERROR. Alle Ausnahmen in onTick() werden abgefangen, damit der
Tick-Zyklus nicht still abbricht. currentTrigger und onTick() sind
package-private für direkte Testbarkeit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der Adapter teilt intern einen FileChannel und ermöglicht so das Schreiben
von Scheduler-Einstellungen auch während eines aktiven OS-Locks. Schreibvorgänge
laufen ohne Lock über eine temporäre Datei (ATOMIC_MOVE); mit Lock direkt über
den Kanal (Truncate → Write → Force). Zeilenenden (CRLF/LF) und alle übrigen
Properties-Zeilen bleiben unverändert erhalten.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Erzeugt das Modul-Gerüst für den autonomen Scheduler-Adapter:
- pom.xml mit Abhängigkeit auf pdf-umbenenner-application (kein Bootstrap,
kein JavaFX, kein Shade-Plugin); flatten-maven-plugin und PIT werden mit
bewusstem Kommentar vom Parent geerbt; JaCoCo-Schwellwerte temporär auf 0
- package-info.java für das Paket de.gecheckt.pdf.umbenenner.adapter.in.scheduler
- SchedulerPlaceholder.java als temporäre Kompilierplatzhalter-Klasse
- Modul in der Parent-pom.xml zwischen adapter-in-gui und adapter-out registriert
Die Abhängigkeitsrichtung (adapter-in-scheduler → application → domain)
verhindert den zyklischen Bezug: Bootstrap wird in einem späteren Schritt
auf dieses Modul angewiesen sein, nicht umgekehrt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Führt ApplicationRunContext als package-private Record ein, der beim
GUI-Start einmalig aus der validierten Konfiguration gebaut wird
(migrate → load → validate → schema-init). Das Ergebnis wird in
guiApplicationRunContext gecacht und von launchGuiBatchRun,
launchGuiMiniBatchRun und resetDocumentStatusForGui wiederverwendet,
sodass die Init-Sequenz nicht bei jedem Lauf wiederholt wird.
GuiStartupContext erhält das neue Feld applicationContextError
(Optional<String>), das einen deutschen Fehlertext trägt, wenn der
Kontext bei Startup nicht initialisiert werden konnte. Alle bisherigen
Konstruktoren und die blank()-Fabrik wurden rückwärtskompatibel
ergänzt.
Der Test-Helfer runnerWithGuiFactory wirft jetzt
ConfigurationLoadingException statt AssertionError, damit
initializeApplicationRunContext() den Fehler gracefully abfangen
und in applicationContextError speichern kann.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der Filter akzeptiert jetzt *.db und *.sqlite. Der vorgeschlagene
Dateiname übernimmt die Endung der aktuell konfigurierten DB-Datei
(neue-datenbank.db bzw. neue-datenbank.sqlite).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>