from __future__ import annotations import re from typing import Any from .models import RecipeSpec FORBIDDEN_UNIT_NAMES = { "bis", "oder", "und", "limette", "zitrone", "saft", "abrieb", } SUSPICIOUS_FOOD_PREFIXES = ( "saft von", "abrieb von", "schale von", ) UNIT_PREFIX_PATTERN = re.compile( r"^\s*\d+(?:[.,/]\d+)?\s*(?:g|kg|ml|l|tl|el|cup|oz)\b", re.IGNORECASE, ) def local_quality_warnings(recipe: RecipeSpec) -> list[dict[str, Any]]: warnings: list[dict[str, Any]] = [] for step_index, step in enumerate(recipe.steps): for ingredient_index, ingredient in enumerate(step.ingredients): location = ( f"Schritt {step_index + 1}, Zutat {ingredient_index + 1} " f"({ingredient.food_name})" ) food_folded = ingredient.food_name.strip().casefold() unit_folded = (ingredient.unit_name or "").strip().casefold() if unit_folded in FORBIDDEN_UNIT_NAMES: warnings.append( { "severity": "blocking", "code": "suspicious_unit", "message": ( f"{location}: Die Einheit „{ingredient.unit_name}“ " "wirkt wie ein Parsingfehler." ), } ) if food_folded.startswith(SUSPICIOUS_FOOD_PREFIXES): warnings.append( { "severity": "blocking", "code": "suspicious_food_prefix", "message": ( f"{location}: Der Food-Name wirkt unvollständig " "oder falsch geparst." ), } ) if UNIT_PREFIX_PATTERN.search(ingredient.food_name): warnings.append( { "severity": "blocking", "code": "amount_inside_food", "message": ( f"{location}: Menge oder Einheit steckt noch im Food-Namen." ), } ) if " oder " in food_folded or " und " in food_folded: warnings.append( { "severity": "warning", "code": "composite_food", "message": ( f"{location}: Alternative oder zusammengesetzte Zutat " "sollte meist als Haupt-Food plus Notiz erfasst werden." ), } ) if ingredient.amount_max is not None and ingredient.amount_max > ingredient.amount: warnings.append( { "severity": "info", "code": "amount_range", "message": ( f"{location}: Mengenbereich " f"{ingredient.amount:g} bis {ingredient.amount_max:g} " "wird in Tandoor über Mindestmenge plus Notiz abgebildet." ), } ) if not ingredient.original_text.strip(): warnings.append( { "severity": "warning", "code": "missing_original_text", "message": f"{location}: original_text fehlt.", } ) if not recipe.steps: warnings.append( { "severity": "blocking", "code": "missing_steps", "message": "Das Rezept enthält keine Schritte.", } ) return warnings