Filter hubs to rail/sea and add graph-based nearest
All checks were successful
Build Docker Image / build (push) Successful in 3m9s
All checks were successful
Build Docker Image / build (push) Successful in 3m9s
This commit is contained in:
@@ -81,11 +81,18 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No
|
||||
nodes = list(cursor)
|
||||
|
||||
# Filter by transport type if specified (only for logistics nodes)
|
||||
if transport_type and node_type in (None, 'logistics'):
|
||||
if node_type in (None, 'logistics'):
|
||||
if transport_type:
|
||||
nodes = [
|
||||
n for n in nodes
|
||||
if transport_type in (n.get('transport_types') or [])
|
||||
]
|
||||
else:
|
||||
# Default: only rail/sea hubs
|
||||
nodes = [
|
||||
n for n in nodes
|
||||
if ('rail' in (n.get('transport_types') or [])) or ('sea' in (n.get('transport_types') or []))
|
||||
]
|
||||
|
||||
return nodes
|
||||
|
||||
@@ -151,4 +158,3 @@ def get_clustered_nodes(db, west, south, east, north, zoom, transport_type=None,
|
||||
logger.info("Returning %d clusters/points for zoom=%d res=%d", len(results), zoom, resolution)
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -325,6 +325,7 @@ class Query(graphene.ObjectType):
|
||||
lon=graphene.Float(required=True, description="Longitude"),
|
||||
radius=graphene.Float(default_value=1000, description="Search radius in km"),
|
||||
product_uuid=graphene.String(description="Filter hubs by product availability"),
|
||||
source_uuid=graphene.String(description="Source node UUID for graph-based nearest hubs"),
|
||||
limit=graphene.Int(default_value=12, description="Max results"),
|
||||
description="Find nearest hubs to coordinates (optionally filtered by product)",
|
||||
)
|
||||
@@ -622,6 +623,8 @@ class Query(graphene.ObjectType):
|
||||
aql = """
|
||||
FOR node IN nodes
|
||||
FILTER node.node_type == 'logistics' OR node.node_type == null
|
||||
LET types = node.transport_types != null ? node.transport_types : []
|
||||
FILTER ('rail' IN types) OR ('sea' IN types)
|
||||
FILTER node.country != null
|
||||
COLLECT country = node.country
|
||||
SORT country ASC
|
||||
@@ -1462,10 +1465,97 @@ class Query(graphene.ObjectType):
|
||||
logger.info("No route found from offer %s to hub %s", offer_uuid, hub_uuid)
|
||||
return None
|
||||
|
||||
def resolve_nearest_hubs(self, info, lat, lon, radius=1000, product_uuid=None, limit=12):
|
||||
def resolve_nearest_hubs(self, info, lat, lon, radius=1000, product_uuid=None, source_uuid=None, limit=12):
|
||||
"""Find nearest hubs to coordinates, optionally filtered by product availability."""
|
||||
db = get_db()
|
||||
|
||||
# Graph-based nearest hubs when source_uuid provided
|
||||
if source_uuid:
|
||||
nodes_col = db.collection('nodes')
|
||||
start = nodes_col.get(source_uuid)
|
||||
if not start:
|
||||
logger.warning("Source node %s not found for nearest hubs", source_uuid)
|
||||
return []
|
||||
|
||||
def is_target_hub(doc):
|
||||
if doc.get('_key') == source_uuid:
|
||||
return False
|
||||
if doc.get('node_type') not in ('logistics', None):
|
||||
return False
|
||||
types = doc.get('transport_types') or []
|
||||
return ('rail' in types) or ('sea' in types)
|
||||
|
||||
def fetch_neighbors(node_key):
|
||||
aql = """
|
||||
FOR edge IN edges
|
||||
FILTER edge.transport_type IN ['auto', 'rail', 'offer']
|
||||
FILTER edge._from == @node_id OR edge._to == @node_id
|
||||
LET neighbor_id = edge._from == @node_id ? edge._to : edge._from
|
||||
LET neighbor = DOCUMENT(neighbor_id)
|
||||
FILTER neighbor != null
|
||||
RETURN {
|
||||
neighbor_key: neighbor._key,
|
||||
neighbor_doc: neighbor,
|
||||
transport_type: edge.transport_type,
|
||||
distance_km: edge.distance_km,
|
||||
travel_time_seconds: edge.travel_time_seconds
|
||||
}
|
||||
"""
|
||||
cursor = db.aql.execute(
|
||||
aql,
|
||||
bind_vars={'node_id': f"nodes/{node_key}"},
|
||||
)
|
||||
return list(cursor)
|
||||
|
||||
queue = []
|
||||
counter = 0
|
||||
heapq.heappush(queue, (0, counter, source_uuid))
|
||||
visited = {}
|
||||
node_docs = {source_uuid: start}
|
||||
found = []
|
||||
expansions = 0
|
||||
|
||||
while queue and len(found) < limit and expansions < Query.MAX_EXPANSIONS:
|
||||
cost, _, node_key = heapq.heappop(queue)
|
||||
if node_key in visited and cost > visited[node_key]:
|
||||
continue
|
||||
visited[node_key] = cost
|
||||
|
||||
node_doc = node_docs.get(node_key)
|
||||
if node_doc and is_target_hub(node_doc):
|
||||
found.append(node_doc)
|
||||
if len(found) >= limit:
|
||||
break
|
||||
|
||||
neighbors = fetch_neighbors(node_key)
|
||||
expansions += 1
|
||||
for neighbor in neighbors:
|
||||
neighbor_key = neighbor.get('neighbor_key')
|
||||
if not neighbor_key:
|
||||
continue
|
||||
node_docs[neighbor_key] = neighbor.get('neighbor_doc')
|
||||
step_cost = neighbor.get('travel_time_seconds') or neighbor.get('distance_km') or 0
|
||||
new_cost = cost + step_cost
|
||||
if neighbor_key in visited and new_cost >= visited[neighbor_key]:
|
||||
continue
|
||||
counter += 1
|
||||
heapq.heappush(queue, (new_cost, counter, neighbor_key))
|
||||
|
||||
hubs = []
|
||||
for node in found:
|
||||
hubs.append(NodeType(
|
||||
uuid=node.get('_key'),
|
||||
name=node.get('name'),
|
||||
latitude=node.get('latitude'),
|
||||
longitude=node.get('longitude'),
|
||||
country=node.get('country'),
|
||||
country_code=node.get('country_code'),
|
||||
synced_at=node.get('synced_at'),
|
||||
transport_types=node.get('transport_types') or [],
|
||||
edges=[],
|
||||
))
|
||||
return hubs
|
||||
|
||||
if product_uuid:
|
||||
# Find hubs that have offers for this product within radius
|
||||
aql = """
|
||||
@@ -1479,6 +1569,8 @@ class Query(graphene.ObjectType):
|
||||
FOR hub IN nodes
|
||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||
FILTER hub.product_uuid == 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
|
||||
LET dist_to_hub = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||
FILTER dist_to_hub <= @radius
|
||||
@@ -1496,6 +1588,8 @@ class Query(graphene.ObjectType):
|
||||
FOR hub IN nodes
|
||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||
FILTER hub.product_uuid == 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
|
||||
LET dist = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||
FILTER dist <= @radius
|
||||
@@ -1715,6 +1809,7 @@ class Query(graphene.ObjectType):
|
||||
FILTER node.product_uuid == null
|
||||
LET types = node.transport_types != null ? node.transport_types : []
|
||||
FILTER @transport_type == null OR @transport_type IN types
|
||||
FILTER @transport_type != null OR ('rail' IN types) OR ('sea' IN types)
|
||||
FILTER @country == null OR node.country == @country
|
||||
{bounds_filter}
|
||||
SORT node.name ASC
|
||||
|
||||
Reference in New Issue
Block a user