Add Doris Kitchen household recipe app
This commit is contained in:
81
home/doris-kitchen/app/services/scoring.py
Normal file
81
home/doris-kitchen/app/services/scoring.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
def normalize_token(value: str) -> str:
|
||||
value = re.sub(r"[^a-z0-9]+", " ", value.lower())
|
||||
return " ".join(part for part in value.split() if part)
|
||||
|
||||
|
||||
def extract_ingredients(payload: dict[str, Any]) -> list[str]:
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for item in payload.get("items") or []:
|
||||
name = normalize_token(str(item.get("name") or ""))
|
||||
if not name or name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
|
||||
def extract_tags(payload: dict[str, Any]) -> list[str]:
|
||||
return [normalize_token(str(tag)) for tag in (payload.get("tags") or []) if normalize_token(str(tag))]
|
||||
|
||||
|
||||
def score_recipe(query: str, payload: dict[str, Any], profile: dict[str, Any]) -> tuple[int, list[str]]:
|
||||
score = 50
|
||||
reasons: list[str] = []
|
||||
haystack = " ".join(
|
||||
[
|
||||
normalize_token(str(payload.get("name") or "")),
|
||||
normalize_token(str(payload.get("description") or "")),
|
||||
" ".join(extract_ingredients(payload)),
|
||||
" ".join(extract_tags(payload)),
|
||||
]
|
||||
)
|
||||
query_terms = [term for term in normalize_token(query).split() if len(term) > 2]
|
||||
if query_terms:
|
||||
matched = sum(1 for term in query_terms if term in haystack)
|
||||
score += matched * 6
|
||||
if matched:
|
||||
reasons.append(f"Matches query terms: {matched}")
|
||||
ingredients = extract_ingredients(payload)
|
||||
tags = extract_tags(payload)
|
||||
approved_ingredients = {normalize_token(k): int(v) for k, v in (profile.get("approved_ingredients") or {}).items()}
|
||||
rejected_ingredients = {normalize_token(k): int(v) for k, v in (profile.get("rejected_ingredients") or {}).items()}
|
||||
approved_tags = {normalize_token(k): int(v) for k, v in (profile.get("approved_tags") or {}).items()}
|
||||
rejected_tags = {normalize_token(k): int(v) for k, v in (profile.get("rejected_tags") or {}).items()}
|
||||
soft_blacklist = {normalize_token(item.get("name", "")) for item in (profile.get("soft_blacklist") or []) if item.get("enabled")}
|
||||
for term in sorted(soft_blacklist):
|
||||
if term and term in haystack:
|
||||
score -= 30
|
||||
reasons.append(f"Soft blacklist term: {term}")
|
||||
liked_hits = 0
|
||||
for ingredient in ingredients:
|
||||
liked_hits += approved_ingredients.get(ingredient, 0)
|
||||
score -= min(rejected_ingredients.get(ingredient, 0) * 7, 18)
|
||||
for tag in tags:
|
||||
liked_hits += approved_tags.get(tag, 0)
|
||||
score -= min(rejected_tags.get(tag, 0) * 5, 12)
|
||||
if liked_hits:
|
||||
score += min(liked_hits * 3, 18)
|
||||
reasons.append("Aligned with prior approvals")
|
||||
title = str(payload.get("name") or "")
|
||||
if len(title.split()) <= 10:
|
||||
score += 3
|
||||
reasons.append("Clear short title")
|
||||
total_time = payload.get("time")
|
||||
if isinstance(total_time, int):
|
||||
if total_time <= 45:
|
||||
score += 6
|
||||
reasons.append("Weeknight-friendly time")
|
||||
elif total_time >= 120:
|
||||
score -= 6
|
||||
description = str(payload.get("description") or "")
|
||||
if description.count("\n") >= 3:
|
||||
score += 4
|
||||
reasons.append("Structured instructions")
|
||||
return max(0, min(100, score)), reasons
|
||||
Reference in New Issue
Block a user