11from fastapi import APIRouter , Depends , HTTPException , Query
2- from sqlalchemy .orm import Session
2+ from sqlalchemy .orm import Session , joinedload
33from sentence_transformers import SentenceTransformer
4- from sqlalchemy import text
4+ from sqlalchemy import text , func
55from app .database .connection import get_db
6- from app .models import Store
6+ from app .models import Store , Brand
77from app .services .recommend_service import HybridRecommender
88from geoalchemy2 .functions import ST_DWithin , ST_SetSRID , ST_MakePoint , ST_Distance
9+ from geoalchemy2 import Geometry
10+ from geoalchemy2 .shape import to_shape
911import logging
1012from app .services .collect_user_data import collect_user_data
1113from collections import defaultdict
1214from app .database .redis_client import r
1315import json
16+ from app .database .es import es
17+
1418
1519router = APIRouter ()
1620model = SentenceTransformer ("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" )
@@ -23,6 +27,12 @@ def get_min_rank(benefits: list) -> str:
2327 return b .rank
2428 return "NONE"
2529
30+ def extract_lat_lng (store ):
31+ if store .location is None :
32+ return None , None
33+ point = to_shape (store .location )
34+ return point .y , point .x
35+
2636@router .get ("/recommend" )
2737def recommend (
2838 user_id :int ,
@@ -33,7 +43,7 @@ def recommend(
3343 ):
3444
3545 # 1. 사용자 정보 수집
36- categories , histories , bookmarks , clicks , searches = collect_user_data (user_id , db )
46+ categories , histories , bookmarks , clicks , searches = collect_user_data (user_id , db , es )
3747
3848 if not (categories or histories or bookmarks or clicks or searches ):
3949 raise HTTPException (status_code = 404 , detail = "사용자 정보가 부족합니다." )
@@ -111,7 +121,7 @@ def hybrid_recommend(
111121 logger .error (f"Redis 캐시 확인 중 오류: { e } " )
112122
113123 # 1. 사용자 텍스트 정보 수집
114- categories , histories , bookmarks , clicks , searches = collect_user_data (user_id , db )
124+ categories , histories , bookmarks , clicks , searches = collect_user_data (user_id , db , es )
115125
116126 if not (categories or histories or bookmarks or clicks or searches ):
117127 raise HTTPException (status_code = 404 , detail = "사용자 정보가 부족합니다." )
@@ -126,17 +136,20 @@ def hybrid_recommend(
126136 logger .debug (f"Recommendation results for user { user_id } : { results } " )
127137
128138 # 4. 위치 기반 필터링: 추천 브랜드 매장 중 반경 km 이내
129- nearby_stores = db .query (Store ).filter (
130- Store .brand_id .in_ (recommended_brand_ids ),
131- ST_DWithin (Store .location , ST_SetSRID (ST_MakePoint (lng , lat ), 4326 ), radius_km * 1000 )
139+ store_query = db .query (Store ).options (
140+ joinedload (Store .brand ).joinedload (Brand .category ),
141+ joinedload (Store .brand ).joinedload (Brand .benefits )
142+ ).filter (
143+ Store .brand_id .in_ (recommended_brand_ids ),
144+ func .ST_DWithin (Store .location , func .ST_SetSRID (func .ST_MakePoint (lng , lat ), 4326 ), radius_km * 1000 )
132145 ).order_by (
133- ST_Distance (Store .location , ST_SetSRID (ST_MakePoint (lng , lat ), 4326 ))
146+ func . ST_Distance (Store .location , func . ST_SetSRID (func . ST_MakePoint (lng , lat ), 4326 ))
134147 ).all ()
135148
136149 # 매장 중 하나씩 결과 연결
137150 store_map = {}
138151 brand_store_map = defaultdict (list )
139- for store in nearby_stores :
152+ for store in store_query :
140153 brand_store_map [store .brand_id ].append (store )
141154 store_map = {bid : stores [0 ] for bid , stores in brand_store_map .items ()}
142155
@@ -147,10 +160,13 @@ def hybrid_recommend(
147160 continue
148161 store = store_map [brand_id ]
149162 brand = store .brand
163+ lat , lng = extract_lat_lng (store )
150164
151165 item = {
152166 "storeId" : store .id ,
153167 "storeName" : store .name ,
168+ "latitude" : lat ,
169+ "longitude" : lng ,
154170 "category" : brand .category .name if brand .category else None ,
155171 "description" : brand .description ,
156172 "isVIPcock" : brand .rank_type in ("VIP" , "VIP_NORMAL" ),
0 commit comments