from data_layer.food_suggest import ( collapse_key, name_tokens, score_name_match, suggest_batch, suggest_for_name, ) def _index(*foods): index = {"foods": [], "by_collapse": {}, "by_token": {}, "by_prefix": {}, "by_suffix": {}} for food in foods: food["_c"] = collapse_key(food["name_de"]) food["_t"] = name_tokens(food["name_de"]) if food["_c"]: index["by_collapse"].setdefault(food["_c"], []).append(food) index["by_prefix"].setdefault(food["_c"][:4], []).append(food) if len(food["_c"]) >= 4: index["by_suffix"].setdefault(food["_c"][-4:], []).append(food) for tok in food["_t"]: index["by_token"].setdefault(tok, []).append(food) return index def test_collapse_treats_space_as_same(): assert collapse_key("Haferflocken") == collapse_key("Hafer Flocken") def test_haferflocken_ranks_simple_name_first(): index = { "foods": [], "by_collapse": {}, "by_token": {}, "by_prefix": {}, "by_suffix": {}, } simple = {"id": "1", "name_de": "Hafer Flocken", "name_en": "oat flakes", "bls_code": "C131111", "catalog_kind": "official_bls"} dish = {"id": "2", "name_de": "Milch-Getreide-Brei, mit Haferflocken und Apfelsaft (geeignet für Beikost)", "name_en": "", "bls_code": "X", "catalog_kind": "official_bls"} for food in (simple, dish): food["_c"] = collapse_key(food["name_de"]) index["by_collapse"].setdefault(food["_c"], []).append(food) index["by_prefix"].setdefault(food["_c"][:4], []).append(food) index["by_suffix"].setdefault(food["_c"][-4:], []).append(food) packed = suggest_for_name(index, "Haferflocken", limit=3) assert packed["suggestions"][0]["name_de"] == "Hafer Flocken" assert packed["suggestions"][0]["score"] >= score_name_match("Haferflocken", dish["name_de"]) def test_close_scores_are_ambiguous(): a = {"id": "1", "name_de": "Olivenöl nativ", "name_en": "", "bls_code": "A", "catalog_kind": "official_bls"} b = {"id": "2", "name_de": "Olivenöl raffiniert", "name_en": "", "bls_code": "B", "catalog_kind": "official_bls"} index = {"foods": [], "by_collapse": {}, "by_token": {}, "by_prefix": {}, "by_suffix": {}} for food in (a, b): food["_c"] = collapse_key(food["name_de"]) index["by_prefix"].setdefault(food["_c"][:4], []).append(food) for tok in food["name_de"].lower().replace(",", "").split(): if len(tok) >= 2: index["by_token"].setdefault(tok, []).append(food) packed = suggest_for_name(index, "Olivenöl", limit=3) assert packed["ambiguous"] is True assert packed["suggestion_count"] >= 2 def test_large_prefix_bucket_keeps_exact_collapse(): index = {"foods": [], "by_collapse": {}, "by_token": {}, "by_prefix": {}, "by_suffix": {}} target = {"id": "hit", "name_de": "Hafer Flocken", "name_en": "", "bls_code": "C", "catalog_kind": "official_bls"} target["_c"] = collapse_key(target["name_de"]) index["by_collapse"][target["_c"]] = [target] index["by_prefix"][target["_c"][:4]] = [target] + [ {"id": str(i), "name_de": f"Hafer Gericht {i}", "name_en": "", "_c": f"hafergericht{i}"} for i in range(80) ] packed = suggest_for_name(index, "Haferflocken", limit=3) assert packed["suggestions"][0]["name_de"] == "Hafer Flocken" def test_suggest_batch_dedupes_names(): class _Cur: def execute(self, *args, **kwargs): return None def fetchall(self): return [] out = suggest_batch(_Cur(), None, ["Haferflocken", "Haferflocken", ""], limit=2) assert "Haferflocken" in out assert out["Haferflocken"]["suggestions"] == [] def test_number_tokens_keep_decimal_fat(): assert "9.5" in name_tokens("Joghurt 9,5%") assert "10" in name_tokens("Joghurt, aus Kuhmilch, 10 % Fett") def test_joghurt_10_ranks_above_35(): light = {"id": "a", "name_de": "Joghurt >3,5% Fett", "name_en": "", "bls_code": "M", "catalog_kind": "official_bls"} full = { "id": "b", "name_de": "Joghurt, aus Kuhmilch, 10 % Fett", "name_en": "", "bls_code": "N", "catalog_kind": "official_bls", } index = _index(light, full) for query in ("Joghurt 10%", "Joghurt 9,5%", "Joghurt 10"): packed = suggest_for_name(index, query, limit=3) assert packed["suggestions"][0]["name_de"] == full["name_de"], query assert packed["suggestions"][0]["score"] > score_name_match(query, light["name_de"])