1
0

Meilenstein-Präfixe aus Klassennamen entfernt

This commit is contained in:
2026-04-02 09:11:52 +02:00
parent c0cdd0ed6e
commit 7d5c21f14c
21 changed files with 501 additions and 455 deletions

View File

@@ -0,0 +1,31 @@
package de.gecheckt.pdf.umbenenner.domain.model;
/**
* Sealed interface representing the complete outcome of document processing.
* <p>
* This interface models all possible document outcomes:
* <ul>
* <li>{@link PreCheckPassed}: Document passed all pre-checks</li>
* <li>{@link PreCheckFailed}: Document failed a pre-check (deterministic content error: no usable text or page limit exceeded)</li>
* <li>{@link TechnicalDocumentError}: Technical failure during candidate access or PDF extraction (I/O, access, parsing, etc.)</li>
* </ul>
* <p>
* Design principles:
* <ul>
* <li>Exhaustive: All document processing outcomes are covered</li>
* <li>Document-centric: Each outcome carries the source candidate for correlation and traceability</li>
* <li>No exceptions: Results are encoded in the type system</li>
* <li>Clear distinction: Deterministic content errors (PreCheckFailed) vs. technical failures (TechnicalDocumentError)</li>
* </ul>
* <p>
* Error classification:
* <ul>
* <li>PreCheckPassed: Extraction succeeded and all pre-checks passed (ready for further processing)</li>
* <li>PreCheckFailed: Extraction succeeded but deterministic content check failed (no usable text, page limit exceeded)</li>
* <li>TechnicalDocumentError: Extraction failed due to technical issue (I/O, file access, PDF parsing, etc.)</li>
* </ul>
*/
public sealed interface DocumentProcessingOutcome
permits PreCheckPassed, PreCheckFailed, TechnicalDocumentError {
// Marker interface; concrete implementations define structure
}

View File

@@ -1,33 +0,0 @@
package de.gecheckt.pdf.umbenenner.domain.model;
/**
* Sealed interface representing the complete outcome of M3 document processing.
* <p>
* This interface models all four possible M3 document outcomes:
* <ul>
* <li>{@link M3PreCheckPassed}: Document passed all M3 pre-checks</li>
* <li>{@link M3PreCheckFailed}: Document failed a pre-check (deterministic content error: no usable text or page limit exceeded)</li>
* <li>{@link M3TechnicalDocumentError}: Technical failure during candidate access or PDF extraction (I/O, access, parsing, etc.)</li>
* </ul>
* <p>
* Design principles:
* <ul>
* <li>Exhaustive: All M3 document processing outcomes are covered (exactly four cases)</li>
* <li>Document-centric: Each outcome carries the source candidate for correlation and traceability</li>
* <li>No exceptions: Results are encoded in the type system</li>
* <li>Clear distinction: Deterministic content errors (M3PreCheckFailed) vs. technical failures (M3TechnicalDocumentError)</li>
* </ul>
* <p>
* Error classification:
* <ul>
* <li>M3PreCheckPassed: Extraction succeeded and all pre-checks passed (ready for M4+)</li>
* <li>M3PreCheckFailed: Extraction succeeded but deterministic content check failed (no usable text, page limit exceeded)</li>
* <li>M3TechnicalDocumentError: Extraction failed due to technical issue (I/O, file access, PDF parsing, etc.)</li>
* </ul>
*
* @since M3-AP-006
*/
public sealed interface M3DocumentProcessingOutcome
permits M3PreCheckPassed, M3PreCheckFailed, M3TechnicalDocumentError {
// Marker interface; concrete implementations define structure
}

View File

@@ -1,30 +0,0 @@
package de.gecheckt.pdf.umbenenner.domain.model;
/**
* Sealed interface representing the outcome of M3 document pre-checks.
* <p>
* This interface introduced in AP-001 establishes the architectural
* pattern for M3 pre-check results. The actual pre-check logic (fachlich validation
* such as "brauchbarer Text" and "Seitenlimit") is implemented in AP-004 via
* {@link de.gecheckt.pdf.umbenenner.application.service.M3PreCheckEvaluator}.
* <p>
* There are two allowed implementations:
* <ul>
* <li>{@link M3PreCheckPassed}: Document passed all M3 pre-checks and is ready for KI integration</li>
* <li>{@link M3PreCheckFailed}: Document failed an M3 pre-check and will not proceed further in this run</li>
* </ul>
* <p>
* Design principles:
* <ul>
* <li>Sealed: enforces exhaustive handling of all cases</li>
* <li>Carries both success path ({@link M3PreCheckPassed}) and failure reason ({@link M3PreCheckFailed})</li>
* <li>Defined early (AP-001) to ensure architecture is established before logic arrives</li>
* <li>Future-extensible for additional pre-check variants in later milestones</li>
* </ul>
*
* @since M3-AP-001
*/
public sealed interface M3ProcessingDecision
permits M3PreCheckPassed, M3PreCheckFailed {
// Marker interface; concrete implementations define structure
}

View File

@@ -3,7 +3,7 @@ package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Represents a document that failed an M3 pre-check.
* Represents a document that failed a pre-check.
* <p>
* This result encapsulates:
* <ul>
@@ -22,12 +22,11 @@ import java.util.Objects;
*
* @param candidate the source document metadata
* @param failureReason a human-readable explanation of the pre-check failure
* @since M3-AP-001
*/
public record M3PreCheckFailed(
public record PreCheckFailed(
SourceDocumentCandidate candidate,
String failureReason
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
) implements ProcessingDecision, DocumentProcessingOutcome {
/**
* Constructor with validation.
*
@@ -36,11 +35,11 @@ public record M3PreCheckFailed(
* @throws NullPointerException if either parameter is null
* @throws IllegalArgumentException if failureReason is empty
*/
public M3PreCheckFailed {
public PreCheckFailed {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(failureReason, "failureReason must not be null");
if (failureReason.isEmpty()) {
throw new IllegalArgumentException("failureReason must not be empty");
}
}
}
}

View File

@@ -1,9 +1,9 @@
package de.gecheckt.pdf.umbenenner.domain.model;
/**
* Enumeration of M3 pre-check failure reasons.
* Enumeration of pre-check failure reasons.
* <p>
* These are the deterministic content errors that can occur during M3 pre-check evaluation.
* These are the deterministic content errors that can occur during pre-check evaluation.
* They distinguish between failures in the document content versus technical extraction failures.
* <p>
* Deterministic content errors:
@@ -12,19 +12,17 @@ package de.gecheckt.pdf.umbenenner.domain.model;
* <li>{@link #PAGE_LIMIT_EXCEEDED}: The document exceeds the configured page limit.</li>
* </ul>
* <p>
* Note: Technical extraction failures (I/O errors, PDFBox failures) are not M3 pre-check reasons;
* Note: Technical extraction failures (I/O errors, PDFBox failures) are not pre-check reasons;
* they are represented as {@link PdfExtractionTechnicalError} in the extraction result.
*
* @since M3-AP-004
*/
public enum M3PreCheckFailureReason {
public enum PreCheckFailureReason {
/**
* The extracted PDF text, after normalization, contains no letters or digits.
* <p>
* This is a deterministic content error: reprocessing the same file in a later run
* will have the same outcome unless the source file is changed.
* <p>
* In M3, retry logic: exactly 1 retry in a later batch run.
* Retry logic: exactly 1 retry in a later batch run.
*/
NO_USABLE_TEXT("No usable text in extracted PDF content"),
@@ -33,13 +31,13 @@ public enum M3PreCheckFailureReason {
* <p>
* This is a deterministic content error: the page count will not change unless the source file is modified.
* <p>
* In M3, retry logic: exactly 1 retry in a later batch run.
* Retry logic: exactly 1 retry in a later batch run.
*/
PAGE_LIMIT_EXCEEDED("Document page count exceeds configured limit");
private final String description;
M3PreCheckFailureReason(String description) {
PreCheckFailureReason(String description) {
this.description = description;
}
@@ -51,4 +49,4 @@ public enum M3PreCheckFailureReason {
public String getDescription() {
return description;
}
}
}

View File

@@ -3,7 +3,7 @@ package de.gecheckt.pdf.umbenenner.domain.model;
import java.util.Objects;
/**
* Represents a document that passed all M3 pre-checks.
* Represents a document that passed all pre-checks.
* <p>
* This result encapsulates:
* <ul>
@@ -11,17 +11,16 @@ import java.util.Objects;
* <li>The successful PDF text extraction result</li>
* </ul>
* <p>
* A document with this decision is ready to proceed to M4 and later milestones
* A document with this decision is ready to proceed to further processing steps
* (fingerprinting, persistence, KI integration, filename generation, target copy).
*
* @param candidate the source document metadata
* @param extraction the successful text extraction result
* @since M3-AP-001
*/
public record M3PreCheckPassed(
public record PreCheckPassed(
SourceDocumentCandidate candidate,
PdfExtractionSuccess extraction
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
) implements ProcessingDecision, DocumentProcessingOutcome {
/**
* Constructor with validation.
*
@@ -29,8 +28,8 @@ public record M3PreCheckPassed(
* @param extraction must be non-null
* @throws NullPointerException if either parameter is null
*/
public M3PreCheckPassed {
public PreCheckPassed {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(extraction, "extraction must not be null");
}
}
}

View File

@@ -0,0 +1,27 @@
package de.gecheckt.pdf.umbenenner.domain.model;
/**
* Sealed interface representing the outcome of document pre-checks.
* <p>
* This interface establishes the architectural pattern for pre-check results.
* The actual pre-check logic (fachlich validation such as "brauchbarer Text" and "Seitenlimit")
* is implemented via {@link de.gecheckt.pdf.umbenenner.application.service.PreCheckEvaluator}.
* <p>
* There are two allowed implementations:
* <ul>
* <li>{@link PreCheckPassed}: Document passed all pre-checks and is ready for further processing</li>
* <li>{@link PreCheckFailed}: Document failed a pre-check and will not proceed further in this run</li>
* </ul>
* <p>
* Design principles:
* <ul>
* <li>Sealed: enforces exhaustive handling of all cases</li>
* <li>Carries both success path ({@link PreCheckPassed}) and failure reason ({@link PreCheckFailed})</li>
* <li>Defined early to ensure architecture is established before logic arrives</li>
* <li>Future-extensible for additional pre-check variants in later milestones</li>
* </ul>
*/
public sealed interface ProcessingDecision
permits PreCheckPassed, PreCheckFailed {
// Marker interface; concrete implementations define structure
}

View File

@@ -20,19 +20,18 @@ import java.util.Objects;
* <li>Out of memory during extraction</li>
* </ul>
* <p>
* This is distinct from {@link M3ExtractedContentError}, which represents problems with the document
* This is distinct from content errors, which represent problems with the document
* content itself rather than infrastructure failures.
*
* @param candidate the source document metadata
* @param errorMessage a description of the technical failure
* @param cause the underlying exception, if any (may be null)
* @since M3-AP-006
*/
public record M3TechnicalDocumentError(
public record TechnicalDocumentError(
SourceDocumentCandidate candidate,
String errorMessage,
Throwable cause
) implements M3DocumentProcessingOutcome {
) implements DocumentProcessingOutcome {
/**
* Constructor with validation.
*
@@ -42,11 +41,11 @@ public record M3TechnicalDocumentError(
* @throws NullPointerException if candidate or errorMessage is null
* @throws IllegalArgumentException if errorMessage is empty
*/
public M3TechnicalDocumentError {
public TechnicalDocumentError {
Objects.requireNonNull(candidate, "candidate must not be null");
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
if (errorMessage.isEmpty()) {
throw new IllegalArgumentException("errorMessage must not be empty");
}
}
}
}

View File

@@ -3,34 +3,34 @@
* <p>
* This package contains the fundamental domain entities and status models required for document processing:
* <ul>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus} — enumeration of all valid document processing states (M2-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.RunId} — unique identifier for a batch run (M2-AP-003)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext} — technical context for a batch run (M2-AP-003)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate} — discovered PDF from source folder (M3-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator} — opaque locator passed from scan adapter to extraction adapter (M3-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount} — typed page count validation (M3-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult} — sealed result of PDF text extraction (M3-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3ProcessingDecision} — sealed result of M3 pre-checks (M3-AP-001)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus} — enumeration of all valid document processing states</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.RunId} — unique identifier for a batch run</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext} — technical context for a batch run</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate} — discovered PDF from source folder</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator} — opaque locator passed from scan adapter to extraction adapter</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount} — typed page count validation</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult} — sealed result of PDF text extraction</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingDecision} — sealed result of pre-checks</li>
* </ul>
* <p>
* Additional classes introduced in M3:
* Additional classes:
* <ul>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason} — enumeration of M3 pre-check failure reasons (M3-AP-004)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome} — sealed interface for all M3 document processing outcomes (M3-AP-006)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError} — technical failure during extraction (M3-AP-006)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason} — enumeration of pre-check failure reasons</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome} — sealed interface for all document processing outcomes</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError} — technical failure during extraction</li>
* </ul>
*
* Implementation classes:
* <ul>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed} — document passed M3 pre-checks (M3-AP-001, M3-AP-004, M3-AP-006)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed} — document failed M3 pre-check (M3-AP-001, M3-AP-004, M3-AP-006)</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed} — document passed pre-checks</li>
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed} — document failed pre-check</li>
* </ul>
*
* M3 Document Processing Outcome Model (M3-AP-006):
* The complete M3 document processing pipeline results in one of three outcomes, all implementing
* {@link de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome}:
* Document Processing Outcome Model:
* The complete document processing pipeline results in one of three outcomes, all implementing
* {@link de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome}:
* <ul>
* <li>Pre-check passed: Document text extracted and validated successfully (ready for M4+)</li>
* <li>Pre-check passed: Document text extracted and validated successfully (ready for further processing)</li>
* <li>Pre-check failed: Deterministic content error (no usable text, page limit exceeded)</li>
* <li>Technical document error: Infrastructure or parsing failure (I/O, access, PDF parsing)</li>
* </ul>
@@ -41,7 +41,5 @@
* <li>Immutable value objects or enumerations</li>
* <li>Reusable across all layers via the Application and Adapter contracts</li>
* </ul>
*
* @since M2-AP-001
*/
package de.gecheckt.pdf.umbenenner.domain.model;

View File

@@ -0,0 +1,111 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for document processing outcome types.
* <p>
* Verifies that all outcome types are properly created and validated.
*/
class DocumentProcessingOutcomeTest {
@TempDir
Path tempDir;
private SourceDocumentCandidate candidate;
@BeforeEach
void setUp() throws Exception {
Path pdfFile = tempDir.resolve("doc.pdf");
Files.createFile(pdfFile);
SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
candidate = new SourceDocumentCandidate("doc.pdf", 1024L, locator);
}
@Test
void testTechnicalDocumentError_ValidConstruction() {
// Act
var error = new TechnicalDocumentError(candidate, "I/O error", null);
// Assert
assertEquals(candidate, error.candidate());
assertEquals("I/O error", error.errorMessage());
assertNull(error.cause());
}
@Test
void testTechnicalDocumentError_WithCause() {
// Arrange
var cause = new RuntimeException("File not found");
// Act
var error = new TechnicalDocumentError(candidate, "I/O error", cause);
// Assert
assertEquals(cause, error.cause());
}
@Test
void testTechnicalDocumentError_WithNullCandidate_ThrowsException() {
assertThrows(NullPointerException.class,
() -> new TechnicalDocumentError(null, "Error", null));
}
@Test
void testTechnicalDocumentError_WithNullErrorMessage_ThrowsException() {
assertThrows(NullPointerException.class,
() -> new TechnicalDocumentError(candidate, null, null));
}
@Test
void testTechnicalDocumentError_WithEmptyErrorMessage_ThrowsException() {
assertThrows(IllegalArgumentException.class,
() -> new TechnicalDocumentError(candidate, "", null));
}
@Test
void testTechnicalDocumentError_IsDocumentProcessingOutcome() {
// Verify type relationship
var error = new TechnicalDocumentError(candidate, "Error", null);
assertInstanceOf(DocumentProcessingOutcome.class, error);
}
@Test
void testPreCheckPassed_IsDocumentProcessingOutcome() {
// Verify type relationship
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
var passed = new PreCheckPassed(candidate, extraction);
assertInstanceOf(DocumentProcessingOutcome.class, passed);
}
@Test
void testPreCheckFailed_IsDocumentProcessingOutcome() {
// Verify type relationship
var failed = new PreCheckFailed(candidate, "Test failure reason");
assertInstanceOf(DocumentProcessingOutcome.class, failed);
}
@Test
void testAllOutcomesAreExhaustive() {
// This test verifies that the outcome types are the only implementations
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
DocumentProcessingOutcome[] outcomes = {
new PreCheckPassed(candidate, extraction),
new PreCheckFailed(candidate, "Deterministic content failure"),
new TechnicalDocumentError(candidate, "Technical extraction error", null)
};
for (DocumentProcessingOutcome outcome : outcomes) {
assertInstanceOf(DocumentProcessingOutcome.class, outcome);
}
}
}

View File

@@ -1,113 +0,0 @@
package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for M3 document processing outcome types.
* <p>
* Verifies that all four outcome types are properly created and validated.
*/
class M3DocumentProcessingOutcomeTest {
@TempDir
Path tempDir;
private SourceDocumentCandidate candidate;
@BeforeEach
void setUp() throws Exception {
Path pdfFile = tempDir.resolve("doc.pdf");
Files.createFile(pdfFile);
SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
candidate = new SourceDocumentCandidate("doc.pdf", 1024L, locator);
}
@Test
void testM3TechnicalDocumentError_ValidConstruction() {
// Act
var error = new M3TechnicalDocumentError(candidate, "I/O error", null);
// Assert
assertEquals(candidate, error.candidate());
assertEquals("I/O error", error.errorMessage());
assertNull(error.cause());
}
@Test
void testM3TechnicalDocumentError_WithCause() {
// Arrange
var cause = new RuntimeException("File not found");
// Act
var error = new M3TechnicalDocumentError(candidate, "I/O error", cause);
// Assert
assertEquals(cause, error.cause());
}
@Test
void testM3TechnicalDocumentError_WithNullCandidate_ThrowsException() {
assertThrows(NullPointerException.class,
() -> new M3TechnicalDocumentError(null, "Error", null));
}
@Test
void testM3TechnicalDocumentError_WithNullErrorMessage_ThrowsException() {
assertThrows(NullPointerException.class,
() -> new M3TechnicalDocumentError(candidate, null, null));
}
@Test
void testM3TechnicalDocumentError_WithEmptyErrorMessage_ThrowsException() {
assertThrows(IllegalArgumentException.class,
() -> new M3TechnicalDocumentError(candidate, "", null));
}
@Test
void testM3TechnicalDocumentError_IsM3DocumentProcessingOutcome() {
// Verify type relationship
var error = new M3TechnicalDocumentError(candidate, "Error", null);
assertInstanceOf(M3DocumentProcessingOutcome.class, error);
}
@Test
void testM3PreCheckPassed_IsM3DocumentProcessingOutcome() {
// Verify type relationship
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
var passed = new M3PreCheckPassed(candidate, extraction);
assertInstanceOf(M3DocumentProcessingOutcome.class, passed);
}
@Test
void testM3PreCheckFailed_IsM3DocumentProcessingOutcome() {
// Verify type relationship
var failed = new M3PreCheckFailed(candidate, "Test failure reason");
assertInstanceOf(M3DocumentProcessingOutcome.class, failed);
}
@Test
void testAllThreeOutcomesAreExhaustive() {
// This test verifies that the three outcome types are the only implementations
// M3 has exactly three outcome types: passed, failed (deterministic), and technical error
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
M3DocumentProcessingOutcome[] outcomes = {
new M3PreCheckPassed(candidate, extraction),
new M3PreCheckFailed(candidate, "Deterministic content failure"),
new M3TechnicalDocumentError(candidate, "Technical extraction error", null)
};
for (M3DocumentProcessingOutcome outcome : outcomes) {
assertInstanceOf(M3DocumentProcessingOutcome.class, outcome);
}
}
}