diff --git a/mealie/services/parser_services/ingredient_parser.py b/mealie/services/parser_services/ingredient_parser.py index 77a17909b..1f042f988 100644 --- a/mealie/services/parser_services/ingredient_parser.py +++ b/mealie/services/parser_services/ingredient_parser.py @@ -12,6 +12,8 @@ from mealie.schema.recipe.recipe_ingredient import ( CreateIngredientFood, CreateIngredientUnit, IngredientConfidence, + IngredientFood, + IngredientUnit, ParsedIngredient, RegisteredParser, ) @@ -28,11 +30,11 @@ class BruteForceParser(ABCIngredientParser): Brute force ingredient parser. """ - async def parse_one(self, ingredient: str) -> ParsedIngredient: - bfi = brute.parse(ingredient, self) + async def parse_one(self, ingredient_string: str) -> ParsedIngredient: + bfi = brute.parse(ingredient_string, self) parsed_ingredient = ParsedIngredient( - input=ingredient, + input=ingredient_string, ingredient=RecipeIngredient( unit=CreateIngredientUnit(name=bfi.unit), food=CreateIngredientFood(name=bfi.food), @@ -41,7 +43,28 @@ class BruteForceParser(ABCIngredientParser): ), ) - return self.find_ingredient_match(parsed_ingredient) + matched_ingredient = self.find_ingredient_match(parsed_ingredient) + + qty_conf = 1 + note_conf = 1 + + unit_obj = matched_ingredient.ingredient.unit + food_obj = matched_ingredient.ingredient.food + + unit_conf = 1 if bfi.unit is None or isinstance(unit_obj, IngredientUnit) else 0 + food_conf = 1 if bfi.food is None or isinstance(food_obj, IngredientFood) else 0 + + avg_conf = (qty_conf + unit_conf + food_conf + note_conf) / 4 + + matched_ingredient.confidence = IngredientConfidence( + average=avg_conf, + quantity=qty_conf, + unit=unit_conf, + food=food_conf, + comment=note_conf, + ) + + return matched_ingredient async def parse(self, ingredients: list[str]) -> list[ParsedIngredient]: return [await self.parse_one(ingredient) for ingredient in ingredients] diff --git a/tests/unit_tests/test_ingredient_parser.py b/tests/unit_tests/test_ingredient_parser.py index e4a27429b..852b9b8e5 100644 --- a/tests/unit_tests/test_ingredient_parser.py +++ b/tests/unit_tests/test_ingredient_parser.py @@ -226,6 +226,46 @@ def test_brute_parser( assert not comment +@pytest.mark.parametrize( + "unit, food, expect_unit_match, expect_food_match, expected_avg", + [ + pytest.param("Cups", "potatoes", True, True, 1.0, id="all matched"), + pytest.param("Cups", "veryuniquefood", True, False, 0.75, id="unit matched only"), + pytest.param("veryuniqueunit", "potatoes", False, True, 0.75, id="food matched only"), + pytest.param("veryuniqueunit", "veryuniquefood", False, False, 0.5, id="neither matched"), + ], +) +def test_brute_parser_confidence( + unit: str, + food: str, + expect_unit_match: bool, + expect_food_match: bool, + expected_avg: float, + unique_local_group_id: UUID4, + parsed_ingredient_data: tuple[list[IngredientFood], list[IngredientUnit]], +): + input_str = f"1 {unit} {food}" + + with session_context() as session: + original_loop = asyncio.get_event_loop() + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + parser = get_parser(RegisteredParser.brute, unique_local_group_id, session) + parsed = loop.run_until_complete(parser.parse_one(input_str)) + finally: + loop.close() + asyncio.set_event_loop(original_loop) + + conf = parsed.confidence + + assert conf.quantity == 1 + assert conf.comment == 1 + assert conf.unit == (1 if expect_unit_match or not unit else 0) + assert conf.food == (1 if expect_food_match or not food else 0) + assert conf.average == expected_avg + + @pytest.mark.parametrize( "input, expected_unit_name, expected_food_name, expect_unit_match, expect_food_match", (