@@ -185,163 +185,176 @@ def get_similar_users(self, target_user_id: int, n_users: int = 5) -> List[int]:
185185 def recommend_items (
186186 self ,
187187 target_user_id : int ,
188- n_recommendations : int = 10
188+ n_recommendations : int = 10 ,
189+ db_session : Session = None # ⭐ 새 파라미터 추가
189190 ) -> List [ItemRecommendation ]:
190191 """
191192 대상 사용자에게 경매 상품 추천
192193
193194 Args:
194195 target_user_id: 추천 대상 사용자 ID
195196 n_recommendations: 추천할 상품 개수 (기본값: 10)
197+ db_session: DB 세션 (매 요청마다 새로 전달)
196198
197199 Returns:
198200 추천 상품 리스트 (ItemRecommendation 객체)
199201 """
200202 from datetime import datetime
201203
202- print (f"사용자 { target_user_id } 에 대한 추천 생성 시작..." )
204+ # ⭐ 세션 선택: 전달받은 세션이 있으면 사용, 없으면 초기화 시 세션 사용
205+ db = db_session if db_session is not None else self .db
203206
204- # 1. 유사 사용자 찾기
205- similar_users = self .get_similar_users (target_user_id , n_users = 5 )
206-
207- # Cold Start 대응: 유사 사용자가 없으면 인기 상품 추천
208- if not similar_users :
209- print (f"사용자 { target_user_id } 의 유사 사용자를 찾을 수 없습니다. 인기 상품으로 대체합니다." )
210- return self ._get_popular_items (target_user_id , n_recommendations )
211-
212- print (f"유사 사용자 { len (similar_users )} 명 발견: { similar_users } " )
213-
214- # 2. 유사 사용자들이 입찰/찜한 상품 수집
215- candidate_items = []
216- for uid in similar_users :
217- # 입찰한 상품
218- candidate_items .extend (self .user_bid_items .get (uid , []))
219- # 찜한 상품
220- candidate_items .extend (self .user_liked_items .get (uid , []))
221-
222- # 3. 대상 사용자가 이미 접한 상품 제외
223- user_interacted = self .user_bid_items .get (target_user_id , set ()) | \
224- self .user_liked_items .get (target_user_id , set ())
225-
226- # 4. 빈도수 계산 (많이 추천될수록 높은 점수)
227- candidate_counts = Counter (candidate_items )
228-
229- # 이미 접한 상품 제거
230- for item_id in user_interacted :
231- candidate_counts .pop (item_id , None )
232-
233- # 5. 상위 N개 추출
234- recommended_item_ids = [item_id for item_id , _ in candidate_counts .most_common (n_recommendations * 2 )]
235-
236- # Cold Start 대응: 후보 아이템이 없으면 인기 상품 추천
237- if not recommended_item_ids :
238- print ("협업 필터링 후보가 없습니다. 인기 상품으로 대체합니다." )
239- return self ._get_popular_items (target_user_id , n_recommendations )
240-
241- # 6. DB에서 상품 상세 정보 조회
242- # 수정: 현재 시간 기준으로 실제 입찰 가능한 상품만 필터링
243- now = datetime .utcnow ()
244- items = self .db .query (ItemDB ).filter (
245- ItemDB .item_id .in_ (recommended_item_ids ),
246- ItemDB .item_status == ItemStatusEnum .BIDDING ,
247- ItemDB .end_time > now # 추가: 경매 종료 시간 체크
248- ).all ()
249-
250- # 7. ItemRecommendation 객체로 변환
251- recommended_items = []
252- for item in items [:n_recommendations ]:
253- recommendation = ItemRecommendation (
254- item_id = item .item_id ,
255- name = item .name ,
256- title = item .title ,
257- category = item .category ,
258- current_price = item .current_price ,
259- end_time = item .end_time ,
260- item_status = item .item_status ,
261- region_name = item .region .sigungu ,
262- view_count = item .view_count ,
263- bid_count = item .bid_count ,
264- recommendation_score = candidate_counts .get (item .item_id , 0 )
265- )
266- recommended_items .append (recommendation )
207+ print (f"사용자 { target_user_id } 에 대한 추천 생성 시작..." )
267208
268- # Cold Start 대응: 결과가 부족하면 인기 상품으로 채우기
269- if len (recommended_items ) < n_recommendations :
270- print (f"추천 결과가 부족합니다 ({ len (recommended_items )} /{ n_recommendations } ). 인기 상품으로 보완합니다." )
271- popular_items = self ._get_popular_items (target_user_id , n_recommendations - len (recommended_items ))
209+ try :
210+ # 1. 유사 사용자 찾기
211+ similar_users = self .get_similar_users (target_user_id , n_users = 5 )
212+
213+ # Cold Start 대응: 유사 사용자가 없으면 인기 상품 추천
214+ if not similar_users :
215+ print (f"사용자 { target_user_id } 의 유사 사용자를 찾을 수 없습니다. 인기 상품으로 대체합니다." )
216+ # ⭐ 롤백 후 인기 상품 조회
217+ db .rollback ()
218+ return self ._get_popular_items (target_user_id , n_recommendations , db )
219+
220+ print (f"유사 사용자 { len (similar_users )} 명 발견: { similar_users } " )
221+
222+ # 2. 유사 사용자들이 입찰/찜한 상품 수집
223+ candidate_items = []
224+ for uid in similar_users :
225+ candidate_items .extend (self .user_bid_items .get (uid , []))
226+ candidate_items .extend (self .user_liked_items .get (uid , []))
227+
228+ # 3. 대상 사용자가 이미 접한 상품 제외
229+ user_interacted = self .user_bid_items .get (target_user_id , set ()) | \
230+ self .user_liked_items .get (target_user_id , set ())
231+
232+ # 4. 빈도수 계산
233+ candidate_counts = Counter (candidate_items )
234+
235+ for item_id in user_interacted :
236+ candidate_counts .pop (item_id , None )
237+
238+ # 5. 상위 N개 추출
239+ recommended_item_ids = [item_id for item_id , _ in candidate_counts .most_common (n_recommendations * 2 )]
272240
273- # 중복 제거
274- existing_ids = {item .item_id for item in recommended_items }
275- for item in popular_items :
276- if item .item_id not in existing_ids :
277- recommended_items .append (item )
278- if len (recommended_items ) >= n_recommendations :
279- break
280-
281- print (f"추천 생성 완료. 추천 상품 수: { len (recommended_items )} " )
282- return recommended_items
241+ # Cold Start 대응: 후보 아이템이 없으면 인기 상품 추천
242+ if not recommended_item_ids :
243+ print ("협업 필터링 후보가 없습니다. 인기 상품으로 대체합니다." )
244+ # ⭐ 롤백 후 인기 상품 조회
245+ db .rollback ()
246+ return self ._get_popular_items (target_user_id , n_recommendations , db )
247+
248+ # ⭐ 6. DB에서 상품 상세 정보 조회 (try-except 추가)
249+ now = datetime .utcnow ()
250+ items = db .query (ItemDB ).filter (
251+ ItemDB .item_id .in_ (recommended_item_ids ),
252+ ItemDB .item_status == ItemStatusEnum .BIDDING ,
253+ ItemDB .end_time > now
254+ ).all ()
255+
256+ # 7. ItemRecommendation 객체로 변환
257+ recommended_items = []
258+ for item in items [:n_recommendations ]:
259+ recommendation = ItemRecommendation (
260+ item_id = item .item_id ,
261+ name = item .name ,
262+ title = item .title ,
263+ category = item .category ,
264+ current_price = item .current_price ,
265+ end_time = item .end_time ,
266+ item_status = item .item_status ,
267+ region_name = item .region .sigungu ,
268+ view_count = item .view_count ,
269+ bid_count = item .bid_count ,
270+ recommendation_score = candidate_counts .get (item .item_id , 0 )
271+ )
272+ recommended_items .append (recommendation )
273+
274+ # Cold Start 대응: 결과가 부족하면 인기 상품으로 채우기
275+ if len (recommended_items ) < n_recommendations :
276+ print (f"추천 결과가 부족합니다 ({ len (recommended_items )} /{ n_recommendations } ). 인기 상품으로 보완합니다." )
277+ # ⭐ 롤백 후 인기 상품 조회
278+ db .rollback ()
279+ popular_items = self ._get_popular_items (target_user_id , n_recommendations - len (recommended_items ), db )
280+
281+ existing_ids = {item .item_id for item in recommended_items }
282+ for item in popular_items :
283+ if item .item_id not in existing_ids :
284+ recommended_items .append (item )
285+ if len (recommended_items ) >= n_recommendations :
286+ break
287+
288+ print (f"추천 생성 완료. 추천 상품 수: { len (recommended_items )} " )
289+ return recommended_items
290+
291+ except Exception as e :
292+ # ⭐ 에러 발생 시 반드시 롤백
293+ print (f"[ERROR] 추천 생성 중 오류 발생: { str (e )} " )
294+ db .rollback ()
295+ # 인기 상품으로 폴백
296+ return self ._get_popular_items (target_user_id , n_recommendations , db )
283297
284- def _get_popular_items (self , target_user_id : int , n_items : int ) -> List [ItemRecommendation ]:
298+ def _get_popular_items (
299+ self ,
300+ target_user_id : int ,
301+ n_items : int ,
302+ db_session : Session = None # ⭐ 새 파라미터 추가
303+ ) -> List [ItemRecommendation ]:
285304 """
286305 인기 상품 추천 (Cold Start 대응)
287- - 최근 3일 내 생성
288- - 입찰 수 많은 순
289- - 사용자가 이미 접한 상품 제외
290-
291- Args:
292- target_user_id: 대상 사용자 ID
293- n_items: 추천할 상품 개수
294-
295- Returns:
296- 인기 상품 리스트
297306 """
298307 from datetime import datetime , timedelta
299308
300- # 사용자가 이미 접한 상품 제외
301- user_interacted = self .user_bid_items .get (target_user_id , set ()) | \
302- self .user_liked_items .get (target_user_id , set ())
303-
304- # 최근 3일 내 생성된 입찰 가능한 인기 상품 조회
305- three_days_ago = datetime .utcnow () - timedelta (days = 3 )
306- now = datetime .utcnow ()
309+ # ⭐ 세션 선택
310+ db = db_session if db_session is not None else self .db
307311
308- # 사용자가 접한 상품이 있을 때만 필터링
309- if user_interacted :
310- items = self .db .query (ItemDB ).filter (
311- ItemDB .item_status == ItemStatusEnum .BIDDING ,
312- ItemDB .end_time > now ,
313- ItemDB .created_at > three_days_ago ,
314- ~ ItemDB .item_id .in_ (user_interacted )
315- ).order_by (
316- ItemDB .bid_count .desc (),
317- ItemDB .view_count .desc ()
318- ).limit (n_items * 2 ).all ()
319- else :
320- items = self .db .query (ItemDB ).filter (
312+ try :
313+ # 사용자가 이미 접한 상품 제외
314+ user_interacted = self .user_bid_items .get (target_user_id , set ()) | \
315+ self .user_liked_items .get (target_user_id , set ())
316+
317+ # 최근 3일 내 생성된 입찰 가능한 인기 상품 조회
318+ three_days_ago = datetime .utcnow () - timedelta (days = 3 )
319+ now = datetime .utcnow ()
320+
321+ # ⭐ 쿼리 간소화
322+ query = db .query (ItemDB ).filter (
321323 ItemDB .item_status == ItemStatusEnum .BIDDING ,
322324 ItemDB .end_time > now ,
323325 ItemDB .created_at > three_days_ago
324- ).order_by (
326+ )
327+
328+ if user_interacted :
329+ query = query .filter (~ ItemDB .item_id .in_ (user_interacted ))
330+
331+ items = query .order_by (
325332 ItemDB .bid_count .desc (),
326333 ItemDB .view_count .desc ()
327334 ).limit (n_items * 2 ).all ()
335+
336+ # ItemRecommendation 객체로 변환
337+ popular_items = []
338+ for item in items [:n_items ]:
339+ recommendation = ItemRecommendation (
340+ item_id = item .item_id ,
341+ name = item .name ,
342+ title = item .title ,
343+ category = item .category ,
344+ current_price = item .current_price ,
345+ end_time = item .end_time ,
346+ item_status = item .item_status ,
347+ region_name = item .region .sigungu ,
348+ view_count = item .view_count ,
349+ bid_count = item .bid_count ,
350+ recommendation_score = 0
351+ )
352+ popular_items .append (recommendation )
353+
354+ return popular_items
328355
329- # ItemRecommendation 객체로 변환
330- popular_items = []
331- for item in items [:n_items ]:
332- recommendation = ItemRecommendation (
333- item_id = item .item_id ,
334- name = item .name ,
335- title = item .title ,
336- category = item .category ,
337- current_price = item .current_price ,
338- end_time = item .end_time ,
339- item_status = item .item_status ,
340- region_name = item .region .sigungu ,
341- view_count = item .view_count ,
342- bid_count = item .bid_count ,
343- recommendation_score = 0 # 인기 상품은 점수 0
344- )
345- popular_items .append (recommendation )
346-
347- return popular_items
356+ except Exception as e :
357+ # ⭐ 에러 발생 시 롤백 후 빈 리스트 반환
358+ print (f"[ERROR] 인기 상품 조회 중 오류 발생: { str (e )} " )
359+ db .rollback ()
360+ return []
0 commit comments