feat(geo): graph-based hubs for product
All checks were successful
Build Docker Image / build (push) Successful in 1m52s
All checks were successful
Build Docker Image / build (push) Successful in 1m52s
This commit is contained in:
@@ -334,6 +334,7 @@ class Query(graphene.ObjectType):
|
|||||||
radius=graphene.Float(default_value=1000, description="Search radius in km"),
|
radius=graphene.Float(default_value=1000, description="Search radius in km"),
|
||||||
product_uuid=graphene.String(description="Filter hubs by product availability"),
|
product_uuid=graphene.String(description="Filter hubs by product availability"),
|
||||||
source_uuid=graphene.String(description="Source node UUID for graph-based nearest hubs"),
|
source_uuid=graphene.String(description="Source node UUID for graph-based nearest hubs"),
|
||||||
|
use_graph=graphene.Boolean(default_value=False, description="Use graph-based reachability for product filter"),
|
||||||
limit=graphene.Int(default_value=12, description="Max results"),
|
limit=graphene.Int(default_value=12, description="Max results"),
|
||||||
description="Find nearest hubs to coordinates (optionally filtered by product)",
|
description="Find nearest hubs to coordinates (optionally filtered by product)",
|
||||||
)
|
)
|
||||||
@@ -1166,6 +1167,58 @@ class Query(graphene.ObjectType):
|
|||||||
logger.error("Error getting hubs for product: %s", e)
|
logger.error("Error getting hubs for product: %s", e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def resolve_hubs_for_product_graph(self, info, product_uuid, limit=500):
|
||||||
|
"""Get hubs that can be reached by graph routes for a product."""
|
||||||
|
db = get_db()
|
||||||
|
ensure_graph()
|
||||||
|
|
||||||
|
aql = """
|
||||||
|
FOR hub IN nodes
|
||||||
|
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||||
|
LET types = hub.transport_types != null ? hub.transport_types : []
|
||||||
|
FILTER ('rail' IN types) OR ('sea' IN types)
|
||||||
|
FILTER hub.latitude != null AND hub.longitude != null
|
||||||
|
RETURN hub
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql)
|
||||||
|
hubs_with_distance = []
|
||||||
|
for hub in cursor:
|
||||||
|
hub_uuid = hub.get('_key')
|
||||||
|
if not hub_uuid:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
routes = self.resolve_offers_by_hub(info, hub_uuid, product_uuid, limit=1)
|
||||||
|
except Exception as route_error:
|
||||||
|
logger.error("Error resolving offers for hub %s: %s", hub_uuid, route_error)
|
||||||
|
continue
|
||||||
|
if not routes:
|
||||||
|
continue
|
||||||
|
distance_km = routes[0].distance_km if routes[0] else None
|
||||||
|
hubs_with_distance.append((distance_km, hub))
|
||||||
|
|
||||||
|
# Sort by graph distance when available
|
||||||
|
hubs_with_distance.sort(key=lambda item: (item[0] is None, item[0] or 0))
|
||||||
|
|
||||||
|
hubs = []
|
||||||
|
for distance_km, hub in hubs_with_distance[:limit]:
|
||||||
|
hubs.append(NodeType(
|
||||||
|
uuid=hub.get('_key'),
|
||||||
|
name=hub.get('name'),
|
||||||
|
latitude=hub.get('latitude'),
|
||||||
|
longitude=hub.get('longitude'),
|
||||||
|
country=hub.get('country'),
|
||||||
|
country_code=hub.get('country_code'),
|
||||||
|
transport_types=hub.get('transport_types'),
|
||||||
|
distance_km=distance_km,
|
||||||
|
))
|
||||||
|
|
||||||
|
logger.info("Found %d graph-reachable hubs for product %s", len(hubs), product_uuid)
|
||||||
|
return hubs
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting graph hubs for product %s: %s", product_uuid, e)
|
||||||
|
return []
|
||||||
|
|
||||||
def resolve_offers_by_hub(self, info, hub_uuid, product_uuid, limit=10):
|
def resolve_offers_by_hub(self, info, hub_uuid, product_uuid, limit=10):
|
||||||
"""
|
"""
|
||||||
Get offers for a product with routes to hub.
|
Get offers for a product with routes to hub.
|
||||||
@@ -1510,7 +1563,7 @@ class Query(graphene.ObjectType):
|
|||||||
logger.info("No route found from offer %s to hub %s", offer_uuid, hub_uuid)
|
logger.info("No route found from offer %s to hub %s", offer_uuid, hub_uuid)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def resolve_nearest_hubs(self, info, lat, lon, radius=1000, product_uuid=None, source_uuid=None, limit=12):
|
def resolve_nearest_hubs(self, info, lat, lon, radius=1000, product_uuid=None, source_uuid=None, use_graph=False, limit=12):
|
||||||
"""Find nearest hubs to coordinates, optionally filtered by product availability."""
|
"""Find nearest hubs to coordinates, optionally filtered by product availability."""
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
@@ -1601,6 +1654,9 @@ class Query(graphene.ObjectType):
|
|||||||
))
|
))
|
||||||
return hubs
|
return hubs
|
||||||
|
|
||||||
|
if product_uuid and use_graph:
|
||||||
|
return self.resolve_hubs_for_product_graph(info, product_uuid, limit=limit)
|
||||||
|
|
||||||
if product_uuid:
|
if product_uuid:
|
||||||
# Find hubs that have offers for this product within radius
|
# Find hubs that have offers for this product within radius
|
||||||
aql = """
|
aql = """
|
||||||
|
|||||||
Reference in New Issue
Block a user