Fix #30: Detailbereich bei SKIPPED-Zeilen mit historischen Informationen befüllen
- Teile DocumentCompletionStatus.SKIPPED in SKIPPED_ALREADY_PROCESSED und SKIPPED_FINAL_FAILURE auf, um den Skip-Grund unterscheidbar zu machen - Führe neuen Typ HistoricalDocumentContext ein (lastTargetFileName, lastSuccessInstant, lastFailureInstant, wasEverSuccessful) - Führe ResolveHistoricalDocumentContextUseCase und DefaultResolveHistoricalDocumentContextUseCase ein - Ersetze GuiHistoricalFileNamePort durch GuiHistoricalDocumentContextPort - Lade historischen Kontext für übersprungene Zeilen im Coordinator-Worker-Thread - Zeige im Detailbereich je nach Skip-Grund: SKIPPED_ALREADY_PROCESSED: "Bereits erfolgreich verarbeitet am [Datum]. Zieldatei: [Name]." SKIPPED_FINAL_FAILURE: "Endgültig fehlgeschlagen am [Datum]. Erneute Verarbeitung nur nach Reset möglich." - Passe alle betroffenen Tests an Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
-6
@@ -17,7 +17,7 @@ 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.GuiBatchRunLauncher;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunTab;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunTab;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalFileNamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalDocumentContextPort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiMiniRunLauncher;
|
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.batchrun.GuiResetDocumentStatusPort;
|
||||||
@@ -373,10 +373,10 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
private final GuiManualFileRenamePort manualFileRenamePort;
|
private final GuiManualFileRenamePort manualFileRenamePort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port used by the processing-run coordinator to resolve the historical AI-proposed
|
* Port used by the processing-run coordinator to resolve the historical processing context
|
||||||
* filename for skipped documents. Supplied by Bootstrap via the startup context.
|
* for skipped documents. Supplied by Bootstrap via the startup context.
|
||||||
*/
|
*/
|
||||||
private final GuiHistoricalFileNamePort historicalFileNamePort;
|
private final GuiHistoricalDocumentContextPort historicalDocumentContextPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Second main tab of the window that drives the live processing-run view. Created
|
* Second main tab of the window that drives the live processing-run view. Created
|
||||||
@@ -453,7 +453,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
this.miniRunLauncher = effectiveContext.miniRunLauncher();
|
this.miniRunLauncher = effectiveContext.miniRunLauncher();
|
||||||
this.resetDocumentStatusPort = effectiveContext.resetDocumentStatusPort();
|
this.resetDocumentStatusPort = effectiveContext.resetDocumentStatusPort();
|
||||||
this.manualFileRenamePort = effectiveContext.manualFileRenamePort();
|
this.manualFileRenamePort = effectiveContext.manualFileRenamePort();
|
||||||
this.historicalFileNamePort = effectiveContext.historicalFileNamePort();
|
this.historicalDocumentContextPort = effectiveContext.historicalDocumentContextPort();
|
||||||
this.batchRunTab = new GuiBatchRunTab(
|
this.batchRunTab = new GuiBatchRunTab(
|
||||||
() -> this.batchRunLauncher,
|
() -> this.batchRunLauncher,
|
||||||
() -> this.miniRunLauncher,
|
() -> this.miniRunLauncher,
|
||||||
@@ -462,7 +462,7 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
this::isSavedConfigurationReady,
|
this::isSavedConfigurationReady,
|
||||||
this::applyBatchRunLockState,
|
this::applyBatchRunLockState,
|
||||||
() -> this.manualFileRenamePort,
|
() -> this.manualFileRenamePort,
|
||||||
() -> this.historicalFileNamePort,
|
() -> this.historicalDocumentContextPort,
|
||||||
this::editorSourceFolder,
|
this::editorSourceFolder,
|
||||||
this::editorTargetFolder);
|
this::editorTargetFolder);
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -6,7 +6,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLaunchOutcome;
|
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.GuiBatchRunLauncher;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalFileNamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalDocumentContextPort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiMiniRunLauncher;
|
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.batchrun.GuiResetDocumentStatusPort;
|
||||||
@@ -42,8 +42,8 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
|||||||
* mini-runs for selected documents, the {@link GuiResetDocumentStatusPort} used to
|
* mini-runs for selected documents, the {@link GuiResetDocumentStatusPort} used to
|
||||||
* reset the persistence status of selected documents, and the
|
* reset the persistence status of selected documents, and the
|
||||||
* {@link GuiManualFileRenamePort} used to manually rename a target file from the GUI, and
|
* {@link GuiManualFileRenamePort} used to manually rename a target file from the GUI, and
|
||||||
* the {@link GuiHistoricalFileNamePort} used to retrieve the historical AI-proposed filename
|
* the {@link GuiHistoricalDocumentContextPort} used to retrieve the historical processing
|
||||||
* for documents that were skipped in the current run.
|
* context for documents that were skipped in the current run.
|
||||||
* <p>
|
* <p>
|
||||||
* All ports and services are supplied by Bootstrap so that the GUI adapter does not need to
|
* 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.
|
* know about provider-specific HTTP details or adapter wiring.
|
||||||
@@ -63,7 +63,7 @@ public record GuiStartupContext(
|
|||||||
GuiMiniRunLauncher miniRunLauncher,
|
GuiMiniRunLauncher miniRunLauncher,
|
||||||
GuiResetDocumentStatusPort resetDocumentStatusPort,
|
GuiResetDocumentStatusPort resetDocumentStatusPort,
|
||||||
GuiManualFileRenamePort manualFileRenamePort,
|
GuiManualFileRenamePort manualFileRenamePort,
|
||||||
GuiHistoricalFileNamePort historicalFileNamePort) {
|
GuiHistoricalDocumentContextPort historicalDocumentContextPort) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a fully wired startup context.
|
* Creates a fully wired startup context.
|
||||||
@@ -85,8 +85,8 @@ public record GuiStartupContext(
|
|||||||
* documents; must not be {@code null}
|
* documents; must not be {@code null}
|
||||||
* @param manualFileRenamePort bridge that renames a target file manually from the GUI;
|
* @param manualFileRenamePort bridge that renames a target file manually from the GUI;
|
||||||
* must not be {@code null}
|
* must not be {@code null}
|
||||||
* @param historicalFileNamePort bridge that resolves the historical AI-proposed filename
|
* @param historicalDocumentContextPort bridge that resolves the historical processing context
|
||||||
* for skipped documents; must not be {@code null}
|
* for skipped documents; must not be {@code null}
|
||||||
*/
|
*/
|
||||||
public GuiStartupContext {
|
public GuiStartupContext {
|
||||||
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
|
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
|
||||||
@@ -115,8 +115,8 @@ public record GuiStartupContext(
|
|||||||
"resetDocumentStatusPort must not be null");
|
"resetDocumentStatusPort must not be null");
|
||||||
manualFileRenamePort = Objects.requireNonNull(manualFileRenamePort,
|
manualFileRenamePort = Objects.requireNonNull(manualFileRenamePort,
|
||||||
"manualFileRenamePort must not be null");
|
"manualFileRenamePort must not be null");
|
||||||
historicalFileNamePort = Objects.requireNonNull(historicalFileNamePort,
|
historicalDocumentContextPort = Objects.requireNonNull(historicalDocumentContextPort,
|
||||||
"historicalFileNamePort must not be null");
|
"historicalDocumentContextPort must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +157,7 @@ public record GuiStartupContext(
|
|||||||
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
||||||
miniRunLauncher, resetDocumentStatusPort, rejectingManualFileRenamePort(),
|
miniRunLauncher, resetDocumentStatusPort, rejectingManualFileRenamePort(),
|
||||||
noOpHistoricalFileNamePort());
|
noOpHistoricalDocumentContextPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,7 +192,7 @@ public record GuiStartupContext(
|
|||||||
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
||||||
rejectingMiniRunLauncher(), rejectingResetPort(), rejectingManualFileRenamePort(),
|
rejectingMiniRunLauncher(), rejectingResetPort(), rejectingManualFileRenamePort(),
|
||||||
noOpHistoricalFileNamePort());
|
noOpHistoricalDocumentContextPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,7 +227,7 @@ public record GuiStartupContext(
|
|||||||
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
technicalTestOrchestrator, correctionExecutionService,
|
technicalTestOrchestrator, correctionExecutionService,
|
||||||
rejectingBatchRunLauncher(), rejectingMiniRunLauncher(), rejectingResetPort(),
|
rejectingBatchRunLauncher(), rejectingMiniRunLauncher(), rejectingResetPort(),
|
||||||
rejectingManualFileRenamePort(), noOpHistoricalFileNamePort());
|
rejectingManualFileRenamePort(), noOpHistoricalDocumentContextPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GuiBatchRunLauncher rejectingBatchRunLauncher() {
|
private static GuiBatchRunLauncher rejectingBatchRunLauncher() {
|
||||||
@@ -256,7 +256,7 @@ public record GuiStartupContext(
|
|||||||
"Kein Umbennennungs-Port in diesem Startkontext verfügbar.");
|
"Kein Umbennennungs-Port in diesem Startkontext verfügbar.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GuiHistoricalFileNamePort noOpHistoricalFileNamePort() {
|
private static GuiHistoricalDocumentContextPort noOpHistoricalDocumentContextPort() {
|
||||||
return (configPath, fingerprint) -> java.util.Optional.empty();
|
return (configPath, fingerprint) -> java.util.Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +334,6 @@ public record GuiStartupContext(
|
|||||||
rejectingMiniRunLauncher(),
|
rejectingMiniRunLauncher(),
|
||||||
rejectingResetPort(),
|
rejectingResetPort(),
|
||||||
rejectingManualFileRenamePort(),
|
rejectingManualFileRenamePort(),
|
||||||
noOpHistoricalFileNamePort());
|
noOpHistoricalDocumentContextPort());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -144,7 +144,7 @@ public final class FileNameEditorPane {
|
|||||||
* <p>
|
* <p>
|
||||||
* Der KI-Vorschlag wird aus {@link GuiBatchRunResultRow#finalFileName()} abgeleitet,
|
* Der KI-Vorschlag wird aus {@link GuiBatchRunResultRow#finalFileName()} abgeleitet,
|
||||||
* der letzte gespeicherte Name aus {@link GuiBatchRunResultRow#effectiveFileName()}.
|
* der letzte gespeicherte Name aus {@link GuiBatchRunResultRow#effectiveFileName()}.
|
||||||
* Bei nicht editierbaren Status (FAILED_*, SKIPPED, reset-pending, kein SUCCESS)
|
* Bei nicht editierbaren Status (FAILED_*, SKIPPED_*, reset-pending, kein SUCCESS)
|
||||||
* wird das Feld deaktiviert.
|
* wird das Feld deaktiviert.
|
||||||
*
|
*
|
||||||
* @param row die neu selektierte Zeile; {@code null} führt zu {@link #clearSelection()}
|
* @param row die neu selektierte Zeile; {@code null} führt zu {@link #clearSelection()}
|
||||||
|
|||||||
+41
-25
@@ -18,6 +18,7 @@ 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.BatchRunProgressObserver;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionEvent;
|
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionEvent;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
|
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.RunSummary;
|
import de.gecheckt.pdf.umbenenner.application.port.in.RunSummary;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
@@ -113,7 +114,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
private final Function<Runnable, Thread> threadFactory;
|
private final Function<Runnable, Thread> threadFactory;
|
||||||
private final Consumer<Runnable> fxDispatcher;
|
private final Consumer<Runnable> fxDispatcher;
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
private final GuiHistoricalFileNamePort historicalFileNamePort;
|
private final GuiHistoricalDocumentContextPort historicalDocumentContextPort;
|
||||||
private final AtomicReference<Thread> activeWorker = new AtomicReference<>();
|
private final AtomicReference<Thread> activeWorker = new AtomicReference<>();
|
||||||
private final AtomicBoolean cancellationRequested = new AtomicBoolean();
|
private final AtomicBoolean cancellationRequested = new AtomicBoolean();
|
||||||
|
|
||||||
@@ -163,16 +164,16 @@ public final class GuiBatchRunCoordinator {
|
|||||||
* @param resetPort bridge to Bootstrap for status-reset-only operations; must
|
* @param resetPort bridge to Bootstrap for status-reset-only operations; must
|
||||||
* not be null
|
* not be null
|
||||||
* @param listener GUI listener invoked on the FX thread; must not be null
|
* @param listener GUI listener invoked on the FX thread; must not be null
|
||||||
* @param historicalFileNamePort port for resolving the historical AI-proposed filename for
|
* @param historicalDocumentContextPort port for resolving the historical AI-proposed filename for
|
||||||
* skipped documents; must not be null
|
* skipped documents; must not be null
|
||||||
*/
|
*/
|
||||||
public GuiBatchRunCoordinator(GuiBatchRunLauncher launcher,
|
public GuiBatchRunCoordinator(GuiBatchRunLauncher launcher,
|
||||||
GuiMiniRunLauncher miniRunLauncher,
|
GuiMiniRunLauncher miniRunLauncher,
|
||||||
GuiResetDocumentStatusPort resetPort,
|
GuiResetDocumentStatusPort resetPort,
|
||||||
Listener listener,
|
Listener listener,
|
||||||
GuiHistoricalFileNamePort historicalFileNamePort) {
|
GuiHistoricalDocumentContextPort historicalDocumentContextPort) {
|
||||||
this(launcher, miniRunLauncher, resetPort,
|
this(launcher, miniRunLauncher, resetPort,
|
||||||
defaultThreadFactory(), defaultFxDispatcher(), listener, historicalFileNamePort);
|
defaultThreadFactory(), defaultFxDispatcher(), listener, historicalDocumentContextPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,7 +201,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
Consumer<Runnable> fxDispatcher,
|
Consumer<Runnable> fxDispatcher,
|
||||||
Listener listener) {
|
Listener listener) {
|
||||||
this(launcher, miniRunLauncher, resetPort, threadFactory, fxDispatcher, listener,
|
this(launcher, miniRunLauncher, resetPort, threadFactory, fxDispatcher, listener,
|
||||||
noOpHistoricalFileNamePort());
|
noOpHistoricalDocumentContextPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -218,7 +219,7 @@ public final class GuiBatchRunCoordinator {
|
|||||||
* @param fxDispatcher dispatcher that schedules a runnable on the JavaFX Application
|
* @param fxDispatcher dispatcher that schedules a runnable on the JavaFX Application
|
||||||
* Thread; must not be null
|
* Thread; must not be null
|
||||||
* @param listener GUI listener; must not be null
|
* @param listener GUI listener; must not be null
|
||||||
* @param historicalFileNamePort port for resolving the historical AI-proposed filename for
|
* @param historicalDocumentContextPort port for resolving the historical AI-proposed filename for
|
||||||
* skipped documents; must not be null
|
* skipped documents; must not be null
|
||||||
*/
|
*/
|
||||||
public GuiBatchRunCoordinator(GuiBatchRunLauncher launcher,
|
public GuiBatchRunCoordinator(GuiBatchRunLauncher launcher,
|
||||||
@@ -227,15 +228,15 @@ public final class GuiBatchRunCoordinator {
|
|||||||
Function<Runnable, Thread> threadFactory,
|
Function<Runnable, Thread> threadFactory,
|
||||||
Consumer<Runnable> fxDispatcher,
|
Consumer<Runnable> fxDispatcher,
|
||||||
Listener listener,
|
Listener listener,
|
||||||
GuiHistoricalFileNamePort historicalFileNamePort) {
|
GuiHistoricalDocumentContextPort historicalDocumentContextPort) {
|
||||||
this.launcher = Objects.requireNonNull(launcher, "launcher must not be null");
|
this.launcher = Objects.requireNonNull(launcher, "launcher must not be null");
|
||||||
this.miniRunLauncher = Objects.requireNonNull(miniRunLauncher, "miniRunLauncher must not be null");
|
this.miniRunLauncher = Objects.requireNonNull(miniRunLauncher, "miniRunLauncher must not be null");
|
||||||
this.resetPort = Objects.requireNonNull(resetPort, "resetPort must not be null");
|
this.resetPort = Objects.requireNonNull(resetPort, "resetPort must not be null");
|
||||||
this.threadFactory = Objects.requireNonNull(threadFactory, "threadFactory must not be null");
|
this.threadFactory = Objects.requireNonNull(threadFactory, "threadFactory must not be null");
|
||||||
this.fxDispatcher = Objects.requireNonNull(fxDispatcher, "fxDispatcher must not be null");
|
this.fxDispatcher = Objects.requireNonNull(fxDispatcher, "fxDispatcher must not be null");
|
||||||
this.listener = Objects.requireNonNull(listener, "listener must not be null");
|
this.listener = Objects.requireNonNull(listener, "listener must not be null");
|
||||||
this.historicalFileNamePort = Objects.requireNonNull(
|
this.historicalDocumentContextPort = Objects.requireNonNull(
|
||||||
historicalFileNamePort, "historicalFileNamePort must not be null");
|
historicalDocumentContextPort, "historicalDocumentContextPort must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -555,10 +556,12 @@ public final class GuiBatchRunCoordinator {
|
|||||||
/**
|
/**
|
||||||
* Wandelt ein {@link DocumentCompletionEvent} in eine {@link GuiBatchRunResultRow} um.
|
* Wandelt ein {@link DocumentCompletionEvent} in eine {@link GuiBatchRunResultRow} um.
|
||||||
* <p>
|
* <p>
|
||||||
* Für übersprungene Dokumente ({@link DocumentCompletionStatus#SKIPPED}) ohne
|
* Für übersprungene Dokumente ({@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}
|
||||||
* vorhandenen Dateinamen wird der historische KI-Dateiname über den
|
* und {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}) wird der historische
|
||||||
* {@link GuiHistoricalFileNamePort} nachgeladen. Schlägt die Abfrage fehl, bleibt
|
* Verarbeitungskontext über den {@link GuiHistoricalDocumentContextPort} nachgeladen.
|
||||||
* die Spalte leer. Die Methode läuft auf dem Worker-Thread.
|
* Für SKIPPED_ALREADY_PROCESSED wird der letzte Zieldateiname aus dem Kontext als
|
||||||
|
* {@code finalName} übernommen. Schlägt die Abfrage fehl, bleibt der Kontext leer.
|
||||||
|
* Die Methode läuft auf dem Worker-Thread.
|
||||||
*
|
*
|
||||||
* @param event das abgeschlossene Kandidatenereignis; darf nicht {@code null} sein
|
* @param event das abgeschlossene Kandidatenereignis; darf nicht {@code null} sein
|
||||||
* @param configFilePath Pfad zur aktiven Konfigurationsdatei; darf nicht {@code null} sein
|
* @param configFilePath Pfad zur aktiven Konfigurationsdatei; darf nicht {@code null} sein
|
||||||
@@ -567,16 +570,6 @@ public final class GuiBatchRunCoordinator {
|
|||||||
private GuiBatchRunResultRow toRow(DocumentCompletionEvent event, Path configFilePath) {
|
private GuiBatchRunResultRow toRow(DocumentCompletionEvent event, Path configFilePath) {
|
||||||
Optional<String> finalName = event.finalFileName() == null
|
Optional<String> finalName = event.finalFileName() == null
|
||||||
? Optional.empty() : Optional.of(event.finalFileName());
|
? Optional.empty() : Optional.of(event.finalFileName());
|
||||||
// Historischen KI-Dateinamen für übersprungene Dokumente nachladen
|
|
||||||
if (finalName.isEmpty() && event.status() == DocumentCompletionStatus.SKIPPED) {
|
|
||||||
try {
|
|
||||||
finalName = historicalFileNamePort.resolveHistoricalFileName(
|
|
||||||
configFilePath, event.fingerprint());
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.warn("Historischer Dateiname konnte nicht abgefragt werden für {}: {}",
|
|
||||||
event.originalFileName(), e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Optional<LocalDate> date = event.resolvedDate() == null
|
Optional<LocalDate> date = event.resolvedDate() == null
|
||||||
? Optional.empty() : Optional.of(event.resolvedDate());
|
? Optional.empty() : Optional.of(event.resolvedDate());
|
||||||
Optional<String> reasoning = event.aiReasoning() == null || event.aiReasoning().isBlank()
|
Optional<String> reasoning = event.aiReasoning() == null || event.aiReasoning().isBlank()
|
||||||
@@ -584,18 +577,41 @@ public final class GuiBatchRunCoordinator {
|
|||||||
Optional<String> failureMessage = event.failureMessage() == null || event.failureMessage().isBlank()
|
Optional<String> failureMessage = event.failureMessage() == null || event.failureMessage().isBlank()
|
||||||
? Optional.empty() : Optional.of(event.failureMessage());
|
? Optional.empty() : Optional.of(event.failureMessage());
|
||||||
Duration duration = event.processingDuration();
|
Duration duration = event.processingDuration();
|
||||||
|
|
||||||
|
// Historischen Kontext für übersprungene Dokumente nachladen
|
||||||
|
boolean isSkipped = event.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED
|
||||||
|
|| event.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE;
|
||||||
|
Optional<HistoricalDocumentContext> historicalContext = Optional.empty();
|
||||||
|
if (isSkipped) {
|
||||||
|
try {
|
||||||
|
historicalContext = historicalDocumentContextPort
|
||||||
|
.resolveHistoricalDocumentContext(configFilePath, event.fingerprint());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Historischer Kontext konnte nicht abgefragt werden für {}: {}",
|
||||||
|
event.originalFileName(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
// Zieldateiname für SKIPPED_ALREADY_PROCESSED aus Kontext übernehmen
|
||||||
|
if (finalName.isEmpty()) {
|
||||||
|
finalName = historicalContext
|
||||||
|
.flatMap(HistoricalDocumentContext::lastTargetFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new GuiBatchRunResultRow(
|
return new GuiBatchRunResultRow(
|
||||||
event.originalFileName(),
|
event.originalFileName(),
|
||||||
event.fingerprint(),
|
event.fingerprint(),
|
||||||
event.status(),
|
event.status(),
|
||||||
finalName,
|
finalName,
|
||||||
|
Optional.empty(),
|
||||||
date,
|
date,
|
||||||
reasoning,
|
reasoning,
|
||||||
failureMessage,
|
failureMessage,
|
||||||
duration);
|
duration,
|
||||||
|
false,
|
||||||
|
historicalContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GuiHistoricalFileNamePort noOpHistoricalFileNamePort() {
|
private static GuiHistoricalDocumentContextPort noOpHistoricalDocumentContextPort() {
|
||||||
return (configPath, fingerprint) -> Optional.empty();
|
return (configPath, fingerprint) -> Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-8
@@ -6,6 +6,7 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
|
import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,6 +45,8 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
|||||||
* never {@code null} and never negative
|
* never {@code null} and never negative
|
||||||
* @param resetPending {@code true} when the document's persistence status has been
|
* @param resetPending {@code true} when the document's persistence status has been
|
||||||
* reset and is awaiting the next processing run
|
* reset and is awaiting the next processing run
|
||||||
|
* @param historicalContext historischer Verarbeitungskontext für übersprungene Dokumente;
|
||||||
|
* leer bei nicht-übersprungenen Zeilen
|
||||||
*/
|
*/
|
||||||
public record GuiBatchRunResultRow(
|
public record GuiBatchRunResultRow(
|
||||||
String originalFileName,
|
String originalFileName,
|
||||||
@@ -55,7 +58,8 @@ public record GuiBatchRunResultRow(
|
|||||||
Optional<String> aiReasoning,
|
Optional<String> aiReasoning,
|
||||||
Optional<String> aiFailureMessage,
|
Optional<String> aiFailureMessage,
|
||||||
Duration processingDuration,
|
Duration processingDuration,
|
||||||
boolean resetPending) {
|
boolean resetPending,
|
||||||
|
Optional<HistoricalDocumentContext> historicalContext) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label shown in the status column when a document's persistence status has been
|
* Label shown in the status column when a document's persistence status has been
|
||||||
@@ -93,11 +97,12 @@ public record GuiBatchRunResultRow(
|
|||||||
if (processingDuration.isNegative()) {
|
if (processingDuration.isNegative()) {
|
||||||
throw new IllegalArgumentException("processingDuration must not be negative");
|
throw new IllegalArgumentException("processingDuration must not be negative");
|
||||||
}
|
}
|
||||||
|
historicalContext = historicalContext == null ? Optional.empty() : historicalContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bequem-Konstruktor für Zeilen, die weder einen manuell korrigierten Dateinamen
|
* Bequem-Konstruktor für Zeilen, die weder einen manuell korrigierten Dateinamen
|
||||||
* tragen noch im reset-pending-Zustand stehen.
|
* tragen noch im reset-pending-Zustand stehen und keinen historischen Kontext haben.
|
||||||
*
|
*
|
||||||
* @param originalFileName the source filename; never {@code null} or blank
|
* @param originalFileName the source filename; never {@code null} or blank
|
||||||
* @param fingerprint the content-based document identity; never {@code null}
|
* @param fingerprint the content-based document identity; never {@code null}
|
||||||
@@ -122,12 +127,13 @@ public record GuiBatchRunResultRow(
|
|||||||
Optional<String> aiFailureMessage,
|
Optional<String> aiFailureMessage,
|
||||||
Duration processingDuration) {
|
Duration processingDuration) {
|
||||||
this(originalFileName, fingerprint, status, finalFileName, Optional.empty(),
|
this(originalFileName, fingerprint, status, finalFileName, Optional.empty(),
|
||||||
resolvedDate, aiReasoning, aiFailureMessage, processingDuration, false);
|
resolvedDate, aiReasoning, aiFailureMessage, processingDuration, false,
|
||||||
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bequem-Konstruktor mit explizitem {@code resetPending}-Flag, aber ohne manuell
|
* Bequem-Konstruktor mit explizitem {@code resetPending}-Flag, aber ohne manuell
|
||||||
* korrigierten Dateinamen.
|
* korrigierten Dateinamen und ohne historischen Kontext.
|
||||||
*
|
*
|
||||||
* @param originalFileName the source filename; never {@code null} or blank
|
* @param originalFileName the source filename; never {@code null} or blank
|
||||||
* @param fingerprint the content-based document identity; never {@code null}
|
* @param fingerprint the content-based document identity; never {@code null}
|
||||||
@@ -154,7 +160,8 @@ public record GuiBatchRunResultRow(
|
|||||||
Duration processingDuration,
|
Duration processingDuration,
|
||||||
boolean resetPending) {
|
boolean resetPending) {
|
||||||
this(originalFileName, fingerprint, status, finalFileName, Optional.empty(),
|
this(originalFileName, fingerprint, status, finalFileName, Optional.empty(),
|
||||||
resolvedDate, aiReasoning, aiFailureMessage, processingDuration, resetPending);
|
resolvedDate, aiReasoning, aiFailureMessage, processingDuration, resetPending,
|
||||||
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,8 +185,10 @@ public record GuiBatchRunResultRow(
|
|||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Duration.ZERO,
|
Duration.ZERO,
|
||||||
true);
|
true,
|
||||||
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -199,7 +208,8 @@ public record GuiBatchRunResultRow(
|
|||||||
case SUCCESS -> "\u2714"; // ✔ HEAVY CHECK MARK
|
case SUCCESS -> "\u2714"; // ✔ HEAVY CHECK MARK
|
||||||
case FAILED_RETRYABLE -> "\u26A0"; // ⚠ WARNING SIGN
|
case FAILED_RETRYABLE -> "\u26A0"; // ⚠ WARNING SIGN
|
||||||
case FAILED_PERMANENT -> "\u2718"; // ✘ HEAVY BALLOT X
|
case FAILED_PERMANENT -> "\u2718"; // ✘ HEAVY BALLOT X
|
||||||
case SKIPPED -> "\u25BA"; // ► BLACK RIGHT-POINTING POINTER
|
case SKIPPED_ALREADY_PROCESSED,
|
||||||
|
SKIPPED_FINAL_FAILURE -> "\u25BA"; // ► BLACK RIGHT-POINTING POINTER
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +229,8 @@ public record GuiBatchRunResultRow(
|
|||||||
case SUCCESS -> "Erfolgreich";
|
case SUCCESS -> "Erfolgreich";
|
||||||
case FAILED_RETRYABLE -> "Fehlgeschlagen (wiederholbar)";
|
case FAILED_RETRYABLE -> "Fehlgeschlagen (wiederholbar)";
|
||||||
case FAILED_PERMANENT -> "Fehlgeschlagen (permanent)";
|
case FAILED_PERMANENT -> "Fehlgeschlagen (permanent)";
|
||||||
case SKIPPED -> "Übersprungen";
|
case SKIPPED_ALREADY_PROCESSED -> "Übersprungen (bereits verarbeitet)";
|
||||||
|
case SKIPPED_FINAL_FAILURE -> "Übersprungen (endgültig fehlgeschlagen)";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+44
-10
@@ -2,7 +2,9 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;
|
|||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -193,7 +195,7 @@ public final class GuiBatchRunTab {
|
|||||||
private final Runnable onRunStateChanged;
|
private final Runnable onRunStateChanged;
|
||||||
private final GuiBatchRunCoordinator coordinator;
|
private final GuiBatchRunCoordinator coordinator;
|
||||||
private final Supplier<GuiManualFileRenamePort> manualFileRenamePortSupplier;
|
private final Supplier<GuiManualFileRenamePort> manualFileRenamePortSupplier;
|
||||||
private final Supplier<GuiHistoricalFileNamePort> historicalFileNamePortSupplier;
|
private final Supplier<GuiHistoricalDocumentContextPort> historicalDocumentContextPortSupplier;
|
||||||
private final Supplier<Optional<Path>> sourceFolderSupplier;
|
private final Supplier<Optional<Path>> sourceFolderSupplier;
|
||||||
private final Supplier<Optional<String>> targetFolderSupplier;
|
private final Supplier<Optional<String>> targetFolderSupplier;
|
||||||
|
|
||||||
@@ -232,8 +234,8 @@ public final class GuiBatchRunTab {
|
|||||||
* null sein
|
* null sein
|
||||||
* @param manualFileRenamePortSupplier Supplier für den manuellen Umbennennungs-Port;
|
* @param manualFileRenamePortSupplier Supplier für den manuellen Umbennennungs-Port;
|
||||||
* darf nicht null sein
|
* darf nicht null sein
|
||||||
* @param historicalFileNamePortSupplier Supplier für den historischen Dateiname-Port;
|
* @param historicalDocumentContextPortSupplier Supplier für den historischen Kontext-Port;
|
||||||
* darf nicht null sein
|
* darf nicht null sein
|
||||||
* @param sourceFolderSupplier Supplier für den konfigurierten Quellordner;
|
* @param sourceFolderSupplier Supplier für den konfigurierten Quellordner;
|
||||||
* darf leeres Optional zurückliefern
|
* darf leeres Optional zurückliefern
|
||||||
* @param targetFolderSupplier Supplier für den konfigurierten Zielordner als
|
* @param targetFolderSupplier Supplier für den konfigurierten Zielordner als
|
||||||
@@ -246,7 +248,7 @@ public final class GuiBatchRunTab {
|
|||||||
BooleanSupplier savedConfigurationReadyCheck,
|
BooleanSupplier savedConfigurationReadyCheck,
|
||||||
Runnable onRunStateChanged,
|
Runnable onRunStateChanged,
|
||||||
Supplier<GuiManualFileRenamePort> manualFileRenamePortSupplier,
|
Supplier<GuiManualFileRenamePort> manualFileRenamePortSupplier,
|
||||||
Supplier<GuiHistoricalFileNamePort> historicalFileNamePortSupplier,
|
Supplier<GuiHistoricalDocumentContextPort> historicalDocumentContextPortSupplier,
|
||||||
Supplier<Optional<Path>> sourceFolderSupplier,
|
Supplier<Optional<Path>> sourceFolderSupplier,
|
||||||
Supplier<Optional<String>> targetFolderSupplier) {
|
Supplier<Optional<String>> targetFolderSupplier) {
|
||||||
Objects.requireNonNull(launcherSupplier, "launcherSupplier must not be null");
|
Objects.requireNonNull(launcherSupplier, "launcherSupplier must not be null");
|
||||||
@@ -258,8 +260,8 @@ public final class GuiBatchRunTab {
|
|||||||
this.onRunStateChanged = Objects.requireNonNull(onRunStateChanged, "onRunStateChanged must not be null");
|
this.onRunStateChanged = Objects.requireNonNull(onRunStateChanged, "onRunStateChanged must not be null");
|
||||||
this.manualFileRenamePortSupplier = Objects.requireNonNull(
|
this.manualFileRenamePortSupplier = Objects.requireNonNull(
|
||||||
manualFileRenamePortSupplier, "manualFileRenamePortSupplier must not be null");
|
manualFileRenamePortSupplier, "manualFileRenamePortSupplier must not be null");
|
||||||
this.historicalFileNamePortSupplier = Objects.requireNonNull(
|
this.historicalDocumentContextPortSupplier = Objects.requireNonNull(
|
||||||
historicalFileNamePortSupplier, "historicalFileNamePortSupplier must not be null");
|
historicalDocumentContextPortSupplier, "historicalDocumentContextPortSupplier must not be null");
|
||||||
this.sourceFolderSupplier = Objects.requireNonNull(
|
this.sourceFolderSupplier = Objects.requireNonNull(
|
||||||
sourceFolderSupplier, "sourceFolderSupplier must not be null");
|
sourceFolderSupplier, "sourceFolderSupplier must not be null");
|
||||||
this.targetFolderSupplier = Objects.requireNonNull(
|
this.targetFolderSupplier = Objects.requireNonNull(
|
||||||
@@ -273,7 +275,7 @@ public final class GuiBatchRunTab {
|
|||||||
(configPath, fingerprints) ->
|
(configPath, fingerprints) ->
|
||||||
resetPortSupplier.get().reset(configPath, fingerprints),
|
resetPortSupplier.get().reset(configPath, fingerprints),
|
||||||
new CoordinatorListener(),
|
new CoordinatorListener(),
|
||||||
historicalFileNamePortSupplier.get());
|
historicalDocumentContextPortSupplier.get());
|
||||||
|
|
||||||
this.tab.setClosable(false);
|
this.tab.setClosable(false);
|
||||||
this.tab.setContent(buildContent());
|
this.tab.setContent(buildContent());
|
||||||
@@ -799,7 +801,8 @@ public final class GuiBatchRunTab {
|
|||||||
row.aiReasoning(),
|
row.aiReasoning(),
|
||||||
row.aiFailureMessage(),
|
row.aiFailureMessage(),
|
||||||
row.processingDuration(),
|
row.processingDuration(),
|
||||||
row.resetPending());
|
row.resetPending(),
|
||||||
|
row.historicalContext());
|
||||||
currentlySelectedRow = updatedRow;
|
currentlySelectedRow = updatedRow;
|
||||||
// Dirty-State vor dem Zeilen-Upsert zurücksetzen, damit das folgende
|
// Dirty-State vor dem Zeilen-Upsert zurücksetzen, damit das folgende
|
||||||
// resultItems.set() keinen Verwerfen-Dialog über den selectedItemProperty-Listener
|
// resultItems.set() keinen Verwerfen-Dialog über den selectedItemProperty-Listener
|
||||||
@@ -1216,7 +1219,7 @@ public final class GuiBatchRunTab {
|
|||||||
case SUCCESS -> "#2e7d32";
|
case SUCCESS -> "#2e7d32";
|
||||||
case FAILED_RETRYABLE -> "#e65100";
|
case FAILED_RETRYABLE -> "#e65100";
|
||||||
case FAILED_PERMANENT -> "#c62828";
|
case FAILED_PERMANENT -> "#c62828";
|
||||||
case SKIPPED -> "#757575";
|
case SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> "#757575";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1228,6 +1231,9 @@ public final class GuiBatchRunTab {
|
|||||||
return String.format("%.1f s", seconds);
|
return String.format("%.1f s", seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DETAIL_DATE_FORMAT =
|
||||||
|
DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm", Locale.GERMANY);
|
||||||
|
|
||||||
private static String buildDetailText(GuiBatchRunResultRow row) {
|
private static String buildDetailText(GuiBatchRunResultRow row) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append("Originaldateiname: ").append(row.originalFileName()).append('\n');
|
builder.append("Originaldateiname: ").append(row.originalFileName()).append('\n');
|
||||||
@@ -1235,6 +1241,34 @@ public final class GuiBatchRunTab {
|
|||||||
builder.append('\n').append(GuiBatchRunResultRow.RESET_PENDING_LABEL);
|
builder.append('\n').append(GuiBatchRunResultRow.RESET_PENDING_LABEL);
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
if (row.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED) {
|
||||||
|
builder.append('\n');
|
||||||
|
row.historicalContext().ifPresentOrElse(ctx -> {
|
||||||
|
ctx.lastSuccessInstant().ifPresentOrElse(
|
||||||
|
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
|
||||||
|
.append(DETAIL_DATE_FORMAT.format(
|
||||||
|
instant.atZone(ZoneId.systemDefault())))
|
||||||
|
.append('.'),
|
||||||
|
() -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||||
|
ctx.lastTargetFileName().ifPresent(name ->
|
||||||
|
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
|
||||||
|
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
if (row.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE) {
|
||||||
|
builder.append('\n');
|
||||||
|
row.historicalContext().ifPresentOrElse(ctx ->
|
||||||
|
ctx.lastFailureInstant().ifPresentOrElse(
|
||||||
|
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
|
||||||
|
.append(DETAIL_DATE_FORMAT.format(
|
||||||
|
instant.atZone(ZoneId.systemDefault())))
|
||||||
|
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
|
||||||
|
() -> builder.append(
|
||||||
|
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
|
||||||
|
() -> builder.append(
|
||||||
|
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
row.effectiveFileName()
|
row.effectiveFileName()
|
||||||
.ifPresent(name -> builder.append("Neuer Dateiname: ").append(name).append('\n'));
|
.ifPresent(name -> builder.append("Neuer Dateiname: ").append(name).append('\n'));
|
||||||
row.resolvedDate()
|
row.resolvedDate()
|
||||||
@@ -1311,7 +1345,7 @@ public final class GuiBatchRunTab {
|
|||||||
switch (row.status()) {
|
switch (row.status()) {
|
||||||
case SUCCESS -> successCount++;
|
case SUCCESS -> successCount++;
|
||||||
case FAILED_RETRYABLE, FAILED_PERMANENT -> failedCount++;
|
case FAILED_RETRYABLE, FAILED_PERMANENT -> failedCount++;
|
||||||
case SKIPPED -> skippedCount++;
|
case SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> skippedCount++;
|
||||||
default -> throw new IllegalStateException(
|
default -> throw new IllegalStateException(
|
||||||
"Unerwarteter Status: " + row.status());
|
"Unerwarteter Status: " + row.status());
|
||||||
}
|
}
|
||||||
|
|||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI-interner Port zum Abfragen des historischen Verarbeitungskontexts einer Quelldatei.
|
||||||
|
* <p>
|
||||||
|
* Wird im Verarbeitungslauf-Tab genutzt, um für übersprungene Dokumente
|
||||||
|
* ({@link de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}
|
||||||
|
* und
|
||||||
|
* {@link de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus#SKIPPED_FINAL_FAILURE})
|
||||||
|
* den historischen Kontext nachzuschlagen. Der Kontext wird im Detailbereich des
|
||||||
|
* Verarbeitungslauf-Tabs angezeigt.
|
||||||
|
* <p>
|
||||||
|
* Die Bootstrap-Schicht liefert die konkrete Implementierung. Sie lädt die
|
||||||
|
* Konfiguration aus {@code configFilePath}, baut den zugehörigen Use-Case auf und
|
||||||
|
* gibt das Ergebnis zurück. Technische Fehler beim Laden oder Abfragen werden intern
|
||||||
|
* abgefangen und als leeres {@link Optional} zurückgegeben.
|
||||||
|
* <p>
|
||||||
|
* Die Implementierung läuft auf dem Worker-Thread des {@link GuiBatchRunCoordinator}
|
||||||
|
* und darf blockieren.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface GuiHistoricalDocumentContextPort {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den historischen Verarbeitungskontext für das durch {@code fingerprint}
|
||||||
|
* identifizierte Dokument zurück, oder ein leeres {@link Optional}, wenn kein
|
||||||
|
* Kontext verfügbar ist.
|
||||||
|
*
|
||||||
|
* @param configFilePath Pfad zur aktiven {@code .properties}-Konfigurationsdatei;
|
||||||
|
* darf nicht {@code null} sein
|
||||||
|
* @param fingerprint inhaltsbasierte Dokumentenidentität; darf nicht {@code null} sein
|
||||||
|
* @return historischer Kontext des Dokuments, oder leer wenn nicht verfügbar
|
||||||
|
*/
|
||||||
|
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContext(
|
||||||
|
Path configFilePath, DocumentFingerprint fingerprint);
|
||||||
|
}
|
||||||
+1
-1
@@ -321,7 +321,7 @@ class FileNameEditorPaneTest {
|
|||||||
Optional.of("2026-01-01 - KI-Vorschlag.pdf"),
|
Optional.of("2026-01-01 - KI-Vorschlag.pdf"),
|
||||||
Optional.of("2026-01-01 - Manuell.pdf"),
|
Optional.of("2026-01-01 - Manuell.pdf"),
|
||||||
Optional.empty(), Optional.empty(), Optional.empty(),
|
Optional.empty(), Optional.empty(), Optional.empty(),
|
||||||
Duration.ofMillis(1), false);
|
Duration.ofMillis(1), false, Optional.empty());
|
||||||
pane.loadSelection(row, "C:\\target");
|
pane.loadSelection(row, "C:\\target");
|
||||||
// lastSavedName = "2026-01-01 - Manuell" (effectiveFileName)
|
// lastSavedName = "2026-01-01 - Manuell" (effectiveFileName)
|
||||||
assertEquals("2026-01-01 - Manuell", pane.textField().getText());
|
assertEquals("2026-01-01 - Manuell", pane.textField().getText());
|
||||||
|
|||||||
+3
-3
@@ -118,7 +118,7 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
GuiBatchRunLauncher launcher = (configPath, observer, token) -> {
|
GuiBatchRunLauncher launcher = (configPath, observer, token) -> {
|
||||||
observer.onRunStarted(new RunId("run-skip"), 1);
|
observer.onRunStarted(new RunId("run-skip"), 1);
|
||||||
observer.onDocumentCompleted(new DocumentCompletionEvent(
|
observer.onDocumentCompleted(new DocumentCompletionEvent(
|
||||||
"c.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED,
|
"c.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED,
|
||||||
null, null, null, null, Duration.ofMillis(5)));
|
null, null, null, null, Duration.ofMillis(5)));
|
||||||
observer.onRunEnded(new RunSummary(0, 0, 1));
|
observer.onRunEnded(new RunSummary(0, 0, 1));
|
||||||
return GuiBatchRunLaunchOutcome.completed();
|
return GuiBatchRunLaunchOutcome.completed();
|
||||||
@@ -131,7 +131,7 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
|
|
||||||
assertEquals(List.of(
|
assertEquals(List.of(
|
||||||
"started:1",
|
"started:1",
|
||||||
"row:SKIPPED:c.pdf",
|
"row:SKIPPED_ALREADY_PROCESSED:c.pdf",
|
||||||
"ended:started=true,completed=true,summary=0/0/1"), events);
|
"ended:started=true,completed=true,summary=0/0/1"), events);
|
||||||
assertFalse(coordinator.isRunning());
|
assertFalse(coordinator.isRunning());
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ class GuiBatchRunCoordinatorTest {
|
|||||||
assertEquals("\u2714", row(DocumentCompletionStatus.SUCCESS).statusIcon());
|
assertEquals("\u2714", row(DocumentCompletionStatus.SUCCESS).statusIcon());
|
||||||
assertEquals("\u26A0", row(DocumentCompletionStatus.FAILED_RETRYABLE).statusIcon());
|
assertEquals("\u26A0", row(DocumentCompletionStatus.FAILED_RETRYABLE).statusIcon());
|
||||||
assertEquals("\u2718", row(DocumentCompletionStatus.FAILED_PERMANENT).statusIcon());
|
assertEquals("\u2718", row(DocumentCompletionStatus.FAILED_PERMANENT).statusIcon());
|
||||||
assertEquals("\u25BA", row(DocumentCompletionStatus.SKIPPED).statusIcon());
|
assertEquals("\u25BA", row(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED).statusIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+7
-2
@@ -109,8 +109,13 @@ class GuiBatchRunResultRowTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void statusIcon_skipped_isPointer() {
|
void statusIcon_skippedAlreadyProcessed_isPointer() {
|
||||||
assertEquals("\u25BA", row(DocumentCompletionStatus.SKIPPED).statusIcon());
|
assertEquals("\u25BA", row(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED).statusIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void statusIcon_skippedFinalFailure_isPointer() {
|
||||||
|
assertEquals("\u25BA", row(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE).statusIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ class GuiBatchRunTabSelectionSmokeTest {
|
|||||||
runOnFx(() -> {
|
runOnFx(() -> {
|
||||||
GuiBatchRunTab tab = makeTab();
|
GuiBatchRunTab tab = makeTab();
|
||||||
tab.upsertResultRowByFingerprint(row("a.pdf", FP1, DocumentCompletionStatus.SUCCESS));
|
tab.upsertResultRowByFingerprint(row("a.pdf", FP1, DocumentCompletionStatus.SUCCESS));
|
||||||
tab.upsertResultRowByFingerprint(row("b.pdf", FP2, DocumentCompletionStatus.SKIPPED));
|
tab.upsertResultRowByFingerprint(row("b.pdf", FP2, DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED));
|
||||||
tab.upsertResultRowByFingerprint(row("c.pdf", FP3, DocumentCompletionStatus.FAILED_PERMANENT));
|
tab.upsertResultRowByFingerprint(row("c.pdf", FP3, DocumentCompletionStatus.FAILED_PERMANENT));
|
||||||
assertEquals(3, tab.resultTable().getItems().size());
|
assertEquals(3, tab.resultTable().getItems().size());
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -107,7 +107,7 @@ class GuiBatchRunTabSmokeTest {
|
|||||||
"b.pdf", DUMMY_FP, DocumentCompletionStatus.FAILED_RETRYABLE,
|
"b.pdf", DUMMY_FP, DocumentCompletionStatus.FAILED_RETRYABLE,
|
||||||
null, null, null, null, Duration.ofMillis(10)));
|
null, null, null, null, Duration.ofMillis(10)));
|
||||||
observer.onDocumentCompleted(new DocumentCompletionEvent(
|
observer.onDocumentCompleted(new DocumentCompletionEvent(
|
||||||
"c.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED,
|
"c.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED,
|
||||||
null, null, null, null, Duration.ofMillis(5)));
|
null, null, null, null, Duration.ofMillis(5)));
|
||||||
observer.onRunEnded(new RunSummary(1, 1, 1));
|
observer.onRunEnded(new RunSummary(1, 1, 1));
|
||||||
return GuiBatchRunLaunchOutcome.completed();
|
return GuiBatchRunLaunchOutcome.completed();
|
||||||
@@ -142,7 +142,7 @@ class GuiBatchRunTabSmokeTest {
|
|||||||
|
|
||||||
// SKIPPED row must carry the ► icon, not ✘.
|
// SKIPPED row must carry the ► icon, not ✘.
|
||||||
GuiBatchRunResultRow skippedRow = tab().resultTable().getItems().get(2);
|
GuiBatchRunResultRow skippedRow = tab().resultTable().getItems().get(2);
|
||||||
assertEquals(DocumentCompletionStatus.SKIPPED, skippedRow.status());
|
assertEquals(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED, skippedRow.status());
|
||||||
assertEquals("\u25BA", skippedRow.statusIcon());
|
assertEquals("\u25BA", skippedRow.statusIcon());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -5,10 +5,10 @@ package de.gecheckt.pdf.umbenenner.application.port.in;
|
|||||||
* {@link BatchRunProgressObserver#onDocumentCompleted(DocumentCompletionEvent)}
|
* {@link BatchRunProgressObserver#onDocumentCompleted(DocumentCompletionEvent)}
|
||||||
* for one processed candidate.
|
* for one processed candidate.
|
||||||
* <p>
|
* <p>
|
||||||
* This enum collapses the finer-grained internal processing status into the four
|
* This enum collapses the finer-grained internal processing status into five
|
||||||
* buckets that an observer (e.g. a GUI progress view) needs to distinguish:
|
* buckets that an observer (e.g. a GUI progress view) needs to distinguish:
|
||||||
* successful completion, retryable failure, permanent failure, and an explicit
|
* successful completion, retryable failure, permanent failure, and two explicit
|
||||||
* skip.
|
* skip variants.
|
||||||
* <p>
|
* <p>
|
||||||
* This classification is purely an observability concern — persistence,
|
* This classification is purely an observability concern — persistence,
|
||||||
* retry decisions, and all other processing rules continue to work against the
|
* retry decisions, and all other processing rules continue to work against the
|
||||||
@@ -36,8 +36,16 @@ public enum DocumentCompletionStatus {
|
|||||||
FAILED_PERMANENT,
|
FAILED_PERMANENT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The candidate was skipped because it was already in a terminal state (either
|
* Der Kandidat wurde übersprungen, weil er in einem früheren Lauf bereits
|
||||||
* previously successful or previously finally failed).
|
* erfolgreich verarbeitet wurde. Der Gesamtstatus im Stammsatz lautet
|
||||||
|
* {@code SUCCESS}.
|
||||||
*/
|
*/
|
||||||
SKIPPED
|
SKIPPED_ALREADY_PROCESSED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Kandidat wurde übersprungen, weil er in einem früheren Lauf bereits
|
||||||
|
* endgültig fehlgeschlagen ist. Der Gesamtstatus im Stammsatz lautet
|
||||||
|
* {@code FAILED_FINAL}.
|
||||||
|
*/
|
||||||
|
SKIPPED_FINAL_FAILURE
|
||||||
}
|
}
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Historischer Kontext eines Dokuments, das in einem früheren Lauf bereits terminal
|
||||||
|
* abgeschlossen wurde.
|
||||||
|
* <p>
|
||||||
|
* Wird genutzt, um im GUI-Detailbereich bei übersprungenen Dokumenten Informationen
|
||||||
|
* darüber anzuzeigen, wann und mit welchem Ergebnis die Datei früher verarbeitet wurde.
|
||||||
|
* <p>
|
||||||
|
* Für Dokumente mit früherem Erfolgsstatus ({@code wasEverSuccessful == true}) sind
|
||||||
|
* {@code lastTargetFileName} und {@code lastSuccessInstant} belegt.
|
||||||
|
* Für Dokumente, die endgültig fehlgeschlagen sind, ist {@code lastFailureInstant} belegt.
|
||||||
|
*
|
||||||
|
* @param lastTargetFileName letzter erfolgreich geschriebener Zieldateiname; leer wenn
|
||||||
|
* das Dokument nie erfolgreich kopiert wurde
|
||||||
|
* @param lastSuccessInstant Zeitpunkt der letzten erfolgreichen Verarbeitung; leer wenn
|
||||||
|
* das Dokument nie erfolgreich verarbeitet wurde
|
||||||
|
* @param lastFailureInstant Zeitpunkt des letzten Fehlschlags; leer wenn noch kein
|
||||||
|
* Fehlschlag aufgetreten ist
|
||||||
|
* @param wasEverSuccessful {@code true} wenn das Dokument mindestens einmal erfolgreich
|
||||||
|
* verarbeitet wurde
|
||||||
|
*/
|
||||||
|
public record HistoricalDocumentContext(
|
||||||
|
Optional<String> lastTargetFileName,
|
||||||
|
Optional<Instant> lastSuccessInstant,
|
||||||
|
Optional<Instant> lastFailureInstant,
|
||||||
|
boolean wasEverSuccessful) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kompakter Konstruktor zur Normalisierung von {@code null}-Werten.
|
||||||
|
*
|
||||||
|
* @throws NullPointerException wenn {@code lastTargetFileName},
|
||||||
|
* {@code lastSuccessInstant} oder
|
||||||
|
* {@code lastFailureInstant} {@code null} sind
|
||||||
|
*/
|
||||||
|
public HistoricalDocumentContext {
|
||||||
|
lastTargetFileName = lastTargetFileName == null ? Optional.empty() : lastTargetFileName;
|
||||||
|
lastSuccessInstant = lastSuccessInstant == null ? Optional.empty() : lastSuccessInstant;
|
||||||
|
lastFailureInstant = lastFailureInstant == null ? Optional.empty() : lastFailureInstant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen Kontext für ein erfolgreich verarbeitetes Dokument.
|
||||||
|
*
|
||||||
|
* @param lastTargetFileName letzter Zieldateiname; darf {@code null} sein
|
||||||
|
* @param lastSuccessInstant Zeitpunkt des Erfolgs; darf {@code null} sein
|
||||||
|
* @return neuer Kontext mit {@code wasEverSuccessful == true}
|
||||||
|
*/
|
||||||
|
public static HistoricalDocumentContext ofSuccess(
|
||||||
|
String lastTargetFileName, Instant lastSuccessInstant) {
|
||||||
|
return new HistoricalDocumentContext(
|
||||||
|
Optional.ofNullable(lastTargetFileName),
|
||||||
|
Optional.ofNullable(lastSuccessInstant),
|
||||||
|
Optional.empty(),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen Kontext für ein endgültig fehlgeschlagenes Dokument.
|
||||||
|
*
|
||||||
|
* @param lastFailureInstant Zeitpunkt des letzten Fehlschlags; darf {@code null} sein
|
||||||
|
* @return neuer Kontext mit {@code wasEverSuccessful == false}
|
||||||
|
*/
|
||||||
|
public static HistoricalDocumentContext ofFinalFailure(Instant lastFailureInstant) {
|
||||||
|
return new HistoricalDocumentContext(
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.ofNullable(lastFailureInstant),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inbound-Port zum Abfragen des historischen Verarbeitungskontexts eines Dokuments.
|
||||||
|
* <p>
|
||||||
|
* Wird im GUI-Verarbeitungslauf-Tab eingesetzt, um für übersprungene Dokumente
|
||||||
|
* anzuzeigen, wann und mit welchem Ergebnis die Datei in einem früheren Lauf
|
||||||
|
* verarbeitet wurde.
|
||||||
|
* <p>
|
||||||
|
* Für Dokumente mit früherem Erfolgsstatus enthält der zurückgegebene Kontext den
|
||||||
|
* letzten Zieldateinamen und den Erfolgszeitpunkt. Für endgültig fehlgeschlagene
|
||||||
|
* Dokumente ist der letzte Fehlzeitpunkt belegt.
|
||||||
|
* <p>
|
||||||
|
* <strong>Architekturgrenzen:</strong> Implementierungen dieses Ports dürfen keine
|
||||||
|
* JDBC-, SQLite-, Dateisystem- oder HTTP-Typen nach außen exponieren. Alle
|
||||||
|
* Infrastrukturdetails verbleiben in der Adapter-Schicht.
|
||||||
|
*/
|
||||||
|
public interface ResolveHistoricalDocumentContextUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den historischen Verarbeitungskontext für das durch den Fingerprint
|
||||||
|
* identifizierte Dokument zurück.
|
||||||
|
* <p>
|
||||||
|
* Liefert einen gefüllten Kontext, wenn das Dokument einen früheren terminalen
|
||||||
|
* Abschluss (Erfolg oder endgültiger Fehlschlag) hat. Gibt ein leeres
|
||||||
|
* {@link Optional} zurück, wenn kein passender Stammsatz vorhanden ist oder
|
||||||
|
* die Abfrage technisch fehlschlägt.
|
||||||
|
* <p>
|
||||||
|
* Wirft keine geprüften Ausnahmen: technische Abfragefehler werden intern
|
||||||
|
* abgefangen und als leeres Ergebnis zurückgegeben.
|
||||||
|
*
|
||||||
|
* @param fingerprint inhaltsbasierte Dokumentenidentität; darf nicht {@code null} sein
|
||||||
|
* @return historischer Kontext des Dokuments, oder leer wenn nicht verfügbar
|
||||||
|
* @throws NullPointerException wenn {@code fingerprint} {@code null} ist
|
||||||
|
*/
|
||||||
|
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContext(
|
||||||
|
DocumentFingerprint fingerprint);
|
||||||
|
}
|
||||||
+3
-2
@@ -13,8 +13,9 @@ package de.gecheckt.pdf.umbenenner.application.port.in;
|
|||||||
* @param failedCount number of candidates that completed with either
|
* @param failedCount number of candidates that completed with either
|
||||||
* {@link DocumentCompletionStatus#FAILED_RETRYABLE} or
|
* {@link DocumentCompletionStatus#FAILED_RETRYABLE} or
|
||||||
* {@link DocumentCompletionStatus#FAILED_PERMANENT}; must be ≥ 0
|
* {@link DocumentCompletionStatus#FAILED_PERMANENT}; must be ≥ 0
|
||||||
* @param skippedCount number of candidates that completed with
|
* @param skippedCount number of candidates that completed with either
|
||||||
* {@link DocumentCompletionStatus#SKIPPED}; must be ≥ 0
|
* {@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED} or
|
||||||
|
* {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}; must be ≥ 0
|
||||||
*/
|
*/
|
||||||
public record RunSummary(int successCount, int failedCount, int skippedCount) {
|
public record RunSummary(int successCount, int failedCount, int skippedCount) {
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -815,7 +815,11 @@ public class DocumentProcessingCoordinator {
|
|||||||
|
|
||||||
logger.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
logger.debug("Skip attempt #{} persisted for '{}' with status {}.",
|
||||||
attemptNumber, candidate.uniqueIdentifier(), skipStatus);
|
attemptNumber, candidate.uniqueIdentifier(), skipStatus);
|
||||||
publishCompletion(candidate, fingerprint, DocumentCompletionStatus.SKIPPED,
|
DocumentCompletionStatus completionStatus =
|
||||||
|
skipStatus == ProcessingStatus.SKIPPED_ALREADY_PROCESSED
|
||||||
|
? DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED
|
||||||
|
: DocumentCompletionStatus.SKIPPED_FINAL_FAILURE;
|
||||||
|
publishCompletion(candidate, fingerprint, completionStatus,
|
||||||
null, null, null, null, attemptStart, now);
|
null, null, null, null, attemptStart, now);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ final class CountingCompletionObserver implements Consumer<DocumentCompletionEve
|
|||||||
switch (event.status()) {
|
switch (event.status()) {
|
||||||
case SUCCESS -> successCount++;
|
case SUCCESS -> successCount++;
|
||||||
case FAILED_RETRYABLE, FAILED_PERMANENT -> failedCount++;
|
case FAILED_RETRYABLE, FAILED_PERMANENT -> failedCount++;
|
||||||
case SKIPPED -> skippedCount++;
|
case SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> skippedCount++;
|
||||||
default -> {
|
default -> {
|
||||||
// Defensive — new status values would be a programming error.
|
// Defensive — new status values would be a programming error.
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalDocumentContextUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardimplementierung von {@link ResolveHistoricalDocumentContextUseCase}.
|
||||||
|
* <p>
|
||||||
|
* Fragt den {@link DocumentRecordRepository} nach dem Stammsatz des angegebenen
|
||||||
|
* Fingerprints ab. Abhängig vom terminalen Zustand des Dokuments wird ein passender
|
||||||
|
* {@link HistoricalDocumentContext} zurückgegeben:
|
||||||
|
* <ul>
|
||||||
|
* <li>Bei früherem Erfolgsstatus: Zieldateiname und Erfolgszeitpunkt aus dem
|
||||||
|
* Stammsatz ({@code lastTargetFileName}, {@code lastSuccessInstant}).</li>
|
||||||
|
* <li>Bei endgültigem Fehlschlag: Fehlzeitpunkt aus dem Stammsatz
|
||||||
|
* ({@code lastFailureInstant}).</li>
|
||||||
|
* <li>In allen anderen Fällen (unbekannt, verarbeitbar) sowie bei technischen
|
||||||
|
* Abfragefehlern: leeres {@link Optional}.</li>
|
||||||
|
* </ul>
|
||||||
|
* Technische Fehler bei der Repository-Abfrage werden intern abgefangen; der Aufrufer
|
||||||
|
* erhält stets ein leeres Ergebnis statt einer Ausnahme.
|
||||||
|
*/
|
||||||
|
public class DefaultResolveHistoricalDocumentContextUseCase
|
||||||
|
implements ResolveHistoricalDocumentContextUseCase {
|
||||||
|
|
||||||
|
private final DocumentRecordRepository documentRecordRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt den Use Case mit dem erforderlichen Dokument-Stammsatz-Repository.
|
||||||
|
*
|
||||||
|
* @param documentRecordRepository Repository zum Lesen von Dokument-Stammsätzen;
|
||||||
|
* darf nicht {@code null} sein
|
||||||
|
* @throws NullPointerException wenn {@code documentRecordRepository} {@code null} ist
|
||||||
|
*/
|
||||||
|
public DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
DocumentRecordRepository documentRecordRepository) {
|
||||||
|
this.documentRecordRepository = Objects.requireNonNull(
|
||||||
|
documentRecordRepository, "documentRecordRepository must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den historischen Verarbeitungskontext für das durch den Fingerprint
|
||||||
|
* identifizierte Dokument zurück.
|
||||||
|
* <p>
|
||||||
|
* Für Dokumente mit früherem Erfolgsstatus enthält der Kontext Zieldateiname und
|
||||||
|
* Erfolgszeitpunkt. Für endgültig fehlgeschlagene Dokumente ist der Fehlzeitpunkt
|
||||||
|
* belegt. Für alle anderen Zustände oder bei Abfragefehlern wird ein leeres
|
||||||
|
* {@link Optional} zurückgegeben.
|
||||||
|
*
|
||||||
|
* @param fingerprint inhaltsbasierte Dokumentenidentität; darf nicht {@code null} sein
|
||||||
|
* @return historischer Kontext des Dokuments, oder leer wenn nicht verfügbar
|
||||||
|
* @throws NullPointerException wenn {@code fingerprint} {@code null} ist
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<HistoricalDocumentContext> resolveHistoricalDocumentContext(
|
||||||
|
DocumentFingerprint fingerprint) {
|
||||||
|
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
||||||
|
try {
|
||||||
|
DocumentRecordLookupResult result =
|
||||||
|
documentRecordRepository.findByFingerprint(fingerprint);
|
||||||
|
if (result instanceof DocumentTerminalSuccess success) {
|
||||||
|
return Optional.of(HistoricalDocumentContext.ofSuccess(
|
||||||
|
success.record().lastTargetFileName(),
|
||||||
|
success.record().lastSuccessInstant()));
|
||||||
|
}
|
||||||
|
if (result instanceof DocumentTerminalFinalFailure failure) {
|
||||||
|
return Optional.of(HistoricalDocumentContext.ofFinalFailure(
|
||||||
|
failure.record().lastFailureInstant()));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -129,7 +129,7 @@ class BatchRunProgressObservationTest {
|
|||||||
assertSame(a, b);
|
assertSame(a, b);
|
||||||
a.onRunStarted(new RunId("r-1"), 5);
|
a.onRunStarted(new RunId("r-1"), 5);
|
||||||
a.onDocumentCompleted(new DocumentCompletionEvent(
|
a.onDocumentCompleted(new DocumentCompletionEvent(
|
||||||
"x.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED, null, null, null, null, Duration.ZERO));
|
"x.pdf", DUMMY_FP, DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED, null, null, null, null, Duration.ZERO));
|
||||||
a.onRunEnded(new RunSummary(0, 0, 0));
|
a.onRunEnded(new RunSummary(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class BatchRunProgressObservationTest {
|
|||||||
DocumentCompletionStatus.SUCCESS,
|
DocumentCompletionStatus.SUCCESS,
|
||||||
DocumentCompletionStatus.FAILED_RETRYABLE,
|
DocumentCompletionStatus.FAILED_RETRYABLE,
|
||||||
DocumentCompletionStatus.FAILED_PERMANENT,
|
DocumentCompletionStatus.FAILED_PERMANENT,
|
||||||
DocumentCompletionStatus.SKIPPED));
|
DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED));
|
||||||
DefaultBatchRunProcessingUseCase useCase = buildUseCase(
|
DefaultBatchRunProcessingUseCase useCase = buildUseCase(
|
||||||
new NoOpLock(), new FixedCandidatesPort(
|
new NoOpLock(), new FixedCandidatesPort(
|
||||||
makeCandidate("a.pdf"),
|
makeCandidate("a.pdf"),
|
||||||
|
|||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceLookupTechnicalFailure;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link DefaultResolveHistoricalDocumentContextUseCase}.
|
||||||
|
*/
|
||||||
|
class DefaultResolveHistoricalDocumentContextUseCaseTest {
|
||||||
|
|
||||||
|
private static final DocumentFingerprint FP = new DocumentFingerprint("a".repeat(64));
|
||||||
|
private static final Instant NOW = Instant.parse("2026-01-15T10:30:00Z");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_withNullRepository_throws() {
|
||||||
|
assertThatNullPointerException()
|
||||||
|
.isThrownBy(() -> new DefaultResolveHistoricalDocumentContextUseCase(null))
|
||||||
|
.withMessageContaining("documentRecordRepository");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_withNullFingerprint_throws() {
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentUnknown()));
|
||||||
|
assertThatNullPointerException()
|
||||||
|
.isThrownBy(() -> useCase.resolveHistoricalDocumentContext(null))
|
||||||
|
.withMessageContaining("fingerprint");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forSuccessRecord_returnsContextWithTargetFileNameAndSuccessInstant() {
|
||||||
|
DocumentRecord record = buildRecord(ProcessingStatus.SUCCESS,
|
||||||
|
"2026-01-01 - Rechnung.pdf", NOW, null);
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentTerminalSuccess(record)));
|
||||||
|
|
||||||
|
Optional<HistoricalDocumentContext> result =
|
||||||
|
useCase.resolveHistoricalDocumentContext(FP);
|
||||||
|
|
||||||
|
assertThat(result).isPresent();
|
||||||
|
assertThat(result.get().wasEverSuccessful()).isTrue();
|
||||||
|
assertThat(result.get().lastTargetFileName()).contains("2026-01-01 - Rechnung.pdf");
|
||||||
|
assertThat(result.get().lastSuccessInstant()).contains(NOW);
|
||||||
|
assertThat(result.get().lastFailureInstant()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forSuccessRecordWithNullTargetFileName_returnsContextWithEmptyFileName() {
|
||||||
|
DocumentRecord record = buildRecord(ProcessingStatus.SUCCESS, null, NOW, null);
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentTerminalSuccess(record)));
|
||||||
|
|
||||||
|
Optional<HistoricalDocumentContext> result =
|
||||||
|
useCase.resolveHistoricalDocumentContext(FP);
|
||||||
|
|
||||||
|
assertThat(result).isPresent();
|
||||||
|
assertThat(result.get().wasEverSuccessful()).isTrue();
|
||||||
|
assertThat(result.get().lastTargetFileName()).isEmpty();
|
||||||
|
assertThat(result.get().lastSuccessInstant()).contains(NOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forFinalFailureRecord_returnsContextWithFailureInstant() {
|
||||||
|
DocumentRecord record = buildRecord(ProcessingStatus.FAILED_FINAL, null, null, NOW);
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentTerminalFinalFailure(record)));
|
||||||
|
|
||||||
|
Optional<HistoricalDocumentContext> result =
|
||||||
|
useCase.resolveHistoricalDocumentContext(FP);
|
||||||
|
|
||||||
|
assertThat(result).isPresent();
|
||||||
|
assertThat(result.get().wasEverSuccessful()).isFalse();
|
||||||
|
assertThat(result.get().lastFailureInstant()).contains(NOW);
|
||||||
|
assertThat(result.get().lastTargetFileName()).isEmpty();
|
||||||
|
assertThat(result.get().lastSuccessInstant()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forUnknownDocument_returnsEmpty() {
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentUnknown()));
|
||||||
|
|
||||||
|
assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forProcessableDocument_returnsEmpty() {
|
||||||
|
DocumentRecord record = buildRecord(ProcessingStatus.READY_FOR_AI, null, null, null);
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new DocumentKnownProcessable(record)));
|
||||||
|
|
||||||
|
assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_forPersistenceLookupFailure_returnsEmpty() {
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(
|
||||||
|
stubRepo(new PersistenceLookupTechnicalFailure("DB-Fehler", null)));
|
||||||
|
|
||||||
|
assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve_whenRepositoryThrows_returnsEmpty() {
|
||||||
|
DocumentRecordRepository throwingRepo = new DocumentRecordRepository() {
|
||||||
|
@Override
|
||||||
|
public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
throw new DocumentPersistenceException("Verbindungsfehler", null);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void create(DocumentRecord record) {}
|
||||||
|
@Override
|
||||||
|
public void update(DocumentRecord record) {}
|
||||||
|
@Override
|
||||||
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var useCase = new DefaultResolveHistoricalDocumentContextUseCase(throwingRepo);
|
||||||
|
|
||||||
|
assertThat(useCase.resolveHistoricalDocumentContext(FP)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Hilfsmethoden
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static DocumentRecordRepository stubRepo(DocumentRecordLookupResult result) {
|
||||||
|
return new DocumentRecordRepository() {
|
||||||
|
@Override
|
||||||
|
public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void create(DocumentRecord record) {}
|
||||||
|
@Override
|
||||||
|
public void update(DocumentRecord record) {}
|
||||||
|
@Override
|
||||||
|
public void deleteByFingerprint(DocumentFingerprint fingerprint) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DocumentRecord buildRecord(
|
||||||
|
ProcessingStatus status,
|
||||||
|
String lastTargetFileName,
|
||||||
|
Instant lastSuccessInstant,
|
||||||
|
Instant lastFailureInstant) {
|
||||||
|
return new DocumentRecord(
|
||||||
|
FP,
|
||||||
|
new SourceDocumentLocator("quell/pfad"),
|
||||||
|
"original.pdf",
|
||||||
|
status,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
lastFailureInstant,
|
||||||
|
lastSuccessInstant,
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now(),
|
||||||
|
lastTargetFileName != null ? "ziel/ordner" : null,
|
||||||
|
lastTargetFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-19
@@ -24,7 +24,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationLoadException;
|
|||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLaunchOutcome;
|
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.GuiBatchRunLauncher;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalFileNamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiHistoricalDocumentContextPort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiManualFileRenamePort;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
|
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.adapter.in.gui.editor.GuiConfigurationEditorStateFactory;
|
||||||
@@ -59,8 +59,9 @@ import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameRequest;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameRequest;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameResult;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameResult;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalFileNameUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultResolveHistoricalFileNameUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalDocumentContextUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultResolveHistoricalDocumentContextUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
|
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
|
||||||
@@ -686,7 +687,7 @@ public class BootstrapRunner {
|
|||||||
de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiResetDocumentStatusPort resetPort =
|
de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiResetDocumentStatusPort resetPort =
|
||||||
this::resetDocumentStatusForGui;
|
this::resetDocumentStatusForGui;
|
||||||
GuiManualFileRenamePort manualRenamePort = this::performGuiManualFileRename;
|
GuiManualFileRenamePort manualRenamePort = this::performGuiManualFileRename;
|
||||||
GuiHistoricalFileNamePort historicalFileNamePort = this::resolveHistoricalFileNameForGui;
|
GuiHistoricalDocumentContextPort historicalDocumentContextPort = this::resolveHistoricalDocumentContextForGui;
|
||||||
|
|
||||||
if (configPathOverride.isEmpty()) {
|
if (configPathOverride.isEmpty()) {
|
||||||
return new GuiStartupContext(
|
return new GuiStartupContext(
|
||||||
@@ -704,7 +705,7 @@ public class BootstrapRunner {
|
|||||||
miniRunLauncher,
|
miniRunLauncher,
|
||||||
resetPort,
|
resetPort,
|
||||||
manualRenamePort,
|
manualRenamePort,
|
||||||
historicalFileNamePort);
|
historicalDocumentContextPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path configPath = Paths.get(configPathOverride.get());
|
Path configPath = Paths.get(configPathOverride.get());
|
||||||
@@ -727,7 +728,7 @@ public class BootstrapRunner {
|
|||||||
miniRunLauncher,
|
miniRunLauncher,
|
||||||
resetPort,
|
resetPort,
|
||||||
manualRenamePort,
|
manualRenamePort,
|
||||||
historicalFileNamePort);
|
historicalDocumentContextPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("GUI startup: configuration file confirmed at: {}", configPath.toAbsolutePath());
|
LOG.info("GUI startup: configuration file confirmed at: {}", configPath.toAbsolutePath());
|
||||||
@@ -736,7 +737,7 @@ public class BootstrapRunner {
|
|||||||
return new GuiStartupContext(loadedState, Optional.empty(), loader, writer,
|
return new GuiStartupContext(loadedState, Optional.empty(), loader, writer,
|
||||||
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
|
||||||
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
|
||||||
miniRunLauncher, resetPort, manualRenamePort, historicalFileNamePort);
|
miniRunLauncher, resetPort, manualRenamePort, historicalDocumentContextPort);
|
||||||
} catch (GuiConfigurationLoadException e) {
|
} catch (GuiConfigurationLoadException e) {
|
||||||
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
|
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
|
||||||
e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
@@ -755,7 +756,7 @@ public class BootstrapRunner {
|
|||||||
miniRunLauncher,
|
miniRunLauncher,
|
||||||
resetPort,
|
resetPort,
|
||||||
manualRenamePort,
|
manualRenamePort,
|
||||||
historicalFileNamePort);
|
historicalDocumentContextPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,28 +1071,28 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the historical AI-proposed target filename for a document identified by
|
* Resolves the historical processing context for the document identified by
|
||||||
* {@code fingerprint}, using the configuration at {@code configFilePath}.
|
* {@code fingerprint}, using the configuration at {@code configFilePath}.
|
||||||
* <p>
|
* <p>
|
||||||
* Loads the configuration, initialises the schema and delegates to
|
* Loads the configuration, initialises the schema and delegates to
|
||||||
* {@link ResolveHistoricalFileNameUseCase}. Technical errors during loading or querying
|
* {@link ResolveHistoricalDocumentContextUseCase}. Technical errors during loading or
|
||||||
* are caught and returned as an empty {@link Optional}; they are never propagated to the
|
* querying are caught and returned as an empty {@link Optional}; they are never propagated
|
||||||
* caller.
|
* to the caller.
|
||||||
* <p>
|
* <p>
|
||||||
* Runs on the GUI worker thread. Blocking I/O is therefore acceptable.
|
* Runs on the GUI worker thread. Blocking I/O is therefore acceptable.
|
||||||
*
|
*
|
||||||
* @param configFilePath path to the active {@code .properties} file; must not be {@code null}
|
* @param configFilePath path to the active {@code .properties} file; must not be {@code null}
|
||||||
* @param fingerprint content-based document identity; must not be {@code null}
|
* @param fingerprint content-based document identity; must not be {@code null}
|
||||||
* @return the last successfully written target filename, or empty if not available
|
* @return the historical processing context, or empty if not available
|
||||||
*/
|
*/
|
||||||
Optional<String> resolveHistoricalFileNameForGui(
|
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContextForGui(
|
||||||
Path configFilePath,
|
Path configFilePath,
|
||||||
DocumentFingerprint fingerprint) {
|
DocumentFingerprint fingerprint) {
|
||||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
||||||
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
|
||||||
|
|
||||||
if (!Files.exists(configFilePath)) {
|
if (!Files.exists(configFilePath)) {
|
||||||
LOG.debug("Historischer Dateiname: Konfigurationsdatei nicht gefunden: {}", configFilePath);
|
LOG.debug("Historischer Kontext: Konfigurationsdatei nicht gefunden: {}", configFilePath);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,11 +1103,11 @@ public class BootstrapRunner {
|
|||||||
String jdbcUrl = buildJdbcUrl(config);
|
String jdbcUrl = buildJdbcUrl(config);
|
||||||
DocumentRecordRepository documentRecordRepository =
|
DocumentRecordRepository documentRecordRepository =
|
||||||
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
|
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
|
||||||
ResolveHistoricalFileNameUseCase useCase =
|
ResolveHistoricalDocumentContextUseCase useCase =
|
||||||
new DefaultResolveHistoricalFileNameUseCase(documentRecordRepository);
|
new DefaultResolveHistoricalDocumentContextUseCase(documentRecordRepository);
|
||||||
return useCase.resolveHistoricalFileName(fingerprint);
|
return useCase.resolveHistoricalDocumentContext(fingerprint);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Historischer Dateiname konnte nicht abgefragt werden für {}: {}",
|
LOG.debug("Historischer Kontext konnte nicht abgefragt werden für {}: {}",
|
||||||
fingerprint.sha256Hex(), e.getMessage());
|
fingerprint.sha256Hex(), e.getMessage());
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user