diff --git a/docs/predictions/standard/fr.rst b/docs/predictions/standard/fr.rst new file mode 100644 index 00000000..c73f47d2 --- /dev/null +++ b/docs/predictions/standard/fr.rst @@ -0,0 +1,8 @@ +France +====== + +Carte Grise +----------- +.. autoclass:: mindee.documents.fr.CarteGriseV1 + :members: + :undoc-members: diff --git a/docs/predictions/standard/index.rst b/docs/predictions/standard/index.rst index 94a73e86..3787753d 100644 --- a/docs/predictions/standard/index.rst +++ b/docs/predictions/standard/index.rst @@ -6,4 +6,5 @@ Off the Shelf ./international ./us + ./fr ./field_types diff --git a/mindee/cli.py b/mindee/cli.py index 5daea413..790c318c 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -42,6 +42,10 @@ class CommandConfig(Generic[TypeDoc]): help="US Bank Check", doc_class=documents.us.TypeBankCheckV1, ), + "fr-carte-grise": CommandConfig( + help="French Carte Grise", + doc_class=documents.fr.TypeCarteGriseV1, + ), } diff --git a/mindee/client.py b/mindee/client.py index 8eabb05c..47a56f37 100644 --- a/mindee/client.py +++ b/mindee/client.py @@ -13,6 +13,7 @@ ) from mindee.documents.base import Document, TypeDocument from mindee.documents.config import DocumentConfig, DocumentConfigDict +from mindee.documents.fr import CarteGriseV1 from mindee.documents.us import BankCheckV1 from mindee.endpoints import OTS_OWNER, CustomEndpoint, HTTPException, StandardEndpoint from mindee.input.page_options import PageOptions @@ -257,6 +258,15 @@ def _init_default_endpoints(self) -> None: ) ], ), + (OTS_OWNER, CarteGriseV1.__name__): DocumentConfig( + document_type="carte_grise_v1", + document_class=CarteGriseV1, + endpoints=[ + StandardEndpoint( + url_name="carte_grise", version="1", api_key=self.api_key + ) + ], + ), (OTS_OWNER, CropperV1.__name__): DocumentConfig( document_type="cropper_v1", document_class=CropperV1, diff --git a/mindee/documents/__init__.py b/mindee/documents/__init__.py index b5e19c16..45846bf0 100644 --- a/mindee/documents/__init__.py +++ b/mindee/documents/__init__.py @@ -1,4 +1,4 @@ -from mindee.documents import us +from mindee.documents import fr, us from mindee.documents.cropper import CropperV1, TypeCropperV1 from mindee.documents.custom import CustomV1, TypeCustomV1 from mindee.documents.financial import FinancialV1, TypeFinancialV1 diff --git a/mindee/documents/fr/__init__.py b/mindee/documents/fr/__init__.py new file mode 100644 index 00000000..62901d9b --- /dev/null +++ b/mindee/documents/fr/__init__.py @@ -0,0 +1 @@ +from .carte_grise.carte_grise_v1 import CarteGriseV1, TypeCarteGriseV1 diff --git a/mindee/documents/fr/carte_grise/__init__.py b/mindee/documents/fr/carte_grise/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mindee/documents/fr/carte_grise/carte_grise_v1.py b/mindee/documents/fr/carte_grise/carte_grise_v1.py new file mode 100644 index 00000000..32674761 --- /dev/null +++ b/mindee/documents/fr/carte_grise/carte_grise_v1.py @@ -0,0 +1,220 @@ +from typing import Optional, TypeVar + +from mindee.documents.base import Document, TypeApiPrediction, clean_out_string +from mindee.fields.date import DateField +from mindee.fields.text import TextField + + +class CarteGriseV1(Document): + formula_number: TextField + """Document formula number""" + mrz1: TextField + """Machine readable zone first line""" + mrz2: TextField + """Machine readable zone second line""" + owner_first_name: TextField + """Vehicle's owner first name""" + owner_surname: TextField + """Vehicle's owner surname""" + a: TextField + """Vehicle license plate number""" + b: DateField + """Vehicle first release date""" + c1: TextField + """Vehicle's owner full name including maiden name""" + c3: TextField + """Vehicle's owner address""" + c41: TextField + """Number of owners of the license certificate""" + c4a: TextField + """Mention about the ownership of the vehicle""" + d1: TextField + """Vehicle brand""" + d3: TextField + """vehicle commercial name""" + e: TextField + """Vehicle identification number (VIN)""" + f1: TextField + """Vehicle's maximum admissible weight""" + f2: TextField + """Vehicle's maximum admissible weight within the license's state""" + f3: TextField + """Vehicle's maximum authorized weight with coupling""" + g: TextField + """Vehicle's weight with coupling if tractor different than category M1""" + g1: TextField + """Vehicle's national empty weight""" + i: DateField + """Car registration date of the given certificate""" + j: TextField + """Vehicle's category""" + j1: TextField + """Vehicle's national type""" + j2: TextField + """Vehicle's body type (CE)""" + j3: TextField + """Vehicle's body type (National designation)""" + p1: TextField + """Vehicle's displacement (cm3)""" + p2: TextField + """Vehicle's maximum net power (kW)""" + p3: TextField + """Vehicle's fuel type""" + p6: TextField + """Vehicle's administrative power (fiscal horse power)""" + q: TextField + """Vehicle's power / weight ratio""" + s1: TextField + """Vehicle's number of seats""" + s2: TextField + """Vehicle's number of standing rooms (person)""" + u1: TextField + """Vehicle's sound level (dB)""" + u2: TextField + """Vehicle's engine rotation speed (min-1)""" + v7: TextField + """Vehicle's CO2 emission""" + x1: DateField + """Next technical control date""" + y1: TextField + """Amount of the regional proportional tax of the gray card (in euros).""" + y2: TextField + """Amount of the additional parafiscal tax of the gray card (in euros).""" + y3: TextField + """Amount of the additional CO2 tax of the gray card (in euros).""" + y4: TextField + """Amount of the fee for managing the registration certificate (in euros).""" + y5: TextField + """Amount of the fee for delivery of the registration certificate in euros.""" + y6: TextField + """Total amount of registration fee to be paid in euros.""" + + def __init__( + self, + api_prediction=None, + input_source=None, + page_n: Optional[int] = None, + document_type="carte_grise", + ): + """ + Bank check document. + + :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=document_type, + 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 document from an API response JSON. + + :param api_prediction: Raw prediction from HTTP response + :param page_n: Page number for multi pages pdf input + """ + # pylint: disable=invalid-name + self.formula_number = TextField(api_prediction["formula_number"], page_n=page_n) + self.mrz1 = TextField(api_prediction["mrz1"], page_n=page_n) + self.mrz2 = TextField(api_prediction["mrz2"], page_n=page_n) + self.owner_first_name = TextField( + api_prediction["owner_first_name"], page_n=page_n + ) + self.owner_surname = TextField(api_prediction["owner_surname"], page_n=page_n) + self.a = TextField(api_prediction["a"], page_n=page_n) + self.b = DateField(api_prediction["b"], page_n=page_n) + self.c1 = TextField(api_prediction["c1"], page_n=page_n) + self.c3 = TextField(api_prediction["c3"], page_n=page_n) + self.c41 = TextField(api_prediction["c41"], page_n=page_n) + self.c4a = TextField(api_prediction["c4a"], page_n=page_n) + self.d1 = TextField(api_prediction["d1"], page_n=page_n) + self.d3 = TextField(api_prediction["d3"], page_n=page_n) + self.e = TextField(api_prediction["e"], page_n=page_n) + self.f1 = TextField(api_prediction["f1"], page_n=page_n) + self.f2 = TextField(api_prediction["f2"], page_n=page_n) + self.f3 = TextField(api_prediction["f3"], page_n=page_n) + self.g = TextField(api_prediction["g"], page_n=page_n) + self.g1 = TextField(api_prediction["g1"], page_n=page_n) + self.i = DateField(api_prediction["i"], page_n=page_n) + self.j = TextField(api_prediction["j"], page_n=page_n) + self.j1 = TextField(api_prediction["j1"], page_n=page_n) + self.j2 = TextField(api_prediction["j2"], page_n=page_n) + self.j3 = TextField(api_prediction["j3"], page_n=page_n) + self.p1 = TextField(api_prediction["p1"], page_n=page_n) + self.p2 = TextField(api_prediction["p2"], page_n=page_n) + self.p3 = TextField(api_prediction["p3"], page_n=page_n) + self.p6 = TextField(api_prediction["p6"], page_n=page_n) + self.q = TextField(api_prediction["q"], page_n=page_n) + self.s1 = TextField(api_prediction["s1"], page_n=page_n) + self.s2 = TextField(api_prediction["s2"], page_n=page_n) + self.u1 = TextField(api_prediction["u1"], page_n=page_n) + self.u2 = TextField(api_prediction["u2"], page_n=page_n) + self.v7 = TextField(api_prediction["v7"], page_n=page_n) + self.x1 = DateField(api_prediction["x1"], page_n=page_n) + self.y1 = TextField(api_prediction["y1"], page_n=page_n) + self.y2 = TextField(api_prediction["y2"], page_n=page_n) + self.y3 = TextField(api_prediction["y3"], page_n=page_n) + self.y4 = TextField(api_prediction["y4"], page_n=page_n) + self.y5 = TextField(api_prediction["y5"], page_n=page_n) + self.y6 = TextField(api_prediction["y6"], page_n=page_n) + + def __str__(self) -> str: + return clean_out_string( + "----- FR Carte Grise V1 -----\n" + f"Filename: {self.filename or ''}\n" + f"formula_number: {self.formula_number}\n" + f"mrz1: {self.mrz1}\n" + f"mrz2: {self.mrz2}\n" + f"owner_first_name: {self.owner_first_name}\n" + f"owner_surname: {self.owner_surname}\n" + f"a: {self.a}\n" + f"b: {self.b}\n" + f"c1: {self.c1}\n" + f"c3: {self.c3}\n" + f"c41: {self.c41}\n" + f"c4a: {self.c4a}\n" + f"d1: {self.d1}\n" + f"d3: {self.d3}\n" + f"e: {self.e}\n" + f"f1: {self.f1}\n" + f"f2: {self.f2}\n" + f"f3: {self.f3}\n" + f"g: {self.g}\n" + f"g1: {self.g1}\n" + f"i: {self.i}\n" + f"j: {self.j}\n" + f"j1: {self.j1}\n" + f"j2: {self.j2}\n" + f"j3: {self.j3}\n" + f"p1: {self.p1}\n" + f"p2: {self.p2}\n" + f"p3: {self.p3}\n" + f"p6: {self.p6}\n" + f"q: {self.q}\n" + f"s1: {self.s1}\n" + f"s2: {self.s2}\n" + f"u1: {self.u1}\n" + f"u2: {self.u2}\n" + f"v7: {self.v7}\n" + f"x1: {self.x1}\n" + f"y1: {self.y1}\n" + f"y2: {self.y2}\n" + f"y3: {self.y3}\n" + f"y4: {self.y4}\n" + f"y5: {self.y5}\n" + f"y6: {self.y6}\n" + "----------------------" + ) + + def _checklist(self) -> None: + pass + + +TypeCarteGriseV1 = TypeVar("TypeCarteGriseV1", bound=CarteGriseV1) diff --git a/tests/__init__.py b/tests/__init__.py index 641a0c78..577c3204 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,6 +2,7 @@ INVOICE_DATA_DIR = "./tests/data/invoice" PASSPORT_DATA_DIR = "./tests/data/passport" US_BANK_CHECK_DATA_DIR = "./tests/data/us/bank_check" +FR_CARTE_GRISE_DATA_DIR = "./tests/data/fr/carte_grise" CUSTOM_DATA_DIR = "./tests/data/custom" CROPPER_DATA_DIR = "./tests/data/cropper" PDF_DATA_DIR = "./tests/data/pdf" diff --git a/tests/data b/tests/data index 9e358354..e29d9e4f 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 9e35835467bbffebab0c2920f06052dd0c935376 +Subproject commit e29d9e4f1b7253f6189599826e4387201a70316b diff --git a/tests/documents/fr/__init__.py b/tests/documents/fr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/documents/fr/test_carte_grise_v1.py b/tests/documents/fr/test_carte_grise_v1.py new file mode 100644 index 00000000..88c7c5a9 --- /dev/null +++ b/tests/documents/fr/test_carte_grise_v1.py @@ -0,0 +1,129 @@ +import json + +import pytest + +from mindee.documents.fr.carte_grise.carte_grise_v1 import CarteGriseV1 +from tests import FR_CARTE_GRISE_DATA_DIR + +FILE_PATH_FR_CARTE_GRISE_V1_COMPLETE = ( + f"{FR_CARTE_GRISE_DATA_DIR}/response_v1/complete.json" +) +FILE_PATH_FR_CARTE_GRISE_V1_EMPTY = f"{FR_CARTE_GRISE_DATA_DIR}/response_v1/empty.json" + + +@pytest.fixture +def carte_grise_v1_doc_object() -> CarteGriseV1: + json_data = json.load(open(FILE_PATH_FR_CARTE_GRISE_V1_COMPLETE)) + return CarteGriseV1(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def carte_grise_v1_doc_object_empty() -> CarteGriseV1: + json_data = json.load(open(FILE_PATH_FR_CARTE_GRISE_V1_EMPTY)) + return CarteGriseV1(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def bank_check_pred(): + json_data = json.load(open(FILE_PATH_FR_CARTE_GRISE_V1_EMPTY)) + return json_data["document"]["inference"]["pages"][0] + + +# Technical tests +def test_constructor(carte_grise_v1_doc_object): + assert carte_grise_v1_doc_object.formula_number.value == "2016AE00000" + assert ( + carte_grise_v1_doc_object.mrz1.value + == "CRFRADFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXVP<<<<" + ) + assert ( + carte_grise_v1_doc_object.mrz2.value + == "CRFRADFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXVP<<<<" + ) + assert carte_grise_v1_doc_object.owner_first_name.value == "JOHN" + assert carte_grise_v1_doc_object.owner_surname.value == "DOE" + assert carte_grise_v1_doc_object.a.value == "DY-757-ZH" + assert carte_grise_v1_doc_object.b.value == "2015-01-01" + assert carte_grise_v1_doc_object.c1.value == "JOHN DOE" + assert carte_grise_v1_doc_object.c3.value == "10 CHM PIOU 13540" + assert carte_grise_v1_doc_object.c41.value == "2 JANE DOE" + assert carte_grise_v1_doc_object.c4a.value == "EST LE PROPRIETAIRE DU VEHICULE" + assert carte_grise_v1_doc_object.d1.value == "PEUGEOT" + assert carte_grise_v1_doc_object.d3.value == "100" + assert carte_grise_v1_doc_object.e.value == "VF000000000000000" + assert carte_grise_v1_doc_object.f1.value == "1100" + assert carte_grise_v1_doc_object.f2.value == "1600" + assert carte_grise_v1_doc_object.f3.value == "1600" + assert carte_grise_v1_doc_object.g.value == "12" + assert carte_grise_v1_doc_object.g1.value == "1115" + assert carte_grise_v1_doc_object.i.value == "2016-01-19" + assert carte_grise_v1_doc_object.j.value == "M1" + assert carte_grise_v1_doc_object.j1.value == "VP" + assert carte_grise_v1_doc_object.j2.value == "123" + assert carte_grise_v1_doc_object.j3.value == "CI" + assert carte_grise_v1_doc_object.p1.value == "1761" + assert carte_grise_v1_doc_object.p2.value == "11" + assert carte_grise_v1_doc_object.p3.value == "ES" + assert carte_grise_v1_doc_object.p6.value == "7" + assert carte_grise_v1_doc_object.q.value == "" + assert carte_grise_v1_doc_object.s1.value == "5" + assert carte_grise_v1_doc_object.s2.value == "" + assert carte_grise_v1_doc_object.u1.value == "33" + assert carte_grise_v1_doc_object.u2.value == "6125" + assert carte_grise_v1_doc_object.v7.value == "198" + assert carte_grise_v1_doc_object.x1.value == "2018-01-04" + assert carte_grise_v1_doc_object.y1.value == "179" + assert carte_grise_v1_doc_object.y2.value == "0" + assert carte_grise_v1_doc_object.y3.value == "0" + assert carte_grise_v1_doc_object.y4.value == "4" + assert carte_grise_v1_doc_object.y5.value == "2.76" + assert carte_grise_v1_doc_object.y6.value == "185.76" + doc_str = ( + open(f"{FR_CARTE_GRISE_DATA_DIR}/response_v1/doc_to_string.txt").read().strip() + ) + print(str(carte_grise_v1_doc_object)) + assert str(carte_grise_v1_doc_object) == doc_str + + +def test_all_na(carte_grise_v1_doc_object_empty): + assert carte_grise_v1_doc_object_empty.formula_number.value is None + assert carte_grise_v1_doc_object_empty.mrz1.value is None + assert carte_grise_v1_doc_object_empty.mrz2.value is None + assert carte_grise_v1_doc_object_empty.owner_first_name.value is None + assert carte_grise_v1_doc_object_empty.owner_surname.value is None + assert carte_grise_v1_doc_object_empty.a.value is None + assert carte_grise_v1_doc_object_empty.b.value is None + assert carte_grise_v1_doc_object_empty.c1.value is None + assert carte_grise_v1_doc_object_empty.c3.value is None + assert carte_grise_v1_doc_object_empty.c41.value is None + assert carte_grise_v1_doc_object_empty.c4a.value is None + assert carte_grise_v1_doc_object_empty.d1.value is None + assert carte_grise_v1_doc_object_empty.d3.value is None + assert carte_grise_v1_doc_object_empty.e.value is None + assert carte_grise_v1_doc_object_empty.f1.value is None + assert carte_grise_v1_doc_object_empty.f2.value is None + assert carte_grise_v1_doc_object_empty.f3.value is None + assert carte_grise_v1_doc_object_empty.g.value is None + assert carte_grise_v1_doc_object_empty.g1.value is None + assert carte_grise_v1_doc_object_empty.i.value is None + assert carte_grise_v1_doc_object_empty.j.value is None + assert carte_grise_v1_doc_object_empty.j1.value is None + assert carte_grise_v1_doc_object_empty.j2.value is None + assert carte_grise_v1_doc_object_empty.j3.value is None + assert carte_grise_v1_doc_object_empty.p1.value is None + assert carte_grise_v1_doc_object_empty.p2.value is None + assert carte_grise_v1_doc_object_empty.p3.value is None + assert carte_grise_v1_doc_object_empty.p6.value is None + assert carte_grise_v1_doc_object_empty.q.value is None + assert carte_grise_v1_doc_object_empty.s1.value is None + assert carte_grise_v1_doc_object_empty.s2.value is None + assert carte_grise_v1_doc_object_empty.u1.value is None + assert carte_grise_v1_doc_object_empty.u2.value is None + assert carte_grise_v1_doc_object_empty.v7.value is None + assert carte_grise_v1_doc_object_empty.x1.value is None + assert carte_grise_v1_doc_object_empty.y1.value is None + assert carte_grise_v1_doc_object_empty.y2.value is None + assert carte_grise_v1_doc_object_empty.y3.value is None + assert carte_grise_v1_doc_object_empty.y4.value is None + assert carte_grise_v1_doc_object_empty.y5.value is None + assert carte_grise_v1_doc_object_empty.y6.value is None