Spaces:
Running
Running
| from flask import Flask, jsonify, request | |
| from flask_cors import CORS | |
| from models.recommender import TourismRecommender | |
| import os | |
| app = Flask(__name__) | |
| CORS(app) # Enable CORS for Laravel | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| # Initialize recommender | |
| print("Initializing Tourism Recommender...") | |
| recommender = TourismRecommender(data_path=os.path.join(BASE_DIR, 'data')) | |
| print("Ready!") | |
| def index(): | |
| """API Health Check""" | |
| return jsonify({ | |
| 'status': 'success', | |
| 'message': 'Bogor Tourism Recommendation API', | |
| 'version': '1.0.0', | |
| 'endpoints': { | |
| 'GET /api/places': 'Get all tourism places with pagination', | |
| 'GET /api/places/id': 'Get specific place details', | |
| 'GET /api/search?q=query': 'Semantic search using IndoBERT', | |
| 'POST /api/recommendations': 'Get recommendations for a place' | |
| } | |
| }) | |
| def get_places(): | |
| """ | |
| Get all tourism places with pagination | |
| Query params: | |
| - limit: number of items per page (default: 20) | |
| - offset: starting position (default: 0) | |
| - category: filter by category (optional) | |
| """ | |
| try: | |
| limit = request.args.get('limit', 20, type=int) | |
| offset = request.args.get('offset', 0, type=int) | |
| category = request.args.get('category', None, type=str) | |
| result = recommender.get_all_places(limit=limit, offset=offset) | |
| # Filter by category if provided | |
| if category: | |
| result['data'] = [p for p in result['data'] if p['kategori'].lower() == category.lower()] | |
| result['total'] = len(result['data']) | |
| return jsonify({ | |
| 'status': 'success', | |
| 'data': result | |
| }) | |
| except Exception as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def get_place(place_id): | |
| """Get detailed information about a specific place""" | |
| try: | |
| place = recommender.get_place_by_id(place_id) | |
| if place is None: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': f'Place with ID {place_id} not found' | |
| }), 404 | |
| return jsonify({ | |
| 'status': 'success', | |
| 'data': place | |
| }) | |
| except Exception as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def get_recommendations(): | |
| """ | |
| Get recommendations for a place | |
| Request body: | |
| - place_id: ID of the place (optional if place_name provided) | |
| - place_name: Name of the place (optional if place_id provided) | |
| - top_n: number of recommendations (default: 10) | |
| """ | |
| try: | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Request body is required' | |
| }), 400 | |
| place_idx = None | |
| place_details = None | |
| top_n = data.get('top_n', 10) | |
| print(f"REQUEST: {data}") | |
| # 1. Try lookup by NAME first (Most reliable if IDs don't match) | |
| if 'place_name' in data and data['place_name']: | |
| print(f" Searching by name: {data['place_name']}") | |
| search_result = recommender.search_by_name(data['place_name']) | |
| if search_result: | |
| place_idx = search_result['query_idx'] | |
| # Get details using this index | |
| place_details = recommender.get_place_by_id(place_idx) | |
| print(f" Found by name: Index {place_idx} -> {place_details['nama']}") | |
| # 2. Fallback to ID if provided and Name lookup failed | |
| if place_idx is None and 'place_id' in data: | |
| print(f" Searching by ID: {data['place_id']}") | |
| try: | |
| # Assuming the ID passed IS the index (fallback) | |
| potential_idx = int(data['place_id']) | |
| # Check if valid index | |
| if 0 <= potential_idx < len(recommender.df): | |
| place_idx = potential_idx | |
| place_details = recommender.get_place_by_id(place_idx) | |
| print(f" Using ID as Index: Index {place_idx} -> {place_details['nama']}") | |
| else: | |
| print(f" ID {potential_idx} is out of bounds (0-{len(recommender.df)-1})") | |
| except ValueError: | |
| pass | |
| if place_idx is None: | |
| print(" Place not found") | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Place not found. Provide valid place_name or place_id.' | |
| }), 404 | |
| # Get recommendations using the resolved INDEX | |
| recommendations = recommender.get_recommendations(place_idx, top_n=top_n) | |
| return jsonify({ | |
| 'status': 'success', | |
| 'data': { | |
| 'place': place_details, | |
| 'recommendations': recommendations, | |
| 'total_recommendations': len(recommendations) | |
| } | |
| }) | |
| except Exception as e: | |
| import traceback | |
| print(f"API ERROR: {str(e)}") | |
| traceback.print_exc() | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def semantic_search(): | |
| """ | |
| Semantic search using IndoBERT similarity. | |
| Query params: | |
| - q: search query string | |
| - limit: max results (default: None = all results) | |
| """ | |
| try: | |
| query = request.args.get('q', '', type=str) | |
| limit = request.args.get('limit', None, type=int) | |
| if not query: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Query parameter "q" is required' | |
| }), 400 | |
| results = recommender.semantic_search(query, top_n=limit) | |
| if isinstance(results, dict): | |
| if results.get('status') != 'success': | |
| return jsonify(results), 500 | |
| results = results.get('data', []) | |
| return jsonify({ | |
| 'status': 'success', | |
| 'query': query, | |
| 'total': len(results), | |
| 'data': results | |
| }) | |
| except Exception as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def not_found(error): | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Endpoint not found' | |
| }), 404 | |
| def internal_error(error): | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Internal server error' | |
| }), 500 | |
| if __name__ == '__main__': | |
| app.run(debug=True, host='0.0.0.0', port=5000) | |