diff --git a/.github/workflows/_test-code-samples.yml b/.github/workflows/_test-code-samples.yml deleted file mode 100644 index 62a04106..00000000 --- a/.github/workflows/_test-code-samples.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test Code Samples - -on: - workflow_call: - workflow_dispatch: - -jobs: - test: - name: Run Code Samples - timeout-minutes: 30 - strategy: - max-parallel: 2 - matrix: - php-version: - - "8.1" - - "8.4" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up Php ${{ matrix.php-version }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - uses: ramsey/composer-install@v2 - - - name: Tests code samples - env: - MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} - run: | - ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 79bbbcee..3273777f 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -13,8 +13,13 @@ 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_FAILURE_WEBHOOK_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FAILURE_WEBHOOK_ID }} MINDEE_V2_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 }} + MINDEE_V2_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} + MINDEE_V2_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} + MINDEE_V2_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} + MINDEE_V2_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} jobs: integration-tests-ubuntu: @@ -25,7 +30,7 @@ jobs: matrix: php-version: - 8.1 - - 8.4 + - 8.5 runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 @@ -60,7 +65,7 @@ jobs: # matrix: # php-version: # - 8.1 -# - 8.4 +# - 8.5 # runs-on: "macos-latest" # steps: # - uses: actions/checkout@v4 @@ -91,7 +96,7 @@ jobs: matrix: php-version: - 8.1 - - 8.4 + - 8.5 runs-on: "windows-latest" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml new file mode 100644 index 00000000..4fe960f6 --- /dev/null +++ b/.github/workflows/_test-smoke.yml @@ -0,0 +1,48 @@ +name: Smoke tests + +on: + workflow_call: + workflow_dispatch: + +env: + MINDEE_ACCOUNT_SE_TESTS: ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} + MINDEE_ENDPOINT_SE_TESTS: ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} + 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_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} + MINDEE_V2_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} + MINDEE_V2_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} + MINDEE_V2_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} + MINDEE_V2_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} + +jobs: + test: + name: Run Smoke Tests + timeout-minutes: 30 + strategy: + max-parallel: 2 + matrix: + php-version: + - "8.1" + - "8.5" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Php ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + - uses: ramsey/composer-install@v2 + + - name: Tests V2 code samples + run: | + ./tests/test_code_samples_v2.sh + + - name: Tests V1 code samples + run: | + ./tests/test_code_samples_v1.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} diff --git a/.github/workflows/_test-units.yml b/.github/workflows/_test-units.yml index 32d2f52d..befbc73e 100644 --- a/.github/workflows/_test-units.yml +++ b/.github/workflows/_test-units.yml @@ -14,6 +14,7 @@ jobs: - 8.2 - 8.3 - 8.4 + - 8.5 runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index ce38faf7..a306b37c 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -5,6 +5,6 @@ on: - cron: '42 0 * * *' jobs: - test-code-samples: - uses: mindee/mindee-api-php/.github/workflows/_test-code-samples.yml@main + smoke-test: + uses: mindee/mindee-api-php/.github/workflows/_test-smoke.yml@main secrets: inherit diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 72e8e32b..0fdf33b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -21,7 +21,7 @@ jobs: uses: ./.github/workflows/_test-integrations.yml needs: test-units secrets: inherit - test-code-samples: - uses: ./.github/workflows/_test-code-samples.yml + smoke-test: + uses: ./.github/workflows/_test-smoke.yml needs: test-units secrets: inherit diff --git a/docs/code_samples/v2_classification.txt b/docs/code_samples/v2_classification.txt new file mode 100644 index 00000000..dffea452 --- /dev/null +++ b/docs/code_samples/v2_classification.txt @@ -0,0 +1,36 @@ +enqueueAndGetResult( + ClassificationResponse::class, + $inputSource, + $classificationParams +); + +// Print a summary of the response +echo strval($response->inference); + +// Access the classification results +$classification = $response->inference->result->classification; diff --git a/docs/code_samples/v2_crop.txt b/docs/code_samples/v2_crop.txt new file mode 100644 index 00000000..2d050c32 --- /dev/null +++ b/docs/code_samples/v2_crop.txt @@ -0,0 +1,36 @@ +enqueueAndGetResult( + CropResponse::class, + $inputSource, + $cropParams +); + +// Print a summary of the response +echo strval($response->inference); + +// Access the crop results +$crops = $response->inference->result->crops; diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/v2_extraction_polling.txt similarity index 93% rename from docs/code_samples/default_v2.txt rename to docs/code_samples/v2_extraction_polling.txt index 96283302..047e07b7 100644 --- a/docs/code_samples/default_v2.txt +++ b/docs/code_samples/v2_extraction_polling.txt @@ -41,3 +41,6 @@ $response = $mindeeClient->enqueueAndGetInference( // Print a summary of the response echo strval($response->inference); + +// Access the extracted fields +$fields = $response->inference->result->fields; diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt new file mode 100644 index 00000000..c5046c56 --- /dev/null +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -0,0 +1,46 @@ +enqueue( + $inputSource, + $inferenceParams +); + +// Print the job ID +echo $response->job->id; + +// IMPORTANT: save a record of which job ID corresponds to which file. diff --git a/docs/code_samples/v2_ocr.txt b/docs/code_samples/v2_ocr.txt new file mode 100644 index 00000000..22af46b8 --- /dev/null +++ b/docs/code_samples/v2_ocr.txt @@ -0,0 +1,36 @@ +enqueueAndGetResult( + OcrResponse::class, + $inputSource, + $ocrParams +); + +// Print a summary of the response +echo strval($response->inference); + +// Access the ocr results +$pages = $response->inference->result->pages; diff --git a/docs/code_samples/v2_split.txt b/docs/code_samples/v2_split.txt new file mode 100644 index 00000000..92f3f56e --- /dev/null +++ b/docs/code_samples/v2_split.txt @@ -0,0 +1,36 @@ +enqueueAndGetResult( + SplitResponse::class, + $inputSource, + $splitParams +); + +// Print a summary of the response +echo strval($response->inference); + +// Access the split results +$splits = $response->inference->result->splits; diff --git a/src/ClientV2.php b/src/ClientV2.php index 651f7ee1..fc0ab147 100644 --- a/src/ClientV2.php +++ b/src/ClientV2.php @@ -6,8 +6,10 @@ use Mindee\Http\MindeeApiV2; use Mindee\Input\InferenceParameters; use Mindee\Input\InputSource; +use Mindee\Parsing\V2\BaseResponse; use Mindee\Parsing\V2\InferenceResponse; use Mindee\Parsing\V2\JobResponse; +use Mindee\V2\ClientOptions\BaseParameters; /** * Mindee Client V2. @@ -44,7 +46,22 @@ public function enqueueInference( InputSource $inputSource, InferenceParameters $params ): JobResponse { - return $this->mindeeApi->reqPostInferenceEnqueue($inputSource, $params); + return $this->enqueue($inputSource, $params); + } + + /** + * Send the document to an asynchronous endpoint and return its ID in the queue. + * @param InputSource $inputSource File to parse. + * @param BaseParameters $params Parameters relating to prediction options. + * @return JobResponse A JobResponse containing the job (queue) corresponding to a document. + * @throws MindeeException Throws if the input document is not provided. + * @category Asynchronous + */ + public function enqueue( + InputSource $inputSource, + BaseParameters $params + ): JobResponse { + return $this->mindeeApi->reqPostEnqueue($inputSource, $params); } /** @@ -59,6 +76,34 @@ public function getInference(string $inferenceId): InferenceResponse return $this->mindeeApi->reqGetInference($inferenceId); } + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultUrl URL of the result. + * @return T A response containing parsing results. + */ + public function getResultFromUrl( + string $responseClass, + string $resultUrl + ): BaseResponse { + return $this->mindeeApi->reqGetResultFromUrl($responseClass, $resultUrl); + } + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultId ID of the result. + * @return T A response containing parsing results. + */ + public function getResult( + string $responseClass, + string $resultId + ): BaseResponse { + return $this->mindeeApi->reqGetResult($responseClass, $resultId); + } + /** * Get the status of an inference that was previously enqueued. * Can be used for polling. @@ -80,44 +125,63 @@ public function getJob(string $jobId): JobResponse * @param InferenceParameters $params Parameters relating to prediction options. * @return InferenceResponse A response containing parsing results. * @throws MindeeException Throws if enqueueing fails, job fails, or times out. - * @category Synchronous */ public function enqueueAndGetInference( InputSource $inputDoc, InferenceParameters $params ): InferenceResponse { + return $this->enqueueAndGetResult(InferenceResponse::class, $inputDoc, $params); + } + + /** + * Send a document to an endpoint and poll the server until the result is sent or + * until the maximum number of tries is reached. + * + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param InputSource $inputDoc Input document to parse. + * @param BaseParameters $params Parameters relating to prediction options. + * @return T A response containing parsing results. + * @throws MindeeException Throws if enqueueing fails, job fails, or times out. + */ + public function enqueueAndGetResult( + string $responseClass, + InputSource $inputDoc, + BaseParameters $params + ): BaseResponse { $pollingOptions = $params->pollingOptions; - $enqueueResponse = $this->enqueueInference($inputDoc, $params); + $enqueueResponse = $this->enqueue($inputDoc, $params); if (empty($enqueueResponse->job->id)) { error_log("Failed enqueueing:\n" . json_encode($enqueueResponse)); throw new MindeeException("Enqueueing of the document failed."); } - $queueId = $enqueueResponse->job->id; - error_log("Successfully enqueued document with job id: " . $queueId); + $jobId = $enqueueResponse->job->id; + error_log("Successfully enqueued document with job ID: " . $jobId); $this->customSleep($pollingOptions->initialDelaySec); $retryCounter = 1; - $pollResults = $this->getJob($queueId); + $pollResults = $this->getJob($jobId); while ($retryCounter < $pollingOptions->maxRetries) { if ($pollResults->job->status === "Failed") { break; } if ($pollResults->job->status === "Processed") { - return $this->getInference($pollResults->job->id); + return $this->getResultFromUrl($responseClass, $pollResults->job->resultUrl); } error_log( - "Polling server for parsing result with queueId: " . $queueId . + "Polling server for parsing result with job ID: " . $jobId . ". Attempt number " . $retryCounter . " of " . $pollingOptions->maxRetries . ". Job status: " . $pollResults->job->status ); $this->customSleep($pollingOptions->delaySec); - $pollResults = $this->getJob($queueId); + $pollResults = $this->getJob($jobId); $retryCounter++; } diff --git a/src/Geometry/Polygon.php b/src/Geometry/Polygon.php index 69ca0ccb..339086d0 100644 --- a/src/Geometry/Polygon.php +++ b/src/Geometry/Polygon.php @@ -160,11 +160,11 @@ public function getCoordinates(): ?array /** * @return string String representation. */ - public function __toString() + public function __toString(): string { - if (!$this->isEmpty()) { - return 'Polygon with ' . count($this->getCoordinates()) . ' points.'; - } - return ''; + $formattedPoints = array_map(fn ($p) => "({$p->getX()},{$p->getY()})", $this->coordinates); + $joinedPoints = implode(", ", $formattedPoints); + + return "($joinedPoints)"; } } diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 81ce6f7a..38c9957f 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -6,8 +6,10 @@ namespace Mindee\Http; +use CurlHandle; use Exception; use Mindee\Error\ErrorCode; +use Mindee\Error\MindeeApiException; use Mindee\Error\MindeeException; // phpcs:disable @@ -21,9 +23,14 @@ use Mindee\Input\InputSource; use Mindee\Input\LocalInputSource; use Mindee\Input\URLInputSource; +use Mindee\Parsing\V2\BaseResponse; use Mindee\Parsing\V2\ErrorResponse; use Mindee\Parsing\V2\InferenceResponse; use Mindee\Parsing\V2\JobResponse; +use Mindee\V2\ClientOptions\BaseParameters; +use ReflectionClass; +use ReflectionException; +use ReflectionProperty; use const Mindee\VERSION; @@ -62,13 +69,10 @@ class MindeeApiV2 */ private function getUserAgent(): string { - switch (PHP_OS_FAMILY) { - case "Darwin": - $os = "macos"; - break; - default: - $os = strtolower(PHP_OS_FAMILY); - } + $os = match (PHP_OS_FAMILY) { + "Darwin" => "macos", + default => strtolower(PHP_OS_FAMILY), + }; return 'mindee-api-php@v' . VERSION . ' php-v' . PHP_VERSION . ' ' . $os; } @@ -156,43 +160,64 @@ protected function setApiKey(?string $apiKey = null): void } /** - * @param InputSource $inputDoc Input document. - * @param InferenceParameters $params Parameters for the inference. + * @param InputSource $inputDoc Input document. + * @param BaseParameters $params Parameters for the inference. * @return JobResponse Server response wrapped in a JobResponse object. * @throws MindeeException Throws if the model ID is not provided. */ - public function reqPostInferenceEnqueue(InputSource $inputDoc, InferenceParameters $params): JobResponse + public function reqPostEnqueue(InputSource $inputDoc, BaseParameters $params): JobResponse { if (!isset($params->modelId)) { throw new MindeeException("Model ID must be provided.", ErrorCode::USER_INPUT_ERROR); } $response = $this->documentEnqueuePost($inputDoc, $params); - return $this->processResponse($response, JobResponse::class); + return $this->processJobResponse($response); } /** * Process the HTTP response and return the appropriate response object. * - * @param array $result Raw HTTP response array with 'data' and 'code' keys. - * @param string $responseType Class name of the response type to instantiate. - * @return JobResponse|InferenceResponse The processed response object. + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return T A response containing parsing results. * @throws MindeeException Throws if HTTP status indicates an error or deserialization fails. - * @throws MindeeV2HttpException Throws if the HTTP status indicates an error. - * @throws MindeeV2HttpUnknownException Throws if the server sends an unexpected reply. */ - private function processResponse(array $result, string $responseType): InferenceResponse|JobResponse - { - $statusCode = $result['code'] ?? -1; + private function processResponse( + string $responseClass, + array $result + ): BaseResponse { + $this->checkValidResponse($result); - if ($statusCode > 399 || $statusCode < 200) { + try { $responseData = json_decode($result['data'], true); - - if ($responseData && isset($responseData['status'])) { - throw new MindeeV2HttpException(new ErrorResponse($responseData)); + $reflectionClass = new ReflectionClass($responseClass); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new MindeeException('JSON decode error: ' . json_last_error_msg()); } - throw new MindeeV2HttpUnknownException(json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + + /** @var T $instance */ + $instance = $reflectionClass->newInstance($responseData); + return $instance; + } catch (Exception $e) { + error_log("Raised '{$e->getMessage()}' Couldn't deserialize response object:\n" . $result['data']); + throw new MindeeException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); } + } + + /** + * Process the HTTP response and return the appropriate response object. + * + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return JobResponse The processed response object. + * @throws MindeeException Throws if HTTP status indicates an error or deserialization fails. + * @throws MindeeApiException Throws if the response type is not recognized. + */ + private function processJobResponse(array $result): JobResponse + { + $this->checkValidResponse($result); try { $responseData = json_decode($result['data'], true); @@ -200,10 +225,10 @@ private function processResponse(array $result, string $responseType): Inference throw new MindeeException('JSON decode error: ' . json_last_error_msg()); } - return new $responseType($responseData); + return new JobResponse($responseData); } catch (Exception $e) { - error_log("Raised '{$e->getMessage()}' Couldn't deserialize response object:\n" . $result['data']); - throw new MindeeException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); + error_log("Raised '{$e->getMessage()}' Couldn't deserialize job response:\n" . $result['data']); + throw new MindeeApiException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); } } @@ -220,8 +245,7 @@ public function reqGetInference(string $inferenceId): InferenceResponse if (!isset($inferenceId)) { throw new MindeeException("Inference ID must be provided.", ErrorCode::USER_INPUT_ERROR); } - $response = $this->inferenceGetRequest($inferenceId); - return $this->processResponse($response, InferenceResponse::class); + return $this->reqGetResult(InferenceResponse::class, $inferenceId); } /** @@ -237,8 +261,59 @@ public function reqGetJob(string $jobId): JobResponse if (!isset($jobId)) { throw new MindeeException("Inference ID must be provided.", ErrorCode::USER_INPUT_ERROR); } - $response = $this->jobGetRequest($jobId); - return $this->processResponse($response, JobResponse::class); + $response = $this->sendGetRequest($this->baseUrl . "/jobs/$jobId"); + return $this->processJobResponse($response, JobResponse::class); + } + + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultId URL of the result. + * @return T A response containing parsing results. + * @throws MindeeException Throws if the server's response contains an error. + * @throws MindeeApiException Throws if the response class is not valid. + */ + public function reqGetResult( + string $responseClass, + string $resultId + ): BaseResponse { + if (!isset($responseClass) || !isset($resultId)) { + throw new MindeeException("Response class and job ID must be provided.", ErrorCode::USER_INPUT_ERROR); + } + + try { + $slugProperty = new ReflectionProperty($responseClass, 'slug'); + } catch (ReflectionException $e) { + throw new MindeeApiException( + "Unable to access slug property of " . $responseClass, + ErrorCode::INTERNAL_LIBRARY_ERROR, + $e + ); + } + $url = $this->baseUrl . "/products/{$slugProperty->getValue()}/results/$resultId"; + $response = $this->sendGetRequest($url); + return $this->processResponse($responseClass, $response); + } + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultUrl URL of the result. + * @return T A response containing parsing results. + * @throws MindeeException Throws if the server's response contains an error. + */ + public function reqGetResultFromUrl( + string $responseClass, + string $resultUrl + ): BaseResponse { + if (!isset($responseClass) || !isset($resultUrl)) { + throw new MindeeException("Response class and result URL must be provided.", ErrorCode::USER_INPUT_ERROR); + } + $response = $this->sendGetRequest($resultUrl); + return $this->processResponse($responseClass, $response); } /** @@ -266,32 +341,14 @@ private function initChannel() /** * Makes a GET call to retrieve a job. - * @param string $jobId ID of the job. - * @return array Server response. - */ - private function jobGetRequest(string $jobId): array - { - $ch = $this->initChannel(); - curl_setopt($ch, CURLOPT_URL, $this->baseUrl . "/jobs/$jobId"); - $resp = [ - 'data' => curl_exec($ch), - 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), - ]; - curl_close($ch); - - return $resp; - } - - /** - * Makes a GET call to retrieve an inference. - * @param string $inferenceId ID of the inference. + * @param string $url URL of the job. * @return array Server response. */ - private function inferenceGetRequest(string $inferenceId): array + private function sendGetRequest(string $url): array { + /** @var CurlHandle $ch */ $ch = $this->initChannel(); - curl_setopt($ch, CURLOPT_URL, $this->baseUrl . "/inferences/$inferenceId"); - + curl_setopt($ch, CURLOPT_URL, $url); $resp = [ 'data' => curl_exec($ch), 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), @@ -304,57 +361,26 @@ private function inferenceGetRequest(string $inferenceId): array /** * Starts a CURL session using POST. * - * @param InputSource $inputSource File to upload. - * @param InferenceParameters $params Inference parameters. + * @param InputSource $inputSource File to upload. + * @param BaseParameters $params Inference parameters. * @return array * @throws MindeeException Throws if the cURL operation doesn't go succeed. */ private function documentEnqueuePost( InputSource $inputSource, - InferenceParameters $params + BaseParameters $params ): array { + /** @var CurlHandle $ch */ $ch = $this->initChannel(); - $postFields = ['model_id' => $params->modelId]; + $postFields = $params->asHash(); + if ($inputSource instanceof URLInputSource) { $postFields['url'] = $inputSource->url; } elseif ($inputSource instanceof LocalInputSource) { $inputSource->checkNeedsFix(); $postFields['file'] = $inputSource->fileObject; } - - if (isset($params->rawText)) { - $postFields['raw_text'] = $params->rawText ? 'true' : 'false'; - } - if (isset($params->polygon)) { - $postFields['polygon'] = $params->polygon ? 'true' : 'false'; - } - if (isset($params->confidence)) { - $postFields['confidence'] = $params->confidence ? 'true' : 'false'; - } - if (isset($params->rag)) { - $postFields['rag'] = $params->rag ? 'true' : 'false'; - } - if (isset($params->webhooksIds) && count($params->webhooksIds) > 0) { - if (PHP_VERSION_ID < 80200 && count($params->webhooksIds) > 1) { - # NOTE: see https://bugs.php.net/bug.php?id=51634 - error_log("PHP version is too low to support webbook array destructuring. - \nOnly the first webhook ID will be sent to the server."); - $postFields['webhook_ids'] = $params->webhooksIds[0]; - } else { - $postFields['webhook_ids'] = $params->webhooksIds; - } - } - if (isset($params->alias)) { - $postFields['alias'] = $params->alias; - } - if (isset($params->textContext)) { - $postFields['text_context'] = $params->textContext; - } - if (isset($params->dataSchema)) { - $postFields['data_schema'] = strval($params->dataSchema); - } - - $url = $this->baseUrl . '/inferences/enqueue'; + $url = $this->baseUrl . "/products/{$params::$slug}/enqueue"; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); $resp = [ @@ -365,9 +391,28 @@ private function documentEnqueuePost( if (!empty($curlError)) { throw new MindeeException("cURL error:\n$curlError"); } - curl_close($ch); return $resp; } + + /** + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return void + * @throws MindeeV2HttpException Throws if the HTTP status indicates an error. + * @throws MindeeV2HttpUnknownException Throws if the server sends an unexpected reply. + */ + private function checkValidResponse(array $result): void + { + $statusCode = $result['code'] ?? -1; + + if ($statusCode > 399 || $statusCode < 200) { + $responseData = json_decode($result['data'], true); + + if ($responseData && isset($responseData['status'])) { + throw new MindeeV2HttpException(new ErrorResponse($responseData)); + } + throw new MindeeV2HttpUnknownException(json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + } + } } diff --git a/src/Input/InferenceParameters.php b/src/Input/InferenceParameters.php index 762e63f7..1cc3f51b 100644 --- a/src/Input/InferenceParameters.php +++ b/src/Input/InferenceParameters.php @@ -2,16 +2,13 @@ namespace Mindee\Input; +use Mindee\V2\ClientOptions\BaseParameters; + /** * Parameters accepted by the asynchronous **inference** v2 endpoint. */ -class InferenceParameters +class InferenceParameters extends BaseParameters { - /** - * @var string Model ID. - */ - public string $modelId; - /** * @var boolean|null Enhance extraction accuracy with Retrieval-Augmented Generation.. */ @@ -33,15 +30,6 @@ class InferenceParameters */ public ?bool $confidence; - /** - * @var string|null Optional file alias. - */ - public ?string $alias; - - /** - * @var array Optional webhook IDs. - */ - public array $webhooksIds; /** * @var string|null Additional text context used by the model during inference. @@ -55,9 +43,9 @@ class InferenceParameters public ?DataSchema $dataSchema; /** - * @var PollingOptions Polling options. + * @var string Slug of the endpoint. */ - public PollingOptions $pollingOptions; + public static string $slug = "extraction"; /** * @param string $modelId ID of the model. @@ -87,29 +75,44 @@ public function __construct( DataSchema|string|array|null $dataSchema = null, ?PollingOptions $pollingOptions = null, ) { - $this->modelId = $modelId; - if (!$pollingOptions) { - $pollingOptions = new PollingOptions(); - } - $this->pollingOptions = $pollingOptions; + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + $this->rag = $rag; $this->rawText = $rawText; $this->polygon = $polygon; $this->confidence = $confidence; - - if (isset($alias)) { - $this->alias = $alias; - } if (isset($textContext)) { $this->textContext = $textContext; } - if (isset($webhooksIds)) { - $this->webhooksIds = $webhooksIds; - } else { - $this->webhooksIds = []; - } if (isset($dataSchema)) { $this->dataSchema = new DataSchema($dataSchema); } } + + /** + * @return array Hash representation. + */ + public function asHash(): array + { + $outHash = parent::asHash(); + if (isset($this->rag)) { + $outHash['rag'] = $this->rag ? 'true' : 'false'; + } + if (isset($this->rawText)) { + $outHash['raw_text'] = $this->rawText ? 'true' : 'false'; + } + if (isset($this->polygon)) { + $outHash['polygon'] = $this->polygon ? 'true' : 'false'; + } + if (isset($this->confidence)) { + $outHash['confidence'] = $this->confidence ? 'true' : 'false'; + } + if (isset($this->textContext)) { + $outHash['text_context'] = $this->textContext; + } + if (isset($this->dataSchema)) { + $outHash['data_schema'] = strval($this->dataSchema); + } + return $outHash; + } } diff --git a/src/Input/LocalResponse.php b/src/Input/LocalResponse.php index 81e037f8..9a16086d 100644 --- a/src/Input/LocalResponse.php +++ b/src/Input/LocalResponse.php @@ -117,7 +117,7 @@ public function isValidHMACSignature(string $secretKey, string $signature): bool } /** - * Deserialize the loaded local response into the requested CommonResponse-derived class. + * Deserialize the loaded local response into the requested BaseResponse-derived class. * * Typically used when dealing with V2 webhook callbacks. * diff --git a/src/Parsing/V2/CommonResponse.php b/src/Parsing/V2/BaseResponse.php similarity index 68% rename from src/Parsing/V2/CommonResponse.php rename to src/Parsing/V2/BaseResponse.php index 7e36eb27..783f7c3e 100644 --- a/src/Parsing/V2/CommonResponse.php +++ b/src/Parsing/V2/BaseResponse.php @@ -5,7 +5,7 @@ /** * Common response base class for V2. */ -abstract class CommonResponse +abstract class BaseResponse { /** * @var array Raw HTTP response from the server. @@ -13,11 +13,11 @@ abstract class CommonResponse private array $rawHttp; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - protected function __construct(array $serverResponse) + protected function __construct(array $rawResponse) { - $this->rawHttp = $serverResponse; + $this->rawHttp = $rawResponse; } /** diff --git a/src/Parsing/V2/Field/FieldLocation.php b/src/Parsing/V2/Field/FieldLocation.php index 2f278ee0..e954a17b 100644 --- a/src/Parsing/V2/Field/FieldLocation.php +++ b/src/Parsing/V2/Field/FieldLocation.php @@ -39,6 +39,6 @@ public function __construct(array $serverResponse) */ public function __toString(): string { - return $this->polygon ? (string)$this->polygon : ''; + return $this->polygon ? $this->polygon . " on page $this->page" : ''; } } diff --git a/src/Parsing/V2/Inference.php b/src/Parsing/V2/Inference.php index f93537d7..81dfecaa 100644 --- a/src/Parsing/V2/Inference.php +++ b/src/Parsing/V2/Inference.php @@ -2,31 +2,14 @@ namespace Mindee\Parsing\V2; +use Mindee\Parsing\Common\SummaryHelper; +use Mindee\V2\Parsing\BaseInference; + /** * Inference class. */ -class Inference +class Inference extends BaseInference { - /** - * @var string ID of the inference. - */ - public string $id; - - /** - * @var InferenceJob Job the inference belongs to. - */ - public InferenceJob $job; - - /** - * @var InferenceModel Model info for the inference. - */ - public InferenceModel $model; - - /** - * @var InferenceFile File info for the inference. - */ - public InferenceFile $file; - /** * @var InferenceActiveOptions Active options for the inference. */ @@ -38,28 +21,26 @@ class Inference public InferenceResult $result; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - public function __construct(array $serverResponse) + public function __construct(array $rawResponse) { - $this->id = $serverResponse['id']; - $this->job = new InferenceJob($serverResponse['job']); - $this->model = new InferenceModel($serverResponse['model']); - $this->file = new InferenceFile($serverResponse['file']); - $this->activeOptions = new InferenceActiveOptions($serverResponse['active_options']); - $this->result = new InferenceResult($serverResponse['result']); + parent::__construct($rawResponse); + $this->activeOptions = new InferenceActiveOptions($rawResponse['active_options']); + $this->result = new InferenceResult($rawResponse['result']); } /** * @return string String representation. */ + /** + * A prettier representation. + * @return string + */ public function __toString(): string { - return "Inference\n#########\n" - . "{$this->job}\n" - . "{$this->model}\n" - . "{$this->file}\n" - . "{$this->activeOptions}\n" - . "{$this->result}\n"; + $str = parent::__toString() . "$this->activeOptions\n\n$this->result\n"; + + return SummaryHelper::cleanOutString($str); } } diff --git a/src/Parsing/V2/InferenceActiveOptions.php b/src/Parsing/V2/InferenceActiveOptions.php index 0e4e82ae..743cc4c8 100644 --- a/src/Parsing/V2/InferenceActiveOptions.php +++ b/src/Parsing/V2/InferenceActiveOptions.php @@ -70,6 +70,6 @@ public function __toString(): string . ':Confidence: ' . SummaryHelper::formatForDisplay($this->confidence) . "\n" . ':RAG: ' . SummaryHelper::formatForDisplay($this->rag) . "\n" . ':Text Context: ' . SummaryHelper::formatForDisplay($this->textContext) . "\n\n" - . $this->dataSchema . "\n"; + . $this->dataSchema; } } diff --git a/src/Parsing/V2/InferenceFile.php b/src/Parsing/V2/InferenceFile.php index f5e1c699..20d1d0e1 100644 --- a/src/Parsing/V2/InferenceFile.php +++ b/src/Parsing/V2/InferenceFile.php @@ -47,6 +47,6 @@ public function __toString(): string . ":Name: $this->name\n" . ":Alias:" . ($this->alias ? " $this->alias" : '') . "\n" . ":Page Count: $this->pageCount\n" - . ":MIME Type: $this->mimeType\n"; + . ":MIME Type: $this->mimeType"; } } diff --git a/src/Parsing/V2/InferenceJob.php b/src/Parsing/V2/InferenceJob.php index a1d798a3..225c5697 100644 --- a/src/Parsing/V2/InferenceJob.php +++ b/src/Parsing/V2/InferenceJob.php @@ -26,6 +26,6 @@ public function __construct(array $serverResponse) public function __toString(): string { return "Job\n===\n" - . ":ID: {$this->id}\n" ; + . ":ID: $this->id" ; } } diff --git a/src/Parsing/V2/InferenceModel.php b/src/Parsing/V2/InferenceModel.php index 69309fce..b11d1c64 100644 --- a/src/Parsing/V2/InferenceModel.php +++ b/src/Parsing/V2/InferenceModel.php @@ -26,6 +26,6 @@ public function __construct(array $serverResponse) public function __toString(): string { return "Model\n=====\n" - . ":ID: {$this->id}\n" ; + . ":ID: $this->id" ; } } diff --git a/src/Parsing/V2/InferenceResponse.php b/src/Parsing/V2/InferenceResponse.php index 27642c7c..cbb3ea87 100644 --- a/src/Parsing/V2/InferenceResponse.php +++ b/src/Parsing/V2/InferenceResponse.php @@ -5,7 +5,7 @@ /** * Inference response class for V2. */ -class InferenceResponse extends CommonResponse +class InferenceResponse extends BaseResponse { /** * @var Inference Inference result. @@ -13,11 +13,16 @@ class InferenceResponse extends CommonResponse public Inference $inference; /** - * @param array $serverResponse Raw server response array. + * @var string Slug for the inference. */ - public function __construct(array $serverResponse) + public static string $slug = "extraction"; + + /** + * @param array $rawResponse Raw server response array. + */ + public function __construct(array $rawResponse) { - parent::__construct($serverResponse); - $this->inference = new Inference($serverResponse['inference']); + parent::__construct($rawResponse); + $this->inference = new Inference($rawResponse['inference']); } } diff --git a/src/Parsing/V2/JobResponse.php b/src/Parsing/V2/JobResponse.php index 536e2c90..5f61841c 100644 --- a/src/Parsing/V2/JobResponse.php +++ b/src/Parsing/V2/JobResponse.php @@ -5,7 +5,7 @@ /** * Job response class. */ -class JobResponse extends CommonResponse +class JobResponse extends BaseResponse { /** * @var Job Job for the polling. @@ -13,11 +13,11 @@ class JobResponse extends CommonResponse public Job $job; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - public function __construct(array $serverResponse) + public function __construct(array $rawResponse) { - parent::__construct($serverResponse); - $this->job = new Job($serverResponse['job']); + parent::__construct($rawResponse); + $this->job = new Job($rawResponse['job']); } } diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseParameters.php new file mode 100644 index 00000000..4b4a8465 --- /dev/null +++ b/src/V2/ClientOptions/BaseParameters.php @@ -0,0 +1,80 @@ + Optional webhook IDs. + */ + public array $webhooksIds; + + /** + * @var PollingOptions Polling options. + */ + public PollingOptions $pollingOptions; + + /** + * @param string $modelId ID of the model. + * @param string|null $alias Optional file alias. + * @param array|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct(string $modelId, ?string $alias, ?array $webhooksIds, ?PollingOptions $pollingOptions) + { + $this->modelId = $modelId; + + if (isset($alias)) { + $this->alias = $alias; + } + if (isset($webhooksIds)) { + $this->webhooksIds = $webhooksIds; + } else { + $this->webhooksIds = []; + } + if (!$pollingOptions) { + $pollingOptions = new PollingOptions(); + } + $this->pollingOptions = $pollingOptions; + } + + /** + * @return array Hash representation. + */ + public function asHash(): array + { + $outHash = ['model_id' => $this->modelId]; + if (isset($this->alias)) { + $outHash['alias'] = $this->alias; + } + + if (isset($this->webhooksIds) && count($this->webhooksIds) > 0) { + if (PHP_VERSION_ID < 80200 && count($this->webhooksIds) > 1) { + // NOTE: see https://bugs.php.net/bug.php?id=51634 + error_log("PHP version is too low to support webbook array destructuring. + \nOnly the first webhook ID will be sent to the server."); + $outHash['webhook_ids'] = $this->webhooksIds[0]; + } else { + foreach ($this->webhooksIds as $webhookId) { + $outHash['webhook_ids[]'] = $webhookId; + } + } + } + return $outHash; + } +} diff --git a/src/V2/Parsing/BaseInference.php b/src/V2/Parsing/BaseInference.php new file mode 100644 index 00000000..27dcb0f9 --- /dev/null +++ b/src/V2/Parsing/BaseInference.php @@ -0,0 +1,55 @@ +id = $rawResponse['id']; + $this->model = new InferenceModel($rawResponse['model']); + $this->file = new InferenceFile($rawResponse['file']); + $this->job = new InferenceJob($rawResponse['job']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $str = "Inference\n#########\n$this->job\n\n$this->model\n\n$this->file\n\n"; + + return SummaryHelper::cleanOutString($str); + } +} diff --git a/src/V2/Product/Classification/ClassificationClassifier.php b/src/V2/Product/Classification/ClassificationClassifier.php new file mode 100644 index 00000000..59913398 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationClassifier.php @@ -0,0 +1,30 @@ +documentType = $rawPrediction['document_type']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Document Type: $this->documentType"; + } +} diff --git a/src/V2/Product/Classification/ClassificationInference.php b/src/V2/Product/Classification/ClassificationInference.php new file mode 100644 index 00000000..f559dc74 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationInference.php @@ -0,0 +1,39 @@ +result = new ClassificationResult($rawResponse['result']); + } + + + /** + * @return string String representation. + */ + /** + * A prettier representation. + * @return string + */ + public function __toString(): string + { + return SummaryHelper::cleanOutString(parent::__toString() . "$this->result\n"); + } +} diff --git a/src/V2/Product/Classification/ClassificationResponse.php b/src/V2/Product/Classification/ClassificationResponse.php new file mode 100644 index 00000000..95758c07 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationResponse.php @@ -0,0 +1,30 @@ +inference = new ClassificationInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Classification/ClassificationResult.php b/src/V2/Product/Classification/ClassificationResult.php new file mode 100644 index 00000000..9c9e8f7b --- /dev/null +++ b/src/V2/Product/Classification/ClassificationResult.php @@ -0,0 +1,30 @@ +classification = new ClassificationClassifier($rawResponse['classification']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Classification\n==============\n" . $this->classification; + } +} diff --git a/src/V2/Product/Classification/Params/ClassificationParameters.php b/src/V2/Product/Classification/Params/ClassificationParameters.php new file mode 100644 index 00000000..c3191c94 --- /dev/null +++ b/src/V2/Product/Classification/Params/ClassificationParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Crop/CropInference.php b/src/V2/Product/Crop/CropInference.php new file mode 100644 index 00000000..9eb13d07 --- /dev/null +++ b/src/V2/Product/Crop/CropInference.php @@ -0,0 +1,33 @@ +result = new CropResult($rawResponse['result']); + } + + /** + * @return string Raw server response array. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Crop/CropItem.php b/src/V2/Product/Crop/CropItem.php new file mode 100644 index 00000000..d38a11fa --- /dev/null +++ b/src/V2/Product/Crop/CropItem.php @@ -0,0 +1,38 @@ +location = new FieldLocation($rawResponse['location']); + $this->objectType = $rawResponse['object_type']; + } + + /** + * @return string String representation. + */ + public function __toString() + { + return "* :Location: $this->location\n :Object Type: $this->objectType"; + } +} diff --git a/src/V2/Product/Crop/CropResponse.php b/src/V2/Product/Crop/CropResponse.php new file mode 100644 index 00000000..cfd07e29 --- /dev/null +++ b/src/V2/Product/Crop/CropResponse.php @@ -0,0 +1,30 @@ +inference = new CropInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Crop/CropResult.php b/src/V2/Product/Crop/CropResult.php new file mode 100644 index 00000000..966b46af --- /dev/null +++ b/src/V2/Product/Crop/CropResult.php @@ -0,0 +1,30 @@ +crops = array_map(fn ($crop) => new CropItem($crop), $rawResponse['crops']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Crops\n=====\n" . implode("\n", $this->crops); + } +} diff --git a/src/V2/Product/Crop/Params/CropParameters.php b/src/V2/Product/Crop/Params/CropParameters.php new file mode 100644 index 00000000..f7547cc1 --- /dev/null +++ b/src/V2/Product/Crop/Params/CropParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Ocr/OcrInference.php b/src/V2/Product/Ocr/OcrInference.php new file mode 100644 index 00000000..6c6a5e4c --- /dev/null +++ b/src/V2/Product/Ocr/OcrInference.php @@ -0,0 +1,33 @@ +result = new OcrResult($rawResponse['result']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Ocr/OcrPage.php b/src/V2/Product/Ocr/OcrPage.php new file mode 100644 index 00000000..55293240 --- /dev/null +++ b/src/V2/Product/Ocr/OcrPage.php @@ -0,0 +1,42 @@ +words = array_map(fn ($word) => new OcrWord($word), $rawResponse['words']); + $this->content = $rawResponse['content']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $ocrWords = "\n"; + + if (!empty($this->words)) { + $ocrWords .= implode("\n\n", $this->words); + } + + return "OCR Words\n---------$ocrWords\n\n:Content: $this->content"; + } +} diff --git a/src/V2/Product/Ocr/OcrResponse.php b/src/V2/Product/Ocr/OcrResponse.php new file mode 100644 index 00000000..1861964e --- /dev/null +++ b/src/V2/Product/Ocr/OcrResponse.php @@ -0,0 +1,30 @@ +inference = new OcrInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Ocr/OcrResult.php b/src/V2/Product/Ocr/OcrResult.php new file mode 100644 index 00000000..56b60128 --- /dev/null +++ b/src/V2/Product/Ocr/OcrResult.php @@ -0,0 +1,41 @@ +pages = array_map(fn ($page) => new OcrPage($page), $rawResponse['pages']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $str = "OCR Result\n##########\n"; + $i = 1; + + foreach ($this->pages as $page) { + $pageNumberTitle = "Page $i"; + $underline = str_repeat("=", strlen($pageNumberTitle)); + + $str .= "$pageNumberTitle\n$underline\n\n$page\n"; + $i++; + } + + return $str; + } +} diff --git a/src/V2/Product/Ocr/OcrWord.php b/src/V2/Product/Ocr/OcrWord.php new file mode 100644 index 00000000..48c070ab --- /dev/null +++ b/src/V2/Product/Ocr/OcrWord.php @@ -0,0 +1,38 @@ +content = $rawResponse['content']; + $this->polygon = new Polygon($rawResponse['polygon']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return $this->content; + } +} diff --git a/src/V2/Product/Ocr/Params/OcrParameters.php b/src/V2/Product/Ocr/Params/OcrParameters.php new file mode 100644 index 00000000..b86dd24e --- /dev/null +++ b/src/V2/Product/Ocr/Params/OcrParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Split/Params/SplitParameters.php b/src/V2/Product/Split/Params/SplitParameters.php new file mode 100644 index 00000000..7d0d61f2 --- /dev/null +++ b/src/V2/Product/Split/Params/SplitParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Split/SplitInference.php b/src/V2/Product/Split/SplitInference.php new file mode 100644 index 00000000..f1071710 --- /dev/null +++ b/src/V2/Product/Split/SplitInference.php @@ -0,0 +1,33 @@ +result = new SplitResult($rawResponse['result']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Split/SplitRange.php b/src/V2/Product/Split/SplitRange.php new file mode 100644 index 00000000..d1d12d27 --- /dev/null +++ b/src/V2/Product/Split/SplitRange.php @@ -0,0 +1,39 @@ +pageRange = $rawResponse['page_range']; + $this->documentType = $rawResponse['document_type']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $pageRangeStr = implode(",", $this->pageRange); + + return "* :Page Range: $pageRangeStr\n :Document Type: $this->documentType"; + } +} diff --git a/src/V2/Product/Split/SplitResponse.php b/src/V2/Product/Split/SplitResponse.php new file mode 100644 index 00000000..d012af8a --- /dev/null +++ b/src/V2/Product/Split/SplitResponse.php @@ -0,0 +1,30 @@ +inference = new SplitInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Split/SplitResult.php b/src/V2/Product/Split/SplitResult.php new file mode 100644 index 00000000..aa61bb46 --- /dev/null +++ b/src/V2/Product/Split/SplitResult.php @@ -0,0 +1,32 @@ +splits = array_map(fn ($split) => new SplitRange($split), $rawResponse['splits']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $splitsStr = implode("\n", $this->splits); + + return "Splits\n======\n$splitsStr"; + } +} diff --git a/tests/V2/ClientV2Test.php b/tests/V2/ClientV2Test.php index 514ba492..3de066ed 100644 --- a/tests/V2/ClientV2Test.php +++ b/tests/V2/ClientV2Test.php @@ -31,7 +31,7 @@ public function testEnqueuePostAsync(): void $predictable = $this->createMock(MindeeApiV2::class); $syntheticResponse = file_get_contents(\TestingUtilities::getV2DataDir() . '/job/ok_processing.json'); $predictable->expects($this->once()) - ->method('reqPostInferenceEnqueue') + ->method('reqPostEnqueue') ->with( $this->isInstanceOf(LocalInputSource::class), $this->isInstanceOf(InferenceParameters::class) diff --git a/tests/V2/Product/ClassificationFunctional.php b/tests/V2/Product/ClassificationFunctional.php new file mode 100644 index 00000000..a2279b3d --- /dev/null +++ b/tests/V2/Product/ClassificationFunctional.php @@ -0,0 +1,55 @@ +client = new ClientV2($apiKey); + + $this->classificationModelId = getenv('MINDEE_V2_CLASSIFICATION_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the classification process using a default sample file. + * + * @return void + */ + public function testClassificationDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/classification/default_invoice.jpg' + ); + + $productParams = new ClassificationParameters($this->classificationModelId); + $response = $this->client->enqueueAndGetResult(ClassificationResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_invoice.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $classifications = $result->classification; + $this->assertNotNull($classifications); + } +} diff --git a/tests/V2/Product/ClassificationTest.php b/tests/V2/Product/ClassificationTest.php new file mode 100644 index 00000000..64d13de0 --- /dev/null +++ b/tests/V2/Product/ClassificationTest.php @@ -0,0 +1,66 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single classification JSON. + * @return void + */ + public function testClassificationWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference(); + + $response = new ClassificationResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $inference->job->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $classification = $inference->result->classification; + $this->assertSame("invoice", $classification->documentType); + } +} diff --git a/tests/V2/Product/CropFunctional.php b/tests/V2/Product/CropFunctional.php new file mode 100644 index 00000000..df5a9e82 --- /dev/null +++ b/tests/V2/Product/CropFunctional.php @@ -0,0 +1,60 @@ +client = new ClientV2($apiKey); + + $this->cropModelId = getenv('MINDEE_V2_CROP_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the crop process using a default sample file. + * + * @return void + */ + public function testCropDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/crop/default_sample.jpg' + ); + + $productParams = new CropParameters($this->cropModelId); + $response = $this->client->enqueueAndGetResult(CropResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $crops = $result->crops; + $this->assertNotNull($crops); + $this->assertCount(2, $crops); + + foreach ($crops as $crop) { + $this->assertNotNull($crop->objectType); + $this->assertNotNull($crop->location); + } + } +} diff --git a/tests/V2/Product/CropTest.php b/tests/V2/Product/CropTest.php new file mode 100644 index 00000000..dbfd3d67 --- /dev/null +++ b/tests/V2/Product/CropTest.php @@ -0,0 +1,168 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Ensures all line endings are identical before comparison so the test + * behaves the same on every platform (LF vs CRLF). + * @param string $input Input string to normalize. + * @return string + */ + private static function normalizeLineEndings(string $input): string + { + return str_replace(["\r\n", "\r"], "\n", $input); + } + + /** + * Should correctly map properties when reading a single crop JSON. + * @return void + */ + public function testCropWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("crop/crop_single.json"); + $response = new CropResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $inference->job->id); + + $this->assertSame("sample.jpeg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $crops = $inference->result->crops; + $this->assertNotNull($crops); + $this->assertCount(1, $crops); + + $firstCrop = $crops[0]; + $this->assertSame("invoice", $firstCrop->objectType); + $this->assertSame(0, $firstCrop->location->page); + + $polygon = $firstCrop->location->polygon; + $this->assertCount(4, $polygon->getCoordinates()); + + // Note: Using assertEquals here instead of assertSame to allow for object value comparison + $this->assertEquals(new Point(0.15, 0.254), $polygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.85, 0.254), $polygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.85, 0.947), $polygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.15, 0.947), $polygon->getCoordinates()[3]); + } + + /** + * Should correctly map properties when reading a multiple crop JSON. + * @return void + */ + public function testCropWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("crop/crop_multiple.json"); + $response = new CropResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $job = $inference->job; + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $job->id); + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $crops = $inference->result->crops; + $this->assertNotNull($crops); + $this->assertCount(2, $crops); + + $firstCrop = $crops[0]; + $this->assertSame("invoice", $firstCrop->objectType); + $this->assertSame(0, $firstCrop->location->page); + + $firstPolygon = $firstCrop->location->polygon; + $this->assertCount(4, $firstPolygon->getCoordinates()); + $this->assertEquals(new Point(0.214, 0.079), $firstPolygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.476, 0.079), $firstPolygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.476, 0.979), $firstPolygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.214, 0.979), $firstPolygon->getCoordinates()[3]); + + $secondCrop = $crops[1]; + $this->assertSame("receipt", $secondCrop->objectType); + $this->assertSame(0, $secondCrop->location->page); + + $secondPolygon = $secondCrop->location->polygon; + $this->assertCount(4, $secondPolygon->getCoordinates()); + $this->assertEquals(new Point(0.547, 0.15), $secondPolygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.862, 0.15), $secondPolygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.862, 0.97), $secondPolygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.547, 0.97), $secondPolygon->getCoordinates()[3]); + } + + /** + * crop_single.rst – RST display must be parsed and exposed + * @return void + */ + public function testRstDisplayMustBeAccessible(): void + { + $jsonSample = self::getInference("crop/crop_single.json"); + $response = new CropResponse($jsonSample); + + $rstReferencePath = TestingUtilities::getV2ProductDir() . "/crop/crop_single.rst"; + $rstReference = file_get_contents($rstReferencePath); + + $inference = $response->inference; + $this->assertNotNull($inference); + + // Assumes your Inference class implements the __toString() magic method + // which maps to C#'s ToString() + $this->assertEquals( + self::normalizeLineEndings($rstReference), + self::normalizeLineEndings((string)$inference) + ); + } +} diff --git a/tests/V2/Product/OcrFunctional.php b/tests/V2/Product/OcrFunctional.php new file mode 100644 index 00000000..70d1eb1d --- /dev/null +++ b/tests/V2/Product/OcrFunctional.php @@ -0,0 +1,55 @@ +client = new ClientV2($apiKey); + + $this->ocrModelId = getenv('MINDEE_V2_OCR_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the OCR process using a default sample file. + * + * @return void + */ + public function testOcrDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/ocr/default_sample.jpg' + ); + + $productParams = new OcrParameters($this->ocrModelId); + $response = $this->client->enqueueAndGetResult(OcrResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $pages = $result->pages; + $this->assertNotNull($pages); + $this->assertCount(1, $pages); + } +} diff --git a/tests/V2/Product/OcrTest.php b/tests/V2/Product/OcrTest.php new file mode 100644 index 00000000..01d027b5 --- /dev/null +++ b/tests/V2/Product/OcrTest.php @@ -0,0 +1,108 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single OCR JSON. + * @return void + */ + public function testOcrWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("ocr/ocr_single.json"); + $response = new OcrResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $pages = $inference->result->pages; + $this->assertNotNull($pages); + $this->assertCount(1, $pages); + + $firstPage = $pages[0]; + $this->assertNotNull($firstPage->words); + + $firstWord = $firstPage->words[0]; + $this->assertSame("Shipper:", $firstWord->content); + // Using the getCoordinates() logic from the corrected file + $this->assertCount(4, $firstWord->polygon->getCoordinates()); + + $fifthWord = $firstPage->words[4]; + $this->assertSame("INC.", $fifthWord->content); + $this->assertCount(4, $fifthWord->polygon->getCoordinates()); + } + + /** + * Should correctly map properties when reading a multiple OCR JSON. + * @return void + */ + public function testOcrWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("ocr/ocr_multiple.json"); + $response = new OcrResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $job = $inference->job; + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $job->id); + + $model = $inference->model; + $this->assertNotNull($model); + + $pages = $inference->result->pages; + $this->assertNotNull($pages); + $this->assertCount(3, $pages); + + foreach ($pages as $page) { + $this->assertNotNull($page->words); + $this->assertNotNull($page->content); + $this->assertIsString($page->content); + } + } +} diff --git a/tests/V2/Product/SplitFunctional.php b/tests/V2/Product/SplitFunctional.php new file mode 100644 index 00000000..92d2cbc8 --- /dev/null +++ b/tests/V2/Product/SplitFunctional.php @@ -0,0 +1,55 @@ +client = new ClientV2($apiKey); + + $this->splitModelId = getenv('MINDEE_V2_SPLIT_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the split process using a default sample file. + * + * @return void + */ + public function testSplitDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/split/default_sample.pdf' + ); + + $productParams = new SplitParameters($this->splitModelId); + $response = $this->client->enqueueAndGetResult(SplitResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.pdf", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $splits = $result->splits; + $this->assertNotNull($splits); + $this->assertCount(2, $splits); + } +} diff --git a/tests/V2/Product/SplitTest.php b/tests/V2/Product/SplitTest.php new file mode 100644 index 00000000..b4925cb0 --- /dev/null +++ b/tests/V2/Product/SplitTest.php @@ -0,0 +1,107 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single split JSON. + * @return void + */ + public function testSplitWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("split/split_single.json"); + $response = new SplitResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $model = $inference->model; + $this->assertNotNull($model); + + $splits = $inference->result->splits; + $this->assertNotNull($splits); + $this->assertCount(1, $splits); + + $firstSplit = $splits[0]; + $this->assertSame("receipt", $firstSplit->documentType); + + $this->assertNotNull($firstSplit->pageRange); + $this->assertCount(2, $firstSplit->pageRange); + $this->assertSame(0, $firstSplit->pageRange[0]); + $this->assertSame(0, $firstSplit->pageRange[1]); + } + + /** + * Should correctly map properties when reading a multiple split JSON. + * @return void + */ + public function testSplitWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("split/split_multiple.json"); + $response = new SplitResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $model = $inference->model; + $this->assertNotNull($model); + + $splits = $inference->result->splits; + $this->assertNotNull($splits); + $this->assertCount(3, $splits); + + $firstSplit = $splits[0]; + $this->assertSame("passport", $firstSplit->documentType); + + $this->assertNotNull($firstSplit->pageRange); + $this->assertCount(2, $firstSplit->pageRange); + $this->assertSame(0, $firstSplit->pageRange[0]); + $this->assertSame(0, $firstSplit->pageRange[1]); + + $secondSplit = $splits[1]; + $this->assertSame("invoice", $secondSplit->documentType); + + $this->assertNotNull($secondSplit->pageRange); + $this->assertCount(2, $secondSplit->pageRange); + $this->assertSame(1, $secondSplit->pageRange[0]); + $this->assertSame(3, $secondSplit->pageRange[1]); + } +} diff --git a/tests/resources b/tests/resources index c2e36f5b..53f0efbc 160000 --- a/tests/resources +++ b/tests/resources @@ -1 +1 @@ -Subproject commit c2e36f5b635386cb9bb922b517c4e02039b0a122 +Subproject commit 53f0efbc08c77c2c085aadd27de9d2d6c359276e diff --git a/tests/test_code_samples.sh b/tests/test_code_samples_v1.sh similarity index 77% rename from tests/test_code_samples.sh rename to tests/test_code_samples_v1.sh index d000d605..1ebc5fe2 100755 --- a/tests/test_code_samples.sh +++ b/tests/test_code_samples_v1.sh @@ -4,9 +4,6 @@ set -e OUTPUT_FILE='../test_code_samples/_test.php' ACCOUNT=$1 ENDPOINT=$2 -API_KEY=$3 -API_KEY_V2=$4 -MODEL_ID=$5 rm -fr ../test_code_samples mkdir ../test_code_samples @@ -34,15 +31,8 @@ composer dump-autoload chmod -R 777 ./ cd - -for f in $(find docs/code_samples -maxdepth 1 -name "*.txt" -not -name "workflow_*.txt" | sort -h) +for f in $(find docs/code_samples -maxdepth 1 -name "*.txt" -not -name "workflow_*.txt" -not -name "v2_*.txt" | sort -h) do - if echo "${f}" | grep -q "default_v2.txt"; then - if [ -z "${API_KEY_V2}" ] || [ -z "${MODEL_ID}" ]; then - echo "Skipping ${f} (API_KEY_V2 or MODEL_ID not supplied)" - echo - continue - fi - fi echo echo "###############################################" echo "${f}" @@ -52,7 +42,6 @@ do sed "s/my-api-key/$API_KEY/" "${f}" > $OUTPUT_FILE sed -i "s/\/path\/to\/the\/file.ext/..\/mindee-api-php\/tests\/resources\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE - # Only keeping the sample for display in the UI if echo "$f" | grep -q "custom_v1.txt" then continue @@ -72,14 +61,6 @@ do sed -i "s/my-version/1/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "default_v2.txt" - then - sed -i "s/MY_API_KEY/$API_KEY_V2/" $OUTPUT_FILE - sed -i "s/MY_MODEL_ID/$MODEL_ID/" $OUTPUT_FILE - else - sed -i "s/my-api-key/$API_KEY/" $OUTPUT_FILE - fi - sleep 0.6 # avoid too many request errors php -d auto_prepend_file=vendor/autoload.php $OUTPUT_FILE done diff --git a/tests/test_code_samples_v2.sh b/tests/test_code_samples_v2.sh new file mode 100755 index 00000000..345f8617 --- /dev/null +++ b/tests/test_code_samples_v2.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -e + +OUTPUT_FILE='../test_code_samples/_test.php' + +rm -fr ../test_code_samples +mkdir ../test_code_samples + +cd ../test_code_samples + +EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] +then + >&2 echo 'ERROR: Invalid installer checksum' + rm composer-setup.php + exit 1 +fi + +php composer-setup.php --quiet +rm composer-setup.php +composer init --no-interaction --name "mindee/test_code_samples" --description "testing package" --author "mindee" --type "project" +composer install +composer config repositories.mindee/mindee '{"type": "path", "url":"../mindee-api-php"}' +composer require mindee/mindee @dev +composer dump-autoload +chmod -R 777 ./ +cd - + +for f in $(find docs/code_samples -maxdepth 1 -name "v2_*.txt" | sort -h) +do + echo "###############################################" + echo "${f}" + echo "###############################################" + echo + + sed "s/MY_API_KEY/$MINDEE_V2_API_KEY/" "${f}" > $OUTPUT_FILE + sed -i "s/\/path\/to\/the\/file.ext/tests\/resources\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE + + if echo "${f}" | grep -q "v2_classification" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_CLASSIFICATION_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_crop" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_CROP_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_extraction" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_FINDOC_MODEL_ID}/" $OUTPUT_FILE + sed -i "s/MY_WEBHOOK_ID/${MINDEE_V2_FAILURE_WEBHOOK_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_ocr" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_OCR_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_split" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_SPLIT_MODEL_ID}/" $OUTPUT_FILE + fi + + echo + sleep 0.6 # avoid too many request errors + php -d auto_prepend_file=vendor/autoload.php $OUTPUT_FILE +done