diff --git a/docs/betrieb.md b/docs/betrieb.md index 8327e53..3076eb6 100644 --- a/docs/betrieb.md +++ b/docs/betrieb.md @@ -544,6 +544,48 @@ Auf Unix-Systemen (headless CI): --- +## GUI: Selektive Wiederverarbeitung und Status-Reset + +Die GUI ermöglicht nach Abschluss eines Verarbeitungslaufs zwei zusätzliche Aktionen auf der Ergebnisliste: + +### Selektion in der Ergebnisliste + +Die Ergebnisliste enthält eine **Checkbox pro Zeile** sowie eine **Master-Checkbox** zum Auswählen aller Einträge. +- Auswahl erfolgt wie im Windows Explorer mit **Shift/Strg-Mehrfachselektion** +- Alle vier Statustypen sind selektierbar: erfolgreich, retryable, permanent fehlgeschlagen, übersprungen +- Während eines Laufs ist die Selektion **gesperrt** + +### Button „Erneut verarbeiten" + +**Aktion:** DB-Status zurücksetzen + sofortiger Mini-Lauf nur für ausgewählte Dateien. + +- Aktiv nur wenn kein Lauf läuft und mindestens 1 Eintrag selektiert ist +- Der Mini-Lauf arbeitet auf einem Snapshot der beim Klick ausgewählten Einträge +- Nicht ausgewählte Einträge bleiben unverändert in der Liste +- Verhalten identisch zu regulärem Lauf (gleiche Anwendungslogik, nur eingeschränkte Dateimenge) + +**Besonderheit bei identischem Zieldateinamen:** Verarbeitet der KI-Provider wieder denselben Dateinamen wie ein vorangegangener erfolgreicher Lauf, erhält der Eintrag **Status erfolgreich** – es wird keine erneute Kopie erzeugt, kein Fehler. + +**Fehlende Quelldatei:** Ist die Datei zum Zeitpunkt des Mini-Laufs nicht mehr vorhanden, erhält der Eintrag **Status permanent fehlgeschlagen** mit Meldung „Quelldatei nicht gefunden". + +### Button „Status zurücksetzen" + +**Aktion:** Nur DB-Status zurücksetzen, keine sofortige Verarbeitung. + +- Aktiv nur wenn kein Lauf läuft und mindestens 1 Eintrag selektiert ist +- Betroffene Zeilen erhalten die Kennzeichnung **„Zurückgesetzt – wartet auf nächsten Lauf"** +- Beim nächsten regulären Lauf werden zurückgesetzte Dateien automatisch mitgenommen +- **Best-effort-Reset:** Erfolgreiche und fehlgeschlagene Resets werden pro Eintrag einzeln durchgeführt; Zusammenfassung zeigt Erfolge und Fehler + +### Verhalten während eines Mini-Laufs + +- Der **Abbrechen-Button** gilt auch für Mini-Läufe (Soft-Stop) +- **Tab 1 „Konfiguration" ist während des Mini-Laufs gesperrt** +- Nach Soft-Stop: bereits verarbeitete Einträge behalten neuen Status, noch nicht gestartete zurückgesetzte Einträge warten auf nächsten regulären Lauf +- Fortschrittsbalken zeigt Fortschritt für die ausgewählte Dateimenge + +--- + ## Weitere Dokumentation Die Bedienung der GUI ist in [`gui-bedienanleitung.md`](gui-bedienanleitung.md) beschrieben. diff --git a/docs/gui-bedienanleitung.md b/docs/gui-bedienanleitung.md index 38510da..a8df0a5 100644 --- a/docs/gui-bedienanleitung.md +++ b/docs/gui-bedienanleitung.md @@ -504,6 +504,80 @@ Hinweisdialog mit zwei Optionen: --- +## 13a. Selektion, Wiederverarbeitung und Status-Reset (V2.8) + +Nach Abschluss eines Verarbeitungslaufs können einzelne oder mehrere Dateien aus der +Ergebnisliste gezielt erneut verarbeitet oder deren Status zurückgesetzt werden. + +### Selektion in der Ergebnisliste + +- Jede Zeile hat eine **Checkbox** am linken Rand +- Zusätzlich eine **Master-Checkbox** oberhalb der Liste zum Auswählen/Abwählen aller Einträge +- **Zeilenklick** (auf Text/Status-Icon) repräsentiert dieselbe Selektionsmenge wie die Checkbox +- **Shift/Strg-Mehrfachselektion** funktioniert wie im Windows Explorer + - Shift+Klick: Bereich vom letzten zur aktuellen Zeile + - Strg+Klick: einzelne Zeilen hinzufügen/entfernen +- Alle vier Statustypen sind selektierbar: ✅ erfolgreich, ⚠️ retryable, ❌ permanent, ⏭️ übersprungen +- Die Selektion bleibt nach Aktionen erhalten, bis ein neuer Lauf gestartet wird + +### Button „Erneut verarbeiten" + +**Wann nutzen:** Der KI-Prompt wurde geändert, das Modell gewechselt oder die Verarbeitung einer Datei +muss aus anderen Gründen wiederholt werden – und das Ergebnis soll sofort verfügbar sein. + +**Was passiert:** +1. Wird ein Button-Klick ausgelöst, wird die aktuelle Selektion als **Snapshot** erfasst +2. Der DB-Status aller selektierten Einträge wird zurückgesetzt +3. Ein **Mini-Lauf** startet sofort und verarbeitet nur diese Dateien +4. Unselektierte Einträge bleiben unverändert in der Liste +5. Die Mini-Lauf-Ergebnisse werden live in den selektierten Zeilen aktualisiert + +**Besonderheiten:** +- Verarbeitet die KI wieder denselben Dateinamen wie der vorherige erfolgreiche Lauf, + erfolgt **keine erneute Kopie** – der Eintrag erhält Status ✅ erfolgreich +- Ist die Quelldatei nicht mehr vorhanden, erhält der Eintrag Status ❌ permanent fehlgeschlagen + mit Meldung „Quelldatei nicht gefunden" + +**Button-Status:** +- **Aktiv:** kein Lauf aktiv UND mindestens 1 Eintrag selektiert +- **Inaktiv:** Lauf läuft ODER keine Selektion + +### Button „Status zurücksetzen" + +**Wann nutzen:** Eine Datei soll später erneut verarbeitet werden, aber nicht sofort – z. B. nach +Behebung eines externen Fehlers oder planmäßig im nächsten regulären Lauf. + +**Was passiert:** +1. Der DB-Status aller selektierten Einträge wird zurückgesetzt +2. Betroffene Zeilen erhalten die Kennzeichnung **„Zurückgesetzt – wartet auf nächsten Lauf"** +3. **Kein sofortiger Mini-Lauf** +4. Beim nächsten regulären Lauf werden diese Dateien automatisch mitgenommen + +**Fehlerbehandlung (Best-effort):** +- Resets werden pro Eintrag einzeln durchgeführt +- Erfolgreiche und fehlgeschlagene Resets werden separat gezählt +- Zusammenfassung im Meldungsbereich zeigt: + - Anzahl ausgewählter Einträge + - Anzahl erfolgreich zurückgesetzt + - Anzahl fehlgeschlagen + betroffene Dateinamen + +**Button-Status:** +- **Aktiv:** kein Lauf aktiv UND mindestens 1 Eintrag selektiert +- **Inaktiv:** Lauf läuft ODER keine Selektion + +### Verhalten während eines Mini-Laufs + +- Der **Abbrechen-Button** löst einen Soft-Stop auch für Mini-Läufe aus: + - bereits verarbeitete Einträge behalten ihren neuen Endstatus + - noch nicht gestartete, aber bereits zurückgesetzte Einträge erhalten Status + „Zurückgesetzt – wartet auf nächsten Lauf" und werden beim nächsten regulären Lauf mitgenommen +- **Tab 1 „Konfiguration" ist während des Mini-Laufs gesperrt** +- Der **Fortschrittsbalken** zeigt den Fortschritt für die ausgewählte Dateimenge + (Nenner = Anzahl selektierter Dateien) +- Beide Buttons „Erneut verarbeiten" und „Status zurücksetzen" sind **deaktiviert** + +--- + ## 14. Bekannte Einschränkungen V2.x | Einschränkung | Erläuterung | diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java index af6a888..e79c2a5 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java @@ -16,6 +16,8 @@ import org.apache.logging.log4j.Logger; import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLauncher; import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunTab; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiMiniRunLauncher; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiResetDocumentStatusPort; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.AiProviderFamilyStringConverter; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiApiKeyMerger; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiChangeState; @@ -344,11 +346,24 @@ public final class GuiConfigurationEditorWorkspace { private final ApiKeyResolutionPort apiKeyResolutionPort; /** - * Launcher used by the processing-run tab to execute a batch run against the saved - * configuration file. Supplied by Bootstrap via the startup context. + * Launcher used by the processing-run tab to execute a regular batch run against the + * saved configuration file. Supplied by Bootstrap via the startup context. */ private final GuiBatchRunLauncher batchRunLauncher; + /** + * Launcher used by the processing-run tab to execute a targeted mini-run for a + * selected set of documents. Supplied by Bootstrap via the startup context. + */ + private final GuiMiniRunLauncher miniRunLauncher; + + /** + * Port used by the processing-run tab to reset the persistence status of selected + * documents without triggering a reprocessing run. Supplied by Bootstrap via the + * startup context. + */ + private final GuiResetDocumentStatusPort resetDocumentStatusPort; + /** * Second main tab of the window that drives the live processing-run view. Created * during workspace construction and wired into the shared {@link #tabPane} alongside @@ -421,8 +436,12 @@ public final class GuiConfigurationEditorWorkspace { triggerLabel -> showUnsavedChangesDialog(triggerLabel)); this.batchRunLauncher = effectiveContext.batchRunLauncher(); + this.miniRunLauncher = effectiveContext.miniRunLauncher(); + this.resetDocumentStatusPort = effectiveContext.resetDocumentStatusPort(); this.batchRunTab = new GuiBatchRunTab( () -> this.batchRunLauncher, + () -> this.miniRunLauncher, + () -> this.resetDocumentStatusPort, this::loadedConfigurationPath, this::isSavedConfigurationReady, this::applyBatchRunLockState); diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java index c3954a8..146dea7 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java @@ -2,11 +2,15 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui; import java.util.Objects; import java.util.Optional; +import java.util.Set; import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLaunchOutcome; import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLauncher; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiMiniRunLauncher; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiResetDocumentStatusPort; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorStateFactory; +import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult; import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.AiModelCatalogPort; import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor; import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort; @@ -15,6 +19,7 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheck import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService; import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort; import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator; +import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint; /** * Immutable startup data for the GUI adapter. @@ -26,9 +31,12 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.Technical * API key provenance from environment variables, the {@link ProviderTechnicalTestService} * used to execute provider-specific technical checks, the {@link PathCheckPort} * used to verify filesystem path accessibility for configuration values, the - * {@link TechnicalTestOrchestrator} used by the "Technische Tests ausführen" action, and the + * {@link TechnicalTestOrchestrator} used by the "Technische Tests ausführen" action, the * {@link CorrectionExecutionService} used to execute corrective actions after a - * technical test run has been confirmed by the user. + * technical test run has been confirmed by the user, the {@link GuiBatchRunLauncher} used + * to execute regular batch runs, the {@link GuiMiniRunLauncher} used to execute targeted + * mini-runs for selected documents, and the {@link GuiResetDocumentStatusPort} used to + * reset the persistence status of selected documents. *

* All ports and services are supplied by Bootstrap so that the GUI adapter does not need to * know about provider-specific HTTP details or adapter wiring. @@ -44,10 +52,12 @@ public record GuiStartupContext( PathCheckPort pathCheckPort, TechnicalTestOrchestrator technicalTestOrchestrator, CorrectionExecutionService correctionExecutionService, - GuiBatchRunLauncher batchRunLauncher) { + GuiBatchRunLauncher batchRunLauncher, + GuiMiniRunLauncher miniRunLauncher, + GuiResetDocumentStatusPort resetDocumentStatusPort) { /** - * Creates a startup context. + * Creates a fully wired startup context. * * @param initialState initial editor state; must not be {@code null} * @param startupNotice optional startup notice; {@code null} becomes empty @@ -59,9 +69,11 @@ public record GuiStartupContext( * @param pathCheckPort port for filesystem path accessibility checks; must not be {@code null} * @param technicalTestOrchestrator orchestrator for the full technical test run; must not be {@code null} * @param correctionExecutionService service for executing confirmed corrective actions; must not be {@code null} - * @param batchRunLauncher bridge that executes a batch run against a stored - * configuration path for the processing-run tab; - * must not be {@code null} + * @param batchRunLauncher bridge that executes a regular batch run; must not be {@code null} + * @param miniRunLauncher bridge that executes a targeted mini-run for selected + * documents; must not be {@code null} + * @param resetDocumentStatusPort bridge that resets the persistence status of selected + * documents; must not be {@code null} */ public GuiStartupContext { initialState = Objects.requireNonNull(initialState, "initialState must not be null"); @@ -84,15 +96,51 @@ public record GuiStartupContext( "correctionExecutionService must not be null"); batchRunLauncher = Objects.requireNonNull(batchRunLauncher, "batchRunLauncher must not be null"); + miniRunLauncher = Objects.requireNonNull(miniRunLauncher, + "miniRunLauncher must not be null"); + resetDocumentStatusPort = Objects.requireNonNull(resetDocumentStatusPort, + "resetDocumentStatusPort must not be null"); } /** - * Backward-compatible constructor that fills the processing-run launcher with a - * no-op implementation. + * Backward-compatible constructor that fills the mini-run launcher and reset port + * with no-op implementations. + * + * @param initialState initial editor state; must not be {@code null} + * @param startupNotice optional startup notice; {@code null} becomes empty + * @param configurationFileLoader file-loading callback; must not be {@code null} + * @param configurationFileWriter file-writing callback; must not be {@code null} + * @param modelCatalogPort port for retrieving AI model lists; must not be {@code null} + * @param apiKeyResolutionPort port for resolving API key provenance; must not be {@code null} + * @param providerTechnicalTestService service for provider-specific technical checks; must not be {@code null} + * @param pathCheckPort port for filesystem path accessibility checks; must not be {@code null} + * @param technicalTestOrchestrator orchestrator for the full technical test run; must not be {@code null} + * @param correctionExecutionService service for executing confirmed corrective actions; must not be {@code null} + * @param batchRunLauncher bridge that executes a regular batch run; must not be {@code null} + */ + public GuiStartupContext( + GuiConfigurationEditorState initialState, + Optional startupNotice, + GuiConfigurationFileLoader configurationFileLoader, + GuiConfigurationFileWriter configurationFileWriter, + AiModelCatalogPort modelCatalogPort, + ApiKeyResolutionPort apiKeyResolutionPort, + ProviderTechnicalTestService providerTechnicalTestService, + PathCheckPort pathCheckPort, + TechnicalTestOrchestrator technicalTestOrchestrator, + CorrectionExecutionService correctionExecutionService, + GuiBatchRunLauncher batchRunLauncher) { + this(initialState, startupNotice, configurationFileLoader, configurationFileWriter, + modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort, + technicalTestOrchestrator, correctionExecutionService, batchRunLauncher, + rejectingMiniRunLauncher(), rejectingResetPort()); + } + + /** + * Backward-compatible constructor that fills the processing-run launcher, mini-run + * launcher and reset port with no-op implementations. *

* Preserves existing callers that were written before the processing-run tab was added. - * The no-op launcher rejects every start request with a clear German message so the - * UI never enters an unsafe state in legacy test wiring. * * @param initialState initial editor state; must not be {@code null} * @param startupNotice optional startup notice; {@code null} becomes empty @@ -119,7 +167,7 @@ public record GuiStartupContext( this(initialState, startupNotice, configurationFileLoader, configurationFileWriter, modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort, technicalTestOrchestrator, correctionExecutionService, - rejectingBatchRunLauncher()); + rejectingBatchRunLauncher(), rejectingMiniRunLauncher(), rejectingResetPort()); } private static GuiBatchRunLauncher rejectingBatchRunLauncher() { @@ -127,20 +175,24 @@ public record GuiStartupContext( "Kein Verarbeitungslauf-Launcher in diesem Startkontext verfügbar."); } + private static GuiMiniRunLauncher rejectingMiniRunLauncher() { + return (configPath, filter, observer, token) -> GuiBatchRunLaunchOutcome.rejected( + "Kein Mini-Run-Launcher in diesem Startkontext verfügbar."); + } + + private static GuiResetDocumentStatusPort rejectingResetPort() { + return (configPath, fingerprints) -> { + java.util.Map failures = new java.util.HashMap<>(); + for (DocumentFingerprint fp : fingerprints) { + failures.put(fp, "Kein Reset-Port in diesem Startkontext verfügbar."); + } + return new ResetDocumentStatusResult(fingerprints.size(), Set.of(), failures); + }; + } + /** - * Creates a blank startup context with no loader or writer side effects, a no-op model - * catalogue port, a no-op API key resolution port, a no-op provider technical test service, - * a no-op path check port, a no-op technical test orchestrator, and a no-op - * correction execution service. + * Creates a blank startup context with no-op implementations for all ports and services. *

- * The no-op model catalogue port always returns {@code IncompleteConfiguration}. - * The no-op API key resolution port always returns {@code ABSENT}. - * The no-op provider technical test service uses the no-op ports above. - * The no-op path check port always returns {@code false} for all checks. - * The no-op technical test orchestrator returns a report where all checkpoints are - * {@link de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CheckpointResult.NotApplicable}. - * The no-op correction execution service uses a no-op {@link ResourceCreationPort} that always - * returns {@link de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted}. * This is safe for environments where no Bootstrap wiring is present, such as isolated * GUI tests. * @@ -208,6 +260,8 @@ public record GuiStartupContext( noOpPathCheckPort, noOpOrchestrator, noOpCorrectionService, - noOpBatchRunLauncher); + noOpBatchRunLauncher, + rejectingMiniRunLauncher(), + rejectingResetPort()); } } diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunCoordinator.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunCoordinator.java index 6793b90..a0bfd4e 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunCoordinator.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunCoordinator.java @@ -5,6 +5,7 @@ import java.time.Duration; import java.time.LocalDate; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -16,12 +17,15 @@ import org.apache.logging.log4j.Logger; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver; import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionEvent; +import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult; import de.gecheckt.pdf.umbenenner.application.port.in.RunSummary; +import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint; import de.gecheckt.pdf.umbenenner.domain.model.RunId; import javafx.application.Platform; /** - * Coordinates a single batch run triggered from the JavaFX GUI. + * Coordinates a single batch run (regular or targeted mini-run) triggered from the + * JavaFX GUI, and optional reset-only operations on selected document fingerprints. *

* The coordinator owns the background worker thread that executes the run, maintains the * cancellation flag, and translates the @@ -30,7 +34,7 @@ import javafx.application.Platform; * *

Threading

*