diff --git a/docs/extras/code_samples/idcard_fr_v2.txt b/docs/extras/code_samples/idcard_fr_v2.txt new file mode 100644 index 00000000..18e9f155 --- /dev/null +++ b/docs/extras/code_samples/idcard_fr_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 Carte Nationale d'Identité by passing the appropriate type +result = input_doc.parse(documents.fr.TypeIdCardV2) + +# Print a brief summary of the parsed data +print(result.document) diff --git a/docs/predictions/standard/documents/fr/id_card_v2.rst b/docs/predictions/standard/documents/fr/id_card_v2.rst new file mode 100644 index 00000000..f47dc5c9 --- /dev/null +++ b/docs/predictions/standard/documents/fr/id_card_v2.rst @@ -0,0 +1,10 @@ +Carte Nationale d'Identité V2 +----------------------------- + +**Sample Code:** + +.. literalinclude:: /extras/code_samples/idcard_fr_v2.txt + :language: Python + +.. autoclass:: mindee.documents.fr.IdCardV2 + :members: diff --git a/docs/predictions/standard/fr.rst b/docs/predictions/standard/fr.rst index e7715d0f..448c5a87 100644 --- a/docs/predictions/standard/fr.rst +++ b/docs/predictions/standard/fr.rst @@ -2,6 +2,7 @@ France ====== .. include:: ./documents/fr/id_card_v1.rst +.. include:: ./documents/fr/id_card_v2.rst .. include:: ./documents/fr/carte_vitale_v1.rst .. include:: ./documents/fr/carte_grise_v1.rst .. include:: ./documents/fr/bank_account_details_v1.rst diff --git a/mindee/client.py b/mindee/client.py index 39abf6b2..7317a8c3 100644 --- a/mindee/client.py +++ b/mindee/client.py @@ -434,6 +434,11 @@ def _init_default_endpoints(self) -> None: url_name="idcard_fr", version="1", ), + ConfigSpec( + doc_class=documents.fr.IdCardV2, + url_name="idcard_fr", + version="2", + ), ConfigSpec( doc_class=documents.fr.CarteVitaleV1, url_name="carte_vitale", diff --git a/mindee/documents/fr/__init__.py b/mindee/documents/fr/__init__.py index 81155374..c57266e1 100644 --- a/mindee/documents/fr/__init__.py +++ b/mindee/documents/fr/__init__.py @@ -9,3 +9,4 @@ 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 +from .id_card.id_card_v2 import IdCardV2, TypeIdCardV2 diff --git a/mindee/documents/fr/id_card/id_card_v2.py b/mindee/documents/fr/id_card/id_card_v2.py new file mode 100644 index 00000000..3d09c5b0 --- /dev/null +++ b/mindee/documents/fr/id_card/id_card_v2.py @@ -0,0 +1,177 @@ +from typing import List, Optional, TypeVar + +from mindee.documents.base import Document, TypeApiPrediction, clean_out_string +from mindee.fields.classification import ClassificationField +from mindee.fields.date import DateField +from mindee.fields.text import TextField + + +class IdCardV2(Document): + """Carte Nationale d'Identité v2 prediction results.""" + + alternate_name: TextField + """The alternate name of the card holder.""" + authority: TextField + """The name of the issuing authority.""" + birth_date: DateField + """The date of birth of the card holder.""" + birth_place: TextField + """The place of birth of the card holder.""" + card_access_number: TextField + """The card access number (CAN).""" + document_number: TextField + """The document number.""" + document_side: ClassificationField + """The sides of the document which are visible.""" + document_type: ClassificationField + """The document type or format.""" + expiry_date: DateField + """The expiry date of the identification card.""" + gender: TextField + """The gender of the card holder.""" + given_names: List[TextField] + """The given name(s) of the card holder.""" + issue_date: DateField + """The date of issue of the identification card.""" + mrz1: TextField + """The Machine Readable Zone, first line.""" + mrz2: TextField + """The Machine Readable Zone, second line.""" + mrz3: TextField + """The Machine Readable Zone, third line.""" + nationality: TextField + """The nationality of the card holder.""" + surname: TextField + """The surname of the card holder.""" + + def __init__( + self, + api_prediction=None, + input_source=None, + page_n: Optional[int] = None, + ): + """ + Carte Nationale d'Identité 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="id_card", + 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.alternate_name = TextField( + api_prediction["alternate_name"], + page_id=page_n, + ) + self.authority = TextField( + api_prediction["authority"], + page_id=page_n, + ) + self.birth_date = DateField( + api_prediction["birth_date"], + page_id=page_n, + ) + self.birth_place = TextField( + api_prediction["birth_place"], + page_id=page_n, + ) + self.card_access_number = TextField( + api_prediction["card_access_number"], + page_id=page_n, + ) + self.document_number = TextField( + api_prediction["document_number"], + page_id=page_n, + ) + self.document_side = ClassificationField( + api_prediction.get("document_side", {}), + page_id=page_n, + ) + self.document_type = ClassificationField( + api_prediction.get("document_type", {}), + page_id=page_n, + ) + self.expiry_date = DateField( + api_prediction["expiry_date"], + page_id=page_n, + ) + self.gender = TextField( + api_prediction["gender"], + page_id=page_n, + ) + self.given_names = [ + TextField(prediction, page_id=page_n) + for prediction in api_prediction["given_names"] + ] + self.issue_date = DateField( + api_prediction["issue_date"], + page_id=page_n, + ) + self.mrz1 = TextField( + api_prediction["mrz1"], + page_id=page_n, + ) + self.mrz2 = TextField( + api_prediction["mrz2"], + page_id=page_n, + ) + self.mrz3 = TextField( + api_prediction["mrz3"], + page_id=page_n, + ) + self.nationality = TextField( + api_prediction["nationality"], + page_id=page_n, + ) + self.surname = TextField( + api_prediction["surname"], + page_id=page_n, + ) + + def __str__(self) -> str: + given_names = f"\n { ' ' * 15 }".join( + [str(item) for item in self.given_names], + ) + return clean_out_string( + "FR Carte Nationale d'Identité V2 Prediction\n" + "===========================================\n" + f":Filename: {self.filename or ''}\n" + f":Document Type: {self.document_type}\n" + f":Document Sides: {self.document_side}\n" + f":Nationality: {self.nationality}\n" + f":Card Access Number: {self.card_access_number}\n" + f":Document Number: {self.document_number}\n" + f":Given Name(s): {given_names}\n" + f":Surname: {self.surname}\n" + f":Alternate Name: {self.alternate_name}\n" + f":Date of Birth: {self.birth_date}\n" + f":Place of Birth: {self.birth_place}\n" + f":Gender: {self.gender}\n" + f":Expiry Date: {self.expiry_date}\n" + f":Mrz Line 1: {self.mrz1}\n" + f":Mrz Line 2: {self.mrz2}\n" + f":Mrz Line 3: {self.mrz3}\n" + f":Date of Issue: {self.issue_date}\n" + f":Issuing Authority: {self.authority}\n" + ) + + +TypeIdCardV2 = TypeVar( + "TypeIdCardV2", + bound=IdCardV2, +) diff --git a/tests/data b/tests/data index d7c235ef..9af816fc 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit d7c235ef536607d8103e21000716c831b0615ff7 +Subproject commit 9af816fc701862298937ca0a00860bc31060e775 diff --git a/tests/documents/fr/test_id_card_v2.py b/tests/documents/fr/test_id_card_v2.py new file mode 100644 index 00000000..3347961a --- /dev/null +++ b/tests/documents/fr/test_id_card_v2.py @@ -0,0 +1,57 @@ +import json + +import pytest + +from mindee.documents.fr import IdCardV2 + +FR_ID_CARD_DATA_DIR = "./tests/data/products/idcard_fr" +FILE_PATH_FR_ID_CARD_V2_COMPLETE = f"{ FR_ID_CARD_DATA_DIR }/response_v2/complete.json" +FILE_PATH_FR_ID_CARD_V2_EMPTY = f"{ FR_ID_CARD_DATA_DIR }/response_v2/empty.json" + + +@pytest.fixture +def id_card_v2_doc() -> IdCardV2: + json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_COMPLETE, encoding="utf-8")) + return IdCardV2(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def id_card_v2_doc_empty() -> IdCardV2: + json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_EMPTY, encoding="utf-8")) + return IdCardV2(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def id_card_v2_page0(): + json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_COMPLETE, encoding="utf-8")) + return IdCardV2(json_data["document"]["inference"]["pages"][0], page_n=0) + + +def test_empty_doc_constructor(id_card_v2_doc_empty): + assert id_card_v2_doc_empty.nationality.value is None + assert id_card_v2_doc_empty.card_access_number.value is None + assert id_card_v2_doc_empty.document_number.value is None + assert len(id_card_v2_doc_empty.given_names) == 0 + assert id_card_v2_doc_empty.surname.value is None + assert id_card_v2_doc_empty.alternate_name.value is None + assert id_card_v2_doc_empty.birth_date.value is None + assert id_card_v2_doc_empty.birth_place.value is None + assert id_card_v2_doc_empty.gender.value is None + assert id_card_v2_doc_empty.expiry_date.value is None + assert id_card_v2_doc_empty.mrz1.value is None + assert id_card_v2_doc_empty.mrz2.value is None + assert id_card_v2_doc_empty.mrz3.value is None + assert id_card_v2_doc_empty.issue_date.value is None + assert id_card_v2_doc_empty.authority.value is None + + +def test_doc_constructor(id_card_v2_doc): + file_path = f"{ FR_ID_CARD_DATA_DIR }/response_v2/doc_to_string.rst" + reference_str = open(file_path, "r", encoding="utf-8").read() + assert str(id_card_v2_doc) == reference_str + + +def test_page0_constructor(id_card_v2_page0): + file_path = f"{ FR_ID_CARD_DATA_DIR }/response_v2/page0_to_string.rst" + reference_str = open(file_path, "r", encoding="utf-8").read() + assert str(id_card_v2_page0) == reference_str