diff --git a/docs/extras/code_samples/bank_account_details_v2.txt b/docs/extras/code_samples/bank_account_details_v2.txt new file mode 100644 index 00000000..1fe53adb --- /dev/null +++ b/docs/extras/code_samples/bank_account_details_v2.txt @@ -0,0 +1,13 @@ +from mindee import Client, documents + +# Init a new client +mindee_client = Client(api_key="my-api-key") + +# Load a file from disk +input_doc = mindee_client.doc_from_path("/path/to/the/file.ext") + +# Parse the Bank Account Details by passing the appropriate type +result = input_doc.parse(documents.fr.TypeBankAccountDetailsV2) + +# Print a brief summary of the parsed data +print(result.document) diff --git a/docs/extras/code_samples/invoice_splitter_v1_async.txt b/docs/extras/code_samples/invoice_splitter_v1_async.txt index c78b840e..71808b68 100644 --- a/docs/extras/code_samples/invoice_splitter_v1_async.txt +++ b/docs/extras/code_samples/invoice_splitter_v1_async.txt @@ -8,7 +8,6 @@ mindee_client = Client(api_key="my-api-key") input_doc = mindee_client.doc_from_path("/path/to/the/file.ext") # Put the document class in a local variable to keep the code DRY - doc_class = documents.TypeInvoiceSplitterV1 # Limit the amount of API calls to retrieve your document diff --git a/docs/predictions/standard/documents/fr/bank_account_details_v2.rst b/docs/predictions/standard/documents/fr/bank_account_details_v2.rst new file mode 100644 index 00000000..ed8e1dfb --- /dev/null +++ b/docs/predictions/standard/documents/fr/bank_account_details_v2.rst @@ -0,0 +1,13 @@ +Bank Account Details V2 +----------------------- + +**Sample Code:** + +.. literalinclude:: /extras/code_samples/bank_account_details_v2.txt + :language: Python + +.. autoclass:: mindee.documents.fr.BankAccountDetailsV2 + :members: + +.. autoclass:: mindee.documents.fr.BankAccountDetailsV2Bban + :members: diff --git a/docs/predictions/standard/fr.rst b/docs/predictions/standard/fr.rst index 03dd4b39..e7715d0f 100644 --- a/docs/predictions/standard/fr.rst +++ b/docs/predictions/standard/fr.rst @@ -5,3 +5,4 @@ France .. include:: ./documents/fr/carte_vitale_v1.rst .. include:: ./documents/fr/carte_grise_v1.rst .. include:: ./documents/fr/bank_account_details_v1.rst +.. include:: ./documents/fr/bank_account_details_v2.rst diff --git a/mindee/client.py b/mindee/client.py index 98532233..39abf6b2 100644 --- a/mindee/client.py +++ b/mindee/client.py @@ -444,6 +444,11 @@ def _init_default_endpoints(self) -> None: url_name="bank_account_details", version="1", ), + ConfigSpec( + doc_class=documents.fr.BankAccountDetailsV2, + url_name="bank_account_details", + version="2", + ), ConfigSpec( doc_class=documents.eu.LicensePlateV1, url_name="license_plates", diff --git a/mindee/documents/fr/__init__.py b/mindee/documents/fr/__init__.py index 3fba88a3..81155374 100644 --- a/mindee/documents/fr/__init__.py +++ b/mindee/documents/fr/__init__.py @@ -2,6 +2,10 @@ BankAccountDetailsV1, TypeBankAccountDetailsV1, ) +from .bank_account_details.bank_account_details_v2 import ( + BankAccountDetailsV2, + TypeBankAccountDetailsV2, +) from .carte_grise.carte_grise_v1 import CarteGriseV1, TypeCarteGriseV1 from .carte_vitale.carte_vitale_v1 import CarteVitaleV1, TypeCarteVitaleV1 from .id_card.id_card_v1 import IdCardV1, TypeIdCardV1 diff --git a/mindee/documents/fr/bank_account_details/bank_account_details_v2.py b/mindee/documents/fr/bank_account_details/bank_account_details_v2.py new file mode 100644 index 00000000..0b557b2b --- /dev/null +++ b/mindee/documents/fr/bank_account_details/bank_account_details_v2.py @@ -0,0 +1,82 @@ +from typing import Optional, TypeVar + +from mindee.documents.base import Document, TypeApiPrediction, clean_out_string +from mindee.fields.text import TextField + +from .bank_account_details_v2_bban import BankAccountDetailsV2Bban + + +class BankAccountDetailsV2(Document): + """Bank Account Details v2 prediction results.""" + + account_holders_names: TextField + """Full extraction of the account holders names.""" + bban: BankAccountDetailsV2Bban + """Full extraction of BBAN, including: branch code, bank code, account and key.""" + iban: TextField + """Full extraction of the IBAN number.""" + swift_code: TextField + """Full extraction of the SWIFT code.""" + + def __init__( + self, + api_prediction=None, + input_source=None, + page_n: Optional[int] = None, + ): + """ + Bank Account Details v2 prediction results. + + :param api_prediction: Raw prediction from HTTP response + :param input_source: Input object + :param page_n: Page number for multi pages pdf input + """ + super().__init__( + input_source=input_source, + document_type="bank_account_details", + api_prediction=api_prediction, + page_n=page_n, + ) + self._build_from_api_prediction(api_prediction["prediction"], page_n=page_n) + + def _build_from_api_prediction( + self, api_prediction: TypeApiPrediction, page_n: Optional[int] = None + ) -> None: + """ + Build the object from the prediction API JSON. + + :param api_prediction: Raw prediction from HTTP response + :param page_n: Page number + """ + self.account_holders_names = TextField( + api_prediction["account_holders_names"], + page_id=page_n, + ) + self.bban = BankAccountDetailsV2Bban( + api_prediction["bban"], + page_id=page_n, + ) + self.iban = TextField( + api_prediction["iban"], + page_id=page_n, + ) + self.swift_code = TextField( + api_prediction["swift_code"], + page_id=page_n, + ) + + def __str__(self) -> str: + return clean_out_string( + "FR Bank Account Details V2 Prediction\n" + "=====================================\n" + f":Filename: {self.filename or ''}\n" + f":Account Holder's Names: {self.account_holders_names}\n" + f":Basic Bank Account Number:\n{self.bban.to_field_list()}\n" + f":IBAN: {self.iban}\n" + f":SWIFT Code: {self.swift_code}\n" + ) + + +TypeBankAccountDetailsV2 = TypeVar( + "TypeBankAccountDetailsV2", bound=BankAccountDetailsV2 +) diff --git a/mindee/documents/fr/bank_account_details/bank_account_details_v2_bban.py b/mindee/documents/fr/bank_account_details/bank_account_details_v2_bban.py new file mode 100644 index 00000000..8e11e64f --- /dev/null +++ b/mindee/documents/fr/bank_account_details/bank_account_details_v2_bban.py @@ -0,0 +1,72 @@ +from typing import Dict, Optional + +from mindee.fields.base import FieldConfidenceMixin, FieldPositionMixin, TypePrediction + + +class BankAccountDetailsV2Bban(FieldPositionMixin, FieldConfidenceMixin): + """Full extraction of BBAN, including: branch code, bank code, account and key.""" + + bban_bank_code: Optional[str] + """The BBAN bank code outputted as a string.""" + bban_branch_code: Optional[str] + """The BBAN branch code outputted as a string.""" + bban_key: Optional[str] + """The BBAN key outputted as a string.""" + bban_number: Optional[str] + """The BBAN Account number outputted as a string.""" + page_n: int + """The document page on which the information was found.""" + + def __init__( + self, + prediction: TypePrediction, + page_id: Optional[int] = None, + ): + self._set_confidence(prediction) + self._set_position(prediction) + + if page_id is None: + try: + self.page_n = prediction["page_id"] + except KeyError: + pass + else: + self.page_n = page_id + + self.bban_bank_code = prediction["bban_bank_code"] + self.bban_branch_code = prediction["bban_branch_code"] + self.bban_key = prediction["bban_key"] + self.bban_number = prediction["bban_number"] + + def _printable_values(self) -> Dict[str, str]: + """Return values for printing.""" + return { + "bban_bank_code": self.bban_bank_code + if self.bban_bank_code is not None + else "", + "bban_branch_code": self.bban_branch_code + if self.bban_branch_code is not None + else "", + "bban_key": self.bban_key if self.bban_key is not None else "", + "bban_number": self.bban_number if self.bban_number is not None else "", + } + + def to_field_list(self) -> str: + """Output the object in a format suitable for inclusion in an rST field list.""" + printable = self._printable_values() + return ( + f" :Bank Code: {printable['bban_bank_code']}\n" + f" :Branch Code: {printable['bban_branch_code']}\n" + f" :Key: {printable['bban_key']}\n" + f" :Account Number: {printable['bban_number']}" + ) + + def __str__(self) -> str: + """Default string representation.""" + printable = self._printable_values() + return ( + f"Bank Code: {printable['bban_bank_code']}, " + f"Branch Code: {printable['bban_branch_code']}, " + f"Key: {printable['bban_key']}, " + f"Account Number: {printable['bban_number']}, " + ).strip() diff --git a/tests/data b/tests/data index b4b725fe..408bebe2 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit b4b725fe0b4654938162a80ef8190c64c6bbb7d2 +Subproject commit 408bebe22c00341ba225319f3478c4e29b2611b5 diff --git a/tests/documents/fr/test_bank_account_details_v2.py b/tests/documents/fr/test_bank_account_details_v2.py new file mode 100644 index 00000000..32bea257 --- /dev/null +++ b/tests/documents/fr/test_bank_account_details_v2.py @@ -0,0 +1,55 @@ +import json + +import pytest + +from mindee.documents.fr import BankAccountDetailsV2 + +FR_BANK_ACCOUNT_DETAILS_DATA_DIR = "./tests/data/fr/bank_account_details" +FILE_PATH_FR_BANK_ACCOUNT_DETAILS_V2_COMPLETE = ( + f"{ FR_BANK_ACCOUNT_DETAILS_DATA_DIR }/response_v2/complete.json" +) +FILE_PATH_FR_BANK_ACCOUNT_DETAILS_V2_EMPTY = ( + f"{ FR_BANK_ACCOUNT_DETAILS_DATA_DIR }/response_v2/empty.json" +) + + +@pytest.fixture +def bank_account_details_v2_doc() -> BankAccountDetailsV2: + json_data = json.load(open(FILE_PATH_FR_BANK_ACCOUNT_DETAILS_V2_COMPLETE)) + return BankAccountDetailsV2(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def bank_account_details_v2_doc_empty() -> BankAccountDetailsV2: + json_data = json.load(open(FILE_PATH_FR_BANK_ACCOUNT_DETAILS_V2_EMPTY)) + return BankAccountDetailsV2(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def bank_account_details_v2_page0(): + json_data = json.load(open(FILE_PATH_FR_BANK_ACCOUNT_DETAILS_V2_COMPLETE)) + return BankAccountDetailsV2( + json_data["document"]["inference"]["pages"][0], page_n=0 + ) + + +def test_empty_doc_constructor(bank_account_details_v2_doc_empty): + assert bank_account_details_v2_doc_empty.account_holders_names.value is None + assert bank_account_details_v2_doc_empty.bban.bban_bank_code is None + assert bank_account_details_v2_doc_empty.bban.bban_branch_code is None + assert bank_account_details_v2_doc_empty.bban.bban_key is None + assert bank_account_details_v2_doc_empty.bban.bban_number is None + assert bank_account_details_v2_doc_empty.iban.value is None + assert bank_account_details_v2_doc_empty.swift_code.value is None + + +def test_doc_constructor(bank_account_details_v2_doc): + file_path = f"{ FR_BANK_ACCOUNT_DETAILS_DATA_DIR }/response_v2/doc_to_string.rst" + reference_str = open(file_path, "r", encoding="utf-8").read() + assert str(bank_account_details_v2_doc) == reference_str + + +def test_page0_constructor(bank_account_details_v2_page0): + file_path = f"{ FR_BANK_ACCOUNT_DETAILS_DATA_DIR }/response_v2/page0_to_string.rst" + reference_str = open(file_path, "r", encoding="utf-8").read() + assert str(bank_account_details_v2_page0) == reference_str