Fix nearest hubs fallback when source missing
All checks were successful
Build Docker Image / build (push) Successful in 1m32s

This commit is contained in:
Ruslan Bakiev
2026-02-05 20:10:11 +07:00
parent 09324bb25e
commit 443dc7fa5d

View File

@@ -1474,87 +1474,87 @@ class Query(graphene.ObjectType):
nodes_col = db.collection('nodes') nodes_col = db.collection('nodes')
start = nodes_col.get(source_uuid) start = nodes_col.get(source_uuid)
if not start: if not start:
logger.warning("Source node %s not found for nearest hubs", source_uuid) logger.warning("Source node %s not found for nearest hubs, falling back to radius search", source_uuid)
return [] source_uuid = None
else:
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 is_target_hub(doc): def fetch_neighbors(node_key):
if doc.get('_key') == source_uuid: aql = """
return False FOR edge IN edges
if doc.get('node_type') not in ('logistics', None): FILTER edge.transport_type IN ['auto', 'rail', 'offer']
return False FILTER edge._from == @node_id OR edge._to == @node_id
types = doc.get('transport_types') or [] LET neighbor_id = edge._from == @node_id ? edge._to : edge._from
return ('rail' in types) or ('sea' in types) 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)
def fetch_neighbors(node_key): queue = []
aql = """ counter = 0
FOR edge IN edges heapq.heappush(queue, (0, counter, source_uuid))
FILTER edge.transport_type IN ['auto', 'rail', 'offer'] visited = {}
FILTER edge._from == @node_id OR edge._to == @node_id node_docs = {source_uuid: start}
LET neighbor_id = edge._from == @node_id ? edge._to : edge._from found = []
LET neighbor = DOCUMENT(neighbor_id) expansions = 0
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 = [] while queue and len(found) < limit and expansions < Query.MAX_EXPANSIONS:
counter = 0 cost, _, node_key = heapq.heappop(queue)
heapq.heappush(queue, (0, counter, source_uuid)) if node_key in visited and cost > visited[node_key]:
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 continue
node_docs[neighbor_key] = neighbor.get('neighbor_doc') visited[node_key] = cost
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 = [] node_doc = node_docs.get(node_key)
for node in found: if node_doc and is_target_hub(node_doc):
hubs.append(NodeType( found.append(node_doc)
uuid=node.get('_key'), if len(found) >= limit:
name=node.get('name'), break
latitude=node.get('latitude'),
longitude=node.get('longitude'), neighbors = fetch_neighbors(node_key)
country=node.get('country'), expansions += 1
country_code=node.get('country_code'), for neighbor in neighbors:
synced_at=node.get('synced_at'), neighbor_key = neighbor.get('neighbor_key')
transport_types=node.get('transport_types') or [], if not neighbor_key:
edges=[], continue
)) node_docs[neighbor_key] = neighbor.get('neighbor_doc')
return hubs 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: if product_uuid:
# Find hubs that have offers for this product within radius # Find hubs that have offers for this product within radius
@@ -1626,6 +1626,46 @@ class Query(graphene.ObjectType):
db = get_db() db = get_db()
ensure_graph() ensure_graph()
# If hub_uuid + product_uuid provided, use graph search to return only offers with routes.
if hub_uuid and product_uuid:
try:
nodes_col = db.collection('nodes')
expanded_limit = max(limit * 5, limit)
route_options = Query.resolve_offers_by_hub(
Query, info, hub_uuid, product_uuid, expanded_limit
)
offers = []
for option in route_options or []:
if not option.routes:
continue
node = nodes_col.get(option.source_uuid)
if not node:
continue
offers.append(OfferWithRouteType(
uuid=node['_key'],
product_uuid=node.get('product_uuid'),
product_name=node.get('product_name'),
supplier_uuid=node.get('supplier_uuid'),
supplier_name=node.get('supplier_name'),
latitude=node.get('latitude'),
longitude=node.get('longitude'),
country=node.get('country'),
country_code=node.get('country_code'),
price_per_unit=node.get('price_per_unit'),
currency=node.get('currency'),
quantity=node.get('quantity'),
unit=node.get('unit'),
distance_km=option.distance_km,
routes=option.routes,
))
if len(offers) >= limit:
break
logger.info("Found %d offers by graph for hub %s", len(offers), hub_uuid)
return offers
except Exception as e:
logger.error("Error finding offers by hub %s: %s", hub_uuid, e)
return []
aql = """ aql = """
FOR offer IN nodes FOR offer IN nodes
FILTER offer.node_type == 'offer' FILTER offer.node_type == 'offer'