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:
2026-04-27 12:00:27 +02:00
parent 1db6e27be8
commit 3f5602de01
22 changed files with 605 additions and 103 deletions
@@ -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.batchrun.GuiBatchRunLaunchOutcome;
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.editor.GuiConfigurationEditorState;
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.ManualFileRenameResult;
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalFileNameUseCase;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultResolveHistoricalFileNameUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
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.out.AiContentSensitivity;
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 =
this::resetDocumentStatusForGui;
GuiManualFileRenamePort manualRenamePort = this::performGuiManualFileRename;
GuiHistoricalFileNamePort historicalFileNamePort = this::resolveHistoricalFileNameForGui;
GuiHistoricalDocumentContextPort historicalDocumentContextPort = this::resolveHistoricalDocumentContextForGui;
if (configPathOverride.isEmpty()) {
return new GuiStartupContext(
@@ -704,7 +705,7 @@ public class BootstrapRunner {
miniRunLauncher,
resetPort,
manualRenamePort,
historicalFileNamePort);
historicalDocumentContextPort);
}
Path configPath = Paths.get(configPathOverride.get());
@@ -727,7 +728,7 @@ public class BootstrapRunner {
miniRunLauncher,
resetPort,
manualRenamePort,
historicalFileNamePort);
historicalDocumentContextPort);
}
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,
modelCatalogPort, apiKeyResolutionPort, providerTechnicalTestService, pathCheckPort,
technicalTestOrchestrator, correctionExecutionService, batchRunLauncher,
miniRunLauncher, resetPort, manualRenamePort, historicalFileNamePort);
miniRunLauncher, resetPort, manualRenamePort, historicalDocumentContextPort);
} catch (GuiConfigurationLoadException e) {
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
e.getMessage(), e);
@@ -755,7 +756,7 @@ public class BootstrapRunner {
miniRunLauncher,
resetPort,
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}.
* <p>
* Loads the configuration, initialises the schema and delegates to
* {@link ResolveHistoricalFileNameUseCase}. Technical errors during loading or querying
* are caught and returned as an empty {@link Optional}; they are never propagated to the
* caller.
* {@link ResolveHistoricalDocumentContextUseCase}. Technical errors during loading or
* querying are caught and returned as an empty {@link Optional}; they are never propagated
* to the caller.
* <p>
* 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 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,
DocumentFingerprint fingerprint) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
if (!Files.exists(configFilePath)) {
LOG.debug("Historischer Dateiname: Konfigurationsdatei nicht gefunden: {}", configFilePath);
LOG.debug("Historischer Kontext: Konfigurationsdatei nicht gefunden: {}", configFilePath);
return Optional.empty();
}
@@ -1102,11 +1103,11 @@ public class BootstrapRunner {
String jdbcUrl = buildJdbcUrl(config);
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
ResolveHistoricalFileNameUseCase useCase =
new DefaultResolveHistoricalFileNameUseCase(documentRecordRepository);
return useCase.resolveHistoricalFileName(fingerprint);
ResolveHistoricalDocumentContextUseCase useCase =
new DefaultResolveHistoricalDocumentContextUseCase(documentRecordRepository);
return useCase.resolveHistoricalDocumentContext(fingerprint);
} 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());
return Optional.empty();
}