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 @@ -93,3 +93,7 @@
## 2026-05-20 - Joined Queries for Integrity Verification
**Learning:** Performing multiple sequential database queries to verify cryptographically chained records (e.g., fetching a record and then its associated token/metadata from another table) introduces unnecessary latency and increases database load.
**Action:** Consolidate associated data retrieval into a single SQL `JOIN` query within the verification hot-path. This reduces database round-trips and improves end-to-end latency for blockchain-style integrity checks.

## 2026-05-21 - Spatial Deduplication Optimization
**Learning:** Calculating distance values with inline math components inside of loops causes slowdowns for datasets due to redundant execution. Adding pre-filtering parameters from the DB level bounding box allows bounding box logic to be bypassed on the Python side.
**Action:** Hoist consistent math calculations outside of loops for bulk distance evaluations. Also introduce pre-filtered booleans on search functions to skip duplicate box filtering on data already passed through DB filters.
4 changes: 2 additions & 2 deletions backend/routers/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async def create_issue(
)

nearby_issues_with_distance = find_nearby_issues(
open_issues, latitude, longitude, radius_meters=50.0
open_issues, latitude, longitude, radius_meters=50.0, pre_filtered=True
)

if nearby_issues_with_distance:
Expand Down Expand Up @@ -342,7 +342,7 @@ def get_nearby_issues(
).order_by(Issue.created_at.desc()).limit(100).all()

nearby_issues_with_distance = find_nearby_issues(
open_issues, latitude, longitude, radius_meters=radius
open_issues, latitude, longitude, radius_meters=radius, pre_filtered=True
)

# Convert to response format and limit results
Expand Down
108 changes: 65 additions & 43 deletions backend/spatial_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def find_nearby_issues(
issues: List[Issue],
target_lat: float,
target_lon: float,
radius_meters: float = 50.0
radius_meters: float = 50.0,
pre_filtered: bool = False
) -> List[Tuple[Issue, float]]:
"""
Find issues within a specified radius of a target location.
Expand All @@ -106,14 +107,16 @@ def find_nearby_issues(
target_lat: Target latitude
target_lon: Target longitude
radius_meters: Search radius in meters (default 50m)
pre_filtered: If True, skips bounding box checks, assuming input is already filtered

Returns:
List of tuples (issue, distance_meters) for issues within radius
"""
nearby_issues = []

# Optimization: pre-filter using a bounding box to avoid math on distant points
min_lat, max_lat, min_lon, max_lon = get_bounding_box(target_lat, target_lon, radius_meters)
if not pre_filtered:
min_lat, max_lat, min_lon, max_lon = get_bounding_box(target_lat, target_lon, radius_meters)

# Optimization: Use inline Equirectangular approximation for short distances (< 10km)
# This avoids function call overhead and repeated radian conversions.
Expand All @@ -123,55 +126,74 @@ def find_nearby_issues(
if issue.latitude is None or issue.longitude is None:
continue

# Apply bounding box pre-filter
if issue.latitude < min_lat or issue.latitude > max_lat or \
issue.longitude < min_lon or issue.longitude > max_lon:
continue
if not pre_filtered:
# Apply bounding box pre-filter
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)
if distance <= radius_meters:
nearby_issues.append((issue, distance))
else:
# Optimized path for common case (small radius)
R = 6371000.0
radius_sq = radius_meters * radius_meters

target_lat_rad = math.radians(target_lat)
target_lon_rad = math.radians(target_lon)
# Cosine term is constant for the target latitude in equirectangular projection
cos_lat = math.cos(target_lat_rad)

for issue in issues:
if issue.latitude is None or issue.longitude is None:
continue

# Apply bounding box pre-filter
if issue.latitude < min_lat or issue.latitude > max_lat or \
issue.longitude < min_lon or issue.longitude > max_lon:
continue

# Inline conversion to radians
lat_rad = math.radians(issue.latitude)
lon_rad = math.radians(issue.longitude)

dlat = lat_rad - target_lat_rad
dlon = lon_rad - target_lon_rad

# Handle longitude wrapping (dateline crossing)
if dlon > math.pi:
dlon -= 2 * math.pi
elif dlon < -math.pi:
dlon += 2 * math.pi

x = dlon * cos_lat
y = dlat

# Squared distance check avoids expensive sqrt()
# (x*R)^2 + (y*R)^2 = R^2 * (x^2 + y^2)
dist_sq = (x*x + y*y) * R * R

if dist_sq <= radius_sq:
nearby_issues.append((issue, math.sqrt(dist_sq)))
# Pre-calculate meters per degree for latitude and longitude to avoid radians math in loop
# 1 degree of latitude is constant (approx)
meters_per_degree_lat = 111194.92664455873 # 6371000.0 * (math.pi / 180.0)
# 1 degree of longitude shrinks towards poles
meters_per_degree_lon = meters_per_degree_lat * math.cos(target_lat * 0.017453292519943295)

Comment on lines +143 to +147
if pre_filtered:
for issue in issues:
lat = issue.latitude
lon = issue.longitude
if lat is None or lon is None:
continue

dlat = lat - target_lat
dlon = lon - target_lon

# Handle longitude wrapping (dateline crossing)
if dlon > 180.0:
dlon -= 360.0
elif dlon < -180.0:
dlon += 360.0

x = dlon * meters_per_degree_lon
y = dlat * meters_per_degree_lat
dist_sq = (x*x + y*y)

if dist_sq <= radius_sq:
nearby_issues.append((issue, math.sqrt(dist_sq)))
else:
for issue in issues:
lat = issue.latitude
lon = issue.longitude
if lat is None or lon is None:
continue

# Apply bounding box pre-filter
if lat < min_lat or lat > max_lat or \
lon < min_lon or lon > max_lon:
continue

dlat = lat - target_lat
dlon = lon - target_lon

# Handle longitude wrapping (dateline crossing)
if dlon > 180.0:
dlon -= 360.0
elif dlon < -180.0:
dlon += 360.0

x = dlon * meters_per_degree_lon
y = dlat * meters_per_degree_lat
dist_sq = (x*x + y*y)

if dist_sq <= radius_sq:
nearby_issues.append((issue, math.sqrt(dist_sq)))

Comment on lines +148 to 197
# Sort by distance (closest first)
nearby_issues.sort(key=lambda x: x[1])
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/test_spatial_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_find_nearby_issues_correctness_and_performance():
result_ref = reference_find_nearby_issues(issues, target_lat, target_lon, radius)

# Get results from current/optimized implementation (one run)
result_curr = find_nearby_issues(issues, target_lat, target_lon, radius)
result_curr = find_nearby_issues(issues, target_lat, target_lon, radius, pre_filtered=False)

# Assert number of results match
print(f"Count Current: {len(result_curr)}")
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_find_nearby_issues_correctness_and_performance():

start_curr = time.time()
for _ in range(iterations):
find_nearby_issues(issues, target_lat, target_lon, radius)
find_nearby_issues(issues, target_lat, target_lon, radius, pre_filtered=False)
time_curr = time.time() - start_curr

print(f"\n[Performance] Reference: {time_ref:.4f}s")
Expand Down
Loading