fix: reject geocode results outside node jurisdiction
Nominatim's viewbox is advisory (bounded=0), so ambiguous place names like "Pinebrook" can resolve to locations 30-40km away in the wrong town. Added a post-geocode distance gate: results farther than geocode_max_km (default 25km) from the node are discarded with a warning log rather than written to the incident. Also logs distance on successful geocodes for easier audit. New config setting: geocode_max_km (float, default 25.0)
This commit is contained in:
@@ -29,6 +29,7 @@ class Settings(BaseSettings):
|
||||
embedding_no_location_threshold: float = 0.97 # slow-path: match without location (very high bar)
|
||||
embedding_cross_tg_threshold: float = 0.85 # cross-TG path: same dept + 2+ shared units
|
||||
location_proximity_km: float = 0.5 # radius for location-proximity matching
|
||||
geocode_max_km: float = 25.0 # reject geocode results farther than this from the node
|
||||
incident_auto_resolve_minutes: int = 90 # auto-resolve after N minutes with no new calls
|
||||
recorrelation_scan_minutes: int = 60 # re-examine orphaned calls ended within this window
|
||||
tg_fast_path_idle_minutes: int = 90 # fast path: max minutes since incident last updated
|
||||
|
||||
@@ -10,6 +10,7 @@ Falls back gracefully if the API is unavailable or returns malformed output.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from typing import Optional
|
||||
from app.internal.logger import logger
|
||||
@@ -273,14 +274,25 @@ async def extract_scenes(
|
||||
return processed
|
||||
|
||||
|
||||
def _geo_dist_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
||||
"""Haversine distance in km between two lat/lon points."""
|
||||
R = 6371.0
|
||||
dlat = math.radians(lat2 - lat1)
|
||||
dlon = math.radians(lon2 - lon1)
|
||||
a = math.sin(dlat / 2) ** 2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) ** 2
|
||||
return R * 2 * math.asin(math.sqrt(a))
|
||||
|
||||
|
||||
async def _geocode_location(
|
||||
location_str: str, node_lat: float, node_lon: float
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Geocode a location string using Nominatim, biased toward the node's area.
|
||||
Returns {"lat": float, "lng": float} or None if geocoding fails.
|
||||
Returns {"lat": float, "lng": float} or None if geocoding fails or the
|
||||
result is farther than geocode_max_km from the node (wrong-jurisdiction guard).
|
||||
"""
|
||||
import httpx
|
||||
from app.config import settings
|
||||
|
||||
viewbox = (
|
||||
f"{node_lon - _GEO_DELTA},{node_lat - _GEO_DELTA},"
|
||||
@@ -304,8 +316,17 @@ async def _geocode_location(
|
||||
r.raise_for_status()
|
||||
results = r.json()
|
||||
if results:
|
||||
coords = {"lat": float(results[0]["lat"]), "lng": float(results[0]["lon"])}
|
||||
logger.info(f"Geocoded '{location_str}' → {coords}")
|
||||
lat = float(results[0]["lat"])
|
||||
lng = float(results[0]["lon"])
|
||||
dist_km = _geo_dist_km(node_lat, node_lon, lat, lng)
|
||||
if dist_km > settings.geocode_max_km:
|
||||
logger.warning(
|
||||
f"Geocoding rejected '{location_str}' → ({lat:.4f}, {lng:.4f}) "
|
||||
f"— {dist_km:.1f}km from node exceeds geocode_max_km={settings.geocode_max_km}"
|
||||
)
|
||||
return None
|
||||
coords = {"lat": lat, "lng": lng}
|
||||
logger.info(f"Geocoded '{location_str}' → {coords} ({dist_km:.1f}km from node)")
|
||||
return coords
|
||||
except Exception as e:
|
||||
logger.warning(f"Geocoding failed for '{location_str}': {e}")
|
||||
|
||||
Reference in New Issue
Block a user