-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
391 lines (314 loc) · 14.2 KB
/
app.py
File metadata and controls
391 lines (314 loc) · 14.2 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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
from flask import Flask, request, jsonify
import logging
from logging.handlers import RotatingFileHandler
import os
import requests
from progress_tracker_service import process_progress_data
from shelter_service import get_shelter_service
from deadlines_service import process_deadlines_data
from missing_service import get_missing_service
# Configure logging
if not os.path.exists('logs'):
os.makedirs('logs')
logger = logging.getLogger('la_fires_api')
logger.setLevel(logging.INFO)
# Create file handler for logging to a file
file_handler = RotatingFileHandler('logs/la_fires_api.log', maxBytes=10485760, backupCount=10)
file_handler.setLevel(logging.INFO)
# Create console handler for logging to console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# Create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# Add the handlers to the logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
app = Flask(__name__)
# Stay Healthy Endpoints
def geocode_address(address: str):
"""
Convert address to lat/lon coordinates using Mapbox Geocoding API
Args:
address: The address string to geocode
Returns:
Tuple of (latitude, longitude) or None if geocoding failed
"""
# Get Mapbox API key from environment
mapbox_token = os.getenv("MAPBOX_ACCESS_TOKEN")
if not mapbox_token:
logger.error("MAPBOX_ACCESS_TOKEN environment variable not set")
return None
try:
# URL encode the address
encoded_address = requests.utils.quote(address)
# Construct Mapbox Geocoding API URL
url = f"https://api.mapbox.com/geocoding/v5/mapbox.places/{encoded_address}.json"
params = {
"access_token": mapbox_token,
"limit": 1, # We only need the top result
"country": "US" # Limit to US results
}
# Make request to Mapbox
logger.info(f"Geocoding address: {address}")
response = requests.get(url, params=params)
# Check response
if response.status_code != 200:
logger.error(f"Mapbox API error: {response.status_code} - {response.text}")
return None
# Parse response
data = response.json()
# Check if we got features back
if not data.get("features") or len(data["features"]) == 0:
logger.warning(f"No geocoding results found for address: {address}")
return None
# Get coordinates [longitude, latitude]
coordinates = data["features"][0]["center"]
# Return as (latitude, longitude) - note the order reversal from Mapbox's format
return (coordinates[1], coordinates[0])
except Exception as e:
logger.error(f"Error geocoding address: {str(e)}")
return None
@app.route('/api/stayhealthy/getshelter', methods=['GET'])
def get_shelter():
logger.info("Endpoint hit: /api/stayhealthy/getshelter")
try:
# Check if direct lat/lon coordinates are provided
direct_lat = request.args.get('lat')
direct_lon = request.args.get('lon')
# Get optional radius parameter (default 50km)
try:
distance = float(request.args.get('distance', 10.0))
except ValueError:
distance = 50.0
# Get optional limit parameter (default 0 means no limit)
try:
limit = int(request.args.get('limit', 0))
except ValueError:
limit = 0
# If direct coordinates are provided, use them
if direct_lat and direct_lon:
try:
lat = float(direct_lat)
lon = float(direct_lon)
logger.info(f"Using direct coordinates: ({lat}, {lon})")
# Initialize shelter service
shelter_service = get_shelter_service()
# Query for nearby shelters using the provided coordinates
shelters = shelter_service.get_shelters_by_location(lat, lon, distance_km=distance)
# Apply limit if specified
if limit > 0 and len(shelters) > limit:
shelters = shelters[:limit]
logger.info(f"Limiting results to {limit} closest shelters")
# Return results with coordinates
return jsonify({
"success": True,
"coordinates": {"lat": lat, "lon": lon},
"search_radius_km": distance,
"shelters": shelters,
"shelter_count": len(shelters),
"source": "direct_coordinates"
})
except ValueError:
return jsonify({
"success": False,
"error": "Invalid latitude or longitude values"
}), 400
# Otherwise, use address geocoding
address = request.args.get('address')
if not address:
return jsonify({
"success": False,
"error": "Either address or lat/lon parameters are required"
}), 400
# Add "Los Angeles, CA" if it doesn't contain city/state info
address_lower = address.lower()
if "los angeles" not in address_lower and "la" not in address_lower:
if "ca" not in address_lower and "california" not in address_lower:
# Neither city nor state found, add both
address = f"{address}, Los Angeles, CA"
else:
# State found but not city, add city
address = f"{address}, Los Angeles"
elif "ca" not in address_lower and "california" not in address_lower:
# City found but not state, add state
address = f"{address}, CA"
logger.info(f"Normalized address: {address}")
# Geocode the address
coordinates = geocode_address(address)
if not coordinates:
return jsonify({
"success": False,
"error": "Could not geocode the provided address"
}), 400
# Get lat/lon from coordinates
lat, lon = coordinates
logger.info(f"Geocoded coordinates: ({lat}, {lon})")
# Initialize shelter service
shelter_service = get_shelter_service()
# Query for nearby shelters using the geocoded coordinates
shelters = shelter_service.get_shelters_by_location(lat, lon, distance_km=distance)
# Apply limit if specified
if limit > 0 and len(shelters) > limit:
shelters = shelters[:limit]
logger.info(f"Limiting results to {limit} closest shelters")
# Return results with original address and coordinates
return jsonify({
"success": True,
"address": address,
"coordinates": {"lat": lat, "lon": lon},
"search_radius_km": distance,
"shelters": shelters,
"shelter_count": len(shelters)
})
except Exception as e:
error_msg = f"Error retrieving shelter data: {str(e)}"
logger.error(error_msg)
return jsonify({"success": False, "error": error_msg}), 500
# Check Progress Endpoint
@app.route('/api/checkprogress', methods=['GET'])
def check_progress():
logger.info("Endpoint hit: /api/checkprogress")
progress_data = process_progress_data()
return jsonify(progress_data)
# Deadlines Endpoint
@app.route('/api/deadlines', methods=['GET'])
def get_deadlines():
logger.info("Endpoint hit: /api/deadlines")
deadlines_data = process_deadlines_data()
return jsonify(deadlines_data)
# Missing Person/Pet Endpoints
@app.route('/api/missing', methods=['GET', 'POST'])
def missing():
logger.info(f"Endpoint hit: /api/missing with method {request.method}")
try:
# Initialize missing service
missing_service = get_missing_service()
if request.method == 'POST':
# Extract content from JSON request
data = request.get_json()
if not data or 'content' not in data:
return jsonify({
"success": False,
"error": "Missing 'content' field in request body"
}), 400
content = data['content']
if not content or not isinstance(content, str) or len(content.strip()) < 10:
return jsonify({
"success": False,
"error": "Content must be a string with at least 10 characters"
}), 400
# Add entry to database
entry_id = missing_service.add_missing_entry(content)
if not entry_id:
return jsonify({
"success": False,
"error": "Failed to add entry"
}), 500
return jsonify({
"success": True,
"message": "Entry added successfully",
"id": entry_id
})
else: # GET request
# Extract query parameter
query = request.args.get('query')
# Get limit parameter
# Default to 1 for queries (top match only) but use 5 for listing all entries
default_limit = 1 if query else 5
try:
limit = min(int(request.args.get('limit', default_limit)), 100) # Cap at 100
except ValueError:
limit = default_limit
if not query:
# If no query is provided, return all entries
entries = missing_service.get_all_missing_entries(limit=limit)
return jsonify({
"success": True,
"query": None,
"count": len(entries),
"entries": entries
})
# Search for similar entries
similar_entries = missing_service.search_missing_entries(query, limit=limit)
return jsonify({
"success": True,
"query": query,
"count": len(similar_entries),
"entries": similar_entries
})
except Exception as e:
error_msg = f"Error processing missing person/pet request: {str(e)}"
logger.error(error_msg)
logger.exception("Stack trace:")
return jsonify({"success": False, "error": error_msg}), 500
@app.route('/api/debug/shelters', methods=['GET'])
def debug_shelters():
"""Debug endpoint to list all shelters with their coordinates"""
logger.info("Endpoint hit: /api/debug/shelters")
try:
# Initialize shelter service
shelter_service = get_shelter_service()
# Get all shelters
all_shelters = shelter_service.get_all_shelters()
# Return first 10 shelters with their coordinates
sample_shelters = all_shelters[:10]
# Count shelters with valid coordinates
valid_coords = sum(1 for s in all_shelters if s.get('lat') != 0 and s.get('lon') != 0)
return jsonify({
"success": True,
"total_shelters": len(all_shelters),
"shelters_with_valid_coords": valid_coords,
"sample_shelters": sample_shelters,
"csv_format": "address,bookinglink,phonenumber(in lat column),latitude(in lon column),longitude(in phonenumber column),notes"
})
except Exception as e:
error_msg = f"Error retrieving shelter data: {str(e)}"
logger.error(error_msg)
return jsonify({"success": False, "error": error_msg}), 500
@app.route('/api/debug/nearest-shelters', methods=['GET'])
def debug_nearest_shelters():
"""Debug endpoint to find the nearest shelters to specific coordinates"""
logger.info("Endpoint hit: /api/debug/nearest-shelters")
try:
# Use Van Nuys / Ventura coordinate for testing
test_lat = 34.18466
test_lon = -118.44873
# Get optional radius parameter (default 100km for wide search)
try:
distance = float(request.args.get('distance', 100.0))
except ValueError:
distance = 100.0
logger.info(f"Testing with coordinates: ({test_lat}, {test_lon}) and radius {distance}km")
# Initialize shelter service
shelter_service = get_shelter_service()
# Get all shelters and calculate distances
all_shelters = shelter_service.get_all_shelters()
# Calculate distance for each shelter
for shelter in all_shelters:
shelter_lat = shelter.get('lat')
shelter_lon = shelter.get('lon')
if shelter_lat is not None and shelter_lon is not None:
distance_km = shelter_service._haversine(test_lat, test_lon, shelter_lat, shelter_lon)
shelter['distance_km'] = round(distance_km, 2)
# Sort by distance
all_shelters.sort(key=lambda x: x.get('distance_km', float('inf')))
# Get shelters within the specified radius
nearby_shelters = [s for s in all_shelters if s.get('distance_km', float('inf')) <= distance]
# Return the closest 10 shelters
closest_shelters = all_shelters[:10]
return jsonify({
"success": True,
"coordinates": {"lat": test_lat, "lon": test_lon},
"search_radius_km": distance,
"shelters_within_radius": len(nearby_shelters),
"closest_shelters": closest_shelters
})
except Exception as e:
error_msg = f"Error retrieving shelter data: {str(e)}"
logger.error(error_msg)
return jsonify({"success": False, "error": error_msg}), 500
if __name__ == '__main__':
logger.info("Starting LA Fires API server")
app.run(debug=True, port=6000)