diff --git a/src/uipath/platform/documents/__init__.py b/src/uipath/platform/documents/__init__.py index 6bee9e0f4..e214067d4 100644 --- a/src/uipath/platform/documents/__init__.py +++ b/src/uipath/platform/documents/__init__.py @@ -19,6 +19,7 @@ ProjectType, Reference, StartExtractionResponse, + StartOperationResponse, ValidateClassificationAction, ValidateExtractionAction, ValidationAction, @@ -43,4 +44,5 @@ "ClassificationResponse", "FileContent", "StartExtractionResponse", + "StartOperationResponse", ] diff --git a/src/uipath/platform/documents/_documents_service.py b/src/uipath/platform/documents/_documents_service.py index 480672356..2bf09673a 100644 --- a/src/uipath/platform/documents/_documents_service.py +++ b/src/uipath/platform/documents/_documents_service.py @@ -9,7 +9,7 @@ from ..._utils import Endpoint from ...tracing import traced from ..common import BaseService, FolderContext, UiPathApiConfig, UiPathExecutionContext -from ..errors import ExtractionNotCompleteException +from ..errors import OperationFailedException, OperationNotCompleteException from .documents import ( ActionPriority, ClassificationResponse, @@ -19,6 +19,7 @@ FileContent, ProjectType, StartExtractionResponse, + StartOperationResponse, ValidateClassificationAction, ValidateExtractionAction, ) @@ -992,6 +993,70 @@ async def start_ixp_extraction_async( document_id=document_id, ) + def _retrieve_operation_result( + self, + url: Endpoint, + operation_id: str, + operation_name: str, + ) -> Dict: + response = self.request( + method="GET", + url=url, + params={"api-version": "1.1"}, + headers=self._get_common_headers(), + ).json() + + status = response.get("status") + if status in ["NotStarted", "Running"]: + raise OperationNotCompleteException( + operation_id=operation_id, + status=response.get("status"), + operation_name=operation_name, + ) + + if status != "Succeeded": + raise OperationFailedException( + operation_id=operation_id, + status=status, + error=response.get("error"), + operation_name=operation_name, + ) + + return response.get("result") + + async def _retrieve_operation_result_async( + self, + url: Endpoint, + operation_id: str, + operation_name: str, + ) -> Dict: + response = ( + await self.request_async( + method="GET", + url=url, + params={"api-version": "1.1"}, + headers=self._get_common_headers(), + ) + ).json() + + status = response.get("status") + if status in ["NotStarted", "Running"]: + raise OperationNotCompleteException( + operation_id=operation_id, + status=response.get("status"), + operation_name=operation_name, + ) + + if status != "Succeeded": + raise OperationFailedException( + operation_id=operation_id, + status=status, + error=response.get("error"), + operation_name=operation_name, + ) + + return response.get("result") + @traced(name="documents_retrieve_ixp_extraction_result", run_type="uipath") def retrieve_ixp_extraction_result( self, @@ -1014,7 +1079,8 @@ def retrieve_ixp_extraction_result( ExtractionResponseIXP: The extraction response containing the extracted data. Raises: - IxpExtractionNotCompleteException: If the extraction is not yet complete. + OperationNotCompleteException: If the extraction is not yet complete. + OperationFailedException: If the extraction operation failed. Examples: ```python @@ -1032,21 +1098,12 @@ def retrieve_ixp_extraction_result( f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/extraction/result/{operation_id}" ) - result = self.request( - method="GET", + extraction_response = self._retrieve_operation_result( url=url, - params={"api-version": "1.1"}, - headers=self._get_common_headers(), - ).json() - - status = result.get("status") - if status in ["NotStarted", "Running"]: - raise ExtractionNotCompleteException( - operation_id=operation_id, - status=status, - ) + operation_id=operation_id, + operation_name="IXP extraction", + ) - extraction_response = result.get("result") extraction_response["projectId"] = project_id extraction_response["tag"] = tag extraction_response["documentTypeId"] = document_type_id @@ -1068,23 +1125,12 @@ async def retrieve_ixp_extraction_result_async( f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/extraction/result/{operation_id}" ) - result = ( - await self.request_async( - method="GET", - url=url, - params={"api-version": "1.1"}, - headers=self._get_common_headers(), - ) - ).json() - - status = result.get("status") - if status in ["NotStarted", "Running"]: - raise ExtractionNotCompleteException( - operation_id=operation_id, - status=status, - ) + extraction_response = await self._retrieve_operation_result_async( + url=url, + operation_id=operation_id, + operation_name="IXP extraction", + ) - extraction_response = result.get("result") extraction_response["projectId"] = project_id extraction_response["tag"] = tag extraction_response["documentTypeId"] = document_type_id @@ -1362,7 +1408,7 @@ def _start_extraction_validation( storage_bucket_name: str, storage_bucket_directory_path: str, extraction_response: ExtractionResponse, - ) -> str: + ) -> StartOperationResponse: if project_type == ProjectType.PRETRAINED: url = Endpoint( f"/du_/api/framework/projects/{project_id}/extractors/{document_type_id}/validation/start" @@ -1372,7 +1418,7 @@ def _start_extraction_validation( f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/validation/start" ) - return self.request( + operation_id = self.request( "POST", url=url, params={"api-version": 1.1}, @@ -1390,6 +1436,13 @@ def _start_extraction_validation( }, ).json()["operationId"] + return StartOperationResponse( + operation_id=operation_id, + document_id=extraction_response.extraction_result.document_id, + project_id=project_id, + tag=tag, + ) + async def _start_extraction_validation_async( self, project_id: str, @@ -1403,7 +1456,7 @@ async def _start_extraction_validation_async( storage_bucket_name: str, storage_bucket_directory_path: str, extraction_response: ExtractionResponse, - ) -> str: + ) -> StartOperationResponse: if project_type == ProjectType.PRETRAINED: url = Endpoint( f"/du_/api/framework/projects/{project_id}/extractors/{document_type_id}/validation/start" @@ -1413,7 +1466,7 @@ async def _start_extraction_validation_async( f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/validation/start" ) - return ( + operation_id = ( await self.request_async( "POST", url=url, @@ -1433,6 +1486,184 @@ async def _start_extraction_validation_async( ) ).json()["operationId"] + return StartOperationResponse( + operation_id=operation_id, + document_id=extraction_response.extraction_result.document_id, + project_id=project_id, + tag=tag, + ) + + @traced(name="documents_start_ixp_extraction_validation", run_type="uipath") + def start_ixp_extraction_validation( + self, + action_title: str, + action_priority: ActionPriority, + action_catalog: str, + action_folder: str, + storage_bucket_name: str, + storage_bucket_directory_path: str, + extraction_response: ExtractionResponseIXP, + ) -> StartOperationResponse: + """Start an IXP extraction validation action without waiting for results (non-blocking). + + Args: + action_title (str): The title of the validation action. + action_priority (ActionPriority): The priority of the validation action. + action_catalog (str): The catalog of the validation action. + action_folder (str): The folder of the validation action. + storage_bucket_name (str): The name of the storage bucket where validation data will be stored. + storage_bucket_directory_path (str): The directory path within the storage bucket. + extraction_response (ExtractionResponseIXP): The extraction response from the IXP extraction process. + + Returns: + StartOperationResponse: Contains the operation_id, document_id, project_id, and tag. + + Examples: + ```python + start_operation_response = service.start_ixp_extraction_validation( + action_title="Validate IXP Extraction", + action_priority=ActionPriority.HIGH, + action_catalog="DefaultCatalog", + action_folder="Validations", + storage_bucket_name="my-storage-bucket", + storage_bucket_directory_path="validations/ixp", + extraction_response=extraction_response, + ) + # start_operation_response can be used to poll for validation results later + ``` + """ + return self._start_extraction_validation( + project_id=extraction_response.project_id, + project_type=ProjectType.IXP, + tag=extraction_response.tag, + document_type_id=str(UUID(int=0)), + action_title=action_title, + action_priority=action_priority, + action_catalog=action_catalog, + action_folder=action_folder, + storage_bucket_name=storage_bucket_name, + storage_bucket_directory_path=storage_bucket_directory_path, + extraction_response=extraction_response, + ) + + @traced( + name="documents_start_ixp_extraction_validation_async", + run_type="uipath", + ) + async def start_ixp_extraction_validation_async( + self, + action_title: str, + action_priority: ActionPriority, + action_catalog: str, + action_folder: str, + storage_bucket_name: str, + storage_bucket_directory_path: str, + extraction_response: ExtractionResponseIXP, + ) -> StartOperationResponse: + """Asynchronous version of the [`start_ixp_extraction_validation`][uipath.platform.documents._documents_service.DocumentsService.start_ixp_extraction_validation] method.""" + return await self._start_extraction_validation_async( + project_id=extraction_response.project_id, + project_type=ProjectType.IXP, + tag=extraction_response.tag, + document_type_id=str(UUID(int=0)), + action_title=action_title, + action_priority=action_priority, + action_catalog=action_catalog, + action_folder=action_folder, + storage_bucket_name=storage_bucket_name, + storage_bucket_directory_path=storage_bucket_directory_path, + extraction_response=extraction_response, + ) + + @traced( + name="documents_retrieve_ixp_extraction_validation_result", + run_type="uipath", + ) + def retrieve_ixp_extraction_validation_result( + self, + project_id: str, + tag: str, + operation_id: str, + ) -> ValidateExtractionAction: + """Retrieve the result of an IXP create validate extraction action operation (single-shot, non-blocking). + + This method retrieves the result of an IXP create validate extraction action that was previously started + with `start_ixp_extraction_validation`. It does not poll - it makes a single request and + returns the result if available, or raises an exception if not complete. + + Args: + operation_id (str): The operation ID returned from `start_ixp_extraction_validation`. + project_id (str): The ID of the IXP project. + tag (str): The tag of the published project version. + + Returns: + ValidateExtractionAction: The validation action + + Raises: + OperationNotCompleteException: If the validation action is not yet complete. + OperationFailedException: If the validation action has failed. + + Examples: + ```python + # After receiving a callback/webhook that validation is complete: + validation_result = service.retrieve_ixp_extraction_validation_result( + operation_id=start_operation_response.operation_id, + project_id=start_operation_response.project_id, + tag=start_operation_response.tag, + ) + ``` + """ + document_type_id = str(UUID(int=0)) + + url = Endpoint( + f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/validation/result/{operation_id}" + ) + + result = self._retrieve_operation_result( + url=url, + operation_id=operation_id, + operation_name="IXP Create Validate Extraction Action", + ) + + result["projectId"] = project_id + result["projectType"] = ProjectType.IXP + result["tag"] = tag + result["documentTypeId"] = str(UUID(int=0)) + result["operationId"] = operation_id + + return ValidateExtractionAction.model_validate(result) + + @traced( + name="documents_retrieve_ixp_extraction_validation_result_async", + run_type="uipath", + ) + async def retrieve_ixp_extraction_validation_result_async( + self, + project_id: str, + tag: str, + operation_id: str, + ) -> ValidateExtractionAction: + """Asynchronous version of the [`retrieve_ixp_extraction_validation_result`][uipath.platform.documents._documents_service.DocumentsService.retrieve_ixp_extraction_validation_result] method.""" + document_type_id = str(UUID(int=0)) + + url = Endpoint( + f"/du_/api/framework/projects/{project_id}/{tag}/document-types/{document_type_id}/validation/result/{operation_id}" + ) + + result = await self._retrieve_operation_result_async( + url=url, + operation_id=operation_id, + operation_name="IXP Create Validate Extraction Action", + ) + + result["projectId"] = project_id + result["projectType"] = ProjectType.IXP + result["tag"] = tag + result["documentTypeId"] = str(UUID(int=0)) + result["operationId"] = operation_id + + return ValidateExtractionAction.model_validate(result) + def _get_classification_validation_result( self, project_id: str, @@ -1811,7 +2042,7 @@ def create_validate_extraction_action( storage_bucket_name=storage_bucket_name, storage_bucket_directory_path=storage_bucket_directory_path, extraction_response=extraction_response, - ) + ).operation_id return self._wait_for_create_validate_extraction_action( project_id=extraction_response.project_id, @@ -1833,19 +2064,21 @@ async def create_validate_extraction_action_async( extraction_response: ExtractionResponse, ) -> ValidateExtractionAction: """Asynchronous version of the [`create_validation_action`][uipath.platform.documents._documents_service.DocumentsService.create_validate_extraction_action] method.""" - operation_id = await self._start_extraction_validation_async( - project_id=extraction_response.project_id, - project_type=extraction_response.project_type, - tag=extraction_response.tag, - document_type_id=extraction_response.document_type_id, - action_title=action_title, - action_priority=action_priority, - action_catalog=action_catalog, - action_folder=action_folder, - storage_bucket_name=storage_bucket_name, - storage_bucket_directory_path=storage_bucket_directory_path, - extraction_response=extraction_response, - ) + operation_id = ( + await self._start_extraction_validation_async( + project_id=extraction_response.project_id, + project_type=extraction_response.project_type, + tag=extraction_response.tag, + document_type_id=extraction_response.document_type_id, + action_title=action_title, + action_priority=action_priority, + action_catalog=action_catalog, + action_folder=action_folder, + storage_bucket_name=storage_bucket_name, + storage_bucket_directory_path=storage_bucket_directory_path, + extraction_response=extraction_response, + ) + ).operation_id return await self._wait_for_create_validate_extraction_action_async( project_id=extraction_response.project_id, diff --git a/src/uipath/platform/documents/documents.py b/src/uipath/platform/documents/documents.py index ea07c395a..54ec253db 100644 --- a/src/uipath/platform/documents/documents.py +++ b/src/uipath/platform/documents/documents.py @@ -166,6 +166,12 @@ class ValidateExtractionAction(ValidationAction): """A model representing a validation action for document extraction.""" document_type_id: str = Field(alias="documentTypeId") + validated_extraction_result: Optional[ExtractionResult] = Field( + alias="validatedExtractionResults", default=None + ) + data_projection: Optional[List[FieldGroupValueProjection]] = Field( + alias="dataProjection", default=None + ) class Reference(BaseModel): @@ -244,8 +250,8 @@ class ClassificationResponse(BaseModel): ) -class StartExtractionResponse(BaseModel): - """A model representing the response from starting an extraction process. +class StartOperationResponse(BaseModel): + """A model representing the response from starting an operation. Attributes: operation_id (str): The ID of the extraction operation, used to poll for results. @@ -264,3 +270,7 @@ class StartExtractionResponse(BaseModel): document_id: str = Field(alias="documentId") project_id: str = Field(alias="projectId") tag: str | None = Field(default=None) + + +class StartExtractionResponse(StartOperationResponse): + """A model representing the response from starting an extraction operation.""" diff --git a/src/uipath/platform/errors/__init__.py b/src/uipath/platform/errors/__init__.py index dbb403395..582341129 100644 --- a/src/uipath/platform/errors/__init__.py +++ b/src/uipath/platform/errors/__init__.py @@ -9,7 +9,8 @@ - UnsupportedDataSourceException: Raised when an operation is attempted on an unsupported data source type - IngestionInProgressException: Raised when a search is attempted on an index during ingestion - BatchTransformNotCompleteException: Raised when attempting to get results from an incomplete batch transform -- IxpExtractionNotCompleteException: Raised when attempting to get results from an incomplete IXP extraction +- OperationNotCompleteException: Raised when attempting to get results from an incomplete operation +- OperationFailedException: Raised when an operation has failed - EnrichedException: Enriched HTTP error with detailed request/response information """ @@ -18,7 +19,8 @@ from ._enriched_exception import EnrichedException from ._folder_not_found_exception import FolderNotFoundException from ._ingestion_in_progress_exception import IngestionInProgressException -from ._ixp_extraction_not_complete_exception import ExtractionNotCompleteException +from ._operation_failed_exception import OperationFailedException +from ._operation_not_complete_exception import OperationNotCompleteException from ._secret_missing_error import SecretMissingError from ._unsupported_data_source_exception import UnsupportedDataSourceException @@ -28,7 +30,8 @@ "EnrichedException", "FolderNotFoundException", "IngestionInProgressException", - "ExtractionNotCompleteException", "SecretMissingError", + "OperationNotCompleteException", + "OperationFailedException", "UnsupportedDataSourceException", ] diff --git a/src/uipath/platform/errors/_ixp_extraction_not_complete_exception.py b/src/uipath/platform/errors/_ixp_extraction_not_complete_exception.py deleted file mode 100644 index f36da1771..000000000 --- a/src/uipath/platform/errors/_ixp_extraction_not_complete_exception.py +++ /dev/null @@ -1,14 +0,0 @@ -class ExtractionNotCompleteException(Exception): - """Raised when attempting to get results from an incomplete IXP extraction. - - This exception is raised when attempting to retrieve results from an IXP - extraction operation that has not yet completed successfully. - """ - - def __init__(self, operation_id: str, status: str): - self.operation_id = operation_id - self.status = status - self.message = ( - f"IXP extraction '{operation_id}' is not complete. Current status: {status}" - ) - super().__init__(self.message) diff --git a/src/uipath/platform/errors/_operation_failed_exception.py b/src/uipath/platform/errors/_operation_failed_exception.py new file mode 100644 index 000000000..216ef7d24 --- /dev/null +++ b/src/uipath/platform/errors/_operation_failed_exception.py @@ -0,0 +1,19 @@ +class OperationFailedException(Exception): + """Raised when attempting to get results from a failed operation. + + This exception is raised when attempting to retrieve results from operation + that failed to complete successfully. + """ + + def __init__( + self, + operation_id: str, + status: str, + error: str, + operation_name: str = "Operation", + ): + self.operation_id = operation_id + self.status = status + self.error = error + self.message = f"{operation_name} '{operation_id}' failed with status: {status} error: {error}" + super().__init__(self.message) diff --git a/src/uipath/platform/errors/_operation_not_complete_exception.py b/src/uipath/platform/errors/_operation_not_complete_exception.py new file mode 100644 index 000000000..50941353c --- /dev/null +++ b/src/uipath/platform/errors/_operation_not_complete_exception.py @@ -0,0 +1,14 @@ +class OperationNotCompleteException(Exception): + """Raised when attempting to get results from an incomplete operation. + + This exception is raised when attempting to retrieve results from operation + that has not yet completed successfully. + """ + + def __init__( + self, operation_id: str, status: str, operation_name: str = "Operation" + ): + self.operation_id = operation_id + self.status = status + self.message = f"{operation_name} '{operation_id}' is not complete. Current status: {status}" + super().__init__(self.message) diff --git a/src/uipath/platform/resume_triggers/_protocol.py b/src/uipath/platform/resume_triggers/_protocol.py index a23a88b9b..8d53791d7 100644 --- a/src/uipath/platform/resume_triggers/_protocol.py +++ b/src/uipath/platform/resume_triggers/_protocol.py @@ -38,7 +38,7 @@ from uipath.platform.context_grounding import DeepRagStatus from uipath.platform.errors import ( BatchTransformNotCompleteException, - ExtractionNotCompleteException, + OperationNotCompleteException, ) from uipath.platform.orchestrator.job import JobState from uipath.platform.resume_triggers._enums import PropertyName, TriggerMarker @@ -263,7 +263,7 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None: project_id, tag, trigger.item_key ) ) - except ExtractionNotCompleteException as e: + except OperationNotCompleteException as e: raise UiPathPendingTriggerError( ErrorCategory.SYSTEM, f"{e.message}", diff --git a/tests/sdk/services/test_documents_service.py b/tests/sdk/services/test_documents_service.py index 436a9770f..3b9b7c29e 100644 --- a/tests/sdk/services/test_documents_service.py +++ b/tests/sdk/services/test_documents_service.py @@ -11,15 +11,16 @@ from uipath.platform.documents import ( ActionPriority, ClassificationResult, + DocumentsService, ExtractionResponse, ProjectType, ValidateClassificationAction, ValidateExtractionAction, ) -from uipath.platform.documents._documents_service import ( # type: ignore[attr-defined] - DocumentsService, +from uipath.platform.errors import ( + OperationFailedException, + OperationNotCompleteException, ) -from uipath.platform.errors import ExtractionNotCompleteException @pytest.fixture @@ -61,7 +62,20 @@ def modern_extraction_response(documents_tests_data_path: Path) -> dict: # type @pytest.fixture def create_validation_action_response(documents_tests_data_path: Path) -> dict: # type: ignore with open( - documents_tests_data_path / "create_validation_action_response.json", + documents_tests_data_path + / "extraction_validation_action_response_unassigned.json", + "r", + ) as f: + return json.load(f) + + +@pytest.fixture +def extraction_validation_action_response_completed( + documents_tests_data_path: Path, +) -> dict: # type: ignore + with open( + documents_tests_data_path + / "extraction_validation_action_response_completed.json", "r", ) as f: return json.load(f) @@ -1424,6 +1438,8 @@ async def test_create_validate_extraction_action_pretrained( create_validation_action_response["tag"] = tag create_validation_action_response["documentTypeId"] = document_type_id create_validation_action_response["operationId"] = operation_id + create_validation_action_response["validatedExtractionResults"] = None + create_validation_action_response["dataProjection"] = None assert response.model_dump() == create_validation_action_response @pytest.mark.parametrize("mode", ["sync", "async"]) @@ -1526,6 +1542,8 @@ async def test_create_validate_extraction_action_ixp( create_validation_action_response["tag"] = tag create_validation_action_response["documentTypeId"] = document_type_id create_validation_action_response["operationId"] = operation_id + create_validation_action_response["validatedExtractionResults"] = None + create_validation_action_response["dataProjection"] = None assert response.model_dump() == create_validation_action_response @pytest.mark.parametrize("mode", ["sync", "async"]) @@ -1997,7 +2015,10 @@ async def test_retrieve_ixp_extraction_result_not_complete( ) # ACT & ASSERT - with pytest.raises(ExtractionNotCompleteException) as exc_info: + with pytest.raises( + OperationNotCompleteException, + match=f"IXP extraction '{operation_id}' is not complete. Current status: Running", + ) as exc_info: if mode == "async": await service.retrieve_ixp_extraction_result_async( project_id=project_id, @@ -2013,3 +2034,309 @@ async def test_retrieve_ixp_extraction_result_not_complete( assert exc_info.value.operation_id == operation_id assert exc_info.value.status == "Running" + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_retrieve_ixp_extraction_result_failed( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + mode: str, + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/staging/document-types/{UUID(int=0)}/extraction/result/{operation_id}?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + json={"status": "Failed", "error": "Dummy extraction error"}, + ) + + # ACT & ASSERT + with pytest.raises( + OperationFailedException, + match=f"IXP extraction '{operation_id}' failed with status: Failed error: Dummy extraction error", + ): + if mode == "async": + await service.retrieve_ixp_extraction_result_async( + project_id=project_id, + tag="staging", + operation_id=operation_id, + ) + else: + service.retrieve_ixp_extraction_result( + project_id=project_id, + tag="staging", + operation_id=operation_id, + ) + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_start_ixp_extraction_validation( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + ixp_extraction_response: dict, # type: ignore + mode: str, + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + tag = "live" + action_title = "TestAction" + action_priority = ActionPriority.HIGH + action_catalog = "TestCatalog" + action_folder = "TestFolder" + storage_bucket_name = "TestBucket" + storage_bucket_directory_path = "Test/Directory/Path" + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/{tag}/document-types/{UUID(int=0)}/validation/start?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + match_json={ + "extractionResult": ixp_extraction_response["extractionResult"], + "documentId": ixp_extraction_response["extractionResult"]["DocumentId"], + "actionTitle": action_title, + "actionPriority": action_priority, + "actionCatalog": action_catalog, + "actionFolder": action_folder, + "storageBucketName": storage_bucket_name, + "allowChangeOfDocumentType": True, + "storageBucketDirectoryPath": storage_bucket_directory_path, + }, + json={"operationId": operation_id}, + ) + + ixp_extraction_response["projectId"] = project_id + ixp_extraction_response["projectType"] = ProjectType.IXP.value + ixp_extraction_response["tag"] = tag + ixp_extraction_response["documentTypeId"] = str(UUID(int=0)) + + # ACT + if mode == "async": + response = await service.start_ixp_extraction_validation_async( + action_title=action_title, + action_priority=action_priority, + action_catalog=action_catalog, + action_folder=action_folder, + storage_bucket_name=storage_bucket_name, + storage_bucket_directory_path=storage_bucket_directory_path, + extraction_response=ExtractionResponse.model_validate( + ixp_extraction_response + ), + ) + else: + response = service.start_ixp_extraction_validation( + action_title=action_title, + action_priority=action_priority, + action_catalog=action_catalog, + action_folder=action_folder, + storage_bucket_name=storage_bucket_name, + storage_bucket_directory_path=storage_bucket_directory_path, + extraction_response=ExtractionResponse.model_validate( + ixp_extraction_response + ), + ) + + # ASSERT + assert response.model_dump() == { + "projectId": project_id, + "tag": tag, + "documentId": ixp_extraction_response["extractionResult"]["DocumentId"], + "operationId": operation_id, + } + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_retrieve_ixp_extraction_validation_result_unassigned( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + create_validation_action_response: dict, # type: ignore + mode: str, + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + tag = "live" + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/{tag}/document-types/{UUID(int=0)}/validation/result/{operation_id}?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + json={"status": "Succeeded", "result": create_validation_action_response}, + ) + + # ACT + if mode == "async": + response = await service.retrieve_ixp_extraction_validation_result_async( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + else: + response = service.retrieve_ixp_extraction_validation_result( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + + # ASSERT + create_validation_action_response["projectId"] = project_id + create_validation_action_response["projectType"] = ProjectType.IXP.value + create_validation_action_response["tag"] = tag + create_validation_action_response["documentTypeId"] = str(UUID(int=0)) + create_validation_action_response["operationId"] = operation_id + create_validation_action_response["validatedExtractionResults"] = None + create_validation_action_response["dataProjection"] = None + assert response.model_dump() == create_validation_action_response + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_retrieve_ixp_extraction_validation_result_completed( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + mode: str, + extraction_validation_action_response_completed: dict, # type: ignore + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + tag = "live" + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/{tag}/document-types/{UUID(int=0)}/validation/result/{operation_id}?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + json={ + "status": "Succeeded", + "result": extraction_validation_action_response_completed, + }, + ) + + # ACT + if mode == "async": + response = await service.retrieve_ixp_extraction_validation_result_async( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + else: + response = service.retrieve_ixp_extraction_validation_result( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + + # ASSERT + extraction_validation_action_response_completed["projectId"] = project_id + extraction_validation_action_response_completed["projectType"] = ( + ProjectType.IXP.value + ) + extraction_validation_action_response_completed["tag"] = tag + extraction_validation_action_response_completed["documentTypeId"] = str( + UUID(int=0) + ) + extraction_validation_action_response_completed["operationId"] = operation_id + assert response.model_dump() == extraction_validation_action_response_completed + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_retrieve_ixp_extraction_validation_result_not_complete( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + mode: str, + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + tag = "live" + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/{tag}/document-types/{UUID(int=0)}/validation/result/{operation_id}?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + json={"status": "Running"}, + ) + + # ACT & ASSERT + with pytest.raises( + OperationNotCompleteException, + match=f"IXP Create Validate Extraction Action '{operation_id}' is not complete. Current status: Running", + ) as exc_info: + if mode == "async": + await service.retrieve_ixp_extraction_validation_result_async( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + else: + service.retrieve_ixp_extraction_validation_result( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + + assert exc_info.value.operation_id == operation_id + assert exc_info.value.status == "Running" + + @pytest.mark.parametrize("mode", ["sync", "async"]) + @pytest.mark.asyncio + async def test_retrieve_ixp_extraction_validation_result_failed( + self, + httpx_mock: HTTPXMock, + service: DocumentsService, + base_url: str, + org: str, + tenant: str, + mode: str, + ): + # ARRANGE + project_id = str(uuid4()) + operation_id = str(uuid4()) + tag = "live" + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/du_/api/framework/projects/{project_id}/{tag}/document-types/{UUID(int=0)}/validation/result/{operation_id}?api-version=1.1", + status_code=200, + match_headers={"X-UiPath-Internal-ConsumptionSourceType": "CodedAgents"}, + json={"status": "Failed", "error": "Dummy error"}, + ) + + # ACT & ASSERT + with pytest.raises( + OperationFailedException, + match=f"IXP Create Validate Extraction Action '{operation_id}' failed with status: Failed error: Dummy error", + ): + if mode == "async": + await service.retrieve_ixp_extraction_validation_result_async( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) + else: + service.retrieve_ixp_extraction_validation_result( + project_id=project_id, + tag=tag, + operation_id=operation_id, + ) diff --git a/tests/sdk/services/tests_data/documents_service/extraction_validation_action_response_completed.json b/tests/sdk/services/tests_data/documents_service/extraction_validation_action_response_completed.json new file mode 100644 index 000000000..0f340b00e --- /dev/null +++ b/tests/sdk/services/tests_data/documents_service/extraction_validation_action_response_completed.json @@ -0,0 +1,1277 @@ +{ + "actionData": { + "type": "Validation", + "id": 10432640, + "status": "Completed", + "title": "Test Validation Action", + "priority": "Medium", + "taskCatalogName": "default_du_actions", + "taskUrl": "https://dummy.uipath.com/82e69757-09ff-4e6d-83e7-d530f2a99999/bd829329-42ff-40aa-96dc-95a781699999/actions_/tasks/10499999", + "folderPath": "Shared", + "folderId": 1744784, + "data": { + "automaticExtractionResultsPath": "TestDirectory/12c04908-67f0-f011-8196-6045bd99999/input_results.zip", + "validatedExtractionResultsPath": "TestDirectory/12c04908-67f0-f011-8196-6045bd99999/output_results.zip", + "documentRejectionDetails": null + }, + "action": "Completed", + "isDeleted": false, + "assignedToUser": { + "id": 4303796, + "emailAddress": "dummy.dummy@dummy.com" + }, + "creatorUser": null, + "deleterUser": null, + "lastModifierUser": { + "id": 4303796, + "emailAddress": "dummy.dummy@dummy.com" + }, + "completedByUser": { + "id": 4303796, + "emailAddress": "dummy.dummy@dummy.com" + }, + "creationTime": "2026-01-22T10:03:02.9434351Z", + "lastAssignedTime": "2026-01-22T10:03:45.587Z", + "completionTime": "2026-01-22T10:03:56.71Z", + "processingTime": null + }, + "validatedExtractionResults": { + "DocumentId": "47f037fe-66f0-f011-8196-6045bd99999", + "ResultsVersion": 1, + "ResultsDocument": { + "Bounds": { + "StartPage": 0, + "PageCount": 1, + "TextStartIndex": 0, + "TextLength": 635, + "PageRange": "1" + }, + "Language": "eng", + "DocumentGroup": "", + "DocumentCategory": "", + "DocumentTypeId": "00000000-0000-0000-0000-000000000000", + "DocumentTypeName": "Default", + "DocumentTypeDataVersion": 0, + "DataVersion": 1, + "DocumentTypeSource": "Automatic", + "DocumentTypeField": { + "Components": [], + "Value": "Default", + "UnformattedValue": "", + "Reference": { "TextStartIndex": 0, "TextLength": 0, "Tokens": [] }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + "Fields": [ + { + "FieldId": "Uber Receipt", + "FieldName": "Uber Receipt", + "FieldType": "Table", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Uber Receipt.Header", + "FieldName": "Header", + "FieldType": "Internal", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Date of Trip", + "FieldName": "Date of Trip", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Date of Trip", + "UnformattedValue": "Date of Trip", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Customer Name", + "FieldName": "Customer Name", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Customer Name", + "UnformattedValue": "Customer Name", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Total Amount Paid", + "FieldName": "Total Amount Paid", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Total Amount Paid", + "UnformattedValue": "Total Amount Paid", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Uber Receipt.Body", + "FieldName": "Body", + "FieldType": "Internal", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Date of Trip", + "FieldName": "Date of Trip", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "12/11/2023 00:00:00", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 565, + "TextLength": 17, + "Tokens": [ + { + "TextStartIndex": 565, + "TextLength": 8, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [47.2702, 464.3362, 33.9578, 5.9088] + ] + }, + { + "TextStartIndex": 574, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [47.2702, 500.5087, 6.6439, 5.9088] + ] + }, + { + "TextStartIndex": 574, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [47.2702, 507.8908, 1.4764, 5.9088] + ] + }, + { + "TextStartIndex": 578, + "TextLength": 4, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [47.2702, 511.5819, 16.2407, 5.9088] + ] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Customer Name", + "FieldName": "Customer Name", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Alex", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 41, + "TextLength": 5, + "Tokens": [ + { + "TextStartIndex": 41, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [84.2, 275.3536, 33.9578, 13.2947] + ] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Total Amount Paid", + "FieldName": "Total Amount Paid", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "66.79 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 584, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 584, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [146.2421, 473.933, 22.1464, 8.1246] + ] + }, + { + "TextStartIndex": 588, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [146.2421, 500.5087, 26.5757, 8.1246] + ] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Uber Receipt > Fare Breakdown", + "FieldName": "Uber Receipt > Fare Breakdown", + "FieldType": "Table", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Uber Receipt > Fare Breakdown.Header", + "FieldName": "Header", + "FieldType": "Internal", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Fare Amount", + "FieldName": "Fare Amount", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Fare Amount", + "UnformattedValue": "Fare Amount", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Subtotal", + "FieldName": "Subtotal", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Subtotal", + "UnformattedValue": "Subtotal", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Promotion Amount", + "FieldName": "Promotion Amount", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "Promotion Amount", + "UnformattedValue": "Promotion Amount", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Uber Receipt > Fare Breakdown.Body", + "FieldName": "Body", + "FieldType": "Internal", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [ + { + "FieldId": "Fare Amount", + "FieldName": "Fare Amount", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "65.29 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 595, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 595, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [187.6035, 493.8648, 14.026, 4.4316] + ] + }, + { + "TextStartIndex": 599, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [187.6035, 509.3672, 16.9789, 4.4316] + ] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Subtotal", + "FieldName": "Subtotal", + "FieldType": "Text", + "IsMissing": false, + "DataSource": "Automatic", + "Values": [ + { + "Components": [], + "Value": "65.29 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 606, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 606, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [224.5333, 491.6501, 14.7643, 5.1702] + ] + }, + { + "TextStartIndex": 610, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [ + [224.5333, 508.629, 18.4553, 5.1702] + ] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Promotion Amount", + "FieldName": "Promotion Amount", + "FieldType": "Text", + "IsMissing": true, + "DataSource": "Automatic", + "Values": [], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Value": "", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "DataVersion": 0, + "OperatorConfirmed": true, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "Tables": [ + { + "FieldId": "Uber Receipt", + "FieldName": "Uber Receipt", + "IsMissing": false, + "DataSource": "Automatic", + "DataVersion": 0, + "OperatorConfirmed": true, + "Values": [ + { + "OperatorConfirmed": true, + "Confidence": 1.0, + "OcrConfidence": 1.0, + "Cells": [ + { + "RowIndex": 0, + "ColumnIndex": 0, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Date of Trip", + "UnformattedValue": "Date of Trip", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 0, + "ColumnIndex": 1, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Customer Name", + "UnformattedValue": "Customer Name", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 0, + "ColumnIndex": 2, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Total Amount Paid", + "UnformattedValue": "Total Amount Paid", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 0, + "IsHeader": false, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "12/11/2023 00:00:00", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 565, + "TextLength": 17, + "Tokens": [ + { + "TextStartIndex": 565, + "TextLength": 8, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[47.2702, 464.3362, 33.9578, 5.9088]] + }, + { + "TextStartIndex": 574, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[47.2702, 500.5087, 6.6439, 5.9088]] + }, + { + "TextStartIndex": 574, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[47.2702, 507.8908, 1.4764, 5.9088]] + }, + { + "TextStartIndex": 578, + "TextLength": 4, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[47.2702, 511.5819, 16.2407, 5.9088]] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 1, + "IsHeader": false, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Alex", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 41, + "TextLength": 5, + "Tokens": [ + { + "TextStartIndex": 41, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[84.2, 275.3536, 33.9578, 13.2947]] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 2, + "IsHeader": false, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "66.79 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 584, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 584, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[146.2421, 473.933, 22.1464, 8.1246]] + }, + { + "TextStartIndex": 588, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[146.2421, 500.5087, 26.5757, 8.1246]] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + } + ], + "ColumnInfo": [ + { + "FieldId": "Date of Trip", + "FieldName": "Date of Trip", + "FieldType": "Text" + }, + { + "FieldId": "Customer Name", + "FieldName": "Customer Name", + "FieldType": "Text" + }, + { + "FieldId": "Total Amount Paid", + "FieldName": "Total Amount Paid", + "FieldType": "Text" + } + ], + "NumberOfRows": 2, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + }, + { + "FieldId": "Uber Receipt > Fare Breakdown", + "FieldName": "Uber Receipt > Fare Breakdown", + "IsMissing": false, + "DataSource": "Automatic", + "DataVersion": 0, + "OperatorConfirmed": true, + "Values": [ + { + "OperatorConfirmed": true, + "Confidence": 1.0, + "OcrConfidence": 1.0, + "Cells": [ + { + "RowIndex": 0, + "ColumnIndex": 0, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Fare Amount", + "UnformattedValue": "Fare Amount", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 0, + "ColumnIndex": 1, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Subtotal", + "UnformattedValue": "Subtotal", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 0, + "ColumnIndex": 2, + "IsHeader": true, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "Promotion Amount", + "UnformattedValue": "Promotion Amount", + "Reference": { + "TextStartIndex": 0, + "TextLength": 0, + "Tokens": [] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Unknown", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 0, + "IsHeader": false, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "65.29 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 595, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 595, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[187.6035, 493.8648, 14.026, 4.4316]] + }, + { + "TextStartIndex": 599, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[187.6035, 509.3672, 16.9789, 4.4316]] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 1, + "IsHeader": false, + "IsMissing": false, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [ + { + "Components": [], + "Value": "65.29 RON", + "UnformattedValue": "", + "Reference": { + "TextStartIndex": 606, + "TextLength": 9, + "Tokens": [ + { + "TextStartIndex": 606, + "TextLength": 3, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[224.5333, 491.6501, 14.7643, 5.1702]] + }, + { + "TextStartIndex": 610, + "TextLength": 5, + "Page": 0, + "PageWidth": 595.0, + "PageHeight": 842.0, + "Boxes": [[224.5333, 508.629, 18.4553, 5.1702]] + } + ] + }, + "DerivedFields": [], + "Confidence": 1.0, + "OperatorConfirmed": true, + "OcrConfidence": 1.0, + "TextType": "Text", + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + { + "RowIndex": 1, + "ColumnIndex": 2, + "IsHeader": false, + "IsMissing": true, + "OperatorConfirmed": true, + "DataSource": "Automatic", + "DataVersion": 0, + "Values": [] + } + ], + "ColumnInfo": [ + { + "FieldId": "Fare Amount", + "FieldName": "Fare Amount", + "FieldType": "Text" + }, + { + "FieldId": "Subtotal", + "FieldName": "Subtotal", + "FieldType": "Text" + }, + { + "FieldId": "Promotion Amount", + "FieldName": "Promotion Amount", + "FieldType": "Text" + } + ], + "NumberOfRows": 2, + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ], + "ValidatorNotes": "", + "ValidatorNotesInfo": "" + } + ] + }, + "ExtractorPayloads": null, + "BusinessRulesResults": null + }, + "dataProjection": [ + { + "fieldGroupName": "Uber Receipt", + "fieldValues": [ + { + "id": "Date of Trip", + "name": "Date of Trip", + "value": "12/11/2023 00:00:00", + "unformattedValue": "", + "confidence": 1, + "ocrConfidence": 1, + "type": "Text" + }, + { + "id": "Customer Name", + "name": "Customer Name", + "value": "Alex", + "unformattedValue": "", + "confidence": 1, + "ocrConfidence": 1, + "type": "Text" + }, + { + "id": "Total Amount Paid", + "name": "Total Amount Paid", + "value": "66.79 RON", + "unformattedValue": "", + "confidence": 1, + "ocrConfidence": 1, + "type": "Text" + } + ] + }, + { + "fieldGroupName": "Uber Receipt > Fare Breakdown", + "fieldValues": [ + { + "id": "Fare Amount", + "name": "Fare Amount", + "value": "65.29 RON", + "unformattedValue": "", + "confidence": 1, + "ocrConfidence": 1, + "type": "Text" + }, + { + "id": "Subtotal", + "name": "Subtotal", + "value": "65.29 RON", + "unformattedValue": "", + "confidence": 1, + "ocrConfidence": 1, + "type": "Text" + }, + { + "id": "Promotion Amount", + "name": "Promotion Amount", + "value": null, + "unformattedValue": null, + "confidence": null, + "ocrConfidence": null, + "type": "Text" + } + ] + } + ], + "actionStatus": "Completed" +} diff --git a/tests/sdk/services/tests_data/documents_service/create_validation_action_response.json b/tests/sdk/services/tests_data/documents_service/extraction_validation_action_response_unassigned.json similarity index 100% rename from tests/sdk/services/tests_data/documents_service/create_validation_action_response.json rename to tests/sdk/services/tests_data/documents_service/extraction_validation_action_response_unassigned.json