Spaces:
Sleeping
Sleeping
| """Rule-based catalog recommendation (spec section 7). | |
| Style-tag overlap + light category context. No ML, no training. Cosine-style | |
| Jaccard is included as a tie-breaker but the dominant signal is plain tag match. | |
| """ | |
| from __future__ import annotations | |
| import functools | |
| import json | |
| from ..config import settings | |
| # A few detected COCO labels -> catalog categories, for gentle room context. | |
| LABEL_TO_CATEGORY = { | |
| "bed": "bed", | |
| "couch": "sofa", | |
| "chair": "chair", | |
| "dining table": "table", | |
| "tv": "storage", | |
| "potted plant": "decor", | |
| } | |
| def load_catalog() -> list[dict]: | |
| return json.loads(settings.CATALOG_JSON.read_text(encoding="utf-8")) | |
| def filter_and_rank( | |
| category: str | None = None, | |
| styles: list[str] | None = None, | |
| detected_labels: list[str] | None = None, | |
| ) -> list[dict]: | |
| """Return catalog items, optionally filtered, ranked recommendation-first.""" | |
| items = load_catalog() | |
| wanted = {s.lower() for s in (styles or [])} | |
| if category: | |
| items = [i for i in items if i["category"] == category] | |
| context_cats = { | |
| LABEL_TO_CATEGORY[lbl] | |
| for lbl in (detected_labels or []) | |
| if lbl in LABEL_TO_CATEGORY | |
| } | |
| ranked: list[dict] = [] | |
| for it in items: | |
| tags = {t.lower() for t in it["style_tags"]} | |
| overlap = len(tags & wanted) | |
| jaccard = overlap / (len(tags | wanted) or 1) | |
| score = overlap + jaccard # exact tag matches dominate | |
| if it["category"] in context_cats: | |
| score += 0.5 | |
| ranked.append({**it, "score": round(float(score), 3)}) | |
| ranked.sort(key=lambda x: (-x["score"], x["price"])) | |
| return ranked | |