feat: add Store.get_element_profiles() for wizard role candidate lookup
This commit is contained in:
parent
1a5fb23dfd
commit
4f1570ee6f
2 changed files with 41 additions and 0 deletions
|
|
@ -571,6 +571,7 @@ class Store:
|
||||||
max_carbs_g: float | None = None,
|
max_carbs_g: float | None = None,
|
||||||
max_sodium_mg: float | None = None,
|
max_sodium_mg: float | None = None,
|
||||||
excluded_ids: list[int] | None = None,
|
excluded_ids: list[int] | None = None,
|
||||||
|
exclude_generic: bool = False,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""Find recipes containing any of the given ingredient names.
|
"""Find recipes containing any of the given ingredient names.
|
||||||
Scores by match count and returns highest-scoring first.
|
Scores by match count and returns highest-scoring first.
|
||||||
|
|
@ -580,6 +581,9 @@ class Store:
|
||||||
|
|
||||||
Nutrition filters use NULL-passthrough: rows without nutrition data
|
Nutrition filters use NULL-passthrough: rows without nutrition data
|
||||||
always pass (they may be estimated or absent entirely).
|
always pass (they may be estimated or absent entirely).
|
||||||
|
|
||||||
|
exclude_generic: when True, skips recipes marked is_generic=1.
|
||||||
|
Pass True for Level 1 ("Use What I Have") to suppress catch-all recipes.
|
||||||
"""
|
"""
|
||||||
if not ingredient_names:
|
if not ingredient_names:
|
||||||
return []
|
return []
|
||||||
|
|
@ -605,6 +609,8 @@ class Store:
|
||||||
placeholders = ",".join("?" * len(excluded_ids))
|
placeholders = ",".join("?" * len(excluded_ids))
|
||||||
extra_clauses.append(f"r.id NOT IN ({placeholders})")
|
extra_clauses.append(f"r.id NOT IN ({placeholders})")
|
||||||
extra_params.extend(excluded_ids)
|
extra_params.extend(excluded_ids)
|
||||||
|
if exclude_generic:
|
||||||
|
extra_clauses.append("r.is_generic = 0")
|
||||||
where_extra = (" AND " + " AND ".join(extra_clauses)) if extra_clauses else ""
|
where_extra = (" AND " + " AND ".join(extra_clauses)) if extra_clauses else ""
|
||||||
|
|
||||||
if self._fts_ready():
|
if self._fts_ready():
|
||||||
|
|
@ -680,6 +686,29 @@ class Store:
|
||||||
def get_recipe(self, recipe_id: int) -> dict | None:
|
def get_recipe(self, recipe_id: int) -> dict | None:
|
||||||
return self._fetch_one("SELECT * FROM recipes WHERE id = ?", (recipe_id,))
|
return self._fetch_one("SELECT * FROM recipes WHERE id = ?", (recipe_id,))
|
||||||
|
|
||||||
|
def get_element_profiles(self, names: list[str]) -> dict[str, list[str]]:
|
||||||
|
"""Return {ingredient_name: [element_tag, ...]} for the given names.
|
||||||
|
|
||||||
|
Only names present in ingredient_profiles are returned -- missing names
|
||||||
|
are silently omitted so callers can distinguish "no profile" from "empty
|
||||||
|
elements list".
|
||||||
|
"""
|
||||||
|
if not names:
|
||||||
|
return {}
|
||||||
|
placeholders = ",".join("?" * len(names))
|
||||||
|
rows = self._fetch_all(
|
||||||
|
f"SELECT name, elements FROM ingredient_profiles WHERE name IN ({placeholders})",
|
||||||
|
tuple(names),
|
||||||
|
)
|
||||||
|
result: dict[str, list[str]] = {}
|
||||||
|
for row in rows:
|
||||||
|
try:
|
||||||
|
elements = json.loads(row["elements"]) if row["elements"] else []
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
elements = []
|
||||||
|
result[row["name"]] = elements
|
||||||
|
return result
|
||||||
|
|
||||||
# ── rate limits ───────────────────────────────────────────────────────
|
# ── rate limits ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
def check_and_increment_rate_limit(
|
def check_and_increment_rate_limit(
|
||||||
|
|
|
||||||
|
|
@ -42,3 +42,15 @@ def test_check_rate_limit_exceeded(store_with_recipes):
|
||||||
allowed, count = store_with_recipes.check_and_increment_rate_limit("leftover_mode", daily_max=5)
|
allowed, count = store_with_recipes.check_and_increment_rate_limit("leftover_mode", daily_max=5)
|
||||||
assert allowed is False
|
assert allowed is False
|
||||||
assert count == 5
|
assert count == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_element_profiles_returns_known_items(store_with_profiles):
|
||||||
|
profiles = store_with_profiles.get_element_profiles(["butter", "parmesan", "unknown_item"])
|
||||||
|
assert profiles["butter"] == ["Richness"]
|
||||||
|
assert "Depth" in profiles["parmesan"]
|
||||||
|
assert "unknown_item" not in profiles
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_element_profiles_empty_list(store_with_profiles):
|
||||||
|
profiles = store_with_profiles.get_element_profiles([])
|
||||||
|
assert profiles == {}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue