Fix #19: Fehlergrund bei fehlgeschlagenem KI-Aufruf im Begründungsbereich anzeigen

- DocumentCompletionEvent um optionales Feld failureMessage erweitert
- DocumentProcessingCoordinator leitet Fehlermeldung bei Fehler-Status durch
- GuiBatchRunResultRow um aiFailureMessage (Optional<String>) ergänzt
- GuiBatchRunCoordinator.toRow() befüllt aiFailureMessage aus dem Event
- GuiBatchRunTab.buildDetailText() zeigt bei fehlendem Reasoning und
  vorhandenem Fehlergrund: "⚠ Fehler: <Meldung>" vor dem Hinweistext
- Alle Tests angepasst und neue Unit-Tests für aiFailureMessage ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 08:17:43 +02:00
parent 67275eb2f5
commit b87e8498e6
11 changed files with 102 additions and 44 deletions
@@ -506,6 +506,8 @@ public final class GuiBatchRunCoordinator {
? Optional.empty() : Optional.of(event.resolvedDate());
Optional<String> reasoning = event.aiReasoning() == null || event.aiReasoning().isBlank()
? Optional.empty() : Optional.of(event.aiReasoning());
Optional<String> failureMessage = event.failureMessage() == null || event.failureMessage().isBlank()
? Optional.empty() : Optional.of(event.failureMessage());
Duration duration = event.processingDuration();
return new GuiBatchRunResultRow(
event.originalFileName(),
@@ -514,6 +516,7 @@ public final class GuiBatchRunCoordinator {
finalName,
date,
reasoning,
failureMessage,
duration);
}
@@ -34,6 +34,9 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
* rename; empty otherwise
* @param aiReasoning the AI reasoning shown in the side panel; empty when no
* reasoning is available for this row
* @param aiFailureMessage eine lesbare Fehlerbeschreibung, wenn der KI-Aufruf oder die
* Verarbeitung fehlgeschlagen ist; leer bei Erfolg und
* übersprungenen Dokumenten
* @param processingDuration wall-clock duration spent on the candidate in this run;
* never {@code null} and never negative
* @param resetPending {@code true} when the document's persistence status has been
@@ -46,6 +49,7 @@ public record GuiBatchRunResultRow(
Optional<String> finalFileName,
Optional<LocalDate> resolvedDate,
Optional<String> aiReasoning,
Optional<String> aiFailureMessage,
Duration processingDuration,
boolean resetPending) {
@@ -79,6 +83,7 @@ public record GuiBatchRunResultRow(
finalFileName = finalFileName == null ? Optional.empty() : finalFileName;
resolvedDate = resolvedDate == null ? Optional.empty() : resolvedDate;
aiReasoning = aiReasoning == null ? Optional.empty() : aiReasoning;
aiFailureMessage = aiFailureMessage == null ? Optional.empty() : aiFailureMessage;
Objects.requireNonNull(processingDuration, "processingDuration must not be null");
if (processingDuration.isNegative()) {
throw new IllegalArgumentException("processingDuration must not be negative");
@@ -97,6 +102,8 @@ public record GuiBatchRunResultRow(
* empty)
* @param aiReasoning the AI reasoning text; may be {@code null} (treated as
* empty)
* @param aiFailureMessage eine lesbare Fehlerbeschreibung bei Fehler; may be
* {@code null} (treated as empty)
* @param processingDuration the wall-clock processing duration; never {@code null}
*/
public GuiBatchRunResultRow(
@@ -106,9 +113,10 @@ public record GuiBatchRunResultRow(
Optional<String> finalFileName,
Optional<LocalDate> resolvedDate,
Optional<String> aiReasoning,
Optional<String> aiFailureMessage,
Duration processingDuration) {
this(originalFileName, fingerprint, status, finalFileName, resolvedDate, aiReasoning,
processingDuration, false);
aiFailureMessage, processingDuration, false);
}
/**
@@ -131,6 +139,7 @@ public record GuiBatchRunResultRow(
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Duration.ZERO,
true);
}
@@ -899,7 +899,11 @@ public final class GuiBatchRunTab {
builder.append('\n');
row.aiReasoning().ifPresentOrElse(
reasoning -> builder.append(reasoning),
() -> builder.append(NO_REASONING_TEXT));
() -> {
row.aiFailureMessage().ifPresent(msg ->
builder.append("\u26A0 Fehler: ").append(msg).append("\n\n"));
builder.append(NO_REASONING_TEXT);
});
return builder.toString();
}
@@ -1012,6 +1016,7 @@ public final class GuiBatchRunTab {
Optional.empty(),
Optional.empty(),
Optional.of(message),
Optional.empty(),
Duration.ZERO,
false);
upsertResultRowByFingerprint(missingRow);