backend updates on recommendation system

This commit is contained in:
sriram
2026-10-07 11:03:42 +05:30
parent 82f5db1250
commit 71dbb2a6e9
6 changed files with 599 additions and 6 deletions

View File

@@ -7,12 +7,20 @@ Money is returned as a decimal string, never a float.
from __future__ import annotations
from decimal import Decimal
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Literal, Optional
from fastapi import APIRouter, HTTPException, Query
from app.electronics.db.connection import connect
from app.electronics.db.repository import product_rating_and_reviews
from app.electronics import recommend as rec
from app.electronics.db.repository import (
other_variants,
product_rating_and_reviews,
rated_products,
rating_sources_for,
review_sentiment_counts,
similar_products,
)
from app.electronics.reviews import select_reviews
router = APIRouter(prefix="/elec", tags=["electronics"])
@@ -106,7 +114,13 @@ def products(
f"LIMIT %(limit)s OFFSET %(offset)s",
{**params, "limit": limit, "offset": offset},
).fetchall()
return {"total": total, "products": [_clean(r) for r in rows]}
sources = rating_sources_for(conn, [r["product_id"] for r in rows])
out = []
for r in rows:
rating = _overall_rating(sources[r["product_id"]])
out.append({**_clean(r), "rating": rating["value"] if rating else None,
"rating_count": rating["count"] if rating else None})
return {"total": total, "products": out}
@router.get("/products/{product_id}")
@@ -185,6 +199,63 @@ def _breakdown(sources: List[dict]) -> Optional[List[dict]]:
for k in ("5", "4", "3", "2", "1")]
@router.get("/products/{product_id}/recommendations")
def recommendations(
product_id: int,
kind: Literal["similar", "better_rated"] = Query("similar", alias="type"),
limit: int = Query(rec.MAX_ITEMS, ge=1, le=12),
tn_only: bool = False,
) -> dict:
"""Products to suggest under this one's ratings and reviews (see
app/electronics/recommend.py), plus the same model's other variants.
type=similar: closest specs; type=better_rated: rated higher, similar price."""
with connect() as conn:
target = conn.execute(
"SELECT product_id, best_price FROM elec.v_brand_catalog WHERE product_id = %s", (product_id,)
).fetchone()
if not target:
raise HTTPException(status_code=404, detail="Product not found or not verified")
if kind == "similar":
similar = similar_products(conn, product_id, band=rec.SIMILAR_PRICE_BAND, tn_only=tn_only)
rated = (rated_products(conn, product_id, band=rec.SIMILAR_PRICE_BAND, tn_only=tn_only)
if len(similar) < rec.MIN_MATCHES else [])
else:
similar = []
rated = rated_products(conn, product_id, band=rec.BETTER_PRICE_BAND, tn_only=tn_only)
ids = {product_id} | {c["product_id"] for c in similar + rated}
ratings = {pid: _overall_rating(src) for pid, src in rating_sources_for(conn, list(ids)).items()}
def with_rating(c: dict) -> dict:
r = ratings.get(c["product_id"])
return {**c, "best_price": _float(c.get("best_price")),
"rating": r["value"] if r else None, "rating_count": r["count"] if r else None}
if kind == "similar":
picked = rec.recommend(with_rating(dict(target)), [with_rating(c) for c in similar],
[with_rating(c) for c in rated], limit)
else:
sentiment = review_sentiment_counts(conn, [c["product_id"] for c in rated])
picked = rec.better_rated(
with_rating(dict(target)),
[{**with_rating(c), "sentiment": sentiment.get(c["product_id"])} for c in rated], limit)
cards = {r["product_id"]: r for r in conn.execute(
"SELECT product_id, brand, display_name, ram_gb, storage_gb, image_url, best_price, best_price_site "
"FROM elec.v_brand_catalog WHERE product_id = ANY(%s)", ([c["product_id"] for c in picked],)
)}
variants = other_variants(conn, product_id)
items = [
{**_clean(cards[c["product_id"]]), "rating": c["rating"], "rating_count": c["rating_count"],
"basis": c["basis"], "reason": c["reason"]}
for c in picked if c["product_id"] in cards
]
return {"product_id": product_id, "type": kind, "items": items,
"other_variants": [_clean(v) for v in variants]}
def _float(value: Any) -> Optional[float]:
return None if value is None else float(value)
@router.get("/products/{product_id}/price-history")
def price_history(product_id: int) -> List[dict]:
with connect() as conn: