""" Customer coordinate store. Purpose: For each incoming order, look up the customer's phone number. If a previous verified delivery exists (rider coords on file), use those coords instead of the (possibly noisy) input delivery coords. If no record exists, create one so future calls benefit from it. Storage: Pure dict + JSON — no FAISS dependency. phone_meta.json maps phone -> {name, rider_lat, rider_lon}. Coord lookup key is phone number, not coordinates, so vector search was never needed; FAISS has been removed. """ import os import json import logging from math import radians, cos, sin, asin, sqrt from threading import Lock logger = logging.getLogger(__name__) STORE_DIR = os.getenv("CUSTOMER_STORE_DIR", "ml_data/faiss_customer") META_PATH = os.path.join(STORE_DIR, "phone_meta.json") # Within this distance the stored rider coords are considered the real delivery point SIMILARITY_THRESHOLD_KM = float(os.getenv("FAISS_SIMILARITY_KM", "0.5")) def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float: """Great-circle distance in km.""" try: lon1, lat1, lon2, lat2 = map(radians, [float(lon1), float(lat1), float(lon2), float(lat2)]) dlon = lon2 - lon1 dlat = lat2 - lat1 a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 return 2 * asin(min(1.0, sqrt(a))) * 6371 except Exception: return float("inf") class CustomerCoordStore: """ Thread-safe customer delivery coordinate store (dict + JSON backend). Primary flow (called per order before assignment): verified_lat, verified_lon, corrected = store.get_verified_coords( phone, customer_name, input_lat, input_lon ) """ def __init__(self): self._lock = Lock() os.makedirs(STORE_DIR, exist_ok=True) self._meta: dict = {} # phone -> {name, rider_lat, rider_lon} self._load() # ------------------------------------------------------------------ # Persistence # ------------------------------------------------------------------ def _load(self): if os.path.exists(META_PATH): try: with open(META_PATH, "r", encoding="utf-8") as f: self._meta = json.load(f) logger.info(f"[CustomerStore] Loaded {len(self._meta)} customer records.") except Exception as e: logger.warning(f"[CustomerStore] Could not load {META_PATH}: {e} — starting fresh.") self._meta = {} else: self._meta = {} logger.info("[CustomerStore] No existing store found — starting fresh.") def _save(self): try: with open(META_PATH, "w", encoding="utf-8") as f: json.dump(self._meta, f, indent=2) except Exception as e: logger.warning(f"[CustomerStore] Save failed: {e}") # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def get_verified_coords( self, phone: str, customer_name: str, input_lat: float, input_lon: float, ) -> tuple: """ Verify / correct delivery coordinates using stored history. Returns: (lat, lon, was_corrected) was_corrected=True → coords replaced with verified stored coords was_corrected=False → input coords used (new customer or new location) """ if not phone or not input_lat or not input_lon: return input_lat, input_lon, False phone = str(phone).strip() with self._lock: record = self._meta.get(phone) if record: stored_lat = record["rider_lat"] stored_lon = record["rider_lon"] dist_km = _haversine(stored_lat, stored_lon, input_lat, input_lon) if dist_km <= SIMILARITY_THRESHOLD_KM: # Stored rider coords match input → they represent the real door logger.info( f"[CustomerStore] {phone}: verified coords used " f"(stored↔input dist={dist_km:.3f}km, within {SIMILARITY_THRESHOLD_KM}km)" ) return stored_lat, stored_lon, True else: # Customer location changed significantly, trust new input logger.info( f"[CustomerStore] {phone}: location changed " f"(dist={dist_km:.3f}km > {SIMILARITY_THRESHOLD_KM}km), using input coords" ) return input_lat, input_lon, False else: # New customer — create record with input coords self._create_record(phone, customer_name, input_lat, input_lon) logger.info(f"[CustomerStore] {phone}: new record created ({input_lat}, {input_lon})") return input_lat, input_lon, False def update_rider_coords(self, phone: str, rider_lat: float, rider_lon: float): """ Called when a delivery is confirmed to update with actual rider GPS. This is what makes stored coords progressively more accurate. """ phone = str(phone).strip() with self._lock: if phone in self._meta: record = self._meta[phone] record["rider_lat"] = rider_lat record["rider_lon"] = rider_lon self._save() logger.info(f"[CustomerStore] {phone}: rider coords updated ({rider_lat}, {rider_lon})") def record_count(self) -> int: return len(self._meta) # ------------------------------------------------------------------ # Internal helpers # ------------------------------------------------------------------ def _create_record(self, phone: str, name: str, lat: float, lon: float): self._meta[phone] = { "name": name, "rider_lat": lat, "rider_lon": lon, } self._save() # Singleton — keep old name for backward compatibility with any imports _store_instance: "CustomerCoordStore | None" = None _store_lock = Lock() def get_faiss_store() -> CustomerCoordStore: """Returns the singleton CustomerCoordStore (backward-compat name).""" global _store_instance if _store_instance is None: with _store_lock: if _store_instance is None: _store_instance = CustomerCoordStore() return _store_instance # Also expose under new name get_customer_store = get_faiss_store