M6 komplett umgesetzt
This commit is contained in:
@@ -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 >= 1
|
||||
* @param sentCharacterCount number of document-text characters sent to the AI; must be >= 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} < 1 or
|
||||
* {@code sentCharacterCount} < 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user