Files
tasteby/backend/api/routes/search.py
joungmin 36bec10bd0 Initial commit: Tasteby - YouTube restaurant map service
Backend (FastAPI + Oracle ADB), Frontend (Next.js), daemon worker.
Features: channel/video/restaurant management, semantic search,
Google OAuth, user reviews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:47:19 +09:00

67 lines
2.0 KiB
Python

"""Search API routes — keyword + semantic search."""
from fastapi import APIRouter, Query
from core import restaurant, vector
from core.db import conn
router = APIRouter()
@router.get("")
def search_restaurants(
q: str = Query(..., min_length=1),
mode: str = Query("keyword", pattern="^(keyword|semantic|hybrid)$"),
limit: int = Query(20, le=100),
):
"""Search restaurants by keyword, semantic similarity, or hybrid."""
if mode == "semantic":
return _semantic_search(q, limit)
elif mode == "hybrid":
kw = _keyword_search(q, limit)
sem = _semantic_search(q, limit)
# merge: keyword results first, then semantic results not already in keyword
seen = {r["id"] for r in kw}
merged = list(kw)
for r in sem:
if r["id"] not in seen:
merged.append(r)
seen.add(r["id"])
return merged[:limit]
else:
return _keyword_search(q, limit)
def _keyword_search(q: str, limit: int) -> list[dict]:
sql = """
SELECT id, name, address, region, latitude, longitude,
cuisine_type, price_range
FROM restaurants
WHERE latitude IS NOT NULL
AND (UPPER(name) LIKE UPPER(:q)
OR UPPER(address) LIKE UPPER(:q)
OR UPPER(region) LIKE UPPER(:q)
OR UPPER(cuisine_type) LIKE UPPER(:q))
FETCH FIRST :lim ROWS ONLY
"""
pattern = f"%{q}%"
with conn() as c:
cur = c.cursor()
cur.execute(sql, {"q": pattern, "lim": limit})
cols = [d[0].lower() for d in cur.description]
return [dict(zip(cols, row)) for row in cur.fetchall()]
def _semantic_search(q: str, limit: int) -> list[dict]:
similar = vector.search_similar(q, top_k=limit)
if not similar:
return []
rest_ids = list({s["restaurant_id"] for s in similar})
results = []
for rid in rest_ids[:limit]:
r = restaurant.get_by_id(rid)
if r and r.get("latitude"):
results.append(r)
return results