Add Flask API endpoints and testing scripts for safety analysis

- Created requirements.txt for Flask and related libraries.
- Implemented test_api.py to validate API endpoints including health check, weather data retrieval, crash analysis, route finding, and single route fetching.
- Developed test_crash_endpoint.py for focused testing on crash analysis endpoint.
- Added test_flask_endpoints.py for lightweight tests using Flask's test client with mocked dependencies.
- Introduced SafetyAnalysisModal component in the frontend for displaying detailed safety analysis results.
- Implemented flaskApi.ts to handle API requests for weather data and crash analysis, including data transformation to match frontend interfaces.
This commit is contained in:
2025-09-28 03:59:32 -04:00
parent a97b79ee37
commit fbb6953473
13 changed files with 1192 additions and 431 deletions

285
llm/test_flask_endpoints.py Normal file
View File

@@ -0,0 +1,285 @@
"""
Lightweight tests for llm/flask_server.py endpoints.
This script injects fake local modules to avoid external network/DB calls,
imports the flask app, and uses Flask's test_client to call endpoints.
"""
import sys
import os
import types
import json
import traceback
# Prepare fake modules to avoid external dependencies (MongoDB, external APIs, LLMs)
fake_mongo = types.ModuleType("gemini_mongo_mateo")
def fake_connect_to_mongodb():
# Return a simple truthy object representing a connection/collection
return {"fake": "collection"}
def fake_get_crashes_within_radius_mongodb(collection, lat, lon, radius_km):
return []
def fake_analyze_mongodb_crash_patterns(crashes, lat, lon, radius_km, weather_summary=None):
return "No crash data available (fake)"
def fake_get_current_weather(lat, lon):
return ({"temp": 20, "weather_code": 0}, "Clear sky")
fake_mongo.connect_to_mongodb = fake_connect_to_mongodb
fake_mongo.get_crashes_within_radius_mongodb = fake_get_crashes_within_radius_mongodb
fake_mongo.analyze_mongodb_crash_patterns = fake_analyze_mongodb_crash_patterns
fake_mongo.get_current_weather = fake_get_current_weather
fake_mongo.MONGO_URI = "mongodb://fake"
# Fake reroute module
fake_reroute = types.ModuleType("gemini_reroute_mateo")
class FakeSafeRouteAnalyzer:
def __init__(self, uri):
self.collection = {"fake": "collection"}
def find_safer_route(self, start_lat, start_lon, end_lat, end_lon):
return {
'recommended_route': {
'route_data': {
'coordinates': [[start_lat, start_lon], [end_lat, end_lon]],
'distance_km': 1.23,
'duration_min': 4.5,
'geometry': None
},
'safety_analysis': {
'average_safety_score': 0.0,
'total_crashes_near_route': 0,
'max_danger_score': 0.0
}
},
'safety_report': 'All good (fake)',
'alternative_routes': []
}
def get_route_from_mapbox(self, start_lat, start_lon, end_lat, end_lon, profile='driving'):
return {
'success': True,
'coordinates': [[start_lat, start_lon], [end_lat, end_lon]],
'distance_km': 1.0,
'duration_min': 3.0,
'geometry': None
}
def analyze_route_safety(self, coordinates):
return {
'total_crashes_near_route': 0,
'average_safety_score': 0.0,
'max_danger_score': 0.0,
'safety_points': [],
'crashes_data': [],
'route_length_points': len(coordinates)
}
def get_current_weather(self, lat, lon):
return fake_get_current_weather(lat, lon)
def generate_safety_report_with_llm(self, safety_data, route_info, weather_summary=None):
return "Fake LLM report"
fake_reroute.SafeRouteAnalyzer = FakeSafeRouteAnalyzer
fake_reroute.MONGO_URI = "mongodb://fake"
# Insert fake modules into sys.modules so importing flask_server uses them
sys.modules['gemini_mongo_mateo'] = fake_mongo
sys.modules['gemini_reroute_mateo'] = fake_reroute
# Also provide package-style names in case flask_server tries them
sys.modules['llm.gemini_mongo_mateo'] = fake_mongo
sys.modules['llm.gemini_reroute_mateo'] = fake_reroute
# If Flask isn't installed in the environment, provide a minimal fake
# implementation sufficient for this test: Flask, request, jsonify and a
# test_client that can call registered route handlers.
if 'flask' not in sys.modules:
import types
flask_mod = types.ModuleType('flask')
class FakeRequest:
def __init__(self):
self.args = {}
self._json = None
def get_json(self, force=False):
return self._json
class FakeResponse:
def __init__(self, data, status_code=200):
self.data = data
self.status_code = status_code
def get_json(self):
# If data already a dict, return it; if string, try parse
if isinstance(self.data, dict):
return self.data
try:
return json.loads(self.data)
except Exception:
return None
class FakeApp:
def __init__(self, name=None):
self._routes = {}
self.request = FakeRequest()
def route(self, path, methods=None):
methods = methods or ['GET']
def decorator(fn):
self._routes[(path, tuple(sorted(methods)))] = fn
# Also store by path for simpler lookup
self._routes[path] = fn
return fn
return decorator
def test_client(self):
app = self
class Client:
def get(self, path):
# parse querystring
if '?' in path:
route, qs = path.split('?', 1)
params = {}
for pair in qs.split('&'):
if '=' in pair:
k, v = pair.split('=', 1)
params[k] = v
else:
route = path
params = {}
app.request.args = params
handler = app._routes.get(route)
if handler is None:
return FakeResponse({'error': 'not found'}, status_code=404)
try:
result = handler()
if isinstance(result, tuple):
body, code = result
return FakeResponse(body, status_code=code)
return FakeResponse(result, status_code=200)
except Exception as e:
return FakeResponse({'error': str(e)}, status_code=500)
def post(self, path, json=None):
app.request._json = json
handler = app._routes.get(path)
if handler is None:
return FakeResponse({'error': 'not found'}, status_code=404)
try:
result = handler()
if isinstance(result, tuple):
body, code = result
return FakeResponse(body, status_code=code)
return FakeResponse(result, status_code=200)
except Exception as e:
traceback.print_exc()
return FakeResponse({'error': str(e)}, status_code=500)
return Client()
def errorhandler(self, code):
def decorator(fn):
# store error handlers by code
if not hasattr(self, '_error_handlers'):
self._error_handlers = {}
self._error_handlers[code] = fn
return fn
return decorator
def fake_jsonify(obj):
return obj
# Populate module
flask_mod.Flask = FakeApp
flask_mod.request = FakeRequest()
flask_mod.jsonify = fake_jsonify
sys.modules['flask'] = flask_mod
# Minimal flask_cors shim
if 'flask_cors' not in sys.modules:
fc = types.ModuleType('flask_cors')
def fake_CORS(app):
return None
fc.CORS = fake_CORS
sys.modules['flask_cors'] = fc
# Load flask_server module from file without executing its __main__ block
import importlib.util
this_dir = os.path.dirname(__file__)
flask_file = os.path.join(this_dir, 'flask_server.py')
spec = importlib.util.spec_from_file_location('flask_server', flask_file)
flask_server = importlib.util.module_from_spec(spec)
# Ensure the module sees the fake modules we inserted
sys.modules['flask_server'] = flask_server
try:
spec.loader.exec_module(flask_server)
except Exception as e:
print('Failed to import flask_server:', e)
traceback.print_exc()
sys.exit(2)
app = getattr(flask_server, 'app', None)
if app is None:
print('flask_server.app not found')
sys.exit(3)
client = app.test_client()
results = {}
# 1) Health
r = client.get('/api/health')
try:
results['health'] = {'status_code': r.status_code, 'json': r.get_json()}
except Exception:
results['health'] = {'status_code': r.status_code, 'data': r.data.decode('utf-8')}
# 2) Weather (valid coords)
r = client.get('/api/weather?lat=38.9072&lon=-77.0369')
results['weather_ok'] = {'status_code': r.status_code, 'json': r.get_json()}
# 3) Weather (invalid coords)
r = client.get('/api/weather')
results['weather_invalid'] = {'status_code': r.status_code, 'json': r.get_json()}
# 4) Analyze crashes (POST)
payload = {'lat': 38.9072, 'lon': -77.0369, 'radius': 1.0}
r = client.post('/api/analyze-crashes', json=payload)
results['analyze_crashes'] = {'status_code': r.status_code, 'json': r.get_json()}
# 5) Find safe route
route_payload = {'start_lat': 38.9, 'start_lon': -77.0, 'end_lat': 38.95, 'end_lon': -77.04}
r = client.post('/api/find-safe-route', json=route_payload)
results['find_safe_route'] = {'status_code': r.status_code, 'json': r.get_json()}
# 6) Get single route
single_payload = {'start_lat': 38.9, 'start_lon': -77.0, 'end_lat': 38.95, 'end_lon': -77.04}
r = client.post('/api/get-single-route', json=single_payload)
results['get_single_route'] = {'status_code': r.status_code, 'json': r.get_json()}
print('\n=== Endpoint test results ===')
print(json.dumps(results, indent=2, default=str))
# Summarize endpoints
summary = {
'/api/health': 'GET - returns service + dependency health',
'/api/weather': 'GET - requires lat & lon query params; returns current weather from LLM module',
'/api/analyze-crashes': 'POST - requires JSON {lat, lon, radius}; returns crash summary, weather, LLM safety analysis',
'/api/find-safe-route': 'POST - requires JSON start_lat,start_lon,end_lat,end_lon; returns recommended route with safety analysis',
'/api/get-single-route': 'POST - similar to find-safe-route but returns single route + LLM safety report'
}
print('\n=== Endpoint summary ===')
for path, desc in summary.items():
print(f"{path}: {desc}")
# Exit code 0
sys.exit(0)