From 1eb09ef18c9f9ada76ed61a6a39548351fbfbaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bournhonesque?= Date: Fri, 25 Oct 2024 15:08:22 +0200 Subject: [PATCH] fix: import nutrient_extraction insight if it brings at least 1 nutrient --- robotoff/insights/importer.py | 36 ++----- tests/unit/insights/test_importer.py | 145 +++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 26 deletions(-) diff --git a/robotoff/insights/importer.py b/robotoff/insights/importer.py index 140f3dc379..884c490efa 100644 --- a/robotoff/insights/importer.py +++ b/robotoff/insights/importer.py @@ -1540,33 +1540,17 @@ def generate_candidates( predictions: list[Prediction], product_id: ProductIdentifier, ) -> Iterator[ProductInsight]: - # Don't generate candidates if the product already has nutrients - if ( - product is not None - and product.nutriments - # If we delete all nutrient values, these computed values are still - # present. We therefore ignore these keys. - and bool( - set( - key - for key in product.nutriments.keys() - if not ( - key.startswith("carbon-footprint-from-known-ingredients") - or key.startswith( - "fruits-vegetables-legumes-estimate-from-ingredients" - ) - or key.startswith( - "fruits-vegetables-nuts-estimate-from-ingredients" - ) - or key.startswith("nova-group") - or key.startswith("nutrition-score-fr") - ) - ) - ) - ): - return - for prediction in predictions: + if product is not None and product.nutriments: + current_keys = set(key for key in product.nutriments.keys()) + prediction_keys = set(prediction.data["nutrients"].keys()) + + # If the prediction brings a nutrient value that is missing in + # the product, we generate an insight, otherwise we + # skip it + if not len(prediction_keys - current_keys): + continue + yield ProductInsight(**prediction.to_dict()) @classmethod diff --git a/tests/unit/insights/test_importer.py b/tests/unit/insights/test_importer.py index 1220c546ab..1271d56c9c 100644 --- a/tests/unit/insights/test_importer.py +++ b/tests/unit/insights/test_importer.py @@ -10,6 +10,7 @@ ExpirationDateImporter, InsightImporter, LabelInsightImporter, + NutrientExtractionImporter, NutritionImageImporter, PackagerCodeInsightImporter, PackagingImporter, @@ -1531,6 +1532,150 @@ def generate_candidates_for_image( ) +class TestNutrientExtractionImporter: + def test_generate_candidates_no_nutrient(self): + product = Product({"code": DEFAULT_BARCODE, "nutriments": {}}) + data = { + "nutrients": { + "energy-kj_100g": { + "entity": "energy-kj_100g", + "value": "100", + "unit": "kj", + "text": "100 kj", + "start": 0, + "end": 1, + "char_start": 0, + "char_end": 6, + } + } + } + predictions = [ + Prediction( + type=PredictionType.nutrient_extraction, + data=data, + barcode=DEFAULT_BARCODE, + source_image=DEFAULT_SOURCE_IMAGE, + predictor="nutrition_extractor", + predictor_version="nutrition_extractor-1.0", + automatic_processing=False, + ) + ] + candidates = list( + NutrientExtractionImporter.generate_candidates( + product, predictions, DEFAULT_PRODUCT_ID + ) + ) + assert len(candidates) == 1 + candidate = candidates[0] + assert candidate.type == "nutrient_extraction" + assert candidate.barcode == DEFAULT_BARCODE + assert candidate.type == InsightType.nutrient_extraction.name + assert candidate.value_tag is None + assert candidate.data == data + assert candidate.source_image == DEFAULT_SOURCE_IMAGE + assert candidate.automatic_processing is False + assert candidate.predictor == "nutrition_extractor" + assert candidate.predictor_version == "nutrition_extractor-1.0" + + def test_generate_candidates_no_new_nutrient(self): + product = Product( + { + "code": DEFAULT_BARCODE, + "nutriments": { + "energy-kj_100g": "100", + "energy-kj_unit": "kJ", + "fat_100g": "10", + "fat_unit": "g", + }, + } + ) + data = { + "nutrients": { + "energy-kj_100g": { + "entity": "energy-kj_100g", + "value": "100", + "unit": "kj", + "text": "100 kj", + "start": 0, + "end": 2, + "char_start": 0, + "char_end": 6, + } + } + } + predictions = [ + Prediction( + type=PredictionType.nutrient_extraction, + data=data, + barcode=DEFAULT_BARCODE, + source_image=DEFAULT_SOURCE_IMAGE, + predictor="nutrition_extractor", + predictor_version="nutrition_extractor-1.0", + automatic_processing=False, + ) + ] + candidates = list( + NutrientExtractionImporter.generate_candidates( + product, predictions, DEFAULT_PRODUCT_ID + ) + ) + assert len(candidates) == 0 + + def test_generate_candidates_new_nutrient(self): + product = Product( + { + "code": DEFAULT_BARCODE, + "nutriments": { + "energy-kj_100g": "100", + "energy-kj_unit": "kJ", + "fat_100g": "10", + "fat_unit": "g", + }, + } + ) + data = { + "nutrients": { + "energy-kj_100g": { + "entity": "energy-kj_100g", + "value": "100", + "unit": "kj", + "text": "100 kj", + "start": 0, + "end": 2, + "char_start": 0, + "char_end": 6, + }, + "saturated-fat_100g": { + "entity": "saturated-fat_100g", + "value": "5", + "unit": "g", + "text": "5 g", + "start": 3, + "end": 4, + "char_start": 7, + "char_end": 10, + }, + } + } + predictions = [ + Prediction( + type=PredictionType.nutrient_extraction, + data=data, + barcode=DEFAULT_BARCODE, + source_image=DEFAULT_SOURCE_IMAGE, + predictor="nutrition_extractor", + predictor_version="nutrition_extractor-1.0", + automatic_processing=False, + ) + ] + candidates = list( + NutrientExtractionImporter.generate_candidates( + product, predictions, DEFAULT_PRODUCT_ID + ) + ) + assert len(candidates) == 1 + + class TestImportInsightsForProducts: def test_import_insights_no_element(self, mocker): get_product_predictions_mock = mocker.patch(