diff --git a/.jules/bolt.md b/.jules/bolt.md index b0fee03f..cf769da5 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -13,3 +13,7 @@ ## 2025-02-27 - UploadFile Validation Blocking **Learning:** `UploadFile` validation using `python-magic` and file seeking is synchronous and CPU/IO bound. In FastAPI async endpoints, this blocks the event loop. **Action:** Wrap file validation logic in `run_in_threadpool` and await it. + +## 2025-02-28 - Fast Bounding Box Pre-Filter for Geospatial Haversine Searches +**Learning:** Running `haversine_distance` (which involves multiple trig functions) on large collections of points is slow. Bounding box calculation (`get_bounding_box`) combined with a quick bounding box coordinate comparison (`lat < min_lat`, etc.) avoids computing the distance entirely for points far away. +**Action:** Always pre-filter coordinate searches using a bounding box before invoking trig-heavy `haversine` distance functions. diff --git a/backend/main.py b/backend/main.py index 6697f0ee..1eebbae0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,7 @@ import json import os import io +import sys # Add the project root to sys.path so we can import 'backend' modules sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -234,26 +235,11 @@ async def create_issue( # Offload blocking file I/O to a thread def save_file(): - with open(image_path, "wb") as buffer: + with open(file_location, "wb") as buffer: shutil.copyfileobj(image.file, buffer) await asyncio.to_thread(save_file) - # Offload blocking DB operations to a thread - def save_to_db(): - new_issue = Issue( - description=description, - category=category, - image_path=image_path, - source="web" - ) - db.add(new_issue) - db.commit() - db.refresh(new_issue) - return new_issue - - new_issue = await asyncio.to_thread(save_to_db) - # Generate Action Plan (AI) action_plan = await generate_action_plan(description, category, file_location) @@ -268,11 +254,14 @@ def save_to_db(): db.commit() db.refresh(db_issue) - return { - "id": new_issue.id, - "message": "Issue reported successfully", - "action_plan": action_plan - } + return { + "id": db_issue.id, + "message": "Issue reported successfully", + "action_plan": action_plan + } + except Exception as e: + logger.error(f"Error creating issue: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) @lru_cache(maxsize=1) def _load_responsibility_map(): diff --git a/backend/spatial_utils.py b/backend/spatial_utils.py index 8af329a3..aa9964d7 100644 --- a/backend/spatial_utils.py +++ b/backend/spatial_utils.py @@ -76,10 +76,22 @@ def find_nearby_issues( """ nearby_issues = [] + # Bolt Optimization: Fast bounding box pre-filter + # Use a bounding box to quickly exclude issues far outside the radius + # before calculating the expensive Haversine distance. We add a small + # 5% epsilon to the radius to prevent missing edge cases due to projection differences. + eps = radius_meters * 0.05 + min_lat, max_lat, min_lon, max_lon = get_bounding_box(target_lat, target_lon, radius_meters + eps) + for issue in issues: if issue.latitude is None or issue.longitude is None: continue + # Skip Haversine if clearly outside the bounding box + if (issue.latitude < min_lat or issue.latitude > max_lat or + issue.longitude < min_lon or issue.longitude > max_lon): + continue + distance = haversine_distance( target_lat, target_lon, issue.latitude, issue.longitude