diff --git a/.github/workflows/test-code-samples.yml b/.github/workflows/test-code-samples.yml index 01761ebe..807871e2 100644 --- a/.github/workflows/test-code-samples.yml +++ b/.github/workflows/test-code-samples.yml @@ -10,9 +10,10 @@ on: jobs: test: - name: Run Tests + name: Run Code Samples timeout-minutes: 30 strategy: + max-parallel: 2 matrix: python-version: - "3.7" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 336167cf..d37189f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,7 @@ jobs: name: Run Tests timeout-minutes: 30 strategy: + max-parallel: 4 matrix: os: - "ubuntu-latest" diff --git a/docs/extras/code_samples/petrol_receipts_v1.txt b/docs/extras/code_samples/petrol_receipts_v1.txt new file mode 100644 index 00000000..bd9eb278 --- /dev/null +++ b/docs/extras/code_samples/petrol_receipts_v1.txt @@ -0,0 +1,14 @@ +from mindee import Client +from mindee.documents.fr import TypePetrolReceiptV1 + +# 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 Petrol Receipt by passing the appropriate type +result = input_doc.parse(TypePetrolReceiptV1) + +# Print a brief summary of the parsed data +print(result.document) diff --git a/mindee/cli.py b/mindee/cli.py index dfc8701e..cc6cb108 100644 --- a/mindee/cli.py +++ b/mindee/cli.py @@ -79,6 +79,10 @@ class CommandConfig(Generic[TypeDoc]): help="FR Bank Account Details", doc_class=documents.fr.TypeBankAccountDetailsV1, ), + "fr-petrol-receipt": CommandConfig( + help="FR Petrol Receipt", + doc_class=documents.fr.TypePetrolReceiptV1, + ), "multi-receipts-detector": CommandConfig( help="Multi Receipts Detector", doc_class=documents.TypeMultiReceiptsDetectorV1, diff --git a/mindee/client.py b/mindee/client.py index b1fa1764..6189ddd9 100644 --- a/mindee/client.py +++ b/mindee/client.py @@ -459,6 +459,11 @@ def _init_default_endpoints(self) -> None: url_name="bank_account_details", version="2", ), + ConfigSpec( + doc_class=documents.fr.PetrolReceiptV1, + url_name="petrol_receipts", + version="1", + ), 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 c57266e1..852bd5d9 100644 --- a/mindee/documents/fr/__init__.py +++ b/mindee/documents/fr/__init__.py @@ -10,3 +10,4 @@ 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 +from .petrol_receipt.petrol_receipt_v1 import PetrolReceiptV1, TypePetrolReceiptV1 diff --git a/mindee/documents/fr/petrol_receipt/__init__.py b/mindee/documents/fr/petrol_receipt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mindee/documents/fr/petrol_receipt/petrol_receipt_v1.py b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1.py new file mode 100644 index 00000000..d8c3af4b --- /dev/null +++ b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1.py @@ -0,0 +1,84 @@ +from typing import Optional, TypeVar + +from mindee.documents.base import Document, TypeApiPrediction, clean_out_string +from mindee.fields.amount import AmountField + +from .petrol_receipt_v1_fuel import PetrolReceiptV1Fuel +from .petrol_receipt_v1_total import PetrolReceiptV1Total + + +class PetrolReceiptV1(Document): + """Petrol Receipt v1 prediction results.""" + + fuel: PetrolReceiptV1Fuel + """The fuel type.""" + price: AmountField + """The price per unit of fuel.""" + total: PetrolReceiptV1Total + """The total amount paid.""" + volume: AmountField + """The volume of fuel purchased.""" + + def __init__( + self, + api_prediction=None, + input_source=None, + page_n: Optional[int] = None, + ): + """ + Petrol Receipt v1 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="petrol_receipt", + 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.fuel = PetrolReceiptV1Fuel( + api_prediction["fuel"], + page_id=page_n, + ) + self.price = AmountField( + api_prediction["price"], + page_id=page_n, + ) + self.total = PetrolReceiptV1Total( + api_prediction["total"], + page_id=page_n, + ) + self.volume = AmountField( + api_prediction["volume"], + page_id=page_n, + ) + + def __str__(self) -> str: + return clean_out_string( + "FR Petrol Receipt V1 Prediction\n" + "===============================\n" + f":Filename: {self.filename or ''}\n" + f":Fuel Type:\n{self.fuel.to_field_list()}\n" + f":Price per Unit: {self.price}\n" + f":Volume: {self.volume}\n" + f":Total Amount:\n{self.total.to_field_list()}\n" + ) + + +TypePetrolReceiptV1 = TypeVar( + "TypePetrolReceiptV1", + bound=PetrolReceiptV1, +) diff --git a/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_fuel.py b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_fuel.py new file mode 100644 index 00000000..f477dbc0 --- /dev/null +++ b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_fuel.py @@ -0,0 +1,56 @@ +from typing import Dict, Optional + +from mindee.fields.base import FieldConfidenceMixin, FieldPositionMixin, TypePrediction + + +class PetrolReceiptV1Fuel(FieldPositionMixin, FieldConfidenceMixin): + """The fuel type.""" + + category: Optional[str] + """The fuel category among a list of 4 possible choices.""" + raw_text: Optional[str] + """As seen on the receipt.""" + 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.category = prediction["category"] + self.raw_text = prediction["raw_text"] + + def _printable_values(self) -> Dict[str, str]: + """Return values for printing.""" + return { + "category": self.category if self.category is not None else "", + "raw_text": self.raw_text if self.raw_text 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" :Category: {printable['category']}\n" + f" :Raw Name: {printable['raw_text']}" + ) + + def __str__(self) -> str: + """Default string representation.""" + printable = self._printable_values() + return ( + f"Category: {printable['category']}, " + f"Raw Name: {printable['raw_text']}, " + ).strip() diff --git a/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_total.py b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_total.py new file mode 100644 index 00000000..cea57bf4 --- /dev/null +++ b/mindee/documents/fr/petrol_receipt/petrol_receipt_v1_total.py @@ -0,0 +1,52 @@ +from typing import Dict, Optional + +from mindee.fields.base import ( + FieldConfidenceMixin, + FieldPositionMixin, + TypePrediction, + float_to_string, + to_opt_float, +) + + +class PetrolReceiptV1Total(FieldPositionMixin, FieldConfidenceMixin): + """The total amount paid.""" + + amount: Optional[float] + """The amount.""" + 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.amount = to_opt_float(prediction, "amount") + + def _printable_values(self) -> Dict[str, str]: + """Return values for printing.""" + return { + "amount": float_to_string(self.amount), + } + + 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" :Amount Paid: {printable['amount']}" + + def __str__(self) -> str: + """Default string representation.""" + printable = self._printable_values() + return (f"Amount Paid: {printable['amount']}, ").strip() diff --git a/tests/data b/tests/data index 6f8045a1..21689cc9 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 6f8045a1b6e8166c07b8c1d9e0132e2ca363663d +Subproject commit 21689cc9da7582bd3862c4d7955cef0480744f64 diff --git a/tests/documents/fr/test_petrol_receipt_v1.py b/tests/documents/fr/test_petrol_receipt_v1.py new file mode 100644 index 00000000..4785a6af --- /dev/null +++ b/tests/documents/fr/test_petrol_receipt_v1.py @@ -0,0 +1,45 @@ +import json + +import pytest + +from mindee.documents.fr import PetrolReceiptV1 + +FR_PETROL_RECEIPT_DATA_DIR = "./tests/data/products/petrol_receipts" +FILE_PATH_FR_PETROL_RECEIPT_V1_COMPLETE = ( + f"{ FR_PETROL_RECEIPT_DATA_DIR }/response_v1/complete.json" +) +FILE_PATH_FR_PETROL_RECEIPT_V1_EMPTY = ( + f"{ FR_PETROL_RECEIPT_DATA_DIR }/response_v1/empty.json" +) + + +@pytest.fixture +def petrol_receipt_v1_doc() -> PetrolReceiptV1: + json_data = json.load( + open(FILE_PATH_FR_PETROL_RECEIPT_V1_COMPLETE, encoding="utf-8"), + ) + return PetrolReceiptV1(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def petrol_receipt_v1_doc_empty() -> PetrolReceiptV1: + json_data = json.load( + open(FILE_PATH_FR_PETROL_RECEIPT_V1_EMPTY, encoding="utf-8"), + ) + return PetrolReceiptV1(json_data["document"]["inference"], page_n=None) + + +@pytest.fixture +def petrol_receipt_v1_page0(): + json_data = json.load( + open(FILE_PATH_FR_PETROL_RECEIPT_V1_COMPLETE, encoding="utf-8"), + ) + return PetrolReceiptV1(json_data["document"]["inference"]["pages"][0], page_n=0) + + +def test_empty_doc_constructor(petrol_receipt_v1_doc_empty): + assert petrol_receipt_v1_doc_empty.fuel.category is None + assert petrol_receipt_v1_doc_empty.fuel.raw_text is None + assert petrol_receipt_v1_doc_empty.price.value is None + assert petrol_receipt_v1_doc_empty.volume.value is None + assert petrol_receipt_v1_doc_empty.total.amount is None