File size: 6,990 Bytes
fbff2aa
 
 
 
 
 
 
 
 
 
 
bb0fa8a
fbff2aa
bb0fa8a
fbff2aa
 
 
 
 
 
 
 
 
 
098a77b
e79ce4a
fbff2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb0fa8a
fbff2aa
 
 
 
 
 
 
 
 
bb0fa8a
fbff2aa
 
 
 
 
 
 
 
 
 
 
bb0fa8a
fbff2aa
bb0fa8a
fbff2aa
 
 
 
bb0fa8a
fbff2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb0fa8a
fbff2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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!")

@app.route('/')
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'
        }
    })

@app.route('/api/places', methods=['GET'])
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

@app.route('/api/places/<int:place_id>', methods=['GET'])
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

@app.route('/api/recommendations', methods=['POST'])
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

@app.route('/api/search', methods=['GET'])
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

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'status': 'error',
        'message': 'Endpoint not found'
    }), 404

@app.errorhandler(500)
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)