Spaces:
Running
Running
github-actions[bot] commited on
Commit Β·
bb0fa8a
1
Parent(s): e79ce4a
Sync Flask API from GitHub 782d7c9418536a8a930bf3f588b6f5d5b1fd593d
Browse files- README.md +0 -1
- app.py +8 -8
- data/check_ids.py +3 -3
- models/recommender.py +13 -13
README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
---
|
| 2 |
title: Bogor Xplore API
|
| 3 |
-
emoji: ποΈ
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
|
|
|
| 1 |
---
|
| 2 |
title: Bogor Xplore API
|
|
|
|
| 3 |
colorFrom: green
|
| 4 |
colorTo: blue
|
| 5 |
sdk: docker
|
app.py
CHANGED
|
@@ -9,9 +9,9 @@ CORS(app) # Enable CORS for Laravel
|
|
| 9 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
|
| 11 |
# Initialize recommender
|
| 12 |
-
print("
|
| 13 |
recommender = TourismRecommender(data_path=os.path.join(BASE_DIR, 'data'))
|
| 14 |
-
print("
|
| 15 |
|
| 16 |
@app.route('/')
|
| 17 |
def index():
|
|
@@ -103,7 +103,7 @@ def get_recommendations():
|
|
| 103 |
place_details = None
|
| 104 |
top_n = data.get('top_n', 10)
|
| 105 |
|
| 106 |
-
print(f"
|
| 107 |
|
| 108 |
# 1. Try lookup by NAME first (Most reliable if IDs don't match)
|
| 109 |
if 'place_name' in data and data['place_name']:
|
|
@@ -113,7 +113,7 @@ def get_recommendations():
|
|
| 113 |
place_idx = search_result['query_idx']
|
| 114 |
# Get details using this index
|
| 115 |
place_details = recommender.get_place_by_id(place_idx)
|
| 116 |
-
print(f"
|
| 117 |
|
| 118 |
# 2. Fallback to ID if provided and Name lookup failed
|
| 119 |
if place_idx is None and 'place_id' in data:
|
|
@@ -125,14 +125,14 @@ def get_recommendations():
|
|
| 125 |
if 0 <= potential_idx < len(recommender.df):
|
| 126 |
place_idx = potential_idx
|
| 127 |
place_details = recommender.get_place_by_id(place_idx)
|
| 128 |
-
print(f"
|
| 129 |
else:
|
| 130 |
-
print(f"
|
| 131 |
except ValueError:
|
| 132 |
pass
|
| 133 |
|
| 134 |
if place_idx is None:
|
| 135 |
-
print("
|
| 136 |
return jsonify({
|
| 137 |
'status': 'error',
|
| 138 |
'message': 'Place not found. Provide valid place_name or place_id.'
|
|
@@ -151,7 +151,7 @@ def get_recommendations():
|
|
| 151 |
})
|
| 152 |
except Exception as e:
|
| 153 |
import traceback
|
| 154 |
-
print(f"
|
| 155 |
traceback.print_exc()
|
| 156 |
return jsonify({
|
| 157 |
'status': 'error',
|
|
|
|
| 9 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
|
| 11 |
# Initialize recommender
|
| 12 |
+
print("Initializing Tourism Recommender...")
|
| 13 |
recommender = TourismRecommender(data_path=os.path.join(BASE_DIR, 'data'))
|
| 14 |
+
print("Ready!")
|
| 15 |
|
| 16 |
@app.route('/')
|
| 17 |
def index():
|
|
|
|
| 103 |
place_details = None
|
| 104 |
top_n = data.get('top_n', 10)
|
| 105 |
|
| 106 |
+
print(f"REQUEST: {data}")
|
| 107 |
|
| 108 |
# 1. Try lookup by NAME first (Most reliable if IDs don't match)
|
| 109 |
if 'place_name' in data and data['place_name']:
|
|
|
|
| 113 |
place_idx = search_result['query_idx']
|
| 114 |
# Get details using this index
|
| 115 |
place_details = recommender.get_place_by_id(place_idx)
|
| 116 |
+
print(f" Found by name: Index {place_idx} -> {place_details['nama']}")
|
| 117 |
|
| 118 |
# 2. Fallback to ID if provided and Name lookup failed
|
| 119 |
if place_idx is None and 'place_id' in data:
|
|
|
|
| 125 |
if 0 <= potential_idx < len(recommender.df):
|
| 126 |
place_idx = potential_idx
|
| 127 |
place_details = recommender.get_place_by_id(place_idx)
|
| 128 |
+
print(f" Using ID as Index: Index {place_idx} -> {place_details['nama']}")
|
| 129 |
else:
|
| 130 |
+
print(f" ID {potential_idx} is out of bounds (0-{len(recommender.df)-1})")
|
| 131 |
except ValueError:
|
| 132 |
pass
|
| 133 |
|
| 134 |
if place_idx is None:
|
| 135 |
+
print(" Place not found")
|
| 136 |
return jsonify({
|
| 137 |
'status': 'error',
|
| 138 |
'message': 'Place not found. Provide valid place_name or place_id.'
|
|
|
|
| 151 |
})
|
| 152 |
except Exception as e:
|
| 153 |
import traceback
|
| 154 |
+
print(f"API ERROR: {str(e)}")
|
| 155 |
traceback.print_exc()
|
| 156 |
return jsonify({
|
| 157 |
'status': 'error',
|
data/check_ids.py
CHANGED
|
@@ -18,14 +18,14 @@ try:
|
|
| 18 |
# Check alignment
|
| 19 |
mismatch = df[df.index != df['id']]
|
| 20 |
if not mismatch.empty:
|
| 21 |
-
print(f"\
|
| 22 |
print("Example Mismatch:")
|
| 23 |
print(mismatch[['id', 'nama']].head(3))
|
| 24 |
else:
|
| 25 |
-
print("\
|
| 26 |
|
| 27 |
else:
|
| 28 |
-
print("\n
|
| 29 |
|
| 30 |
except Exception as e:
|
| 31 |
print(f"Error: {e}")
|
|
|
|
| 18 |
# Check alignment
|
| 19 |
mismatch = df[df.index != df['id']]
|
| 20 |
if not mismatch.empty:
|
| 21 |
+
print(f"\nMismatch found! {len(mismatch)} rows have Index != ID")
|
| 22 |
print("Example Mismatch:")
|
| 23 |
print(mismatch[['id', 'nama']].head(3))
|
| 24 |
else:
|
| 25 |
+
print("\nIndex matches ID exactly!")
|
| 26 |
|
| 27 |
else:
|
| 28 |
+
print("\n'id' column NOT FOUND in CSV. Pure index usage.")
|
| 29 |
|
| 30 |
except Exception as e:
|
| 31 |
print(f"Error: {e}")
|
models/recommender.py
CHANGED
|
@@ -24,7 +24,7 @@ class TourismRecommender:
|
|
| 24 |
"""
|
| 25 |
|
| 26 |
def __init__(self, data_path='data/'):
|
| 27 |
-
print("
|
| 28 |
|
| 29 |
self.data_path = data_path
|
| 30 |
|
|
@@ -37,7 +37,7 @@ class TourismRecommender:
|
|
| 37 |
indobert_embeddings_path = os.path.join(data_path, 'indobert_embeddings.npy')
|
| 38 |
|
| 39 |
# Load data
|
| 40 |
-
print("
|
| 41 |
self.df = pd.read_csv(data_csv_path)
|
| 42 |
|
| 43 |
if 'deskripsi_clean' in self.df.columns:
|
|
@@ -62,8 +62,8 @@ class TourismRecommender:
|
|
| 62 |
# Load similarity matrices
|
| 63 |
self.ngram_sim = self._expand_square_matrix(np.load(ngram_similarity_path), 'ngram_similarity')
|
| 64 |
self.indobert_sim = self._expand_square_matrix(np.load(indobert_similarity_path), 'indobert_similarity')
|
| 65 |
-
print(f"
|
| 66 |
-
print(f"
|
| 67 |
|
| 68 |
# Load TF-IDF untuk query search (if needed)
|
| 69 |
self.tfidf_matrix = self._expand_rows(np.load(tfidf_matrix_path), 'tfidf_matrix')
|
|
@@ -75,15 +75,15 @@ class TourismRecommender:
|
|
| 75 |
self.indobert_embeddings = self._expand_rows(np.load(indobert_embeddings_path), 'indobert_embeddings')
|
| 76 |
|
| 77 |
# Load IndoBERT model untuk query embedding
|
| 78 |
-
print("
|
| 79 |
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 80 |
self.tokenizer = AutoTokenizer.from_pretrained("indobenchmark/indobert-base-p1")
|
| 81 |
self.model = AutoModel.from_pretrained("indobenchmark/indobert-base-p1")
|
| 82 |
self.model.eval()
|
| 83 |
self.model.to(self.device)
|
| 84 |
-
print(f"
|
| 85 |
|
| 86 |
-
print(f"\
|
| 87 |
print(f" Total destinations: {len(self.df)}")
|
| 88 |
print(f" Method: IndoBERT (Search) & N-Gram (Detail Recommendations)")
|
| 89 |
|
|
@@ -99,7 +99,7 @@ class TourismRecommender:
|
|
| 99 |
expanded = np.zeros((target_len, target_len), dtype=matrix.dtype)
|
| 100 |
expanded[np.ix_(self.valid_text_indices, self.valid_text_indices)] = matrix
|
| 101 |
np.fill_diagonal(expanded, 1.0)
|
| 102 |
-
print(f"
|
| 103 |
return expanded
|
| 104 |
|
| 105 |
raise ValueError(
|
|
@@ -120,7 +120,7 @@ class TourismRecommender:
|
|
| 120 |
expanded_shape = (target_len, *values.shape[1:])
|
| 121 |
expanded = np.zeros(expanded_shape, dtype=values.dtype)
|
| 122 |
expanded[self.valid_text_indices] = values
|
| 123 |
-
print(f"
|
| 124 |
return expanded
|
| 125 |
|
| 126 |
raise ValueError(
|
|
@@ -183,7 +183,7 @@ class TourismRecommender:
|
|
| 183 |
"""
|
| 184 |
# Validate index
|
| 185 |
if place_idx is None or place_idx < 0 or place_idx >= len(self.df):
|
| 186 |
-
print(f"
|
| 187 |
return []
|
| 188 |
|
| 189 |
# Use pre-computed N-Gram similarity matrix directly
|
|
@@ -316,14 +316,14 @@ class TourismRecommender:
|
|
| 316 |
|
| 317 |
if place_idx is not None:
|
| 318 |
# Use pre-computed IndoBERT similarity matrix (like notebook)
|
| 319 |
-
print(f"
|
| 320 |
sim_scores = self.indobert_sim[place_idx]
|
| 321 |
|
| 322 |
# Get top N (excluding itself at index 0)
|
| 323 |
top_indices = sim_scores.argsort()[::-1][1:top_n+1]
|
| 324 |
else:
|
| 325 |
# Fall back to on-the-fly query encoding
|
| 326 |
-
print(f"
|
| 327 |
query_emb = self._get_query_embedding(query)
|
| 328 |
query_emb = query_emb.reshape(1, -1)
|
| 329 |
sim_scores = cosine_similarity(query_emb, self.indobert_embeddings)[0]
|
|
@@ -333,7 +333,7 @@ class TourismRecommender:
|
|
| 333 |
for idx in top_indices:
|
| 334 |
# SAFEGUARD: Ignore indices that are out of bounds
|
| 335 |
if idx >= len(self.df):
|
| 336 |
-
print(f"
|
| 337 |
continue
|
| 338 |
|
| 339 |
place = self.df.iloc[idx]
|
|
|
|
| 24 |
"""
|
| 25 |
|
| 26 |
def __init__(self, data_path='data/'):
|
| 27 |
+
print("Initializing Tourism Recommender...")
|
| 28 |
|
| 29 |
self.data_path = data_path
|
| 30 |
|
|
|
|
| 37 |
indobert_embeddings_path = os.path.join(data_path, 'indobert_embeddings.npy')
|
| 38 |
|
| 39 |
# Load data
|
| 40 |
+
print(" Loading pre-computed data from dataset...")
|
| 41 |
self.df = pd.read_csv(data_csv_path)
|
| 42 |
|
| 43 |
if 'deskripsi_clean' in self.df.columns:
|
|
|
|
| 62 |
# Load similarity matrices
|
| 63 |
self.ngram_sim = self._expand_square_matrix(np.load(ngram_similarity_path), 'ngram_similarity')
|
| 64 |
self.indobert_sim = self._expand_square_matrix(np.load(indobert_similarity_path), 'indobert_similarity')
|
| 65 |
+
print(f" Loaded ngram_similarity: {self.ngram_sim.shape}")
|
| 66 |
+
print(f" Loaded indobert_similarity: {self.indobert_sim.shape}")
|
| 67 |
|
| 68 |
# Load TF-IDF untuk query search (if needed)
|
| 69 |
self.tfidf_matrix = self._expand_rows(np.load(tfidf_matrix_path), 'tfidf_matrix')
|
|
|
|
| 75 |
self.indobert_embeddings = self._expand_rows(np.load(indobert_embeddings_path), 'indobert_embeddings')
|
| 76 |
|
| 77 |
# Load IndoBERT model untuk query embedding
|
| 78 |
+
print(" Loading IndoBERT model for query encoding...")
|
| 79 |
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 80 |
self.tokenizer = AutoTokenizer.from_pretrained("indobenchmark/indobert-base-p1")
|
| 81 |
self.model = AutoModel.from_pretrained("indobenchmark/indobert-base-p1")
|
| 82 |
self.model.eval()
|
| 83 |
self.model.to(self.device)
|
| 84 |
+
print(f" IndoBERT model loaded! Device: {self.device}")
|
| 85 |
|
| 86 |
+
print(f"\nRecommender ready!")
|
| 87 |
print(f" Total destinations: {len(self.df)}")
|
| 88 |
print(f" Method: IndoBERT (Search) & N-Gram (Detail Recommendations)")
|
| 89 |
|
|
|
|
| 99 |
expanded = np.zeros((target_len, target_len), dtype=matrix.dtype)
|
| 100 |
expanded[np.ix_(self.valid_text_indices, self.valid_text_indices)] = matrix
|
| 101 |
np.fill_diagonal(expanded, 1.0)
|
| 102 |
+
print(f" Expanded {name}: {matrix.shape} -> {expanded.shape}")
|
| 103 |
return expanded
|
| 104 |
|
| 105 |
raise ValueError(
|
|
|
|
| 120 |
expanded_shape = (target_len, *values.shape[1:])
|
| 121 |
expanded = np.zeros(expanded_shape, dtype=values.dtype)
|
| 122 |
expanded[self.valid_text_indices] = values
|
| 123 |
+
print(f" Expanded {name}: {values.shape} -> {expanded.shape}")
|
| 124 |
return expanded
|
| 125 |
|
| 126 |
raise ValueError(
|
|
|
|
| 183 |
"""
|
| 184 |
# Validate index
|
| 185 |
if place_idx is None or place_idx < 0 or place_idx >= len(self.df):
|
| 186 |
+
print(f"Invalid Place Index: {place_idx}")
|
| 187 |
return []
|
| 188 |
|
| 189 |
# Use pre-computed N-Gram similarity matrix directly
|
|
|
|
| 316 |
|
| 317 |
if place_idx is not None:
|
| 318 |
# Use pre-computed IndoBERT similarity matrix (like notebook)
|
| 319 |
+
print(f" Using IndoBERT similarity matrix for place: {self.df.iloc[place_idx]['nama']}")
|
| 320 |
sim_scores = self.indobert_sim[place_idx]
|
| 321 |
|
| 322 |
# Get top N (excluding itself at index 0)
|
| 323 |
top_indices = sim_scores.argsort()[::-1][1:top_n+1]
|
| 324 |
else:
|
| 325 |
# Fall back to on-the-fly query encoding
|
| 326 |
+
print(f" Query not found as place name, using query encoding...")
|
| 327 |
query_emb = self._get_query_embedding(query)
|
| 328 |
query_emb = query_emb.reshape(1, -1)
|
| 329 |
sim_scores = cosine_similarity(query_emb, self.indobert_embeddings)[0]
|
|
|
|
| 333 |
for idx in top_indices:
|
| 334 |
# SAFEGUARD: Ignore indices that are out of bounds
|
| 335 |
if idx >= len(self.df):
|
| 336 |
+
print(f" Ignored out-of-bounds index: {idx}")
|
| 337 |
continue
|
| 338 |
|
| 339 |
place = self.df.iloc[idx]
|