From c8175dcccab903c02251746a582a15ffe188442c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 11:45:46 +0100 Subject: [PATCH 01/12] add classification classes --- .../com/mindee/parsing/v2/CommonResponse.java | 4 -- .../java/com/mindee/parsing/v2/Inference.java | 12 ++---- .../com/mindee/v2/parsing/BaseInference.java | 18 +++++++-- .../ClassificationClassifier.java | 23 ++++++++++++ .../ClassificationInference.java | 9 +++++ .../ClassificationResponse.java | 20 ++++++++++ .../classification/ClassificationResult.java | 34 +++++++++++++++++ .../java/com/mindee/TestingUtilities.java | 4 ++ .../mindee/v2/product/ClassificationTest.java | 37 +++++++++++++++++++ 9 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/mindee/v2/product/classification/ClassificationClassifier.java create mode 100644 src/main/java/com/mindee/v2/product/classification/ClassificationInference.java create mode 100644 src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java create mode 100644 src/main/java/com/mindee/v2/product/classification/ClassificationResult.java create mode 100644 src/test/java/com/mindee/v2/product/ClassificationTest.java diff --git a/src/main/java/com/mindee/parsing/v2/CommonResponse.java b/src/main/java/com/mindee/parsing/v2/CommonResponse.java index 5165eeda7..72f625f3f 100644 --- a/src/main/java/com/mindee/parsing/v2/CommonResponse.java +++ b/src/main/java/com/mindee/parsing/v2/CommonResponse.java @@ -16,8 +16,4 @@ public abstract class CommonResponse { * This is not formatted in any way by the library and may contain newline and tab characters. */ private String rawResponse; - - public void setRawResponse(String contents) { - rawResponse = contents; - } } diff --git a/src/main/java/com/mindee/parsing/v2/Inference.java b/src/main/java/com/mindee/parsing/v2/Inference.java index 9f4517d10..6fecd35d9 100644 --- a/src/main/java/com/mindee/parsing/v2/Inference.java +++ b/src/main/java/com/mindee/parsing/v2/Inference.java @@ -15,23 +15,17 @@ @JsonIgnoreProperties(ignoreUnknown = true) @AllArgsConstructor @NoArgsConstructor -public class Inference extends BaseInference { +public class Inference extends BaseInference { /** * Active options for the inference. */ @JsonProperty("active_options") private InferenceActiveOptions activeOptions; - /** - * Model result values. - */ - @JsonProperty("result") - private InferenceResult result; - @Override public String toString() { StringJoiner joiner = new StringJoiner("\n"); - joiner.add(activeOptions.toString()).add("").add(result != null ? result.toString() : ""); - return super.toString() + "\n" + joiner.toString().trim() + "\n"; + joiner.add(toStringBase()).add(activeOptions.toString()).add("").add(result.toString()); + return joiner.toString().trim() + "\n"; } } diff --git a/src/main/java/com/mindee/v2/parsing/BaseInference.java b/src/main/java/com/mindee/v2/parsing/BaseInference.java index b1919acd8..132ad758a 100644 --- a/src/main/java/com/mindee/v2/parsing/BaseInference.java +++ b/src/main/java/com/mindee/v2/parsing/BaseInference.java @@ -17,7 +17,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) @AllArgsConstructor @NoArgsConstructor -public abstract class BaseInference { +public abstract class BaseInference { /** * Inference ID. */ @@ -42,8 +42,13 @@ public abstract class BaseInference { @JsonProperty("file") protected InferenceFile file; - @Override - public String toString() { + /** + * Result of the inference. + */ + @JsonProperty("result") + protected TResult result; + + protected String toStringBase() { StringJoiner joiner = new StringJoiner("\n"); joiner .add("Inference") @@ -55,4 +60,11 @@ public String toString() { .add(file.toString()); return joiner.toString().trim() + "\n"; } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add(toStringBase()).add("").add(result.toString()); + return joiner.toString().trim() + "\n"; + } } diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationClassifier.java b/src/main/java/com/mindee/v2/product/classification/ClassificationClassifier.java new file mode 100644 index 000000000..ac9292210 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationClassifier.java @@ -0,0 +1,23 @@ +package com.mindee.v2.product.classification; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +/** + * Classification of the document type from the source file. + */ +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClassificationClassifier { + /** + * The document type, as identified on given classification values. + */ + @JsonProperty("document_type") + private String documentType; + + @Override + public String toString() { + return "Document Type: " + documentType; + } +} diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationInference.java b/src/main/java/com/mindee/v2/product/classification/ClassificationInference.java new file mode 100644 index 000000000..6db16acc3 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationInference.java @@ -0,0 +1,9 @@ +package com.mindee.v2.product.classification; + +import com.mindee.v2.parsing.BaseInference; + +/** + * The inference result for a classification utility request. + */ +public class ClassificationInference extends BaseInference { +} diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java b/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java new file mode 100644 index 000000000..f151bbd67 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java @@ -0,0 +1,20 @@ +package com.mindee.v2.product.classification; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.parsing.v2.CommonResponse; +import lombok.Getter; + +/** + * Response for a classification utility inference. + */ +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClassificationResponse extends CommonResponse { + + /** + * The inference result for a classification utility request. + */ + @JsonProperty("inference") + private ClassificationInference inference; +} diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java b/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java new file mode 100644 index 000000000..28c63eaf7 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java @@ -0,0 +1,34 @@ +package com.mindee.v2.product.classification; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Result of the document classifier inference. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public final class ClassificationResult { + /** + * Classification of the document type from the source file. + */ + @JsonProperty("classification") + private ClassificationClassifier classification; + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add("Classification").add("=============="); + joiner.add(classification.toString()); + + return joiner.toString(); + } +} diff --git a/src/test/java/com/mindee/TestingUtilities.java b/src/test/java/com/mindee/TestingUtilities.java index 560a5f627..c2e17ecfe 100644 --- a/src/test/java/com/mindee/TestingUtilities.java +++ b/src/test/java/com/mindee/TestingUtilities.java @@ -39,6 +39,10 @@ public static void assertStringEqualsFile(String expected, String filePath) thro Assertions.assertEquals(expectedSummary, actualSummary); } + public static void assertStringEqualsFile(String expected, Path filePath) throws IOException { + assertStringEqualsFile(expected, filePath.toString()); + } + /** * Retrieves the version from an RST prediction output. * diff --git a/src/test/java/com/mindee/v2/product/ClassificationTest.java b/src/test/java/com/mindee/v2/product/ClassificationTest.java new file mode 100644 index 000000000..b1a1cae1b --- /dev/null +++ b/src/test/java/com/mindee/v2/product/ClassificationTest.java @@ -0,0 +1,37 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.getV2ResourcePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.mindee.input.LocalResponse; +import com.mindee.v2.product.classification.ClassificationResponse; +import java.io.IOException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("MindeeV2 - Classification Model Tests") +public class ClassificationTest { + private ClassificationResponse loadResponse(String filePath) throws IOException { + LocalResponse localResponse = new LocalResponse(getV2ResourcePath(filePath)); + return localResponse.deserializeResponse(ClassificationResponse.class); + } + + @Nested + @DisplayName("Classification with single value") + class SinglePredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + ClassificationResponse response = loadResponse( + "products/classification/classification_single.json" + ); + assertNotNull(response.getInference()); + assertEquals( + "invoice", + response.getInference().getResult().getClassification().getDocumentType() + ); + } + } +} From b00398f47d1ab2184ab8cc4c251419ec21e0d351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 13:56:13 +0100 Subject: [PATCH 02/12] update polygon printing --- src/main/java/com/mindee/geometry/Point.java | 5 +++++ src/main/java/com/mindee/geometry/Polygon.java | 18 ++++++++++++++++++ .../mindee/parsing/v2/field/FieldLocation.java | 5 +++++ .../com/mindee/v2/parsing/BaseInference.java | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mindee/geometry/Point.java b/src/main/java/com/mindee/geometry/Point.java index fb925d790..0efa24c3d 100644 --- a/src/main/java/com/mindee/geometry/Point.java +++ b/src/main/java/com/mindee/geometry/Point.java @@ -35,4 +35,9 @@ public boolean equals(Object object) { public int hashCode() { return x.hashCode() + y.hashCode(); } + + @Override + public String toString() { + return String.format("(%s,%s)", x, y); + } } diff --git a/src/main/java/com/mindee/geometry/Polygon.java b/src/main/java/com/mindee/geometry/Polygon.java index 8369204b0..e01985e78 100644 --- a/src/main/java/com/mindee/geometry/Polygon.java +++ b/src/main/java/com/mindee/geometry/Polygon.java @@ -57,6 +57,9 @@ public boolean isEmpty() { return coordinates.isEmpty(); } + /** + * Default string representation. + */ public String toString() { if (!isEmpty()) { return String.format("Polygon with %s points.", getCoordinates().size()); @@ -64,6 +67,21 @@ public String toString() { return ""; } + /** + * String representation with precise coordinates. + */ + public String toStringPrecise() { + StringBuilder builder = new StringBuilder("("); + for (int i = 0; i < coordinates.size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(coordinates.get(i)); + } + builder.append(")"); + return builder.toString(); + } + @Override public boolean equals(Object object) { if (this == object) { diff --git a/src/main/java/com/mindee/parsing/v2/field/FieldLocation.java b/src/main/java/com/mindee/parsing/v2/field/FieldLocation.java index 35a7ec2ce..f82c40888 100644 --- a/src/main/java/com/mindee/parsing/v2/field/FieldLocation.java +++ b/src/main/java/com/mindee/parsing/v2/field/FieldLocation.java @@ -32,4 +32,9 @@ public class FieldLocation { */ @JsonProperty("page") private int page; + + @Override + public String toString() { + return polygon.toStringPrecise() + " on page " + page; + } } diff --git a/src/main/java/com/mindee/v2/parsing/BaseInference.java b/src/main/java/com/mindee/v2/parsing/BaseInference.java index 132ad758a..26053d457 100644 --- a/src/main/java/com/mindee/v2/parsing/BaseInference.java +++ b/src/main/java/com/mindee/v2/parsing/BaseInference.java @@ -64,7 +64,7 @@ protected String toStringBase() { @Override public String toString() { StringJoiner joiner = new StringJoiner("\n"); - joiner.add(toStringBase()).add("").add(result.toString()); + joiner.add(toStringBase()).add(result.toString()); return joiner.toString().trim() + "\n"; } } From d720d68b32dbfe539a904b8b61969f088cca7c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 15:07:35 +0100 Subject: [PATCH 03/12] add crop classes --- .../mindee/v2/product/crop/CropInference.java | 9 ++ .../com/mindee/v2/product/crop/CropItem.java | 36 ++++++++ .../mindee/v2/product/crop/CropResponse.java | 20 +++++ .../mindee/v2/product/crop/CropResult.java | 36 ++++++++ .../mindee/v2/product/ClassificationTest.java | 2 +- .../java/com/mindee/v2/product/CropTest.java | 86 +++++++++++++++++++ 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/mindee/v2/product/crop/CropInference.java create mode 100644 src/main/java/com/mindee/v2/product/crop/CropItem.java create mode 100644 src/main/java/com/mindee/v2/product/crop/CropResponse.java create mode 100644 src/main/java/com/mindee/v2/product/crop/CropResult.java create mode 100644 src/test/java/com/mindee/v2/product/CropTest.java diff --git a/src/main/java/com/mindee/v2/product/crop/CropInference.java b/src/main/java/com/mindee/v2/product/crop/CropInference.java new file mode 100644 index 000000000..695b1facf --- /dev/null +++ b/src/main/java/com/mindee/v2/product/crop/CropInference.java @@ -0,0 +1,9 @@ +package com.mindee.v2.product.crop; + +import com.mindee.v2.parsing.BaseInference; + +/** + * The inference result for a classification utility request. + */ +public class CropInference extends BaseInference { +} diff --git a/src/main/java/com/mindee/v2/product/crop/CropItem.java b/src/main/java/com/mindee/v2/product/crop/CropItem.java new file mode 100644 index 000000000..1c8c1aa1c --- /dev/null +++ b/src/main/java/com/mindee/v2/product/crop/CropItem.java @@ -0,0 +1,36 @@ +package com.mindee.v2.product.crop; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.parsing.v2.field.FieldLocation; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Result of a cropped document region. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public class CropItem { + /** + * Type or classification of the detected object. + */ + @JsonProperty("object_type") + private String objectType; + /** + * Location which includes cropping coordinates for the detected object, within the source + * document. + */ + @JsonProperty("location") + private FieldLocation location; + + @Override + public String toString() { + return "* :Location: " + location + "\n :Object Type: " + objectType; + } +} diff --git a/src/main/java/com/mindee/v2/product/crop/CropResponse.java b/src/main/java/com/mindee/v2/product/crop/CropResponse.java new file mode 100644 index 000000000..a2c0d314c --- /dev/null +++ b/src/main/java/com/mindee/v2/product/crop/CropResponse.java @@ -0,0 +1,20 @@ +package com.mindee.v2.product.crop; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.parsing.v2.CommonResponse; +import lombok.Getter; + +/** + * Response for a crop utility inference. + */ +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class CropResponse extends CommonResponse { + + /** + * The inference result for a crop utility request. + */ + @JsonProperty("inference") + private CropInference inference; +} diff --git a/src/main/java/com/mindee/v2/product/crop/CropResult.java b/src/main/java/com/mindee/v2/product/crop/CropResult.java new file mode 100644 index 000000000..52ac4208f --- /dev/null +++ b/src/main/java/com/mindee/v2/product/crop/CropResult.java @@ -0,0 +1,36 @@ +package com.mindee.v2.product.crop; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Result of a crop utility inference. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public final class CropResult { + /** + * List of objects and their cropping coordinates identified in the source document. + */ + @JsonProperty("crops") + private ArrayList crops; + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add("Crops").add("====="); + for (CropItem item : this.crops) { + joiner.add(item.toString()); + } + return joiner.toString(); + } +} diff --git a/src/test/java/com/mindee/v2/product/ClassificationTest.java b/src/test/java/com/mindee/v2/product/ClassificationTest.java index b1a1cae1b..d73f029bb 100644 --- a/src/test/java/com/mindee/v2/product/ClassificationTest.java +++ b/src/test/java/com/mindee/v2/product/ClassificationTest.java @@ -19,7 +19,7 @@ private ClassificationResponse loadResponse(String filePath) throws IOException } @Nested - @DisplayName("Classification with single value") + @DisplayName("Result with single value") class SinglePredictionTest { @Test @DisplayName("all properties must be valid") diff --git a/src/test/java/com/mindee/v2/product/CropTest.java b/src/test/java/com/mindee/v2/product/CropTest.java new file mode 100644 index 000000000..7d6b1c7e5 --- /dev/null +++ b/src/test/java/com/mindee/v2/product/CropTest.java @@ -0,0 +1,86 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.assertStringEqualsFile; +import static com.mindee.TestingUtilities.getV2ResourcePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.mindee.input.LocalResponse; +import com.mindee.v2.product.crop.CropItem; +import com.mindee.v2.product.crop.CropResponse; +import java.io.IOException; +import java.util.ArrayList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("MindeeV2 - Crop Model Tests") +public class CropTest { + private CropResponse loadResponse(String filePath) throws IOException { + LocalResponse localResponse = new LocalResponse(getV2ResourcePath(filePath)); + return localResponse.deserializeResponse(CropResponse.class); + } + + @Nested + @DisplayName("Result with single value") + class SinglePredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + CropResponse response = loadResponse("products/crop/crop_single.json"); + assertNotNull(response.getInference()); + + ArrayList crops = response.getInference().getResult().getCrops(); + assertEquals(1, crops.size()); + + CropItem crop1 = crops.get(0); + assertEquals("invoice", crop1.getObjectType()); + assertNotNull(crop1.getLocation().getPolygon()); + assertEquals(0, crop1.getLocation().getPage()); + } + + @Test + @DisplayName("RST output must be valid") + void mustHaveValidDisplay() throws IOException { + CropResponse response = loadResponse("products/crop/crop_single.json"); + assertStringEqualsFile( + response.getInference().toString(), + getV2ResourcePath("products/crop/crop_single.rst") + ); + } + } + + @Nested + @DisplayName("Result with multiple values") + class MultiPredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + CropResponse response = loadResponse("products/crop/crop_multiple.json"); + assertNotNull(response.getInference()); + + ArrayList crops = response.getInference().getResult().getCrops(); + assertEquals(2, crops.size()); + + CropItem crop1 = crops.get(0); + assertEquals("invoice", crop1.getObjectType()); + assertNotNull(crop1.getLocation().getPolygon()); + assertEquals(0, crop1.getLocation().getPage()); + + CropItem crop2 = crops.get(1); + assertEquals("invoice", crop2.getObjectType()); + assertNotNull(crop2.getLocation().getPolygon()); + assertEquals(0, crop2.getLocation().getPage()); + } + + @Test + @DisplayName("RST output must be valid") + void mustHaveValidDisplay() throws IOException { + CropResponse response = loadResponse("products/crop/crop_multiple.json"); + assertStringEqualsFile( + response.getInference().toString(), + getV2ResourcePath("products/crop/crop_multiple.rst") + ); + } + } +} From 0975f89c51ca7b792e39dcc2012fed356f3cf41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 15:54:27 +0100 Subject: [PATCH 04/12] add OCR classes --- .../classification/ClassificationResult.java | 2 +- .../mindee/v2/product/crop/CropResult.java | 4 +- .../mindee/v2/product/ocr/OcrInference.java | 9 +++ .../com/mindee/v2/product/ocr/OcrPage.java | 31 ++++++++++ .../mindee/v2/product/ocr/OcrResponse.java | 20 ++++++ .../com/mindee/v2/product/ocr/OcrResult.java | 36 +++++++++++ .../com/mindee/v2/product/ocr/OcrWord.java | 31 ++++++++++ .../java/com/mindee/v2/product/OcrTest.java | 62 +++++++++++++++++++ 8 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/mindee/v2/product/ocr/OcrInference.java create mode 100644 src/main/java/com/mindee/v2/product/ocr/OcrPage.java create mode 100644 src/main/java/com/mindee/v2/product/ocr/OcrResponse.java create mode 100644 src/main/java/com/mindee/v2/product/ocr/OcrResult.java create mode 100644 src/main/java/com/mindee/v2/product/ocr/OcrWord.java create mode 100644 src/test/java/com/mindee/v2/product/OcrTest.java diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java b/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java index 28c63eaf7..77ccd1d82 100644 --- a/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationResult.java @@ -26,7 +26,7 @@ public final class ClassificationResult { @Override public String toString() { StringJoiner joiner = new StringJoiner("\n"); - joiner.add("Classification").add("=============="); + joiner.add("Classification\n=============="); joiner.add(classification.toString()); return joiner.toString(); diff --git a/src/main/java/com/mindee/v2/product/crop/CropResult.java b/src/main/java/com/mindee/v2/product/crop/CropResult.java index 52ac4208f..7beea3e74 100644 --- a/src/main/java/com/mindee/v2/product/crop/CropResult.java +++ b/src/main/java/com/mindee/v2/product/crop/CropResult.java @@ -27,8 +27,8 @@ public final class CropResult { @Override public String toString() { StringJoiner joiner = new StringJoiner("\n"); - joiner.add("Crops").add("====="); - for (CropItem item : this.crops) { + joiner.add("Crops\n====="); + for (CropItem item : crops) { joiner.add(item.toString()); } return joiner.toString(); diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrInference.java b/src/main/java/com/mindee/v2/product/ocr/OcrInference.java new file mode 100644 index 000000000..0bf1ee74d --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/OcrInference.java @@ -0,0 +1,9 @@ +package com.mindee.v2.product.ocr; + +import com.mindee.v2.parsing.BaseInference; + +/** + * The inference result for an OCR utility request. + */ +public class OcrInference extends BaseInference { +} diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrPage.java b/src/main/java/com/mindee/v2/product/ocr/OcrPage.java new file mode 100644 index 000000000..19334108c --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/OcrPage.java @@ -0,0 +1,31 @@ +package com.mindee.v2.product.ocr; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * OCR result for a single page. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public class OcrPage { + /** + * Full text content extracted from the document page. + */ + @JsonProperty("content") + private String content; + + /** + * List of words extracted from the document page. + */ + @JsonProperty("words") + private ArrayList words; +} diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java b/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java new file mode 100644 index 000000000..7e66c46c7 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java @@ -0,0 +1,20 @@ +package com.mindee.v2.product.ocr; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.parsing.v2.CommonResponse; +import lombok.Getter; + +/** + * Response for an OCR utility inference. + */ +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class OcrResponse extends CommonResponse { + + /** + * The inference result for an OCR utility request. + */ + @JsonProperty("inference") + private OcrInference inference; +} diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrResult.java b/src/main/java/com/mindee/v2/product/ocr/OcrResult.java new file mode 100644 index 000000000..711996974 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/OcrResult.java @@ -0,0 +1,36 @@ +package com.mindee.v2.product.ocr; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Result of the OCR utility inference. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public final class OcrResult { + /** + * List of OCR results for each page in the document. + */ + @JsonProperty("pages") + private ArrayList pages; + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add("Pages\n======"); + for (OcrPage page : pages) { + joiner.add(page.toString()); + } + return joiner.toString(); + } +} diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrWord.java b/src/main/java/com/mindee/v2/product/ocr/OcrWord.java new file mode 100644 index 000000000..18097da13 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/OcrWord.java @@ -0,0 +1,31 @@ +package com.mindee.v2.product.ocr; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.geometry.Polygon; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * OCR result for a single word extracted from the document page. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public class OcrWord { + /** + * Text content of the word. + */ + @JsonProperty("content") + private String content; + + /** + * Position information as a list of points in clockwise order. + */ + @JsonProperty("polygon") + private Polygon polygon; +} diff --git a/src/test/java/com/mindee/v2/product/OcrTest.java b/src/test/java/com/mindee/v2/product/OcrTest.java new file mode 100644 index 000000000..c39268be0 --- /dev/null +++ b/src/test/java/com/mindee/v2/product/OcrTest.java @@ -0,0 +1,62 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.getV2ResourcePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.mindee.input.LocalResponse; +import com.mindee.v2.product.ocr.OcrPage; +import com.mindee.v2.product.ocr.OcrResponse; +import java.io.IOException; +import java.util.ArrayList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("MindeeV2 - OCR Model Tests") +public class OcrTest { + private OcrResponse loadResponse(String filePath) throws IOException { + LocalResponse localResponse = new LocalResponse(getV2ResourcePath(filePath)); + return localResponse.deserializeResponse(OcrResponse.class); + } + + @Nested + @DisplayName("Result with single value") + class SinglePredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + OcrResponse response = loadResponse("products/ocr/ocr_single.json"); + assertNotNull(response.getInference()); + + ArrayList pages = response.getInference().getResult().getPages(); + assertEquals(1, pages.size()); + assertEquals(305, pages.get(0).getWords().size()); + assertEquals("Shipper:", pages.get(0).getWords().get(0).getContent()); + } + } + + @Nested + @DisplayName("Result with multiple values") + class MultiPredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + OcrResponse response = loadResponse("products/ocr/ocr_multiple.json"); + assertNotNull(response.getInference()); + + ArrayList pages = response.getInference().getResult().getPages(); + assertEquals(3, pages.size()); + + OcrPage page1 = pages.get(0); + assertNotNull(page1.getContent()); + assertEquals(295, page1.getWords().size()); + assertEquals("FICTIOCORP", page1.getWords().get(0).getContent()); + + OcrPage page2 = pages.get(1); + assertNotNull(page2.getContent()); + assertEquals(450, page2.getWords().size()); + assertEquals("KEOLIO", page2.getWords().get(0).getContent()); + } + } +} From cfbd000ad8f61bf76c2df6658c939e2bec3b93c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 16:07:50 +0100 Subject: [PATCH 05/12] add Split classes --- .../com/mindee/v2/product/ocr/OcrResult.java | 4 +- .../v2/product/split/SplitInference.java | 9 +++ .../mindee/v2/product/split/SplitRange.java | 32 ++++++++++ .../v2/product/split/SplitResponse.java | 20 ++++++ .../mindee/v2/product/split/SplitResult.java | 36 +++++++++++ .../java/com/mindee/v2/product/SplitTest.java | 64 +++++++++++++++++++ 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/mindee/v2/product/split/SplitInference.java create mode 100644 src/main/java/com/mindee/v2/product/split/SplitRange.java create mode 100644 src/main/java/com/mindee/v2/product/split/SplitResponse.java create mode 100644 src/main/java/com/mindee/v2/product/split/SplitResult.java create mode 100644 src/test/java/com/mindee/v2/product/SplitTest.java diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrResult.java b/src/main/java/com/mindee/v2/product/ocr/OcrResult.java index 711996974..7a5cfc286 100644 --- a/src/main/java/com/mindee/v2/product/ocr/OcrResult.java +++ b/src/main/java/com/mindee/v2/product/ocr/OcrResult.java @@ -28,8 +28,8 @@ public final class OcrResult { public String toString() { StringJoiner joiner = new StringJoiner("\n"); joiner.add("Pages\n======"); - for (OcrPage page : pages) { - joiner.add(page.toString()); + for (OcrPage item : pages) { + joiner.add(item.toString()); } return joiner.toString(); } diff --git a/src/main/java/com/mindee/v2/product/split/SplitInference.java b/src/main/java/com/mindee/v2/product/split/SplitInference.java new file mode 100644 index 000000000..4d07f0a8c --- /dev/null +++ b/src/main/java/com/mindee/v2/product/split/SplitInference.java @@ -0,0 +1,9 @@ +package com.mindee.v2.product.split; + +import com.mindee.v2.parsing.BaseInference; + +/** + * The inference result for a split utility request. + */ +public class SplitInference extends BaseInference { +} diff --git a/src/main/java/com/mindee/v2/product/split/SplitRange.java b/src/main/java/com/mindee/v2/product/split/SplitRange.java new file mode 100644 index 000000000..254805204 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/split/SplitRange.java @@ -0,0 +1,32 @@ +package com.mindee.v2.product.split; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * A single document as identified when splitting a multi-document source file. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public class SplitRange { + /** + * 0-based page indexes, where the first integer indicates the start page and the second integer + * indicates the end page. + */ + @JsonProperty("page_range") + public ArrayList pageRange; + + /** + * The document type, as identified on given classification values. + */ + @JsonProperty("document_type") + public String documentType; +} diff --git a/src/main/java/com/mindee/v2/product/split/SplitResponse.java b/src/main/java/com/mindee/v2/product/split/SplitResponse.java new file mode 100644 index 000000000..8204c9d15 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/split/SplitResponse.java @@ -0,0 +1,20 @@ +package com.mindee.v2.product.split; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.parsing.v2.CommonResponse; +import lombok.Getter; + +/** + * Response for a crop utility inference. + */ +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +public class SplitResponse extends CommonResponse { + + /** + * The inference result for a split utility request. + */ + @JsonProperty("inference") + private SplitInference inference; +} diff --git a/src/main/java/com/mindee/v2/product/split/SplitResult.java b/src/main/java/com/mindee/v2/product/split/SplitResult.java new file mode 100644 index 000000000..41deb8ecc --- /dev/null +++ b/src/main/java/com/mindee/v2/product/split/SplitResult.java @@ -0,0 +1,36 @@ +package com.mindee.v2.product.split; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Result of the document splitter inference. + */ +@Getter +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +public final class SplitResult { + /** + * List of documents identified within a multi-document source file. + */ + @JsonProperty("splits") + private ArrayList splits; + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + joiner.add("Splits\n======"); + for (SplitRange item : splits) { + joiner.add(item.toString()); + } + return joiner.toString(); + } +} diff --git a/src/test/java/com/mindee/v2/product/SplitTest.java b/src/test/java/com/mindee/v2/product/SplitTest.java new file mode 100644 index 000000000..d708adc68 --- /dev/null +++ b/src/test/java/com/mindee/v2/product/SplitTest.java @@ -0,0 +1,64 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.getV2ResourcePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.mindee.input.LocalResponse; +import com.mindee.v2.product.split.SplitRange; +import com.mindee.v2.product.split.SplitResponse; +import java.io.IOException; +import java.util.ArrayList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("MindeeV2 - Split Model Tests") +public class SplitTest { + private SplitResponse loadResponse(String filePath) throws IOException { + LocalResponse localResponse = new LocalResponse(getV2ResourcePath(filePath)); + return localResponse.deserializeResponse(SplitResponse.class); + } + + @Nested + @DisplayName("Result with single value") + class SinglePredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + SplitResponse response = loadResponse("products/split/split_single.json"); + assertNotNull(response.getInference()); + + ArrayList splits = response.getInference().getResult().getSplits(); + assertEquals(1, splits.size()); + assertEquals("receipt", splits.get(0).getDocumentType()); + assertEquals(0, splits.get(0).getPageRange().get(0)); + } + } + + @Nested + @DisplayName("Result with multiple values") + class MultiPredictionTest { + @Test + @DisplayName("all properties must be valid") + void mustHaveValidProperties() throws IOException { + SplitResponse response = loadResponse("products/split/split_multiple.json"); + assertNotNull(response.getInference()); + + ArrayList splits = response.getInference().getResult().getSplits(); + assertEquals(3, splits.size()); + + SplitRange split1 = splits.get(0); + assertEquals("invoice", split1.getDocumentType()); + assertEquals(0, split1.getPageRange().get(0)); + + SplitRange split2 = splits.get(1); + assertEquals("invoice", split2.getDocumentType()); + assertEquals(1, split2.getPageRange().get(0)); + + SplitRange split3 = splits.get(2); + assertEquals("invoice", split3.getDocumentType()); + assertEquals(4, split3.getPageRange().get(0)); + } + } +} From 2c5c6b81cf2c12233f0aa48e07b00b3fdaf217ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Thu, 12 Mar 2026 17:03:04 +0100 Subject: [PATCH 06/12] add post slug to v2 parameters --- .../java/com/mindee/InferenceParameters.java | 2 +- src/main/java/com/mindee/MindeeClientV2.java | 105 ++++++++++++++---- .../java/com/mindee/http/MindeeApiV2.java | 11 +- .../java/com/mindee/http/MindeeHttpApiV2.java | 18 +-- .../v2/clientOptions/BaseParameters.java | 4 + .../java/com/mindee/MindeeClientV2IT.java | 6 +- .../java/com/mindee/MindeeClientV2Test.java | 16 ++- 7 files changed, 115 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/mindee/InferenceParameters.java b/src/main/java/com/mindee/InferenceParameters.java index c9b40ccee..ab79241f5 100644 --- a/src/main/java/com/mindee/InferenceParameters.java +++ b/src/main/java/com/mindee/InferenceParameters.java @@ -51,7 +51,7 @@ private InferenceParameters( String textContext, String dataSchema ) { - super(modelId, alias, webhookIds, pollingOptions); + super(modelId, alias, webhookIds, pollingOptions, "extraction"); this.rag = rag; this.rawText = rawText; this.polygon = polygon; diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index 1af7030c5..d5a70ccf8 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -5,6 +5,7 @@ import com.mindee.http.MindeeHttpExceptionV2; import com.mindee.input.LocalInputSource; import com.mindee.input.URLInputSource; +import com.mindee.parsing.v2.CommonResponse; import com.mindee.parsing.v2.ErrorResponse; import com.mindee.parsing.v2.InferenceResponse; import com.mindee.parsing.v2.JobResponse; @@ -33,23 +34,46 @@ public MindeeClientV2(MindeeApiV2 mindeeApi) { } /** - * Enqueue a document in the asynchronous queue. + * @deprecated use `enqueue` instead. */ public JobResponse enqueueInference( LocalInputSource inputSource, - BaseParameters params + InferenceParameters params ) throws IOException { - return mindeeApi.reqPostInferenceEnqueue(inputSource, params); + return enqueue(inputSource, params); } /** - * Enqueue a document in the asynchronous queue. + * @deprecated use `enqueue` instead. */ public JobResponse enqueueInference( URLInputSource inputSource, BaseParameters params ) throws IOException { - return mindeeApi.reqPostInferenceEnqueue(inputSource, params); + return enqueue(inputSource, params); + } + + /** + * Enqueue a document in the asynchronous queue. + * + * @param inputSource The local input source to send. + * @param params The parameters to send along with the file. + */ + public JobResponse enqueue( + LocalInputSource inputSource, + BaseParameters params + ) throws IOException { + return mindeeApi.reqPostEnqueue(inputSource, params); + } + + /** + * Enqueue a document in the asynchronous queue. + * + * @param inputSource The URL input source to send. + * @param params The parameters to send along with the file. + */ + public JobResponse enqueue(URLInputSource inputSource, BaseParameters params) throws IOException { + return mindeeApi.reqPostEnqueue(inputSource, params); } /** @@ -63,51 +87,83 @@ public JobResponse getJob(String jobId) { return mindeeApi.reqGetJob(jobId); } + /** + * @deprecated use `getResult` instead. + */ + public InferenceResponse getInference(String inferenceId) { + return getResult(InferenceResponse.class, inferenceId); + } + /** * Get the result of an inference that was previously enqueued. * The inference will only be available after it has finished processing. */ - public InferenceResponse getInference(String inferenceId) { + public TResponse getResult( + Class responseClass, + String inferenceId + ) { if (inferenceId == null || inferenceId.trim().isEmpty()) { throw new IllegalArgumentException("inferenceId must not be null or blank."); } - return mindeeApi.reqGetInference(inferenceId); + return mindeeApi.reqGetResult(responseClass, inferenceId); + } + + /** + * @deprecated use `enqueueAndGetResult` instead. + */ + public InferenceResponse enqueueAndGetInference( + LocalInputSource inputSource, + InferenceParameters options + ) throws IOException, InterruptedException { + return enqueueAndGetResult(InferenceResponse.class, inputSource, options); + } + + /** + * @deprecated use `enqueueAndGetResult` instead. + */ + public InferenceResponse enqueueAndGetInference( + URLInputSource inputSource, + InferenceParameters options + ) throws IOException, InterruptedException { + return enqueueAndGetResult(InferenceResponse.class, inputSource, options); } /** * Send a local file to an async queue, poll, and parse when complete. * - * @param inputSource The input source to send. - * @param options The options to send along with the file. + * @param inputSource The local input source to send. + * @param params The parameters to send along with the file. * @return an instance of {@link InferenceResponse}. * @throws IOException Throws if the file can't be accessed. * @throws InterruptedException Throws if the thread is interrupted. */ - public InferenceResponse enqueueAndGetInference( + public TResponse enqueueAndGetResult( + Class responseClass, LocalInputSource inputSource, - BaseParameters options + BaseParameters params ) throws IOException, InterruptedException { - validatePollingOptions(options.getPollingOptions()); - JobResponse job = enqueueInference(inputSource, options); - return pollAndFetch(job, options); + validatePollingOptions(params.getPollingOptions()); + JobResponse job = enqueue(inputSource, params); + return pollAndFetch(responseClass, job, params); } /** - * Send a local file to an async queue, poll, and parse when complete. + * Send a remote file to an async queue, poll, and parse when complete. * - * @param inputSource The input source to send. - * @param options The options to send along with the file. + * @param inputSource The URL input source to send. + * @param params The parameters to send along with the file. * @return an instance of {@link InferenceResponse}. * @throws IOException Throws if the file can't be accessed. * @throws InterruptedException Throws if the thread is interrupted. */ - public InferenceResponse enqueueAndGetInference( + public TResponse enqueueAndGetResult( + Class responseClass, URLInputSource inputSource, - BaseParameters options + BaseParameters params ) throws IOException, InterruptedException { - validatePollingOptions(options.getPollingOptions()); - JobResponse job = enqueueInference(inputSource, options); - return pollAndFetch(job, options); + validatePollingOptions(params.getPollingOptions()); + JobResponse job = enqueue(inputSource, params); + return pollAndFetch(responseClass, job, params); } /** @@ -117,7 +173,8 @@ public InferenceResponse enqueueAndGetInference( * @return an instance of {@link InferenceResponse}. * @throws InterruptedException Throws if interrupted. */ - private InferenceResponse pollAndFetch( + private TResponse pollAndFetch( + Class responseClass, JobResponse initialJob, BaseParameters options ) throws InterruptedException { @@ -135,7 +192,7 @@ private InferenceResponse pollAndFetch( attempts = max; } if (resp.getJob().getStatus().equals("Processed")) { - return getInference(resp.getJob().getId()); + return getResult(responseClass, resp.getJob().getId()); } attempts++; } diff --git a/src/main/java/com/mindee/http/MindeeApiV2.java b/src/main/java/com/mindee/http/MindeeApiV2.java index 66ea62865..35e8d3a7c 100644 --- a/src/main/java/com/mindee/http/MindeeApiV2.java +++ b/src/main/java/com/mindee/http/MindeeApiV2.java @@ -2,8 +2,8 @@ import com.mindee.input.LocalInputSource; import com.mindee.input.URLInputSource; +import com.mindee.parsing.v2.CommonResponse; import com.mindee.parsing.v2.ErrorResponse; -import com.mindee.parsing.v2.InferenceResponse; import com.mindee.parsing.v2.JobResponse; import com.mindee.v2.clientOptions.BaseParameters; import java.io.IOException; @@ -18,7 +18,7 @@ public abstract class MindeeApiV2 extends MindeeApiCommon { * @param inputSource Local input source from URL. * @param options parameters. */ - public abstract JobResponse reqPostInferenceEnqueue( + public abstract JobResponse reqPostEnqueue( LocalInputSource inputSource, BaseParameters options ) throws IOException; @@ -29,7 +29,7 @@ public abstract JobResponse reqPostInferenceEnqueue( * @param inputSource Remote input source from URL. * @param options parameters. */ - public abstract JobResponse reqPostInferenceEnqueue( + public abstract JobResponse reqPostEnqueue( URLInputSource inputSource, BaseParameters options ) throws IOException; @@ -46,7 +46,10 @@ public abstract JobResponse reqPostInferenceEnqueue( * * @param inferenceId ID of the inference to poll. */ - abstract public InferenceResponse reqGetInference(String inferenceId); + abstract public TResponse reqGetResult( + Class responseClass, + String inferenceId + ); /** * Creates an "unknown error" response from an HTTP status code. diff --git a/src/main/java/com/mindee/http/MindeeHttpApiV2.java b/src/main/java/com/mindee/http/MindeeHttpApiV2.java index 6fcb61601..9ac421c1d 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApiV2.java +++ b/src/main/java/com/mindee/http/MindeeHttpApiV2.java @@ -7,7 +7,6 @@ import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.CommonResponse; import com.mindee.parsing.v2.ErrorResponse; -import com.mindee.parsing.v2.InferenceResponse; import com.mindee.parsing.v2.JobResponse; import com.mindee.v2.clientOptions.BaseParameters; import java.io.IOException; @@ -68,8 +67,9 @@ private MindeeHttpApiV2(MindeeSettingsV2 mindeeSettings, HttpClientBuilder httpC * @return A job response. */ @Override - public JobResponse reqPostInferenceEnqueue(LocalInputSource inputSource, BaseParameters options) { - String url = this.mindeeSettings.getBaseUrl() + "/products/extraction/enqueue"; + public JobResponse reqPostEnqueue(LocalInputSource inputSource, BaseParameters options) { + String url = String + .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), options.getSlug()); HttpPost post = buildHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -93,8 +93,9 @@ public JobResponse reqPostInferenceEnqueue(LocalInputSource inputSource, BasePar * @return A job response. */ @Override - public JobResponse reqPostInferenceEnqueue(URLInputSource inputSource, BaseParameters options) { - String url = this.mindeeSettings.getBaseUrl() + "/products/extraction/enqueue"; + public JobResponse reqPostEnqueue(URLInputSource inputSource, BaseParameters options) { + String url = String + .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), options.getSlug()); HttpPost post = buildHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -167,7 +168,10 @@ public JobResponse reqGetJob(String jobId) { } @Override - public InferenceResponse reqGetInference(String inferenceId) { + public TResponse reqGetResult( + Class responseClass, + String inferenceId + ) { String url = this.mindeeSettings.getBaseUrl() + "/products/extraction/results/" + inferenceId; HttpGet get = new HttpGet(url); @@ -189,7 +193,7 @@ public InferenceResponse reqGetInference(String inferenceId) { throw getHttpError(response); } String raw = EntityUtils.toString(entity, StandardCharsets.UTF_8); - return deserializeOrThrow(raw, InferenceResponse.class, status); + return deserializeOrThrow(raw, responseClass, status); } finally { EntityUtils.consumeQuietly(entity); } diff --git a/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java b/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java index c0b7ac9e3..38c861f17 100644 --- a/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java +++ b/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java @@ -23,6 +23,10 @@ public abstract class BaseParameters { * Polling options. Set only if having timeout issues. */ protected final AsyncPollingOptions pollingOptions; + /** + * Slug of the product type. + */ + private final String slug; public MultipartEntityBuilder buildHttpBody(MultipartEntityBuilder builder) { builder.addTextBody("model_id", this.getModelId()); diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java index 106582b16..86650ef89 100644 --- a/src/test/java/com/mindee/MindeeClientV2IT.java +++ b/src/test/java/com/mindee/MindeeClientV2IT.java @@ -53,7 +53,8 @@ void parseFile_emptyMultiPage_mustSucceed() throws IOException, InterruptedExcep ) .build(); - InferenceResponse response = mindeeClient.enqueueAndGetInference(source, params); + InferenceResponse response = mindeeClient + .enqueueAndGetResult(InferenceResponse.class, source, params); assertNotNull(response); Inference inference = response.getInference(); assertNotNull(inference); @@ -147,7 +148,8 @@ void parseFile_dataSchemaReplace_mustSucceed() throws IOException, InterruptedEx ) .build(); - InferenceResponse response = mindeeClient.enqueueAndGetInference(source, params); + InferenceResponse response = mindeeClient + .enqueueAndGetResult(InferenceResponse.class, source, params); assertNotNull(response); Inference inference = response.getInference(); assertNotNull(inference); diff --git a/src/test/java/com/mindee/MindeeClientV2Test.java b/src/test/java/com/mindee/MindeeClientV2Test.java index d3e41f968..90ddb2b4a 100644 --- a/src/test/java/com/mindee/MindeeClientV2Test.java +++ b/src/test/java/com/mindee/MindeeClientV2Test.java @@ -3,8 +3,7 @@ import static com.mindee.TestingUtilities.getResourcePath; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.atMostOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,10 +38,8 @@ class Enqueue { @DisplayName("sends exactly one HTTP call and yields a non-null response") void enqueue_post_async() throws IOException { MindeeApiV2 predictable = Mockito.mock(MindeeApiV2.class); - when( - predictable - .reqPostInferenceEnqueue(any(LocalInputSource.class), any(InferenceParameters.class)) - ).thenReturn(new JobResponse()); + when(predictable.reqPostEnqueue(any(LocalInputSource.class), any(InferenceParameters.class))) + .thenReturn(new JobResponse()); MindeeClientV2 mindeeClient = makeClientWithMockedApi(predictable); @@ -55,7 +52,7 @@ void enqueue_post_async() throws IOException { assertNotNull(response, "enqueue() must return a response"); verify(predictable, atMostOnce()) - .reqPostInferenceEnqueue(any(LocalInputSource.class), any(InferenceParameters.class)); + .reqPostEnqueue(any(LocalInputSource.class), any(InferenceParameters.class)); } } @@ -100,7 +97,8 @@ void document_getInference_async() throws IOException { InferenceResponse processing = mapper.readValue(json, InferenceResponse.class); - when(predictable.reqGetInference(anyString())).thenReturn(processing); + when(predictable.reqGetResult(eq(InferenceResponse.class), anyString())) + .thenReturn(processing); MindeeClientV2 mindeeClient = makeClientWithMockedApi(predictable); @@ -123,7 +121,7 @@ void document_getInference_async() throws IOException { .getValue(), "Result must deserialize fields properly." ); - verify(predictable, atMostOnce()).reqGetInference(anyString()); + verify(predictable, atMostOnce()).reqGetResult(eq(InferenceResponse.class), anyString()); } } From c7c941c591c67f07fa17477b264e349c8894eda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Fri, 13 Mar 2026 12:02:57 +0100 Subject: [PATCH 07/12] update test resources --- docs/code_samples/v2_extraction.txt | 12 +++++------- src/main/java/com/mindee/MindeeClientV2.java | 2 +- src/test/java/com/mindee/v2/product/CropTest.java | 2 +- src/test/java/com/mindee/v2/product/SplitTest.java | 4 ++-- src/test/resources | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/code_samples/v2_extraction.txt b/docs/code_samples/v2_extraction.txt index c35f0e11a..bdd711c3e 100644 --- a/docs/code_samples/v2_extraction.txt +++ b/docs/code_samples/v2_extraction.txt @@ -2,7 +2,6 @@ import com.mindee.MindeeClientV2; import com.mindee.InferenceParameters; import com.mindee.input.LocalInputSource; import com.mindee.parsing.v2.InferenceResponse; -import java.io.File; import java.io.IOException; public class SimpleMindeeClientV2 { @@ -19,7 +18,7 @@ public class SimpleMindeeClientV2 { // Set inference parameters // Note: modelId is mandatory. - InferenceParameters inferenceParams = InferenceParameters + InferenceParameters extractionParams = InferenceParameters // ID of the model, required. .builder(modelId) @@ -38,14 +37,13 @@ public class SimpleMindeeClientV2 { .build(); // Load a file from disk - LocalInputSource inputSource = new LocalInputSource( - new File(filePath) - ); + LocalInputSource inputSource = new LocalInputSource(filePath); // Send for processing - InferenceResponse response = mindeeClient.enqueueAndGetInference( + InferenceResponse response = mindeeClient.enqueueAndGetResult( + InferenceResponse.class, inputSource, - inferenceParams + extractionParams ); // Print a summary of the response diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index d5a70ccf8..808e0f393 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -48,7 +48,7 @@ public JobResponse enqueueInference( */ public JobResponse enqueueInference( URLInputSource inputSource, - BaseParameters params + InferenceParameters params ) throws IOException { return enqueue(inputSource, params); } diff --git a/src/test/java/com/mindee/v2/product/CropTest.java b/src/test/java/com/mindee/v2/product/CropTest.java index 7d6b1c7e5..f0df46732 100644 --- a/src/test/java/com/mindee/v2/product/CropTest.java +++ b/src/test/java/com/mindee/v2/product/CropTest.java @@ -68,7 +68,7 @@ void mustHaveValidProperties() throws IOException { assertEquals(0, crop1.getLocation().getPage()); CropItem crop2 = crops.get(1); - assertEquals("invoice", crop2.getObjectType()); + assertEquals("receipt", crop2.getObjectType()); assertNotNull(crop2.getLocation().getPolygon()); assertEquals(0, crop2.getLocation().getPage()); } diff --git a/src/test/java/com/mindee/v2/product/SplitTest.java b/src/test/java/com/mindee/v2/product/SplitTest.java index d708adc68..08a21bef8 100644 --- a/src/test/java/com/mindee/v2/product/SplitTest.java +++ b/src/test/java/com/mindee/v2/product/SplitTest.java @@ -49,7 +49,7 @@ void mustHaveValidProperties() throws IOException { assertEquals(3, splits.size()); SplitRange split1 = splits.get(0); - assertEquals("invoice", split1.getDocumentType()); + assertEquals("passport", split1.getDocumentType()); assertEquals(0, split1.getPageRange().get(0)); SplitRange split2 = splits.get(1); @@ -57,7 +57,7 @@ void mustHaveValidProperties() throws IOException { assertEquals(1, split2.getPageRange().get(0)); SplitRange split3 = splits.get(2); - assertEquals("invoice", split3.getDocumentType()); + assertEquals("receipt", split3.getDocumentType()); assertEquals(4, split3.getPageRange().get(0)); } } diff --git a/src/test/resources b/src/test/resources index c2e36f5b6..53f0efbc0 160000 --- a/src/test/resources +++ b/src/test/resources @@ -1 +1 @@ -Subproject commit c2e36f5b635386cb9bb922b517c4e02039b0a122 +Subproject commit 53f0efbc08c77c2c085aadd27de9d2d6c359276e From 5e93607972d7186e54425842bdc309ff12a391fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Fri, 13 Mar 2026 12:08:45 +0100 Subject: [PATCH 08/12] use annotation for product slugs --- .github/workflows/_test-integrations.yml | 2 +- .../java/com/mindee/InferenceParameters.java | 34 +++--------- src/main/java/com/mindee/MindeeClientV2.java | 16 +----- .../java/com/mindee/http/MindeeApiV2.java | 22 ++++++++ .../java/com/mindee/http/MindeeHttpApiV2.java | 17 ++++-- .../mindee/parsing/v2/InferenceResponse.java | 4 ++ .../v2/clientOptions/BaseParameters.java | 52 +++++++++++++++++-- .../java/com/mindee/v2/http/ProductInfo.java | 17 ++++++ .../ClassificationResponse.java | 2 + .../mindee/v2/product/crop/CropResponse.java | 2 + .../mindee/v2/product/ocr/OcrResponse.java | 2 + .../v2/product/split/SplitResponse.java | 2 + .../java/com/mindee/MindeeClientV2IT.java | 4 +- 13 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/mindee/v2/http/ProductInfo.java diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index bca2f4546..d494c57d4 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -35,7 +35,7 @@ jobs: MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} - MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} + MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} run: | mvn clean test-compile failsafe:integration-test failsafe:verify diff --git a/src/main/java/com/mindee/InferenceParameters.java b/src/main/java/com/mindee/InferenceParameters.java index ab79241f5..285bda60e 100644 --- a/src/main/java/com/mindee/InferenceParameters.java +++ b/src/main/java/com/mindee/InferenceParameters.java @@ -1,7 +1,7 @@ package com.mindee; import com.mindee.v2.clientOptions.BaseParameters; -import java.util.Objects; +import com.mindee.v2.http.ProductInfo; import lombok.EqualsAndHashCode; import lombok.Getter; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; @@ -11,6 +11,7 @@ */ @Getter @EqualsAndHashCode(callSuper = true) +@ProductInfo(slug = "extraction") public final class InferenceParameters extends BaseParameters { /** * Enhance extraction accuracy with Retrieval-Augmented Generation. @@ -51,7 +52,7 @@ private InferenceParameters( String textContext, String dataSchema ) { - super(modelId, alias, webhookIds, pollingOptions, "extraction"); + super(modelId, alias, webhookIds, pollingOptions); this.rag = rag; this.rawText = rawText; this.polygon = polygon; @@ -96,21 +97,16 @@ public static Builder builder(String modelId) { /** * Fluent builder for {@link InferenceParameters}. */ - public static final class Builder { - - private final String modelId; + public static final class Builder extends BaseParameters.BaseBuilder { private Boolean rag = null; private Boolean rawText = null; private Boolean polygon = null; private Boolean confidence = null; - private String alias; - private String[] webhookIds = new String[] {}; private String textContext; private String dataSchema; - private AsyncPollingOptions pollingOptions = AsyncPollingOptions.builder().build(); - private Builder(String modelId) { - this.modelId = Objects.requireNonNull(modelId, "modelId must not be null"); + Builder(String modelId) { + super(modelId); } /** Enhance extraction accuracy with Retrieval-Augmented Generation. */ @@ -140,18 +136,6 @@ public Builder confidence(Boolean confidence) { return this; } - /** Set an alias for the uploaded document. */ - public Builder alias(String alias) { - this.alias = alias; - return this; - } - - /** Provide IDs of webhooks to forward the API response to. */ - public Builder webhookIds(String[] webhookIds) { - this.webhookIds = webhookIds; - return this; - } - /** Provide additional text context used by the model during inference. */ public Builder textContext(String textContext) { this.textContext = textContext; @@ -164,12 +148,6 @@ public Builder dataSchema(String dataSchema) { return this; } - /** Set polling options. */ - public Builder pollingOptions(AsyncPollingOptions pollingOptions) { - this.pollingOptions = pollingOptions; - return this; - } - /** Build an immutable {@link InferenceParameters} instance. */ public InferenceParameters build() { return new InferenceParameters( diff --git a/src/main/java/com/mindee/MindeeClientV2.java b/src/main/java/com/mindee/MindeeClientV2.java index 808e0f393..9dc2f23f3 100644 --- a/src/main/java/com/mindee/MindeeClientV2.java +++ b/src/main/java/com/mindee/MindeeClientV2.java @@ -142,7 +142,7 @@ public TResponse enqueueAndGetResult( LocalInputSource inputSource, BaseParameters params ) throws IOException, InterruptedException { - validatePollingOptions(params.getPollingOptions()); + params.validatePollingOptions(); JobResponse job = enqueue(inputSource, params); return pollAndFetch(responseClass, job, params); } @@ -161,7 +161,7 @@ public TResponse enqueueAndGetResult( URLInputSource inputSource, BaseParameters params ) throws IOException, InterruptedException { - validatePollingOptions(params.getPollingOptions()); + params.validatePollingOptions(); JobResponse job = enqueue(inputSource, params); return pollAndFetch(responseClass, job, params); } @@ -210,16 +210,4 @@ private static MindeeApiV2 createDefaultApiV2(String apiKey) { : new MindeeSettingsV2(apiKey); return MindeeHttpApiV2.builder().mindeeSettings(settings).build(); } - - private static void validatePollingOptions(AsyncPollingOptions p) { - if (p.getInitialDelaySec() < 1) { - throw new IllegalArgumentException("Initial delay must be ≥ 1 s"); - } - if (p.getIntervalSec() < 1) { - throw new IllegalArgumentException("Interval must be ≥ 1 s"); - } - if (p.getMaxRetries() < 2) { - throw new IllegalArgumentException("Max retries must be ≥ 2"); - } - } } diff --git a/src/main/java/com/mindee/http/MindeeApiV2.java b/src/main/java/com/mindee/http/MindeeApiV2.java index 35e8d3a7c..c93db9791 100644 --- a/src/main/java/com/mindee/http/MindeeApiV2.java +++ b/src/main/java/com/mindee/http/MindeeApiV2.java @@ -1,11 +1,13 @@ package com.mindee.http; +import com.mindee.MindeeException; import com.mindee.input.LocalInputSource; import com.mindee.input.URLInputSource; import com.mindee.parsing.v2.CommonResponse; import com.mindee.parsing.v2.ErrorResponse; import com.mindee.parsing.v2.JobResponse; import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; import java.io.IOException; /** @@ -63,4 +65,24 @@ protected ErrorResponse makeUnknownError(int statusCode) { null ); } + + protected ProductInfo getResponseProductInfo(Class responseClass) { + ProductInfo productInfo = responseClass.getAnnotation(ProductInfo.class); + if (productInfo == null) { + throw new MindeeException( + "The class " + responseClass.getSimpleName() + " is not annotated with @ProductInfo" + ); + } + return productInfo; + } + + protected ProductInfo getParamsProductInfo(Class responseClass) { + ProductInfo productInfo = responseClass.getAnnotation(ProductInfo.class); + if (productInfo == null) { + throw new MindeeException( + "The class " + responseClass.getSimpleName() + " is not annotated with @ProductInfo" + ); + } + return productInfo; + } } diff --git a/src/main/java/com/mindee/http/MindeeHttpApiV2.java b/src/main/java/com/mindee/http/MindeeHttpApiV2.java index 9ac421c1d..735a7d63c 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApiV2.java +++ b/src/main/java/com/mindee/http/MindeeHttpApiV2.java @@ -9,6 +9,7 @@ import com.mindee.parsing.v2.ErrorResponse; import com.mindee.parsing.v2.JobResponse; import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; @@ -68,8 +69,9 @@ private MindeeHttpApiV2(MindeeSettingsV2 mindeeSettings, HttpClientBuilder httpC */ @Override public JobResponse reqPostEnqueue(LocalInputSource inputSource, BaseParameters options) { + ProductInfo productInfo = getParamsProductInfo(options.getClass()); String url = String - .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), options.getSlug()); + .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), productInfo.slug()); HttpPost post = buildHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -94,8 +96,9 @@ public JobResponse reqPostEnqueue(LocalInputSource inputSource, BaseParameters o */ @Override public JobResponse reqPostEnqueue(URLInputSource inputSource, BaseParameters options) { + ProductInfo productInfo = getParamsProductInfo(options.getClass()); String url = String - .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), options.getSlug()); + .format("%s/products/%s/enqueue", this.mindeeSettings.getBaseUrl(), productInfo.slug()); HttpPost post = buildHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -172,8 +175,14 @@ public TResponse reqGetResult( Class responseClass, String inferenceId ) { - - String url = this.mindeeSettings.getBaseUrl() + "/products/extraction/results/" + inferenceId; + ProductInfo productInfo = getResponseProductInfo(responseClass); + String url = String + .format( + "%s/products/%s/results/%s", + this.mindeeSettings.getBaseUrl(), + productInfo.slug(), + inferenceId + ); HttpGet get = new HttpGet(url); if (this.mindeeSettings.getApiKey().isPresent()) { diff --git a/src/main/java/com/mindee/parsing/v2/InferenceResponse.java b/src/main/java/com/mindee/parsing/v2/InferenceResponse.java index 5280ae3ca..a68de9a4a 100644 --- a/src/main/java/com/mindee/parsing/v2/InferenceResponse.java +++ b/src/main/java/com/mindee/parsing/v2/InferenceResponse.java @@ -1,12 +1,16 @@ package com.mindee.parsing.v2; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.mindee.v2.http.ProductInfo; import lombok.Getter; /** * Response for an extraction inference. */ @Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@ProductInfo(slug = "extraction") public class InferenceResponse extends CommonResponse { /** diff --git a/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java b/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java index 38c861f17..7d38c309f 100644 --- a/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java +++ b/src/main/java/com/mindee/v2/clientOptions/BaseParameters.java @@ -1,6 +1,7 @@ package com.mindee.v2.clientOptions; import com.mindee.AsyncPollingOptions; +import java.util.Objects; import lombok.Data; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; @@ -23,10 +24,6 @@ public abstract class BaseParameters { * Polling options. Set only if having timeout issues. */ protected final AsyncPollingOptions pollingOptions; - /** - * Slug of the product type. - */ - private final String slug; public MultipartEntityBuilder buildHttpBody(MultipartEntityBuilder builder) { builder.addTextBody("model_id", this.getModelId()); @@ -38,4 +35,51 @@ public MultipartEntityBuilder buildHttpBody(MultipartEntityBuilder builder) { } return builder; } + + public void validatePollingOptions() { + if (pollingOptions.getInitialDelaySec() < 1) { + throw new IllegalArgumentException("Initial delay must be ≥ 1 s"); + } + if (pollingOptions.getIntervalSec() < 1) { + throw new IllegalArgumentException("Interval must be ≥ 1 s"); + } + if (pollingOptions.getMaxRetries() < 2) { + throw new IllegalArgumentException("Max retries must be ≥ 2"); + } + } + + protected static abstract class BaseBuilder> { + protected final String modelId; + protected String alias; + protected String[] webhookIds = new String[] {}; + protected AsyncPollingOptions pollingOptions = AsyncPollingOptions.builder().build(); + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + + protected BaseBuilder(String modelId) { + this.modelId = Objects.requireNonNull(modelId, "modelId must not be null"); + } + + /** Set an alias for the uploaded document. */ + public T alias(String alias) { + this.alias = alias; + return self(); + } + + /** Provide IDs of webhooks to forward the API response to. */ + public T webhookIds(String[] webhookIds) { + this.webhookIds = webhookIds; + return self(); + } + + /** Set polling options. */ + public T pollingOptions(AsyncPollingOptions pollingOptions) { + this.pollingOptions = pollingOptions; + return self(); + } + } + } diff --git a/src/main/java/com/mindee/v2/http/ProductInfo.java b/src/main/java/com/mindee/v2/http/ProductInfo.java new file mode 100644 index 000000000..05057a265 --- /dev/null +++ b/src/main/java/com/mindee/v2/http/ProductInfo.java @@ -0,0 +1,17 @@ +package com.mindee.v2.http; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Base info for all v2 products. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ProductInfo { + String slug(); +} diff --git a/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java b/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java index f151bbd67..e21c33ab5 100644 --- a/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java +++ b/src/main/java/com/mindee/v2/product/classification/ClassificationResponse.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.mindee.parsing.v2.CommonResponse; +import com.mindee.v2.http.ProductInfo; import lombok.Getter; /** @@ -10,6 +11,7 @@ */ @Getter @JsonIgnoreProperties(ignoreUnknown = true) +@ProductInfo(slug = "classification") public class ClassificationResponse extends CommonResponse { /** diff --git a/src/main/java/com/mindee/v2/product/crop/CropResponse.java b/src/main/java/com/mindee/v2/product/crop/CropResponse.java index a2c0d314c..7d39b2cb9 100644 --- a/src/main/java/com/mindee/v2/product/crop/CropResponse.java +++ b/src/main/java/com/mindee/v2/product/crop/CropResponse.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.mindee.parsing.v2.CommonResponse; +import com.mindee.v2.http.ProductInfo; import lombok.Getter; /** @@ -10,6 +11,7 @@ */ @Getter @JsonIgnoreProperties(ignoreUnknown = true) +@ProductInfo(slug = "crop") public class CropResponse extends CommonResponse { /** diff --git a/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java b/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java index 7e66c46c7..36dc333a6 100644 --- a/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java +++ b/src/main/java/com/mindee/v2/product/ocr/OcrResponse.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.mindee.parsing.v2.CommonResponse; +import com.mindee.v2.http.ProductInfo; import lombok.Getter; /** @@ -10,6 +11,7 @@ */ @Getter @JsonIgnoreProperties(ignoreUnknown = true) +@ProductInfo(slug = "ocr") public class OcrResponse extends CommonResponse { /** diff --git a/src/main/java/com/mindee/v2/product/split/SplitResponse.java b/src/main/java/com/mindee/v2/product/split/SplitResponse.java index 8204c9d15..248612323 100644 --- a/src/main/java/com/mindee/v2/product/split/SplitResponse.java +++ b/src/main/java/com/mindee/v2/product/split/SplitResponse.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.mindee.parsing.v2.CommonResponse; +import com.mindee.v2.http.ProductInfo; import lombok.Getter; /** @@ -10,6 +11,7 @@ */ @Getter @JsonIgnoreProperties(ignoreUnknown = true) +@ProductInfo(slug = "split") public class SplitResponse extends CommonResponse { /** diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java index 86650ef89..c96225be5 100644 --- a/src/test/java/com/mindee/MindeeClientV2IT.java +++ b/src/test/java/com/mindee/MindeeClientV2IT.java @@ -30,7 +30,7 @@ class MindeeClientV2IT { @BeforeAll void setUp() { String apiKey = System.getenv("MINDEE_V2_API_KEY"); - modelId = System.getenv("MINDEE_V2_FINDOC_MODEL_ID"); + modelId = System.getenv("MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID"); mindeeClient = new MindeeClientV2(apiKey); } @@ -212,7 +212,7 @@ void invalidJob_mustThrowError() { } @Test - @DisplayName("URL input source - A url param should not raise errors.") + @DisplayName("URL input source - A URL param should not raise errors.") void urlInputSource_mustNotRaiseErrors() throws IOException, InterruptedException { URLInputSource urlSource = URLInputSource .builder(System.getenv("MINDEE_V2_SE_TESTS_BLANK_PDF_URL")) From 36fbe6ab5053c9ef7747af2fccb10957eb171630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Fri, 13 Mar 2026 14:30:20 +0100 Subject: [PATCH 09/12] add utility calls --- .github/workflows/_test-code-samples.yml | 1 + .github/workflows/_test-integrations.yml | 17 +++-- docs/code_samples/v2_classification.txt | 46 +++++++++++++ docs/code_samples/v2_crop.txt | 44 ++++++++++++ docs/code_samples/v2_extraction.txt | 4 ++ docs/code_samples/v2_ocr.txt | 44 ++++++++++++ docs/code_samples/v2_split.txt | 44 ++++++++++++ .../params/ClassificationParameters.java | 39 +++++++++++ .../product/crop/params/CropParameters.java | 40 +++++++++++ .../v2/product/ocr/params/OcrParameters.java | 40 +++++++++++ .../product/split/params/SplitParameters.java | 40 +++++++++++ .../java/com/mindee/v2/product/CropIT.java | 68 ++++++++++++++++++ .../java/com/mindee/v2/product/SplitIT.java | 69 +++++++++++++++++++ tests/test_v2_code_samples.sh | 10 +-- 14 files changed, 495 insertions(+), 11 deletions(-) create mode 100644 docs/code_samples/v2_classification.txt create mode 100644 docs/code_samples/v2_crop.txt create mode 100644 docs/code_samples/v2_ocr.txt create mode 100644 docs/code_samples/v2_split.txt create mode 100644 src/main/java/com/mindee/v2/product/classification/params/ClassificationParameters.java create mode 100644 src/main/java/com/mindee/v2/product/crop/params/CropParameters.java create mode 100644 src/main/java/com/mindee/v2/product/ocr/params/OcrParameters.java create mode 100644 src/main/java/com/mindee/v2/product/split/params/SplitParameters.java create mode 100644 src/test/java/com/mindee/v2/product/CropIT.java create mode 100644 src/test/java/com/mindee/v2/product/SplitIT.java diff --git a/.github/workflows/_test-code-samples.yml b/.github/workflows/_test-code-samples.yml index 896227d18..bf5dcac2c 100644 --- a/.github/workflows/_test-code-samples.yml +++ b/.github/workflows/_test-code-samples.yml @@ -12,6 +12,7 @@ env: MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} MINDEE_V2_SE_TESTS_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} + MINDEE_V2_SE_TESTS_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} jobs: test_sample_code: diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index d494c57d4..1d191603a 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -4,6 +4,17 @@ on: workflow_call: workflow_dispatch: +env: + MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} + WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} + MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} + MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} + MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} + MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} + MINDEE_V2_SE_TESTS_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} + MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} + MINDEE_V2_SE_TESTS_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} + jobs: integration_tests: name: Run Integration Tests @@ -31,11 +42,5 @@ jobs: cache: "maven" - name: Verify with Maven - env: - MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} - WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} - MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} - MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} - MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} run: | mvn clean test-compile failsafe:integration-test failsafe:verify diff --git a/docs/code_samples/v2_classification.txt b/docs/code_samples/v2_classification.txt new file mode 100644 index 000000000..6ddfb4967 --- /dev/null +++ b/docs/code_samples/v2_classification.txt @@ -0,0 +1,46 @@ +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.v2.product.classification.ClassificationClassifier; +import com.mindee.v2.product.classification.ClassificationResponse; +import com.mindee.v2.product.classification.ClassificationResult; +import com.mindee.v2.product.classification.params.ClassificationParameters; +import java.io.File; +import java.io.IOException; + +public class SimpleMindeeClientV2 { + + public static void main(String[] args) + throws IOException, InterruptedException + { + String apiKey = "MY_API_KEY"; + String filePath = "/path/to/the/file.ext"; + String modelId = "MY_MODEL_ID"; + + // Init a new client + MindeeClientV2 mindeeClient = new MindeeClientV2(apiKey); + + // Set inference parameters + // Note: modelId is mandatory. + ClassificationParameters classificationParams = ClassificationParameters + // ID of the model, required. + .builder(modelId) + .build(); + + // Load a file from disk + LocalInputSource inputSource = new LocalInputSource(filePath); + + // Send for processing + ClassificationResponse response = mindeeClient.enqueueAndGetResult( + ClassificationResponse.class, + inputSource, + classificationParams + ); + + // Print a summary of the response + System.out.println(response.getInference().toString()); + + // Access the classification result + ClassificationResult result = response.getInference().getResult(); + ClassificationClassifier classification = result.getClassification(); + } +} diff --git a/docs/code_samples/v2_crop.txt b/docs/code_samples/v2_crop.txt new file mode 100644 index 000000000..97242b00b --- /dev/null +++ b/docs/code_samples/v2_crop.txt @@ -0,0 +1,44 @@ +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.v2.product.crop.CropResponse; +import com.mindee.v2.product.crop.CropResult; +import com.mindee.v2.product.crop.params.CropParameters; +import java.io.File; +import java.io.IOException; + +public class SimpleMindeeClientV2 { + + public static void main(String[] args) + throws IOException, InterruptedException + { + String apiKey = "MY_API_KEY"; + String filePath = "/path/to/the/file.ext"; + String modelId = "MY_MODEL_ID"; + + // Init a new client + MindeeClientV2 mindeeClient = new MindeeClientV2(apiKey); + + // Set inference parameters + // Note: modelId is mandatory. + CropParameters cropParams = CropParameters + // ID of the model, required. + .builder(modelId) + .build(); + + // Load a file from disk + LocalInputSource inputSource = new LocalInputSource(filePath); + + // Send for processing + CropResponse response = mindeeClient.enqueueAndGetResult( + CropResponse.class, + inputSource, + cropParams + ); + + // Print a summary of the response + System.out.println(response.getInference().toString()); + + // Access the crop results + CropResult result = response.getInference().getResult(); + } +} diff --git a/docs/code_samples/v2_extraction.txt b/docs/code_samples/v2_extraction.txt index bdd711c3e..4f6dce58d 100644 --- a/docs/code_samples/v2_extraction.txt +++ b/docs/code_samples/v2_extraction.txt @@ -2,6 +2,7 @@ import com.mindee.MindeeClientV2; import com.mindee.InferenceParameters; import com.mindee.input.LocalInputSource; import com.mindee.parsing.v2.InferenceResponse; +import com.mindee.parsing.v2.InferenceResult; import java.io.IOException; public class SimpleMindeeClientV2 { @@ -48,5 +49,8 @@ public class SimpleMindeeClientV2 { // Print a summary of the response System.out.println(response.getInference().toString()); + + // Access the result fields + InferenceResult result = response.getInference().getResult(); } } diff --git a/docs/code_samples/v2_ocr.txt b/docs/code_samples/v2_ocr.txt new file mode 100644 index 000000000..a6cdcd771 --- /dev/null +++ b/docs/code_samples/v2_ocr.txt @@ -0,0 +1,44 @@ +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.v2.product.ocr.OcrResponse; +import com.mindee.v2.product.ocr.OcrResult; +import com.mindee.v2.product.ocr.params.OcrParameters; +import java.io.File; +import java.io.IOException; + +public class SimpleMindeeClientV2 { + + public static void main(String[] args) + throws IOException, InterruptedException + { + String apiKey = "MY_API_KEY"; + String filePath = "/path/to/the/file.ext"; + String modelId = "MY_MODEL_ID"; + + // Init a new client + MindeeClientV2 mindeeClient = new MindeeClientV2(apiKey); + + // Set inference parameters + // Note: modelId is mandatory. + OcrParameters ocrParams = OcrParameters + // ID of the model, required. + .builder(modelId) + .build(); + + // Load a file from disk + LocalInputSource inputSource = new LocalInputSource(filePath); + + // Send for processing + OcrResponse response = mindeeClient.enqueueAndGetResult( + OcrResponse.class, + inputSource, + ocrParams + ); + + // Print a summary of the response + System.out.println(response.getInference().toString()); + + // Access the result OCR pages + OcrResult result = response.getInference().getResult(); + } +} diff --git a/docs/code_samples/v2_split.txt b/docs/code_samples/v2_split.txt new file mode 100644 index 000000000..cd9b56b8f --- /dev/null +++ b/docs/code_samples/v2_split.txt @@ -0,0 +1,44 @@ +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.v2.product.split.SplitResponse; +import com.mindee.v2.product.split.SplitResult; +import com.mindee.v2.product.split.params.SplitParameters; +import java.io.File; +import java.io.IOException; + +public class SimpleMindeeClientV2 { + + public static void main(String[] args) + throws IOException, InterruptedException + { + String apiKey = "MY_API_KEY"; + String filePath = "/path/to/the/file.ext"; + String modelId = "MY_MODEL_ID"; + + // Init a new client + MindeeClientV2 mindeeClient = new MindeeClientV2(apiKey); + + // Set inference parameters + // Note: modelId is mandatory. + SplitParameters splitParams = SplitParameters + // ID of the model, required. + .builder(modelId) + .build(); + + // Load a file from disk + LocalInputSource inputSource = new LocalInputSource(filePath); + + // Send for processing + SplitResponse response = mindeeClient.enqueueAndGetResult( + SplitResponse.class, + inputSource, + splitParams + ); + + // Print a summary of the response + System.out.println(response.getInference().toString()); + + // Access the split result + SplitResult result = response.getInference().getResult(); + } +} diff --git a/src/main/java/com/mindee/v2/product/classification/params/ClassificationParameters.java b/src/main/java/com/mindee/v2/product/classification/params/ClassificationParameters.java new file mode 100644 index 000000000..7fa37267e --- /dev/null +++ b/src/main/java/com/mindee/v2/product/classification/params/ClassificationParameters.java @@ -0,0 +1,39 @@ +package com.mindee.v2.product.classification.params; + +import com.mindee.AsyncPollingOptions; +import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; + +@ProductInfo(slug = "classification") +public class ClassificationParameters extends BaseParameters { + public ClassificationParameters( + String modelId, + String alias, + String[] webhookIds, + AsyncPollingOptions pollingOptions + ) { + super(modelId, alias, webhookIds, pollingOptions); + } + + /** + * Create a new builder. + * + * @param modelId the mandatory model identifier + * @return a fresh {@link ClassificationParameters.Builder} + */ + public static Builder builder(String modelId) { + return new Builder(modelId); + } + + public static final class Builder extends BaseParameters.BaseBuilder { + + Builder(String modelId) { + super(modelId); + } + + /** Build an immutable {@link ClassificationParameters} instance. */ + public ClassificationParameters build() { + return new ClassificationParameters(modelId, alias, webhookIds, pollingOptions); + } + } +} diff --git a/src/main/java/com/mindee/v2/product/crop/params/CropParameters.java b/src/main/java/com/mindee/v2/product/crop/params/CropParameters.java new file mode 100644 index 000000000..0daa2c6fe --- /dev/null +++ b/src/main/java/com/mindee/v2/product/crop/params/CropParameters.java @@ -0,0 +1,40 @@ +package com.mindee.v2.product.crop.params; + +import com.mindee.AsyncPollingOptions; +import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; + +@ProductInfo(slug = "crop") +public class CropParameters extends BaseParameters { + + public CropParameters( + String modelId, + String alias, + String[] webhookIds, + AsyncPollingOptions pollingOptions + ) { + super(modelId, alias, webhookIds, pollingOptions); + } + + /** + * Create a new builder. + * + * @param modelId the mandatory model identifier + * @return a fresh {@link CropParameters.Builder} + */ + public static Builder builder(String modelId) { + return new Builder(modelId); + } + + public static final class Builder extends BaseParameters.BaseBuilder { + + Builder(String modelId) { + super(modelId); + } + + /** Build an immutable {@link CropParameters} instance. */ + public CropParameters build() { + return new CropParameters(modelId, alias, webhookIds, pollingOptions); + } + } +} diff --git a/src/main/java/com/mindee/v2/product/ocr/params/OcrParameters.java b/src/main/java/com/mindee/v2/product/ocr/params/OcrParameters.java new file mode 100644 index 000000000..0ab4a11a5 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/ocr/params/OcrParameters.java @@ -0,0 +1,40 @@ +package com.mindee.v2.product.ocr.params; + +import com.mindee.AsyncPollingOptions; +import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; + +@ProductInfo(slug = "ocr") +public class OcrParameters extends BaseParameters { + + public OcrParameters( + String modelId, + String alias, + String[] webhookIds, + AsyncPollingOptions pollingOptions + ) { + super(modelId, alias, webhookIds, pollingOptions); + } + + /** + * Create a new builder. + * + * @param modelId the mandatory model identifier + * @return a fresh {@link OcrParameters.Builder} + */ + public static Builder builder(String modelId) { + return new Builder(modelId); + } + + public static final class Builder extends BaseParameters.BaseBuilder { + + Builder(String modelId) { + super(modelId); + } + + /** Build an immutable {@link OcrParameters} instance. */ + public OcrParameters build() { + return new OcrParameters(modelId, alias, webhookIds, pollingOptions); + } + } +} diff --git a/src/main/java/com/mindee/v2/product/split/params/SplitParameters.java b/src/main/java/com/mindee/v2/product/split/params/SplitParameters.java new file mode 100644 index 000000000..4624b3177 --- /dev/null +++ b/src/main/java/com/mindee/v2/product/split/params/SplitParameters.java @@ -0,0 +1,40 @@ +package com.mindee.v2.product.split.params; + +import com.mindee.AsyncPollingOptions; +import com.mindee.v2.clientOptions.BaseParameters; +import com.mindee.v2.http.ProductInfo; + +@ProductInfo(slug = "split") +public class SplitParameters extends BaseParameters { + + public SplitParameters( + String modelId, + String alias, + String[] webhookIds, + AsyncPollingOptions pollingOptions + ) { + super(modelId, alias, webhookIds, pollingOptions); + } + + /** + * Create a new builder. + * + * @param modelId the mandatory model identifier + * @return a fresh {@link SplitParameters.Builder} + */ + public static Builder builder(String modelId) { + return new Builder(modelId); + } + + public static final class Builder extends BaseParameters.BaseBuilder { + + Builder(String modelId) { + super(modelId); + } + + /** Build an immutable {@link SplitParameters} instance. */ + public SplitParameters build() { + return new SplitParameters(modelId, alias, webhookIds, pollingOptions); + } + } +} diff --git a/src/test/java/com/mindee/v2/product/CropIT.java b/src/test/java/com/mindee/v2/product/CropIT.java new file mode 100644 index 000000000..f4d445967 --- /dev/null +++ b/src/test/java/com/mindee/v2/product/CropIT.java @@ -0,0 +1,68 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.getResourcePath; +import static org.junit.jupiter.api.Assertions.*; + +import com.mindee.AsyncPollingOptions; +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.parsing.v2.InferenceFile; +import com.mindee.v2.product.crop.CropInference; +import com.mindee.v2.product.crop.CropResponse; +import com.mindee.v2.product.crop.CropResult; +import com.mindee.v2.product.crop.params.CropParameters; +import java.io.IOException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Tag("integration") +@DisplayName("MindeeV2 –Integration Tests - Crop") +class CropIT { + + private MindeeClientV2 mindeeClient; + private String modelId; + + @BeforeAll + void setUp() { + String apiKey = System.getenv("MINDEE_V2_API_KEY"); + modelId = System.getenv("MINDEE_V2_SE_TESTS_CROP_MODEL_ID"); + mindeeClient = new MindeeClientV2(apiKey); + } + + @Test + @DisplayName("Empty, multi-page PDF – enqueue & parse must succeed") + void parseFile_emptyMultiPage_mustSucceed() throws IOException, InterruptedException { + LocalInputSource source = new LocalInputSource( + getResourcePath("file_types/pdf/multipage_cut-2.pdf") + ); + CropParameters params = CropParameters + .builder(modelId) + .alias("java_integration-test_crop_multipage") + .pollingOptions( + AsyncPollingOptions.builder().initialDelaySec(3.0).intervalSec(1.5).maxRetries(80).build() + ) + .build(); + + CropResponse response = mindeeClient.enqueueAndGetResult(CropResponse.class, source, params); + assertNotNull(response); + CropInference inference = response.getInference(); + assertNotNull(inference); + + InferenceFile file = inference.getFile(); + assertNotNull(file); + assertEquals("multipage_cut-2.pdf", file.getName()); + assertEquals(2, file.getPageCount()); + + assertNotNull(inference.getModel()); + assertEquals(modelId, inference.getModel().getId()); + + CropResult result = inference.getResult(); + assertNotNull(result); + assertEquals(1, result.getCrops().size()); + assertEquals("other", result.getCrops().get(0).getObjectType()); + } +} diff --git a/src/test/java/com/mindee/v2/product/SplitIT.java b/src/test/java/com/mindee/v2/product/SplitIT.java new file mode 100644 index 000000000..ce897416a --- /dev/null +++ b/src/test/java/com/mindee/v2/product/SplitIT.java @@ -0,0 +1,69 @@ +package com.mindee.v2.product; + +import static com.mindee.TestingUtilities.getResourcePath; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.mindee.AsyncPollingOptions; +import com.mindee.MindeeClientV2; +import com.mindee.input.LocalInputSource; +import com.mindee.parsing.v2.InferenceFile; +import com.mindee.v2.product.split.SplitInference; +import com.mindee.v2.product.split.SplitResponse; +import com.mindee.v2.product.split.SplitResult; +import com.mindee.v2.product.split.params.SplitParameters; +import java.io.IOException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Tag("integration") +@DisplayName("MindeeV2 –Integration Tests - Split") +class SplitIT { + + private MindeeClientV2 mindeeClient; + private String modelId; + + @BeforeAll + void setUp() { + String apiKey = System.getenv("MINDEE_V2_API_KEY"); + modelId = System.getenv("MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID"); + mindeeClient = new MindeeClientV2(apiKey); + } + + @Test + @DisplayName("Empty, multi-page PDF – enqueue & parse must succeed") + void parseFile_emptyMultiPage_mustSucceed() throws IOException, InterruptedException { + LocalInputSource source = new LocalInputSource( + getResourcePath("file_types/pdf/multipage_cut-2.pdf") + ); + SplitParameters params = SplitParameters + .builder(modelId) + .alias("java_integration-test_split_multipage") + .pollingOptions( + AsyncPollingOptions.builder().initialDelaySec(3.0).intervalSec(1.5).maxRetries(80).build() + ) + .build(); + + SplitResponse response = mindeeClient.enqueueAndGetResult(SplitResponse.class, source, params); + assertNotNull(response); + SplitInference inference = response.getInference(); + assertNotNull(inference); + + InferenceFile file = inference.getFile(); + assertNotNull(file); + assertEquals("multipage_cut-2.pdf", file.getName()); + assertEquals(2, file.getPageCount()); + + assertNotNull(inference.getModel()); + assertEquals(modelId, inference.getModel().getId()); + + SplitResult result = inference.getResult(); + assertNotNull(result); + assertTrue(result.getSplits().isEmpty()); + } +} diff --git a/tests/test_v2_code_samples.sh b/tests/test_v2_code_samples.sh index 5c030cc23..6920a93e0 100755 --- a/tests/test_v2_code_samples.sh +++ b/tests/test_v2_code_samples.sh @@ -18,27 +18,27 @@ do sed "s/MY_API_KEY/${MINDEE_V2_API_KEY}/" "${f}" > $OUTPUT_FILE sed -i "s/\/path\/to\/the\/file.ext/src\/test\/resources\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE - if echo "${f}" | grep -q "v2_classification.txt" + if echo "${f}" | grep -q "v2_classification" then sed -i "s/MY_MODEL_ID/${MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID}/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "v2_crop.txt" + if echo "${f}" | grep -q "v2_crop" then sed -i "s/MY_MODEL_ID/${MINDEE_V2_SE_TESTS_CROP_MODEL_ID}/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "v2_extraction.txt" + if echo "${f}" | grep -q "v2_extraction" then sed -i "s/MY_MODEL_ID/${MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID}/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "v2_ocr.txt" + if echo "${f}" | grep -q "v2_ocr" then sed -i "s/MY_MODEL_ID/${MINDEE_V2_SE_TESTS_OCR_MODEL_ID}/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "v2_split.txt" + if echo "${f}" | grep -q "v2_split" then sed -i "s/MY_MODEL_ID/${MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID}/" $OUTPUT_FILE fi From fb6b87453e0ac32ee7e77ca1f39633cb3fb25449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Mon, 16 Mar 2026 10:20:31 +0100 Subject: [PATCH 10/12] enable annotation processor --- tests/test_v2_code_samples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_v2_code_samples.sh b/tests/test_v2_code_samples.sh index 6920a93e0..22dd7bea2 100755 --- a/tests/test_v2_code_samples.sh +++ b/tests/test_v2_code_samples.sh @@ -44,7 +44,7 @@ do fi sleep 0.5 # avoid too many request errors - javac -cp ./target/dependency/*:./target/* "${OUTPUT_FILE}" + javac -proc:full -cp ./target/dependency/*:./target/* "${OUTPUT_FILE}" java -cp .:./target/dependency/*:./target/* SimpleMindeeClientV2 done From b9743d8dcaa94fe8ab3e22cddcad6bbe33dd400a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Mon, 16 Mar 2026 11:30:38 +0100 Subject: [PATCH 11/12] update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cbad0e621..d7fac2370 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ src-lomboked # test jshell file _test.jsh -SimpleMindeeClient.java +SimpleMindeeClientV* # Compiled class file *.class From ae3d9264afd0290f9c8723fc509292766eb1b2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Mon, 16 Mar 2026 12:01:55 +0100 Subject: [PATCH 12/12] fix for java 8 --- tests/test_v2_code_samples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_v2_code_samples.sh b/tests/test_v2_code_samples.sh index 22dd7bea2..f847d75e4 100755 --- a/tests/test_v2_code_samples.sh +++ b/tests/test_v2_code_samples.sh @@ -44,7 +44,7 @@ do fi sleep 0.5 # avoid too many request errors - javac -proc:full -cp ./target/dependency/*:./target/* "${OUTPUT_FILE}" + javac -Xlint:-options -cp ./target/dependency/*:./target/* "${OUTPUT_FILE}" java -cp .:./target/dependency/*:./target/* SimpleMindeeClientV2 done