119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
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
|