diff --git a/vector/README.md b/vector/README.md index 726bd26..2fb7ab7 100644 --- a/vector/README.md +++ b/vector/README.md @@ -94,7 +94,8 @@ vector/ | Method | Endpoint | 설명 | 요청 모델 | 응답 모델 | |--------|----------|------|-----------|-----------| | `POST` | `/find-reviews` | 리뷰 수집 및 Vector DB 저장 | VectorBuildRequest | FindReviewsResponse | -| `POST` | `/action-recommendation` | AI 기반 비즈니스 추천 | ActionRecommendationRequest | ActionRecommendationSimpleResponse | +| `POST` | `/action-recommendation` | AI 기반 비즈니스 추천 | ActionRecommendationRequest | +| `GET` | `/store/{store_id}` | store_id로 매장 정보 조회 | Path Parameter | StoreInfoResponse ActionRecommendationSimpleResponse | ## 🚀 빠른 시작 @@ -779,6 +780,80 @@ curl -X POST "http://vector-api.20.249.191.180.nip.io/action-recommendation" \ } ``` +### 3. 매장 정보 조회 API +#### 요청 예시 +```bash +curl -X GET "http://vector-api.20.249.191.180.nip.io/store/501745730" +``` + +#### 응답 예시 (성공) +```json +{ + "success": true, + "message": "매장 정보 조회 성공: 맛있는 치킨집", + "store_id": "501745730", + "store_info": { + "id": "501745730", + "place_name": "맛있는 치킨집", + "category_name": "음식점 > 치킨", + "address_name": "서울특별시 강남구 역삼동 123-45", + "phone": "02-123-4567", + "place_url": "http://place.map.kakao.com/501745730", + "x": "127.0276543", + "y": "37.4979234" + }, + "reviews": [ + { + "reviewer_name": "김○○", + "rating": 5, + "date": "2024-06-10", + "content": "치킨이 정말 맛있어요!", + "badges": ["맛집", "친절", "빠른배달"], + "likes": 12 + } + ], + "review_summary": { + "total_reviews": 23, + "average_rating": 4.2, + "rating_distribution": { + "5": 12, + "4": 8, + "3": 2, + "2": 1, + "1": 0 + }, + "total_likes": 145, + "common_keywords": ["맛집", "친절", "빠른배달", "바삭함"] + }, + "metadata": { + "store_id": "501745730", + "store_name": "맛있는 치킨집", + "food_category": "치킨", + "region": "서울특별시 강남구 역삼동", + "last_updated": "2024-06-17T10:30:00" + }, + "last_updated": "2024-06-17T10:30:00", + "total_reviews": 23, + "execution_time": 0.15 +} +``` + +#### 응답 예시 (실패) +```json +{ + "success": false, + "message": "매장 정보를 찾을 수 없습니다: store_id=999999", + "store_id": "999999", + "store_info": null, + "reviews": null, + "review_summary": null, + "metadata": null, + "last_updated": null, + "total_reviews": 0, + "execution_time": 0.05 +} +``` + ## ⚙️ 환경 설정 ### 필수 환경변수 diff --git a/vector/app/main.py b/vector/app/main.py index 3d50c71..fa9a769 100644 --- a/vector/app/main.py +++ b/vector/app/main.py @@ -30,7 +30,7 @@ from contextlib import asynccontextmanager from datetime import datetime from typing import Optional import asyncio -from fastapi import FastAPI, HTTPException, Depends +from fastapi import FastAPI, HTTPException, Depends, Path from fastapi.responses import HTMLResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field @@ -673,4 +673,63 @@ async def health_check(): if __name__ == "__main__": import uvicorn - uvicorn.run(app, host=settings.HOST, port=settings.PORT) \ No newline at end of file + uvicorn.run(app, host=settings.HOST, port=settings.PORT) + +@app.get( + "/store/{store_id}", + response_model=StoreInfoResponse, + summary="매장 정보 조회", + description="store_id를 이용하여 Vector DB에서 매장 기본정보와 리뷰 정보를 조회합니다" +) +async def get_store_info( + store_id: str = Path(..., description="조회할 매장 ID", example="501745730"), + vector_service: VectorService = Depends(get_vector_service) +): + """🏪 store_id로 매장 정보 조회 API""" + start_time = datetime.now() + + try: + logger.info(f"🔍 매장 정보 조회 요청: store_id={store_id}") + + # Vector DB에서 매장 정보 조회 + store_data = vector_service.get_store_by_id(store_id) + + execution_time = (datetime.now() - start_time).total_seconds() + + if not store_data: + return StoreInfoResponse( + success=False, + message=f"매장 정보를 찾을 수 없습니다: store_id={store_id}", + store_id=store_id, + execution_time=execution_time + ) + + # 성공 응답 + store_info = store_data.get('store_info', {}) + reviews = store_data.get('reviews', []) + review_summary = store_data.get('review_summary', {}) + metadata = store_data.get('metadata', {}) + + return StoreInfoResponse( + success=True, + message=f"매장 정보 조회 성공: {store_info.get('place_name', store_id)}", + store_id=store_id, + store_info=store_info, + reviews=reviews, + review_summary=review_summary, + metadata=metadata, + last_updated=store_data.get('combined_at'), + total_reviews=len(reviews), + execution_time=execution_time + ) + + except Exception as e: + execution_time = (datetime.now() - start_time).total_seconds() + logger.error(f"❌ 매장 정보 조회 실패: store_id={store_id}, error={e}") + + return StoreInfoResponse( + success=False, + message=f"매장 정보 조회 중 오류 발생: {str(e)}", + store_id=store_id, + execution_time=execution_time + ) \ No newline at end of file diff --git a/vector/app/models/vector_models.py b/vector/app/models/vector_models.py index 2e632f7..6b33c94 100644 --- a/vector/app/models/vector_models.py +++ b/vector/app/models/vector_models.py @@ -108,4 +108,26 @@ class VectorDBStatusResponse(BaseModel): """Vector DB 상태 조회 응답""" success: bool = Field(description="조회 성공 여부") status: VectorDBStatus = Field(description="DB 상태 정보") - message: str = Field(description="응답 메시지") \ No newline at end of file + message: str = Field(description="응답 메시지") + +class StoreInfoRequest(BaseModel): + """매장 정보 조회 요청 모델""" + store_id: str = Field( + ..., + description="조회할 매장 ID", + example="501745730" + ) + +class StoreInfoResponse(BaseModel): + """매장 정보 조회 응답 모델""" + success: bool = Field(description="조회 성공 여부") + message: str = Field(description="응답 메시지") + store_id: str = Field(description="매장 ID") + store_info: Optional[Dict[str, Any]] = Field(None, description="매장 기본 정보") + reviews: Optional[List[Dict[str, Any]]] = Field(None, description="리뷰 목록") + review_summary: Optional[Dict[str, Any]] = Field(None, description="리뷰 요약 정보") + metadata: Optional[Dict[str, Any]] = Field(None, description="Vector DB 메타데이터") + last_updated: Optional[str] = Field(None, description="마지막 업데이트 시간") + total_reviews: int = Field(default=0, description="총 리뷰 수") + execution_time: Optional[float] = Field(None, description="실행 시간(초)") + \ No newline at end of file diff --git a/vector/app/services/vector_service.py b/vector/app/services/vector_service.py index 613d6a4..510151a 100644 --- a/vector/app/services/vector_service.py +++ b/vector/app/services/vector_service.py @@ -1178,4 +1178,64 @@ class VectorService: return { 'added_count': added_count, 'add_details': add_details - } \ No newline at end of file + } + + def get_store_by_id(self, store_id: str) -> Optional[Dict[str, Any]]: + """ + store_id로 특정 매장의 정보를 조회합니다. + + Args: + store_id: 조회할 매장 ID + + Returns: + 매장 정보 딕셔너리 또는 None + """ + try: + if not self.is_ready(): + logger.warning("⚠️ VectorService가 준비되지 않음") + return None + + logger.info(f"🔍 매장 정보 조회 시작: store_id={store_id}") + + # Vector DB에서 해당 store_id로 검색 + results = self.collection.get( + where={"store_id": store_id}, + include=['documents', 'metadatas', 'ids'] + ) + + if not results or not results.get('metadatas') or not results['metadatas']: + logger.warning(f"❌ 매장 정보를 찾을 수 없음: store_id={store_id}") + return None + + # 첫 번째 결과 사용 (store_id는 유니크해야 함) + metadata = results['metadatas'][0] + document = results['documents'][0] if results.get('documents') else None + document_id = results['ids'][0] if results.get('ids') else None + + logger.info(f"✅ 매장 정보 조회 성공: {metadata.get('store_name', 'Unknown')}") + + # 문서에서 JSON 파싱 (combine_store_and_reviews 형태로 저장되어 있음) + store_data = None + if document: + try: + import json + store_data = json.loads(document) + except json.JSONDecodeError as e: + logger.error(f"❌ JSON 파싱 실패: {e}") + return None + + # 응답 데이터 구성 + result = { + "metadata": metadata, + "document_id": document_id, + "store_info": store_data.get('store_info') if store_data else None, + "reviews": store_data.get('reviews', []) if store_data else [], + "review_summary": store_data.get('review_summary', {}) if store_data else {}, + "combined_at": store_data.get('combined_at') if store_data else None + } + + return result + + except Exception as e: + logger.error(f"❌ 매장 정보 조회 실패: store_id={store_id}, error={e}") + return None \ No newline at end of file