1
0

M6 komplett umgesetzt

This commit is contained in:
2026-04-07 12:26:14 +02:00
parent 506f5ac32e
commit 8bcd80d70a
51 changed files with 5960 additions and 536 deletions

View File

@@ -0,0 +1,53 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Carries the AI-related traceability data produced during an AI naming attempt.
* <p>
* This record aggregates the metadata required to persist full AI traceability in
* the processing attempt history:
* <ul>
* <li>AI infrastructure details (model name, prompt identifier)</li>
* <li>Request size metrics (processed pages, sent character count)</li>
* <li>Raw AI output (for audit and diagnostics; stored in SQLite, not in log files)</li>
* </ul>
* <p>
* This context is produced whenever an AI call is attempted, regardless of whether
* the call succeeded or failed. Fields that could not be determined (e.g. raw response
* on connection failure) may be {@code null}.
*
* @param modelName the AI model name used in the request; never null
* @param promptIdentifier stable identifier of the prompt template; never null
* @param processedPageCount number of PDF pages included in the extraction; must be &gt;= 1
* @param sentCharacterCount number of document-text characters sent to the AI; must be &gt;= 0
* @param aiRawResponse the complete raw AI response body; {@code null} if the call did
* not return a response body (e.g. timeout or connection error)
*/
public record AiAttemptContext(
String modelName,
String promptIdentifier,
int processedPageCount,
int sentCharacterCount,
String aiRawResponse) {
/**
* Compact constructor validating mandatory fields.
*
* @throws NullPointerException if {@code modelName} or {@code promptIdentifier} is null
* @throws IllegalArgumentException if {@code processedPageCount} &lt; 1 or
* {@code sentCharacterCount} &lt; 0
*/
public AiAttemptContext {
Objects.requireNonNull(modelName, "modelName must not be null");
Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null");
if (processedPageCount < 1) {
throw new IllegalArgumentException(
"processedPageCount must be >= 1, but was: " + processedPageCount);
}
if (sentCharacterCount < 0) {
throw new IllegalArgumentException(
"sentCharacterCount must be >= 0, but was: " + sentCharacterCount);
}
}
}

View File

@@ -0,0 +1,40 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Outcome indicating a deterministic functional (content) failure in the AI naming pipeline.
* <p>
* Functional failures occur when the AI returns a structurally valid response but the
* content violates the applicable fachliche rules, for example:
* <ul>
* <li>Title exceeds 20 characters</li>
* <li>Title contains prohibited special characters</li>
* <li>Title is a generic placeholder (e.g., "Dokument", "Scan")</li>
* <li>AI-provided date is present but not a valid YYYY-MM-DD string</li>
* </ul>
* <p>
* These failures are deterministic: retrying the same document against the same AI
* and prompt is unlikely to resolve the issue without a document or prompt change.
* The content error counter is incremented, and the standard one-retry rule applies.
*
* @param candidate the source document candidate; never null
* @param errorMessage human-readable description of the validation failure; never null
* @param aiContext AI traceability context for the attempt record; never null
*/
public record AiFunctionalFailure(
SourceDocumentCandidate candidate,
String errorMessage,
AiAttemptContext aiContext) implements DocumentProcessingOutcome {
/**
* Compact constructor validating mandatory fields.
*
* @throws NullPointerException if any field is null
*/
public AiFunctionalFailure {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
Objects.requireNonNull(aiContext, "aiContext must not be null");
}
}

View File

@@ -0,0 +1,40 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Outcome indicating a transient technical failure during the AI naming pipeline.
* <p>
* Technical failures include:
* <ul>
* <li>AI service not reachable</li>
* <li>HTTP timeout</li>
* <li>Connection error</li>
* <li>Unparseable or structurally invalid AI response (missing mandatory fields, invalid JSON)</li>
* </ul>
* <p>
* These failures are retryable. The transient error counter is incremented.
*
* @param candidate the source document candidate; never null
* @param errorMessage human-readable description of the failure; never null
* @param cause the underlying exception, or {@code null} if not applicable
* @param aiContext AI traceability context captured before or during the failure; never null
*/
public record AiTechnicalFailure(
SourceDocumentCandidate candidate,
String errorMessage,
Throwable cause,
AiAttemptContext aiContext) implements DocumentProcessingOutcome {
/**
* Compact constructor validating mandatory fields.
*
* @throws NullPointerException if {@code candidate}, {@code errorMessage}, or
* {@code aiContext} is null
*/
public AiTechnicalFailure {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
Objects.requireNonNull(aiContext, "aiContext must not be null");
}
}

View File

@@ -26,6 +26,7 @@ package de.gecheckt.pdf.umbenenner.domain.model;
* </ul>
*/
public sealed interface DocumentProcessingOutcome
permits PreCheckPassed, PreCheckFailed, TechnicalDocumentError {
permits PreCheckPassed, PreCheckFailed, TechnicalDocumentError,
NamingProposalReady, AiTechnicalFailure, AiFunctionalFailure {
// Marker interface; concrete implementations define structure
}

View File

@@ -0,0 +1,39 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Outcome indicating that an AI naming pipeline completed successfully and produced
* a validated naming proposal ready for persistence.
* <p>
* This outcome is returned when:
* <ol>
* <li>PDF text extraction and pre-checks passed.</li>
* <li>The AI was invoked and returned a parseable response.</li>
* <li>The response passed all semantic validation rules.</li>
* <li>A {@link NamingProposal} was produced.</li>
* </ol>
* <p>
* The document master record will be updated to {@link ProcessingStatus#PROPOSAL_READY};
* a physical target copy is not yet produced at this stage.
*
* @param candidate the source document candidate; never null
* @param proposal the validated naming proposal ready for persistence; never null
* @param aiContext AI traceability data required for the processing attempt record; never null
*/
public record NamingProposalReady(
SourceDocumentCandidate candidate,
NamingProposal proposal,
AiAttemptContext aiContext) implements DocumentProcessingOutcome {
/**
* Compact constructor validating all fields.
*
* @throws NullPointerException if any field is null
*/
public NamingProposalReady {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(proposal, "proposal must not be null");
Objects.requireNonNull(aiContext, "aiContext must not be null");
}
}