-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.py
More file actions
290 lines (237 loc) · 11.1 KB
/
Copy pathapp.py
File metadata and controls
290 lines (237 loc) · 11.1 KB
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
from flask import Flask, render_template, request, jsonify
import os
import tempfile
from werkzeug.utils import secure_filename
import logging
import time
from core.route_processor import RouteProcessor
from core.map_generator import MapGenerator
from core.country_detector import CountryDetector
from adapters import AdapterManager
from utils.date_utils import parse_date, get_date_range
from config import Config
from api import api_bp
app = Flask(__name__)
app.config.from_object(Config)
app.register_blueprint(api_bp)
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize components with cache
cache_manager = Config.get_cache_manager()
route_processor = RouteProcessor(buffer_km=0.01, distance_threshold_km=0.01)
map_generator = MapGenerator()
country_detector = CountryDetector()
adapter_manager = AdapterManager()
@app.route('/')
def index():
"""Render the main page with available countries and cache stats"""
available_countries = adapter_manager.get_available_countries()
cache_stats = cache_manager.get_stats()
return render_template('index.html',
countries=available_countries,
cache_stats=cache_stats)
@app.route('/cache/stats')
def cache_stats():
"""Get cache statistics"""
return jsonify(cache_manager.get_stats())
@app.route('/cache/clear', methods=['POST'])
def clear_cache():
"""Clear all cache entries"""
success = cache_manager.clear()
return jsonify({
'success': success,
'message': 'Cache cleared successfully' if success else 'Failed to clear cache'
})
@app.route('/debug/cache')
def debug_cache():
"""Debug cache effectiveness"""
cache_stats = cache_manager.get_stats()
# Show sample cache keys
sample_keys = []
total_keys = 0
if hasattr(cache_manager.cache, 'keys'):
all_keys = cache_manager.cache.keys()
sample_keys = all_keys[:10] # First 10 keys
total_keys = len(all_keys)
elif hasattr(cache_manager.cache, 'get_size'):
total_keys = cache_manager.cache.get_size()
# Get Redis info if available
redis_info = {}
if hasattr(cache_manager.cache, 'get_info'):
redis_info = cache_manager.cache.get_info()
return jsonify({
'cache_type': type(cache_manager.cache).__name__,
'stats': cache_stats,
'sample_keys': sample_keys,
'total_keys': total_keys,
'redis_info': redis_info
})
@app.route('/test-france')
def test_france():
"""Test France adapter directly"""
try:
adapter = adapter_manager.get_adapter('FR')
if not adapter:
return jsonify({'error': 'France adapter not found'}), 404
# Test with demo data
start_date, end_date = get_date_range('2025-06-19')
# Time the fetch
start_time = time.time()
works = adapter.fetch_roadworks(start_date, end_date)
fetch_time = time.time() - start_time
return jsonify({
'success': True,
'country': 'France',
'adapter_name': adapter.get_country_name(),
'works_count': len(works),
'fetch_time_seconds': round(fetch_time, 2),
'works_sample': works[:3] if works else [] # First 3 works
})
except Exception as e:
logger.error(f"Error testing France: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/upload', methods=['POST'])
def upload_file():
"""Handle GPX file upload and route analysis with caching and performance timing"""
if 'gpx_file' not in request.files:
return jsonify({'error': 'No file uploaded'}), 400
file = request.files['gpx_file']
ride_date = request.form.get('ride_date')
selected_countries = request.form.getlist('countries')
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
if not ride_date:
return jsonify({'error': 'No ride date provided'}), 400
# Start total timing
total_start_time = time.time()
try:
# Save and process uploaded file
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# Parse GPX file - Time this step
parse_start = time.time()
coordinates = route_processor.parse_gpx_file(file_path)
parse_time = time.time() - parse_start
logger.info(f"⏱️ GPX parsing took {parse_time:.2f}s")
if not coordinates:
return jsonify({'error': 'Could not parse GPX file'}), 400
# Log sample coordinates for debugging
logger.info(f"📍 Sample coordinates: {coordinates[:5]}")
logger.info(f"📊 Total coordinates parsed: {len(coordinates)}")
# Detect countries along the route - Time this step
detect_start = time.time()
if not selected_countries:
detected_countries = country_detector.detect_countries_along_route(coordinates)
else:
detected_countries = selected_countries
detect_time = time.time() - detect_start
logger.info(f"⏱️ Country detection took {detect_time:.2f}s")
logger.info(f"🌍 Detected countries: {detected_countries}")
# Debug each adapter's bounding box
for code in ['BE', 'NL', 'GB', 'FR']:
adapter = adapter_manager.get_adapter(code)
if adapter:
bbox = adapter.get_supported_bbox()
logger.info(f"📦 {code} bbox: {bbox}")
# Get date range for queries
start_date, end_date = get_date_range(ride_date)
logger.info(f"📅 Date range: {start_date} to {end_date}")
# Calculate bbox once for all adapters
bbox_calc_start = time.time()
bbox = route_processor.calculate_bbox(coordinates)
bbox_calc_time = time.time() - bbox_calc_start
logger.info(f"⏱️ Bbox calculation took {bbox_calc_time:.2f}s")
logger.info(f"📦 Route bbox: {bbox}")
# Fetch roadworks from all relevant countries (with caching and timing!)
all_works = []
used_adapters = []
total_fetch_time = 0
logger.info(f"🚀 Starting roadworks fetch for {len(detected_countries)} countries...")
for country_code in detected_countries:
adapter = adapter_manager.get_adapter(country_code)
if adapter:
# Add timing and cache debugging
start_time = time.time()
logger.info(f"🔍 Fetching works for {country_code}...")
logger.info(f"🔑 Using bbox: {bbox}")
logger.info(f"📅 Date range: {start_date} to {end_date}")
# This call is now automatically cached with performance logging!
works = adapter.fetch_roadworks(start_date, end_date, bbox)
fetch_time = time.time() - start_time
total_fetch_time += fetch_time
logger.info(f"⏱️ {country_code} took {fetch_time:.2f}s, got {len(works)} works")
all_works.extend([(work, adapter) for work in works])
used_adapters.append(adapter)
# Log cache performance for this adapter
if hasattr(adapter, 'get_cache_stats'):
adapter_stats = adapter.get_cache_stats()
logger.info(f"📊 {country_code} cache stats: {adapter_stats}")
logger.info(f"⏱️ Total roadworks fetching took {total_fetch_time:.2f}s")
logger.info(f"📊 Total works fetched: {len(all_works)}")
# Analyze works near the route - Time this step
analysis_start = time.time()
nearby_works = route_processor.check_works_near_route(coordinates, all_works)
analysis_time = time.time() - analysis_start
logger.info(f"⏱️ Route analysis took {analysis_time:.2f}s")
logger.info(f"⚠️ Works near route: {len(nearby_works)}")
# Generate detour suggestions - Time this step
detour_start = time.time()
detours = route_processor.generate_detour_suggestions(coordinates, nearby_works[:3])
detour_time = time.time() - detour_start
logger.info(f"⏱️ Detour generation took {detour_time:.2f}s")
# Create interactive map - Time this step
map_start = time.time()
map_html = map_generator.create_map(coordinates, nearby_works, detours)
map_time = time.time() - map_start
logger.info(f"⏱️ Map generation took {map_time:.2f}s")
# Clean up uploaded file
os.remove(file_path)
# Calculate total processing time
total_time = time.time() - total_start_time
logger.info(f"⏱️ TOTAL PROCESSING TIME: {total_time:.2f}s")
# Include cache stats and performance metrics in response
cache_stats = cache_manager.get_stats()
# Performance breakdown
performance_metrics = {
'total_time': round(total_time, 2),
'gpx_parsing': round(parse_time, 2),
'country_detection': round(detect_time, 2),
'bbox_calculation': round(bbox_calc_time, 2),
'roadworks_fetching': round(total_fetch_time, 2),
'route_analysis': round(analysis_time, 2),
'detour_generation': round(detour_time, 2),
'map_generation': round(map_time, 2)
}
logger.info(f"📈 Performance breakdown: {performance_metrics}")
response_data = {
'route_points': len(coordinates),
'countries_checked': [adapter.country_name for adapter in used_adapters],
'works_found': len(nearby_works),
'works_details': [
{
'description': work_info['adapter'].get_work_info(work_info['work'])['description'],
'start_date': work_info['adapter'].get_work_info(work_info['work'])['start_date'],
'end_date': work_info['adapter'].get_work_info(work_info['work'])['end_date'],
'distance_km': round(work_info['distance_km'], 2),
'country': work_info['adapter'].country_name,
'source': work_info['adapter'].get_work_info(work_info['work'])['source']
}
for work_info in nearby_works
],
'detours_generated': len(detours),
'map_html': map_html,
'cache_stats': cache_stats,
'performance_metrics': performance_metrics,
'bbox_used': bbox,
'detected_countries': detected_countries
}
return jsonify(response_data)
except Exception as e:
if 'file_path' in locals() and os.path.exists(file_path):
os.remove(file_path)
logger.error(f"❌ Error processing file: {e}")
return jsonify({'error': f'Error processing file: {str(e)}'}), 500
if __name__ == '__main__':
app.run(debug=True)