Johnntirs's picture
Room Visualizer backend (Docker)
0cdb4e8
Raw
History Blame Contribute Delete
1.71 kB
"""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",
}
@functools.lru_cache(maxsize=1)
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