Add POI feature and imporve the overall backend implementation

This commit is contained in:
Urtzi Alfaro
2025-11-12 15:34:10 +01:00
parent e8096cd979
commit 5783c7ed05
173 changed files with 16862 additions and 9078 deletions

View File

@@ -0,0 +1,208 @@
"""
POI Cache Service
Caches POI detection results to avoid hammering Overpass API.
POI data doesn't change frequently, so aggressive caching is appropriate.
"""
from typing import Optional, Dict, Any
import json
import structlog
from datetime import timedelta
from app.core.poi_config import (
POI_CACHE_TTL_DAYS,
POI_COORDINATE_PRECISION
)
logger = structlog.get_logger()
class POICacheService:
"""
Redis-based cache for POI detection results.
Caches results by rounded coordinates to allow reuse for nearby locations.
Reduces load on Overpass API and improves onboarding performance.
"""
def __init__(self, redis_client):
"""
Initialize cache service.
Args:
redis_client: Redis client instance
"""
self.redis = redis_client
self.cache_ttl_days = POI_CACHE_TTL_DAYS
self.coordinate_precision = POI_COORDINATE_PRECISION
def _generate_cache_key(self, latitude: float, longitude: float) -> str:
"""
Generate cache key from coordinates.
Rounds coordinates to specified precision (default 4 decimals ≈ 10m).
This allows cache reuse for bakeries in very close proximity.
Args:
latitude: Bakery latitude
longitude: Bakery longitude
Returns:
Redis cache key
"""
lat_rounded = round(latitude, self.coordinate_precision)
lon_rounded = round(longitude, self.coordinate_precision)
return f"poi_cache:{lat_rounded}:{lon_rounded}"
async def get_cached_pois(
self,
latitude: float,
longitude: float
) -> Optional[Dict[str, Any]]:
"""
Get cached POI results for location.
Args:
latitude: Bakery latitude
longitude: Bakery longitude
Returns:
Cached POI detection results or None if not cached
"""
cache_key = self._generate_cache_key(latitude, longitude)
try:
cached_data = await self.redis.get(cache_key)
if cached_data:
logger.info(
"POI cache hit",
cache_key=cache_key,
location=(latitude, longitude)
)
return json.loads(cached_data)
else:
logger.debug(
"POI cache miss",
cache_key=cache_key,
location=(latitude, longitude)
)
return None
except Exception as e:
logger.warning(
"Failed to retrieve POI cache",
error=str(e),
cache_key=cache_key
)
return None
async def cache_poi_results(
self,
latitude: float,
longitude: float,
poi_data: Dict[str, Any]
) -> bool:
"""
Cache POI detection results.
Args:
latitude: Bakery latitude
longitude: Bakery longitude
poi_data: Complete POI detection results
Returns:
True if cached successfully, False otherwise
"""
cache_key = self._generate_cache_key(latitude, longitude)
ttl_seconds = self.cache_ttl_days * 24 * 60 * 60
try:
await self.redis.setex(
cache_key,
ttl_seconds,
json.dumps(poi_data)
)
logger.info(
"POI results cached",
cache_key=cache_key,
ttl_days=self.cache_ttl_days,
location=(latitude, longitude)
)
return True
except Exception as e:
logger.error(
"Failed to cache POI results",
error=str(e),
cache_key=cache_key
)
return False
async def invalidate_cache(
self,
latitude: float,
longitude: float
) -> bool:
"""
Invalidate cached POI results for location.
Useful for manual refresh or data corrections.
Args:
latitude: Bakery latitude
longitude: Bakery longitude
Returns:
True if invalidated successfully
"""
cache_key = self._generate_cache_key(latitude, longitude)
try:
deleted = await self.redis.delete(cache_key)
if deleted:
logger.info(
"POI cache invalidated",
cache_key=cache_key,
location=(latitude, longitude)
)
return bool(deleted)
except Exception as e:
logger.error(
"Failed to invalidate POI cache",
error=str(e),
cache_key=cache_key
)
return False
async def get_cache_stats(self) -> Dict[str, Any]:
"""
Get cache statistics.
Returns:
Dictionary with cache stats (key count, memory usage, etc.)
"""
try:
# Count POI cache keys
pattern = "poi_cache:*"
cursor = 0
key_count = 0
while True:
cursor, keys = await self.redis.scan(
cursor=cursor,
match=pattern,
count=100
)
key_count += len(keys)
if cursor == 0:
break
return {
"total_cached_locations": key_count,
"cache_ttl_days": self.cache_ttl_days,
"coordinate_precision": self.coordinate_precision
}
except Exception as e:
logger.error("Failed to get cache stats", error=str(e))
return {
"error": str(e)
}