Spaces:
Sleeping
Sleeping
Upload 16 files
Browse files- main.py +28 -4
- modules/retrieval.py +30 -4
- schemas.py +25 -7
main.py
CHANGED
|
@@ -6,6 +6,7 @@ import os
|
|
| 6 |
import requests
|
| 7 |
from fastapi.security import OAuth2PasswordRequestForm
|
| 8 |
from schemas import SortOption
|
|
|
|
| 9 |
|
| 10 |
# Import modules
|
| 11 |
from schemas import (
|
|
@@ -18,7 +19,7 @@ from modules.retrieval import retrieve_context, get_destinations_paginated, get_
|
|
| 18 |
from modules.generation import build_rag_prompt, call_llm_api, parse_llm_response
|
| 19 |
from modules.weather import get_current_weather
|
| 20 |
from core.config import settings
|
| 21 |
-
|
| 22 |
import favorites
|
| 23 |
|
| 24 |
# Khởi tạo Vectorizer
|
|
@@ -48,6 +49,14 @@ app.add_middleware(
|
|
| 48 |
allow_headers=["*"],
|
| 49 |
)
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
@app.get("/", tags=["Health"])
|
| 52 |
def root():
|
| 53 |
"""Health check endpoint"""
|
|
@@ -93,12 +102,27 @@ app.include_router(favorites.router, prefix="/favorites", tags=["User Favorites"
|
|
| 93 |
# ==========================================
|
| 94 |
@app.get("/destinations", response_model=PaginatedResponse)
|
| 95 |
async def list_destinations(
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
page: int = Query(1, ge=1),
|
| 99 |
limit: int = Query(10, ge=1, le=50)
|
| 100 |
):
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
return get_destinations_paginated(filters, sort_by, page, limit)
|
| 103 |
|
| 104 |
# ==========================================
|
|
|
|
| 6 |
import requests
|
| 7 |
from fastapi.security import OAuth2PasswordRequestForm
|
| 8 |
from schemas import SortOption
|
| 9 |
+
from fastapi.responses import JSONResponse
|
| 10 |
|
| 11 |
# Import modules
|
| 12 |
from schemas import (
|
|
|
|
| 19 |
from modules.generation import build_rag_prompt, call_llm_api, parse_llm_response
|
| 20 |
from modules.weather import get_current_weather
|
| 21 |
from core.config import settings
|
| 22 |
+
from typing import List, Optional
|
| 23 |
import favorites
|
| 24 |
|
| 25 |
# Khởi tạo Vectorizer
|
|
|
|
| 49 |
allow_headers=["*"],
|
| 50 |
)
|
| 51 |
|
| 52 |
+
@app.exception_handler(Exception)
|
| 53 |
+
async def global_exception_handler(request: Request, exc: Exception):
|
| 54 |
+
print(f"🔥 LỖI NGHIÊM TRỌNG (500) TẠI {request.url}: {exc}")
|
| 55 |
+
return JSONResponse(
|
| 56 |
+
status_code=500,
|
| 57 |
+
content={"message": "Internal Server Error", "detail": str(exc)},
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
@app.get("/", tags=["Health"])
|
| 61 |
def root():
|
| 62 |
"""Health check endpoint"""
|
|
|
|
| 102 |
# ==========================================
|
| 103 |
@app.get("/destinations", response_model=PaginatedResponse)
|
| 104 |
async def list_destinations(
|
| 105 |
+
|
| 106 |
+
budget_range: Optional[List[str]] = Query(None, description="Lọc theo ngân sách"),
|
| 107 |
+
available_time: Optional[List[str]] = Query(None, description="Lọc theo thời gian"),
|
| 108 |
+
companion_tag: Optional[List[str]] = Query(None, description="Lọc theo người đi cùng"),
|
| 109 |
+
season_tag: Optional[List[str]] = Query(None, description="Lọc theo mùa"),
|
| 110 |
+
location_province: Optional[str] = Query(None, description="Lọc theo tỉnh"),
|
| 111 |
+
# -----------------------
|
| 112 |
+
|
| 113 |
+
sort_by: SortOption = Query(SortOption.RATING_DESC),
|
| 114 |
page: int = Query(1, ge=1),
|
| 115 |
limit: int = Query(10, ge=1, le=50)
|
| 116 |
):
|
| 117 |
+
# Bước 2: Gom lại thành object HardConstraints để truyền cho hàm xử lý
|
| 118 |
+
filters = HardConstraints(
|
| 119 |
+
budget_range=budget_range,
|
| 120 |
+
available_time=available_time,
|
| 121 |
+
companion_tag=companion_tag,
|
| 122 |
+
season_tag=season_tag,
|
| 123 |
+
location_province=location_province
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
return get_destinations_paginated(filters, sort_by, page, limit)
|
| 127 |
|
| 128 |
# ==========================================
|
modules/retrieval.py
CHANGED
|
@@ -77,10 +77,36 @@ def get_destinations_paginated(filters: HardConstraints, sort_option: SortOption
|
|
| 77 |
.limit(limit)
|
| 78 |
|
| 79 |
results = []
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
return {
|
| 85 |
"data": results,
|
| 86 |
"total": total_docs,
|
|
|
|
| 77 |
.limit(limit)
|
| 78 |
|
| 79 |
results = []
|
| 80 |
+
print(f"--- Đang xử lý trang {page} ---")
|
| 81 |
+
|
| 82 |
+
for index, doc in enumerate(cursor):
|
| 83 |
+
try:
|
| 84 |
+
# 1. FIX LỖI OBJECT ID (Quan trọng nhất)
|
| 85 |
+
if "_id" in doc:
|
| 86 |
+
doc["_id"] = str(doc["_id"]) # Chuyển ObjectId thành string ngay lập tức
|
| 87 |
+
|
| 88 |
+
# 2. Xử lý ID
|
| 89 |
+
# Ưu tiên landmark_id, nếu không có thì lấy _id đã string hóa
|
| 90 |
+
doc["id"] = str(doc.get("landmark_id", doc.get("_id", "")))
|
| 91 |
+
|
| 92 |
+
# 3. Xử lý Rating (Chống null)
|
| 93 |
+
if doc.get("overall_rating") is None:
|
| 94 |
+
doc["overall_rating"] = 0.0
|
| 95 |
+
|
| 96 |
+
# 4. Xử lý Image (Chống null)
|
| 97 |
+
if doc.get("image_urls") is None:
|
| 98 |
+
doc["image_urls"] = []
|
| 99 |
+
|
| 100 |
+
results.append(doc)
|
| 101 |
+
|
| 102 |
+
except Exception as e:
|
| 103 |
+
# IN LỖI RA TERMINAL ĐỂ BẠN THẤY
|
| 104 |
+
print(f"❌ LỖI DATA TẠI ITEM #{index} (Landmark ID: {doc.get('landmark_id')}):")
|
| 105 |
+
print(f" Lý do: {e}")
|
| 106 |
+
# continue giúp bỏ qua item lỗi này, không làm sập cả trang
|
| 107 |
+
continue
|
| 108 |
+
# --- KẾT THÚC ĐOẠN CODE SỬA ---
|
| 109 |
+
|
| 110 |
return {
|
| 111 |
"data": results,
|
| 112 |
"total": total_docs,
|
schemas.py
CHANGED
|
@@ -17,13 +17,31 @@ class WeatherInfo(BaseModel):
|
|
| 17 |
icon: str = Field(..., description="Mã icon của OpenWeather")
|
| 18 |
|
| 19 |
class HardConstraints(BaseModel):
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
# --- 2. PAGINATION & LIST VIEW SCHEMAS ---
|
| 28 |
class DestinationSummary(BaseModel):
|
| 29 |
# Khớp với logic: Backend sẽ gán landmark_id vào field này
|
|
|
|
| 17 |
icon: str = Field(..., description="Mã icon của OpenWeather")
|
| 18 |
|
| 19 |
class HardConstraints(BaseModel):
|
| 20 |
+
budget_range: Optional[List[str]] = Query(
|
| 21 |
+
default=None,
|
| 22 |
+
description="Ngân sách (multi-select), VD: ?budget_range=thấp&budget_range=cao"
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
available_time: Optional[List[str]] = Query(
|
| 26 |
+
default=None,
|
| 27 |
+
description="Thời gian rảnh"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
companion_tag: Optional[List[str]] = Query(
|
| 31 |
+
default=None,
|
| 32 |
+
description="Bạn đồng hành"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
season_tag: Optional[List[str]] = Query(
|
| 36 |
+
default=None,
|
| 37 |
+
description="Mùa"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
location_province: Optional[str] = Query(
|
| 41 |
+
default=None,
|
| 42 |
+
description="Lọc theo tỉnh thành"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
# --- 2. PAGINATION & LIST VIEW SCHEMAS ---
|
| 46 |
class DestinationSummary(BaseModel):
|
| 47 |
# Khớp với logic: Backend sẽ gán landmark_id vào field này
|