Rezept-Zuordnung: "vegan" ist eine Eigenschaft, kein Lebensmittel
Der Wortanker nahm jedes geteilte Wort ab vier Zeichen - also auch
"vegan". Damit passte jedes vegane Produkt auf jede vegane Zutat: "vegane
Butter" landete auf "veganes Muehlen Knusper Bleu", "veganer griechischer
Joghurt" auf "Vegane Muehlen Nuggets" und "veganer Lime Curd" auf "Vegane
Muehlen-Schnitzel auf Basis von Soja". Geteilt wurde dort nur die
Eigenschaft, nicht die Sache.
Beschreibende Woerter begruenden deshalb keinen Treffer mehr - vegan,
vegetarisch, bio, frisch, geschmolzen, ungesalzen, geschaelt, geschnitten,
Zimmertemperatur, oder, optional und so weiter, dazu reine Zahlen. Als
Wortanfaenge aufgefuehrt, weil Deutsch beugt ("vegan", "vegane",
"veganer", "veganes"). Sie sperren aber nicht: "vegane Butter" findet
weiterhin eine "Vegane Butter", nur eben ueber das Wort "Butter".
Wichtig war, die Liste auf BEIDEN Seiten anzuwenden. Sonst steckt "vegan"
weiter im Produktnamen und der Teilstring-Vergleich greift von dort aus.
Gegenprobe mit dem echten Katalog: Von zehn Zutaten bleiben die drei
richtigen Treffer (Mehl, Vollmilch, Aepfel), die sieben erfundenen sind
weg.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4e34accee3
commit
41d262cb2d
@@ -122,13 +122,41 @@ def _stamm(text: str) -> str:
|
|||||||
return roh.replace("ae", "a").replace("oe", "o").replace("ue", "u")
|
return roh.replace("ae", "a").replace("oe", "o").replace("ue", "u")
|
||||||
|
|
||||||
|
|
||||||
def _woerter(text: str) -> list[str]:
|
# Wörter, die eine Zutat BESCHREIBEN, aber nicht benennen. Sie dürfen allein
|
||||||
"""Bedeutungstragende Wörter einer Zutat – sehr kurze fallen weg.
|
# keinen Treffer begründen, sonst passt jedes vegane Produkt auf jede vegane
|
||||||
|
# Zutat: „vegane Butter" landete so auf „veganes Mühlen Knusper Bleu", und
|
||||||
|
# „veganer Lime Curd" auf „Vegane Mühlen-Schnitzel". Geteilt wird dort nur die
|
||||||
|
# Eigenschaft, nicht die Sache.
|
||||||
|
#
|
||||||
|
# Als Wortanfänge aufgeführt, weil Deutsch beugt: „vegan", „vegane", „veganer",
|
||||||
|
# „veganes" sollen alle gemeint sein. Verglichen wird gegen die zusammengezogene
|
||||||
|
# Form aus ``_stamm`` (ae/oe/ue schon aufgelöst).
|
||||||
|
_NUR_EIGENSCHAFT = (
|
||||||
|
"vegan", "vegetar", "bio", "biologisch", "frisch", "roh",
|
||||||
|
"gekocht", "gebacken", "geschmolz", "gesalz", "ungesalz", "gezuckert",
|
||||||
|
"getrock", "gefror", "gerieb", "gewurf", "geschnitt", "geschal", "gehackt",
|
||||||
|
"gemahl", "gepresst", "zimmertemperatur", "lauwarm",
|
||||||
|
"klein", "gross", "mittel", "fein", "grob", "ganz",
|
||||||
|
"oder", "sowie", "etwa", "circa", "nach", "ohne", "optional", "evtl",
|
||||||
|
"block", "stueck", "stuck", "gehauft", "gestrich",
|
||||||
|
)
|
||||||
|
|
||||||
„1 TL" und „oder" sollen keine Treffer begründen; „Mehl", „Apfel",
|
|
||||||
„Vollmilch" sehr wohl.
|
def _nur_eigenschaft(wort: str) -> bool:
|
||||||
|
return wort.isdigit() or wort.startswith(_NUR_EIGENSCHAFT)
|
||||||
|
|
||||||
|
|
||||||
|
def _woerter(text: str) -> list[str]:
|
||||||
|
"""Bedeutungstragende Wörter einer Zutat.
|
||||||
|
|
||||||
|
Weg fallen sehr kurze („1 TL"), reine Zahlen und alles, was die Zutat nur
|
||||||
|
beschreibt (siehe ``_NUR_EIGENSCHAFT``). Übrig bleibt, was sie IST: „Mehl",
|
||||||
|
„Apfel", „Butter", „Joghurt".
|
||||||
"""
|
"""
|
||||||
return [w for w in _stamm(text).split() if len(w) >= 4]
|
return [
|
||||||
|
w for w in _stamm(text).split()
|
||||||
|
if len(w) >= 4 and not _nur_eigenschaft(w)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _hat_anker(zutat_woerter: list[str], kandidat: str) -> bool:
|
def _hat_anker(zutat_woerter: list[str], kandidat: str) -> bool:
|
||||||
@@ -143,9 +171,14 @@ def _hat_anker(zutat_woerter: list[str], kandidat: str) -> bool:
|
|||||||
ziel = _stamm(kandidat)
|
ziel = _stamm(kandidat)
|
||||||
if not ziel:
|
if not ziel:
|
||||||
return False
|
return False
|
||||||
ziel_woerter = [w for w in ziel.split() if len(w) >= 4]
|
ziel_woerter = [
|
||||||
|
w for w in ziel.split() if len(w) >= 4 and not _nur_eigenschaft(w)
|
||||||
|
]
|
||||||
|
# Auch der Teilstring-Vergleich darf nicht über eine Eigenschaft laufen:
|
||||||
|
# „vegan" steckt sonst weiter in „veganes Mühlen Knusper Bleu".
|
||||||
|
ziel_ohne = " ".join(ziel_woerter)
|
||||||
for wort in zutat_woerter:
|
for wort in zutat_woerter:
|
||||||
if wort in ziel:
|
if wort in ziel_ohne:
|
||||||
return True
|
return True
|
||||||
# Umgekehrt auch: Artikel „Milch" passt zur Zutat „Buttermilch".
|
# Umgekehrt auch: Artikel „Milch" passt zur Zutat „Buttermilch".
|
||||||
for zw in ziel_woerter:
|
for zw in ziel_woerter:
|
||||||
|
|||||||
@@ -256,6 +256,60 @@ def test_kategorie_wird_zuerst_durchsucht(db):
|
|||||||
assert zutat.status == "vorhanden"
|
assert zutat.status == "vorhanden"
|
||||||
|
|
||||||
|
|
||||||
|
def test_vegan_allein_begruendet_keinen_treffer(db):
|
||||||
|
"""„vegan" ist eine Eigenschaft, kein Lebensmittel.
|
||||||
|
|
||||||
|
Sonst passt jedes vegane Produkt auf jede vegane Zutat: „vegane Butter"
|
||||||
|
landete auf „veganes Mühlen Knusper Bleu", „veganer Lime Curd" auf
|
||||||
|
„Vegane Mühlen-Schnitzel".
|
||||||
|
"""
|
||||||
|
fleisch = _kategorie(db, "Fleischersatz")
|
||||||
|
_artikel(db, "veganes Mühlen Knusper Bleu", fleisch, bestand=400)
|
||||||
|
_artikel(db, "Vegane Mühlen Nuggets Klassisch", fleisch, bestand=400)
|
||||||
|
_artikel(db, "Vegane Mühlen-Schnitzel auf Basis von Soja", fleisch, bestand=400)
|
||||||
|
|
||||||
|
zutaten = [
|
||||||
|
_zutat("vegane Butter, Block, ungesalzen", 45, "g"),
|
||||||
|
_zutat("veganer griechischer Joghurt oder Skyr", 80, "g"),
|
||||||
|
_zutat("veganer Lime Curd", 120, "g"),
|
||||||
|
]
|
||||||
|
for zutat in pruefe_zutaten(db, zutaten):
|
||||||
|
assert zutat.status == "kein_treffer", zutat.text
|
||||||
|
assert zutat.ziel_name == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_vegane_zutat_findet_das_passende_vegane_produkt(db):
|
||||||
|
"""Die Eigenschaft darf nicht sperren, nur nicht allein tragen."""
|
||||||
|
molk = _kategorie(db, "Molkereiprodukte")
|
||||||
|
butter = _artikel(db, "Vegane Butter", molk, bestand=500)
|
||||||
|
_artikel(db, "Vegane Mühlen-Schnitzel", molk, bestand=400)
|
||||||
|
|
||||||
|
[zutat] = pruefe_zutaten(db, [_zutat("vegane Butter, Block, ungesalzen", 45, "g")])
|
||||||
|
|
||||||
|
assert zutat.ziel_id == butter.id
|
||||||
|
assert zutat.status == "vorhanden"
|
||||||
|
|
||||||
|
|
||||||
|
def test_zutat_aus_lauter_eigenschaften_trifft_nichts(db):
|
||||||
|
cat = _kategorie(db, "Trockenwaren")
|
||||||
|
_artikel(db, "Weissmehl", cat, bestand=1000)
|
||||||
|
|
||||||
|
[zutat] = pruefe_zutaten(db, [_zutat("frisch gemahlen, optional", 1, "g")])
|
||||||
|
|
||||||
|
assert zutat.status == "kein_treffer"
|
||||||
|
|
||||||
|
|
||||||
|
def test_beiwoerter_stoeren_den_echten_treffer_nicht(db):
|
||||||
|
cat = _kategorie(db, "Obst & Gemüse")
|
||||||
|
aepfel = _artikel(db, "Äpfel", cat, bestand=3000)
|
||||||
|
|
||||||
|
[zutat] = pruefe_zutaten(
|
||||||
|
db, [_zutat("Apfel geschält und in 3 mm dünne Scheiben geschnitten", 900, "g")]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert zutat.ziel_id == aepfel.id
|
||||||
|
|
||||||
|
|
||||||
def test_umlaut_und_mehrzahl_finden_sich(db):
|
def test_umlaut_und_mehrzahl_finden_sich(db):
|
||||||
"""Rezept schreibt „Apfel", im Katalog stehen „Äpfel"."""
|
"""Rezept schreibt „Apfel", im Katalog stehen „Äpfel"."""
|
||||||
cat = _kategorie(db, "Obst & Gemüse")
|
cat = _kategorie(db, "Obst & Gemüse")
|
||||||
|
|||||||
Reference in New Issue
Block a user