Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
31 changes: 10 additions & 21 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__))))
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Failed issue uploads now hit a NameError while handling the original exception because logger is undefined. Define the module logger (and import logging) or remove this logging call so this handler reliably returns its 500 response.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/main.py, line 263:

<comment>Failed issue uploads now hit a `NameError` while handling the original exception because `logger` is undefined. Define the module logger (and import `logging`) or remove this logging call so this handler reliably returns its 500 response.</comment>

<file context>
@@ -268,11 +254,14 @@ def save_to_db():
+            "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))
 
</file context>

raise HTTPException(status_code=500, detail=str(e))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Upload failures expose raw internal exception text to clients. Log the exception server-side and return a stable generic error detail instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/main.py, line 264:

<comment>Upload failures expose raw internal exception text to clients. Log the exception server-side and return a stable generic error detail instead.</comment>

<file context>
@@ -268,11 +254,14 @@ def save_to_db():
+        }
+    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)
</file context>
Suggested change
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail="Unable to create issue") from e


@lru_cache(maxsize=1)
def _load_responsibility_map():
Expand Down
12 changes: 12 additions & 0 deletions backend/spatial_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The bounding box pre-filter doesn't handle longitude wrapping at the international date line (±180°). When the target is near ±180° and the radius is large enough that the bounding box spans across the date line, the simple issue.longitude < min_lon or issue.longitude > max_lon comparison breaks: points geographically close (within radius) on the other side of the date line are erroneously filtered out. Since haversine_distance still runs on filtered-in points and get_bounding_box is generally correct for the India longitude range (68°E–97°E), this is a latent correctness bug rather than an active production issue. Consider normalizing the bounding box longitude range and adding the wrapping case: when the box spans the date line, a point is outside only if its longitude falls in the gap between the normalized ranges.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/spatial_utils.py, line 91:

<comment>The bounding box pre-filter doesn't handle longitude wrapping at the international date line (±180°). When the target is near ±180° and the radius is large enough that the bounding box spans across the date line, the simple `issue.longitude < min_lon or issue.longitude > max_lon` comparison breaks: points geographically close (within radius) on the other side of the date line are erroneously filtered out. Since `haversine_distance` still runs on filtered-in points and `get_bounding_box` is generally correct for the India longitude range (68°E–97°E), this is a latent correctness bug rather than an active production issue. Consider normalizing the bounding box longitude range and adding the wrapping case: when the box spans the date line, a point is outside only if its longitude falls in the gap between the normalized ranges.</comment>

<file context>
@@ -76,10 +76,22 @@ def find_nearby_issues(
             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
</file context>

issue.longitude < min_lon or issue.longitude > max_lon):
continue
Comment on lines +90 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for any geographic scope restrictions or antimeridian handling in the codebase.
rg -nP --type=py -C2 'longitude|antimeridian|dateline|180' backend/ --glob '!**/spatial_utils.py' | head -40

Repository: RohanExploit/VishwaGuru

Length of output: 2533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== spatial_utils.py =="
ast-grep outline backend/spatial_utils.py --view expanded || true

echo
echo "== Search for geographic scope / region restrictions =="
rg -nP --type=py -C2 'India|Maharashtra|country|state|region|bounded|latitude|longitude|antimeridian|dateline|180' backend/ frontend/ . --glob '!**/spatial_utils.py' | head -200

echo
echo "== Files mentioning spatial queries or bounding boxes =="
rg -n --glob '*.py' 'get_bounding_box|haversine_distance|bounding box|spatial' backend | head -200

Repository: RohanExploit/VishwaGuru

Length of output: 15270


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend/spatial_utils.py (relevant section) =="
sed -n '1,130p' backend/spatial_utils.py | cat -n

echo
echo "== Search for coordinate validation / geocoding restrictions =="
rg -n --glob '*.py' 'ge=-180|le=180|ge=-90|le=90|Maharashtra|India|country|state|district|city|longitude|latitude' backend | head -300

Repository: RohanExploit/VishwaGuru

Length of output: 23827


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find_nearby_issues call sites =="
rg -n --glob '*.py' 'find_nearby_issues\(' backend | cat

echo
echo "== get_bounding_box implementation call context =="
rg -n -C3 --glob '*.py' 'get_bounding_box\(' backend/spatial_utils.py backend | cat

Repository: RohanExploit/VishwaGuru

Length of output: 2390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend/spatial_utils.py (lines 1-130) =="
sed -n '1,130p' backend/spatial_utils.py | cat -n

echo
echo "== find_nearby_issues call sites =="
rg -n --glob '*.py' 'find_nearby_issues\(' backend | cat

Repository: RohanExploit/VishwaGuru

Length of output: 5299


Handle antimeridian wrap in the bounding-box prefilter. backend/spatial_utils.py:90-93 can drop valid nearby issues when get_bounding_box() crosses ±180°, because the raw issue.longitude < min_lon or issue.longitude > max_lon check doesn’t normalize wrapped ranges. Normalize longitude comparison here or special-case wrapped boxes before skipping Haversine.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/spatial_utils.py` around lines 90 - 93, Update the bounding-box
prefilter near the Haversine skip to handle antimeridian-crossing ranges
returned by get_bounding_box(). When min_lon exceeds max_lon, treat longitudes
outside the wrapped interval as out of bounds; otherwise preserve the existing
normal comparison, ensuring valid nearby issues are not skipped before
Haversine.


distance = haversine_distance(
target_lat, target_lon,
issue.latitude, issue.longitude
Expand Down
Loading