chore: initial import
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
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
|
||||
Reference in New Issue
Block a user