Update app.py
Browse files
app.py
CHANGED
|
@@ -187,30 +187,81 @@ def get_unified_recommendations_api(
|
|
| 187 |
|
| 188 |
|
| 189 |
# JALUR 2 & 3: Pengguna Baru (dengan atau tanpa probe)
|
| 190 |
-
elif preferred_categories:
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
mask = df_wisata[valid_categories].sum(axis=1) > 0
|
| 195 |
-
|
| 196 |
-
if
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
sort_cols.append('Jumlah Ulasan (Google Maps)')
|
| 204 |
-
|
| 205 |
-
if not sort_cols:
|
| 206 |
-
ranked_wisata = filtered_wisata.head(n)
|
| 207 |
else:
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
#
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
return ranked_wisata[display_cols].to_dict(orient='records')
|
| 213 |
-
|
| 214 |
else:
|
| 215 |
# Jika tidak ada input yang valid
|
| 216 |
return []
|
|
@@ -219,6 +270,7 @@ def get_unified_recommendations_api(
|
|
| 219 |
class RecommendationRequest(BaseModel):
|
| 220 |
user_id: Optional[str] = Field(None, description="ID unik pengguna yang sudah ada di sistem.")
|
| 221 |
preferred_categories: Optional[List[str]] = Field(None, description="List kategori yang disukai pengguna baru, cth: ['budaya', 'seni'].")
|
|
|
|
| 222 |
n: int = Field(10, gt=0, le=50, description="Jumlah rekomendasi yang diinginkan.")
|
| 223 |
|
| 224 |
class Config:
|
|
@@ -237,14 +289,15 @@ class RecommendationRequest(BaseModel):
|
|
| 237 |
@app.post("/recommendations", summary="Dapatkan Rekomendasi Wisata")
|
| 238 |
async def get_recommendations(request: RecommendationRequest):
|
| 239 |
"""
|
| 240 |
-
Endpoint ini memberikan rekomendasi wisata berdasarkan
|
| 241 |
|
| 242 |
1. **Untuk Pengguna yang Sudah Ada**: Sediakan `user_id`.
|
| 243 |
-
2. **Untuk Pengguna Baru**: Sediakan
|
|
|
|
| 244 |
|
| 245 |
-
Jika `user_id` disediakan,
|
| 246 |
"""
|
| 247 |
-
|
| 248 |
if not request.user_id and not request.preferred_categories:
|
| 249 |
raise HTTPException(
|
| 250 |
status_code=400,
|
|
@@ -252,14 +305,19 @@ async def get_recommendations(request: RecommendationRequest):
|
|
| 252 |
)
|
| 253 |
|
| 254 |
if request.user_id:
|
| 255 |
-
# Cek apakah user sudah ada DI DATA YANG SEDANG BERJALAN
|
| 256 |
if request.user_id not in MODEL_DATA['user_encoder'].classes_:
|
| 257 |
print(f"API mendeteksi user_id baru: '{request.user_id}'. Menjalankan proses penambahan...")
|
|
|
|
|
|
|
| 258 |
add_new_user_to_artifacts(request.user_id)
|
| 259 |
|
| 260 |
recommendations = get_unified_recommendations_api(
|
| 261 |
raw_user_id=request.user_id,
|
| 262 |
preferred_categories=request.preferred_categories,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
n=request.n
|
| 264 |
)
|
| 265 |
|
|
|
|
| 187 |
|
| 188 |
|
| 189 |
# JALUR 2 & 3: Pengguna Baru (dengan atau tanpa probe)
|
| 190 |
+
elif preferred_categories and probe_user_raw_ids:
|
| 191 |
+
# Ini adalah jalur yang benar untuk probing (Logika dari model asli)
|
| 192 |
+
print(f"Membuat rekomendasi untuk pengguna baru dengan preferensi: {preferred_categories} (NCF probe)")
|
| 193 |
+
|
| 194 |
+
# 1. Validasi Kategori
|
| 195 |
+
valid_categories = [cat for cat in preferred_categories if cat in df_wisata.columns and cat in list_kategori_wisata_valid_passed]
|
| 196 |
+
if not valid_categories:
|
| 197 |
+
print(f"Tidak ada kategori valid dari preferensi: {preferred_categories}")
|
| 198 |
+
return []
|
| 199 |
+
|
| 200 |
+
# 2. Filter Kandidat Wisata Berdasarkan Kategori
|
| 201 |
mask = df_wisata[valid_categories].sum(axis=1) > 0
|
| 202 |
+
candidate_wisata_df = df_wisata[mask].copy()
|
| 203 |
+
if candidate_wisata_df.empty:
|
| 204 |
+
print(f"Tidak ditemukan tempat wisata yang cocok dengan kategori: {valid_categories}")
|
| 205 |
+
return []
|
| 206 |
+
|
| 207 |
+
# 3. Normalisasi Rating Google
|
| 208 |
+
if 'Overall Rating (Google Maps)' in candidate_wisata_df.columns:
|
| 209 |
+
candidate_wisata_df['google_rating_norm'] = normalize_series_min_max(candidate_wisata_df['Overall Rating (Google Maps)'])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
else:
|
| 211 |
+
candidate_wisata_df['google_rating_norm'] = 0.0
|
| 212 |
+
|
| 213 |
+
# 4. Validasi Probe User IDs
|
| 214 |
+
valid_probe_user_ids_int = []
|
| 215 |
+
for raw_id in probe_user_raw_ids:
|
| 216 |
+
try:
|
| 217 |
+
valid_probe_user_ids_int.append(user_encoder_passed.transform([raw_id])[0])
|
| 218 |
+
except ValueError:
|
| 219 |
+
print(f"Warning: Probe user ID '{raw_id}' tidak dikenal.")
|
| 220 |
+
|
| 221 |
+
# 5. Hitung Skor NCF untuk setiap kandidat wisata
|
| 222 |
+
if not valid_probe_user_ids_int:
|
| 223 |
+
print("Warning: Tidak ada probe user ID yang valid. NCF appeal score akan 0.")
|
| 224 |
+
candidate_wisata_df['avg_ncf_appeal_score'] = 0.0
|
| 225 |
+
else:
|
| 226 |
+
avg_ncf_appeal_scores = []
|
| 227 |
+
for _, row_cand in candidate_wisata_df.iterrows():
|
| 228 |
+
item_id_raw = row_cand['ID Tempat']
|
| 229 |
+
try:
|
| 230 |
+
item_id_int = item_encoder_passed.transform([item_id_raw])[0]
|
| 231 |
+
item_features = df_wisata_features_passed[df_wisata_features_passed['item_id_int'] == item_id_int][feature_columns_ncf_passed].iloc[[0]].values.astype(np.float32)
|
| 232 |
+
|
| 233 |
+
if item_features.size == 0:
|
| 234 |
+
avg_ncf_appeal_scores.append(0.0)
|
| 235 |
+
continue
|
| 236 |
+
|
| 237 |
+
ncf_preds_for_item = []
|
| 238 |
+
for user_id_int in valid_probe_user_ids_int:
|
| 239 |
+
model_input = {
|
| 240 |
+
'user_input': np.array([user_id_int]),
|
| 241 |
+
'item_input': np.array([item_id_int]),
|
| 242 |
+
'features_input': item_features
|
| 243 |
+
}
|
| 244 |
+
pred = ncf_model.predict(model_input, verbose=0)[0][0]
|
| 245 |
+
ncf_preds_for_item.append(pred)
|
| 246 |
+
|
| 247 |
+
avg_ncf_appeal_scores.append(np.mean(ncf_preds_for_item) if ncf_preds_for_item else 0.0)
|
| 248 |
+
|
| 249 |
+
except ValueError:
|
| 250 |
+
avg_ncf_appeal_scores.append(0.0) # Item tidak dikenal oleh encoder
|
| 251 |
+
|
| 252 |
+
candidate_wisata_df['avg_ncf_appeal_score'] = avg_ncf_appeal_scores
|
| 253 |
+
|
| 254 |
+
# 6. Hitung Skor Akhir dan Lakukan Ranking
|
| 255 |
+
candidate_wisata_df['final_score %'] = (
|
| 256 |
+
(weight_google_rating_new * candidate_wisata_df['google_rating_norm'].fillna(0) +
|
| 257 |
+
weight_ncf_appeal_new * candidate_wisata_df['avg_ncf_appeal_score']) * 100
|
| 258 |
+
)
|
| 259 |
+
ranked_wisata = candidate_wisata_df.sort_values(by='final_score %', ascending=False).head(n)
|
| 260 |
+
|
| 261 |
+
# 7. Kembalikan hasil dalam format yang diinginkan
|
| 262 |
+
display_cols = ['ID Tempat', 'Nama Wisata', 'final_score %']
|
| 263 |
return ranked_wisata[display_cols].to_dict(orient='records')
|
| 264 |
+
|
| 265 |
else:
|
| 266 |
# Jika tidak ada input yang valid
|
| 267 |
return []
|
|
|
|
| 270 |
class RecommendationRequest(BaseModel):
|
| 271 |
user_id: Optional[str] = Field(None, description="ID unik pengguna yang sudah ada di sistem.")
|
| 272 |
preferred_categories: Optional[List[str]] = Field(None, description="List kategori yang disukai pengguna baru, cth: ['budaya', 'seni'].")
|
| 273 |
+
probe_user_ids: Optional[List[str]] = Field(None, description="(Opsional) Daftar ID pengguna 'probe' untuk NCF hybrid.")
|
| 274 |
n: int = Field(10, gt=0, le=50, description="Jumlah rekomendasi yang diinginkan.")
|
| 275 |
|
| 276 |
class Config:
|
|
|
|
| 289 |
@app.post("/recommendations", summary="Dapatkan Rekomendasi Wisata")
|
| 290 |
async def get_recommendations(request: RecommendationRequest):
|
| 291 |
"""
|
| 292 |
+
Endpoint ini memberikan rekomendasi wisata berdasarkan tiga skenario:
|
| 293 |
|
| 294 |
1. **Untuk Pengguna yang Sudah Ada**: Sediakan `user_id`.
|
| 295 |
+
2. **Untuk Pengguna Baru (Hybrid Probing)**: Sediakan `preferred_categories` DAN `probe_user_ids`. Ini akan memberikan hasil yang lebih cerdas.
|
| 296 |
+
3. **Untuk Pengguna Baru (Fallback Sederhana)**: Sediakan HANYA `preferred_categories`. Rekomendasi akan didasarkan pada popularitas dalam kategori tersebut.
|
| 297 |
|
| 298 |
+
Jika `user_id` disediakan, input lain akan diabaikan.
|
| 299 |
"""
|
| 300 |
+
|
| 301 |
if not request.user_id and not request.preferred_categories:
|
| 302 |
raise HTTPException(
|
| 303 |
status_code=400,
|
|
|
|
| 305 |
)
|
| 306 |
|
| 307 |
if request.user_id:
|
|
|
|
| 308 |
if request.user_id not in MODEL_DATA['user_encoder'].classes_:
|
| 309 |
print(f"API mendeteksi user_id baru: '{request.user_id}'. Menjalankan proses penambahan...")
|
| 310 |
+
# Fungsi ini mungkin perlu Anda tinjau kembali, apakah pengguna baru seharusnya langsung ditambahkan
|
| 311 |
+
# atau hanya ditangani sebagai pengguna anonim.
|
| 312 |
add_new_user_to_artifacts(request.user_id)
|
| 313 |
|
| 314 |
recommendations = get_unified_recommendations_api(
|
| 315 |
raw_user_id=request.user_id,
|
| 316 |
preferred_categories=request.preferred_categories,
|
| 317 |
+
|
| 318 |
+
# Teruskan probe_user_ids ke fungsi utama
|
| 319 |
+
probe_user_raw_ids=request.probe_user_ids,
|
| 320 |
+
|
| 321 |
n=request.n
|
| 322 |
)
|
| 323 |
|