Skip to content

Commit b670da9

Browse files
authored
✨ add support for FR ID card v2 (#157)
1 parent e322198 commit b670da9

File tree

8 files changed

+265
-1
lines changed

8 files changed

+265
-1
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from mindee import Client, documents
2+
3+
# Init a new client
4+
mindee_client = Client(api_key="my-api-key")
5+
6+
# Load a file from disk
7+
input_doc = mindee_client.doc_from_path("/path/to/the/file.ext")
8+
9+
# Parse the Carte Nationale d'Identité by passing the appropriate type
10+
result = input_doc.parse(documents.fr.TypeIdCardV2)
11+
12+
# Print a brief summary of the parsed data
13+
print(result.document)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Carte Nationale d'Identité V2
2+
-----------------------------
3+
4+
**Sample Code:**
5+
6+
.. literalinclude:: /extras/code_samples/idcard_fr_v2.txt
7+
:language: Python
8+
9+
.. autoclass:: mindee.documents.fr.IdCardV2
10+
:members:

docs/predictions/standard/fr.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ France
22
======
33

44
.. include:: ./documents/fr/id_card_v1.rst
5+
.. include:: ./documents/fr/id_card_v2.rst
56
.. include:: ./documents/fr/carte_vitale_v1.rst
67
.. include:: ./documents/fr/carte_grise_v1.rst
78
.. include:: ./documents/fr/bank_account_details_v1.rst

mindee/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,11 @@ def _init_default_endpoints(self) -> None:
434434
url_name="idcard_fr",
435435
version="1",
436436
),
437+
ConfigSpec(
438+
doc_class=documents.fr.IdCardV2,
439+
url_name="idcard_fr",
440+
version="2",
441+
),
437442
ConfigSpec(
438443
doc_class=documents.fr.CarteVitaleV1,
439444
url_name="carte_vitale",

mindee/documents/fr/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from .carte_grise.carte_grise_v1 import CarteGriseV1, TypeCarteGriseV1
1010
from .carte_vitale.carte_vitale_v1 import CarteVitaleV1, TypeCarteVitaleV1
1111
from .id_card.id_card_v1 import IdCardV1, TypeIdCardV1
12+
from .id_card.id_card_v2 import IdCardV2, TypeIdCardV2
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
from typing import List, Optional, TypeVar
2+
3+
from mindee.documents.base import Document, TypeApiPrediction, clean_out_string
4+
from mindee.fields.classification import ClassificationField
5+
from mindee.fields.date import DateField
6+
from mindee.fields.text import TextField
7+
8+
9+
class IdCardV2(Document):
10+
"""Carte Nationale d'Identité v2 prediction results."""
11+
12+
alternate_name: TextField
13+
"""The alternate name of the card holder."""
14+
authority: TextField
15+
"""The name of the issuing authority."""
16+
birth_date: DateField
17+
"""The date of birth of the card holder."""
18+
birth_place: TextField
19+
"""The place of birth of the card holder."""
20+
card_access_number: TextField
21+
"""The card access number (CAN)."""
22+
document_number: TextField
23+
"""The document number."""
24+
document_side: ClassificationField
25+
"""The sides of the document which are visible."""
26+
document_type: ClassificationField
27+
"""The document type or format."""
28+
expiry_date: DateField
29+
"""The expiry date of the identification card."""
30+
gender: TextField
31+
"""The gender of the card holder."""
32+
given_names: List[TextField]
33+
"""The given name(s) of the card holder."""
34+
issue_date: DateField
35+
"""The date of issue of the identification card."""
36+
mrz1: TextField
37+
"""The Machine Readable Zone, first line."""
38+
mrz2: TextField
39+
"""The Machine Readable Zone, second line."""
40+
mrz3: TextField
41+
"""The Machine Readable Zone, third line."""
42+
nationality: TextField
43+
"""The nationality of the card holder."""
44+
surname: TextField
45+
"""The surname of the card holder."""
46+
47+
def __init__(
48+
self,
49+
api_prediction=None,
50+
input_source=None,
51+
page_n: Optional[int] = None,
52+
):
53+
"""
54+
Carte Nationale d'Identité v2 prediction results.
55+
56+
:param api_prediction: Raw prediction from HTTP response
57+
:param input_source: Input object
58+
:param page_n: Page number for multi pages pdf input
59+
"""
60+
super().__init__(
61+
input_source=input_source,
62+
document_type="id_card",
63+
api_prediction=api_prediction,
64+
page_n=page_n,
65+
)
66+
self._build_from_api_prediction(api_prediction["prediction"], page_n=page_n)
67+
68+
def _build_from_api_prediction(
69+
self, api_prediction: TypeApiPrediction, page_n: Optional[int] = None
70+
) -> None:
71+
"""
72+
Build the object from the prediction API JSON.
73+
74+
:param api_prediction: Raw prediction from HTTP response
75+
:param page_n: Page number
76+
"""
77+
self.alternate_name = TextField(
78+
api_prediction["alternate_name"],
79+
page_id=page_n,
80+
)
81+
self.authority = TextField(
82+
api_prediction["authority"],
83+
page_id=page_n,
84+
)
85+
self.birth_date = DateField(
86+
api_prediction["birth_date"],
87+
page_id=page_n,
88+
)
89+
self.birth_place = TextField(
90+
api_prediction["birth_place"],
91+
page_id=page_n,
92+
)
93+
self.card_access_number = TextField(
94+
api_prediction["card_access_number"],
95+
page_id=page_n,
96+
)
97+
self.document_number = TextField(
98+
api_prediction["document_number"],
99+
page_id=page_n,
100+
)
101+
self.document_side = ClassificationField(
102+
api_prediction.get("document_side", {}),
103+
page_id=page_n,
104+
)
105+
self.document_type = ClassificationField(
106+
api_prediction.get("document_type", {}),
107+
page_id=page_n,
108+
)
109+
self.expiry_date = DateField(
110+
api_prediction["expiry_date"],
111+
page_id=page_n,
112+
)
113+
self.gender = TextField(
114+
api_prediction["gender"],
115+
page_id=page_n,
116+
)
117+
self.given_names = [
118+
TextField(prediction, page_id=page_n)
119+
for prediction in api_prediction["given_names"]
120+
]
121+
self.issue_date = DateField(
122+
api_prediction["issue_date"],
123+
page_id=page_n,
124+
)
125+
self.mrz1 = TextField(
126+
api_prediction["mrz1"],
127+
page_id=page_n,
128+
)
129+
self.mrz2 = TextField(
130+
api_prediction["mrz2"],
131+
page_id=page_n,
132+
)
133+
self.mrz3 = TextField(
134+
api_prediction["mrz3"],
135+
page_id=page_n,
136+
)
137+
self.nationality = TextField(
138+
api_prediction["nationality"],
139+
page_id=page_n,
140+
)
141+
self.surname = TextField(
142+
api_prediction["surname"],
143+
page_id=page_n,
144+
)
145+
146+
def __str__(self) -> str:
147+
given_names = f"\n { ' ' * 15 }".join(
148+
[str(item) for item in self.given_names],
149+
)
150+
return clean_out_string(
151+
"FR Carte Nationale d'Identité V2 Prediction\n"
152+
"===========================================\n"
153+
f":Filename: {self.filename or ''}\n"
154+
f":Document Type: {self.document_type}\n"
155+
f":Document Sides: {self.document_side}\n"
156+
f":Nationality: {self.nationality}\n"
157+
f":Card Access Number: {self.card_access_number}\n"
158+
f":Document Number: {self.document_number}\n"
159+
f":Given Name(s): {given_names}\n"
160+
f":Surname: {self.surname}\n"
161+
f":Alternate Name: {self.alternate_name}\n"
162+
f":Date of Birth: {self.birth_date}\n"
163+
f":Place of Birth: {self.birth_place}\n"
164+
f":Gender: {self.gender}\n"
165+
f":Expiry Date: {self.expiry_date}\n"
166+
f":Mrz Line 1: {self.mrz1}\n"
167+
f":Mrz Line 2: {self.mrz2}\n"
168+
f":Mrz Line 3: {self.mrz3}\n"
169+
f":Date of Issue: {self.issue_date}\n"
170+
f":Issuing Authority: {self.authority}\n"
171+
)
172+
173+
174+
TypeIdCardV2 = TypeVar(
175+
"TypeIdCardV2",
176+
bound=IdCardV2,
177+
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import json
2+
3+
import pytest
4+
5+
from mindee.documents.fr import IdCardV2
6+
7+
FR_ID_CARD_DATA_DIR = "./tests/data/products/idcard_fr"
8+
FILE_PATH_FR_ID_CARD_V2_COMPLETE = f"{ FR_ID_CARD_DATA_DIR }/response_v2/complete.json"
9+
FILE_PATH_FR_ID_CARD_V2_EMPTY = f"{ FR_ID_CARD_DATA_DIR }/response_v2/empty.json"
10+
11+
12+
@pytest.fixture
13+
def id_card_v2_doc() -> IdCardV2:
14+
json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_COMPLETE, encoding="utf-8"))
15+
return IdCardV2(json_data["document"]["inference"], page_n=None)
16+
17+
18+
@pytest.fixture
19+
def id_card_v2_doc_empty() -> IdCardV2:
20+
json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_EMPTY, encoding="utf-8"))
21+
return IdCardV2(json_data["document"]["inference"], page_n=None)
22+
23+
24+
@pytest.fixture
25+
def id_card_v2_page0():
26+
json_data = json.load(open(FILE_PATH_FR_ID_CARD_V2_COMPLETE, encoding="utf-8"))
27+
return IdCardV2(json_data["document"]["inference"]["pages"][0], page_n=0)
28+
29+
30+
def test_empty_doc_constructor(id_card_v2_doc_empty):
31+
assert id_card_v2_doc_empty.nationality.value is None
32+
assert id_card_v2_doc_empty.card_access_number.value is None
33+
assert id_card_v2_doc_empty.document_number.value is None
34+
assert len(id_card_v2_doc_empty.given_names) == 0
35+
assert id_card_v2_doc_empty.surname.value is None
36+
assert id_card_v2_doc_empty.alternate_name.value is None
37+
assert id_card_v2_doc_empty.birth_date.value is None
38+
assert id_card_v2_doc_empty.birth_place.value is None
39+
assert id_card_v2_doc_empty.gender.value is None
40+
assert id_card_v2_doc_empty.expiry_date.value is None
41+
assert id_card_v2_doc_empty.mrz1.value is None
42+
assert id_card_v2_doc_empty.mrz2.value is None
43+
assert id_card_v2_doc_empty.mrz3.value is None
44+
assert id_card_v2_doc_empty.issue_date.value is None
45+
assert id_card_v2_doc_empty.authority.value is None
46+
47+
48+
def test_doc_constructor(id_card_v2_doc):
49+
file_path = f"{ FR_ID_CARD_DATA_DIR }/response_v2/doc_to_string.rst"
50+
reference_str = open(file_path, "r", encoding="utf-8").read()
51+
assert str(id_card_v2_doc) == reference_str
52+
53+
54+
def test_page0_constructor(id_card_v2_page0):
55+
file_path = f"{ FR_ID_CARD_DATA_DIR }/response_v2/page0_to_string.rst"
56+
reference_str = open(file_path, "r", encoding="utf-8").read()
57+
assert str(id_card_v2_page0) == reference_str

0 commit comments

Comments
 (0)