Replace graph traversal queries with DISTANCE() queries
All checks were successful
Build Docker Image / build (push) Successful in 1m53s
All checks were successful
Build Docker Image / build (push) Successful in 1m53s
- Add new resolvers: products, offersByProduct, hubsNearOffer, suppliers, productsBySupplier, offersBySupplierProduct, productsNearHub, offersToHub, deliveryToHub - Remove broken queries that caused OOM on 234k edges - Use DISTANCE() for geographic proximity instead of graph traversal
This commit is contained in:
@@ -91,6 +91,33 @@ class ClusterPointType(graphene.ObjectType):
|
|||||||
name = graphene.String(description="Node name (only for single points)")
|
name = graphene.String(description="Node name (only for single points)")
|
||||||
|
|
||||||
|
|
||||||
|
class ProductType(graphene.ObjectType):
|
||||||
|
"""Unique product from offers."""
|
||||||
|
uuid = graphene.String()
|
||||||
|
name = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class SupplierType(graphene.ObjectType):
|
||||||
|
"""Unique supplier from offers."""
|
||||||
|
uuid = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class OfferNodeType(graphene.ObjectType):
|
||||||
|
"""Offer node with location and product info."""
|
||||||
|
uuid = graphene.String()
|
||||||
|
product_uuid = graphene.String()
|
||||||
|
product_name = graphene.String()
|
||||||
|
supplier_uuid = graphene.String()
|
||||||
|
latitude = graphene.Float()
|
||||||
|
longitude = graphene.Float()
|
||||||
|
country = graphene.String()
|
||||||
|
country_code = graphene.String()
|
||||||
|
price_per_unit = graphene.String()
|
||||||
|
currency = graphene.String()
|
||||||
|
quantity = graphene.String()
|
||||||
|
unit = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
"""Root query."""
|
"""Root query."""
|
||||||
MAX_EXPANSIONS = 20000
|
MAX_EXPANSIONS = 20000
|
||||||
@@ -183,32 +210,63 @@ class Query(graphene.ObjectType):
|
|||||||
description="Get clustered nodes for map display (server-side clustering)",
|
description="Get clustered nodes for map display (server-side clustering)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Business-oriented queries for catalog navigation
|
# Catalog navigation queries
|
||||||
find_products_for_hub = graphene.List(
|
products = graphene.List(
|
||||||
graphene.String,
|
ProductType,
|
||||||
hub_uuid=graphene.String(required=True),
|
description="Get unique products from all offers",
|
||||||
description="Find unique product UUIDs that can be delivered to this hub",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
find_hubs_for_product = graphene.List(
|
offers_by_product = graphene.List(
|
||||||
NodeType,
|
OfferNodeType,
|
||||||
product_uuid=graphene.String(required=True),
|
product_uuid=graphene.String(required=True),
|
||||||
description="Find logistics hubs where this product can be delivered",
|
description="Get all offers for a product",
|
||||||
)
|
)
|
||||||
|
|
||||||
find_supplier_product_hubs = graphene.List(
|
hubs_near_offer = graphene.List(
|
||||||
NodeType,
|
NodeType,
|
||||||
|
offer_uuid=graphene.String(required=True),
|
||||||
|
limit=graphene.Int(default_value=12),
|
||||||
|
description="Get nearest hubs to an offer location",
|
||||||
|
)
|
||||||
|
|
||||||
|
suppliers = graphene.List(
|
||||||
|
SupplierType,
|
||||||
|
description="Get unique suppliers from all offers",
|
||||||
|
)
|
||||||
|
|
||||||
|
products_by_supplier = graphene.List(
|
||||||
|
ProductType,
|
||||||
|
supplier_uuid=graphene.String(required=True),
|
||||||
|
description="Get products offered by a supplier",
|
||||||
|
)
|
||||||
|
|
||||||
|
offers_by_supplier_product = graphene.List(
|
||||||
|
OfferNodeType,
|
||||||
supplier_uuid=graphene.String(required=True),
|
supplier_uuid=graphene.String(required=True),
|
||||||
product_uuid=graphene.String(required=True),
|
product_uuid=graphene.String(required=True),
|
||||||
description="Find hubs where this supplier can deliver this product",
|
description="Get offers from a supplier for a specific product",
|
||||||
)
|
)
|
||||||
|
|
||||||
find_offers_for_hub_by_product = graphene.List(
|
products_near_hub = graphene.List(
|
||||||
|
ProductType,
|
||||||
|
hub_uuid=graphene.String(required=True),
|
||||||
|
radius_km=graphene.Float(default_value=500),
|
||||||
|
description="Get products available near a hub",
|
||||||
|
)
|
||||||
|
|
||||||
|
offers_to_hub = graphene.List(
|
||||||
ProductRouteOptionType,
|
ProductRouteOptionType,
|
||||||
hub_uuid=graphene.String(required=True),
|
hub_uuid=graphene.String(required=True),
|
||||||
product_uuid=graphene.String(required=True),
|
product_uuid=graphene.String(required=True),
|
||||||
limit_sources=graphene.Int(default_value=10),
|
limit=graphene.Int(default_value=10),
|
||||||
description="Find product offers that can be delivered to hub (with routes)",
|
description="Get offers for a product with routes to hub",
|
||||||
|
)
|
||||||
|
|
||||||
|
delivery_to_hub = graphene.Field(
|
||||||
|
ProductRouteOptionType,
|
||||||
|
offer_uuid=graphene.String(required=True),
|
||||||
|
hub_uuid=graphene.String(required=True),
|
||||||
|
description="Get delivery route from offer to hub",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -796,180 +854,258 @@ class Query(graphene.ObjectType):
|
|||||||
clusters = get_clustered_nodes(db, west, south, east, north, zoom, transport_type)
|
clusters = get_clustered_nodes(db, west, south, east, north, zoom, transport_type)
|
||||||
return [ClusterPointType(**c) for c in clusters]
|
return [ClusterPointType(**c) for c in clusters]
|
||||||
|
|
||||||
def resolve_find_products_for_hub(self, info, hub_uuid):
|
def resolve_products(self, info):
|
||||||
"""
|
"""Get unique products from all offers."""
|
||||||
Find unique product UUIDs that can be delivered to this hub.
|
|
||||||
Uses reverse traversal from hub to find reachable offer nodes.
|
|
||||||
"""
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
ensure_graph()
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'offer'
|
||||||
|
FILTER node.product_uuid != null
|
||||||
|
COLLECT product_uuid = node.product_uuid INTO offers
|
||||||
|
LET first_offer = FIRST(offers).node
|
||||||
|
RETURN {
|
||||||
|
uuid: product_uuid,
|
||||||
|
name: first_offer.product_name
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql)
|
||||||
|
products = [ProductType(uuid=p['uuid'], name=p.get('name')) for p in cursor]
|
||||||
|
logger.info("Found %d unique products", len(products))
|
||||||
|
return products
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting products: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
# Check hub exists
|
def resolve_offers_by_product(self, info, product_uuid):
|
||||||
|
"""Get all offers for a product."""
|
||||||
|
db = get_db()
|
||||||
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'offer'
|
||||||
|
FILTER node.product_uuid == @product_uuid
|
||||||
|
RETURN node
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql, bind_vars={'product_uuid': product_uuid})
|
||||||
|
offers = []
|
||||||
|
for node in cursor:
|
||||||
|
offers.append(OfferNodeType(
|
||||||
|
uuid=node['_key'],
|
||||||
|
product_uuid=node.get('product_uuid'),
|
||||||
|
product_name=node.get('product_name'),
|
||||||
|
supplier_uuid=node.get('supplier_uuid'),
|
||||||
|
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'),
|
||||||
|
))
|
||||||
|
logger.info("Found %d offers for product %s", len(offers), product_uuid)
|
||||||
|
return offers
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting offers by product: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_hubs_near_offer(self, info, offer_uuid, limit=12):
|
||||||
|
"""Get nearest hubs to an offer location."""
|
||||||
|
db = get_db()
|
||||||
|
nodes_col = db.collection('nodes')
|
||||||
|
offer = nodes_col.get(offer_uuid)
|
||||||
|
if not offer:
|
||||||
|
logger.info("Offer %s not found", offer_uuid)
|
||||||
|
return []
|
||||||
|
|
||||||
|
lat = offer.get('latitude')
|
||||||
|
lon = offer.get('longitude')
|
||||||
|
if lat is None or lon is None:
|
||||||
|
logger.info("Offer %s has no coordinates", offer_uuid)
|
||||||
|
return []
|
||||||
|
|
||||||
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'logistics' OR node.node_type == null
|
||||||
|
FILTER node.product_uuid == null
|
||||||
|
FILTER node.latitude != null AND node.longitude != null
|
||||||
|
LET dist = DISTANCE(node.latitude, node.longitude, @lat, @lon) / 1000
|
||||||
|
SORT dist ASC
|
||||||
|
LIMIT @limit
|
||||||
|
RETURN MERGE(node, {distance_km: dist})
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql, bind_vars={'lat': lat, 'lon': lon, 'limit': limit})
|
||||||
|
hubs = []
|
||||||
|
for node in cursor:
|
||||||
|
hubs.append(NodeType(
|
||||||
|
uuid=node['_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=[],
|
||||||
|
))
|
||||||
|
logger.info("Found %d hubs near offer %s", len(hubs), offer_uuid)
|
||||||
|
return hubs
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting hubs near offer: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_suppliers(self, info):
|
||||||
|
"""Get unique suppliers from all offers."""
|
||||||
|
db = get_db()
|
||||||
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'offer'
|
||||||
|
FILTER node.supplier_uuid != null
|
||||||
|
COLLECT supplier_uuid = node.supplier_uuid
|
||||||
|
RETURN { uuid: supplier_uuid }
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql)
|
||||||
|
suppliers = [SupplierType(uuid=s['uuid']) for s in cursor]
|
||||||
|
logger.info("Found %d unique suppliers", len(suppliers))
|
||||||
|
return suppliers
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting suppliers: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_products_by_supplier(self, info, supplier_uuid):
|
||||||
|
"""Get products offered by a supplier."""
|
||||||
|
db = get_db()
|
||||||
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'offer'
|
||||||
|
FILTER node.supplier_uuid == @supplier_uuid
|
||||||
|
FILTER node.product_uuid != null
|
||||||
|
COLLECT product_uuid = node.product_uuid INTO offers
|
||||||
|
LET first_offer = FIRST(offers).node
|
||||||
|
RETURN {
|
||||||
|
uuid: product_uuid,
|
||||||
|
name: first_offer.product_name
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql, bind_vars={'supplier_uuid': supplier_uuid})
|
||||||
|
products = [ProductType(uuid=p['uuid'], name=p.get('name')) for p in cursor]
|
||||||
|
logger.info("Found %d products for supplier %s", len(products), supplier_uuid)
|
||||||
|
return products
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting products by supplier: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_offers_by_supplier_product(self, info, supplier_uuid, product_uuid):
|
||||||
|
"""Get offers from a supplier for a specific product."""
|
||||||
|
db = get_db()
|
||||||
|
aql = """
|
||||||
|
FOR node IN nodes
|
||||||
|
FILTER node.node_type == 'offer'
|
||||||
|
FILTER node.supplier_uuid == @supplier_uuid
|
||||||
|
FILTER node.product_uuid == @product_uuid
|
||||||
|
RETURN node
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cursor = db.aql.execute(aql, bind_vars={
|
||||||
|
'supplier_uuid': supplier_uuid,
|
||||||
|
'product_uuid': product_uuid
|
||||||
|
})
|
||||||
|
offers = []
|
||||||
|
for node in cursor:
|
||||||
|
offers.append(OfferNodeType(
|
||||||
|
uuid=node['_key'],
|
||||||
|
product_uuid=node.get('product_uuid'),
|
||||||
|
product_name=node.get('product_name'),
|
||||||
|
supplier_uuid=node.get('supplier_uuid'),
|
||||||
|
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'),
|
||||||
|
))
|
||||||
|
logger.info("Found %d offers for supplier %s product %s", len(offers), supplier_uuid, product_uuid)
|
||||||
|
return offers
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error getting offers by supplier product: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_products_near_hub(self, info, hub_uuid, radius_km=500):
|
||||||
|
"""Get products available near a hub (within radius)."""
|
||||||
|
db = get_db()
|
||||||
nodes_col = db.collection('nodes')
|
nodes_col = db.collection('nodes')
|
||||||
hub = nodes_col.get(hub_uuid)
|
hub = nodes_col.get(hub_uuid)
|
||||||
if not hub:
|
if not hub:
|
||||||
logger.info("Hub %s not found", hub_uuid)
|
logger.info("Hub %s not found", hub_uuid)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Find all offer nodes reachable from hub via graph traversal
|
lat = hub.get('latitude')
|
||||||
# Offer nodes have product_uuid field
|
lon = hub.get('longitude')
|
||||||
|
if lat is None or lon is None:
|
||||||
|
logger.info("Hub %s has no coordinates", hub_uuid)
|
||||||
|
return []
|
||||||
|
|
||||||
aql = """
|
aql = """
|
||||||
FOR v, e, p IN 1..10 ANY @hub_id GRAPH 'optovia_graph'
|
|
||||||
FILTER v.product_uuid != null
|
|
||||||
COLLECT product_uuid = v.product_uuid
|
|
||||||
RETURN product_uuid
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cursor = db.aql.execute(
|
|
||||||
aql,
|
|
||||||
bind_vars={'hub_id': f'nodes/{hub_uuid}'},
|
|
||||||
)
|
|
||||||
product_uuids = list(cursor)
|
|
||||||
logger.info("Found %d products for hub %s", len(product_uuids), hub_uuid)
|
|
||||||
return product_uuids
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Error finding products for hub: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def resolve_find_hubs_for_product(self, info, product_uuid):
|
|
||||||
"""
|
|
||||||
Find logistics hubs where this product can be delivered.
|
|
||||||
Finds offer nodes with this product, then finds reachable hubs.
|
|
||||||
"""
|
|
||||||
db = get_db()
|
|
||||||
ensure_graph()
|
|
||||||
|
|
||||||
# Find all offer nodes with this product_uuid
|
|
||||||
aql_offers = """
|
|
||||||
FOR node IN nodes
|
FOR node IN nodes
|
||||||
FILTER node.product_uuid == @product_uuid
|
FILTER node.node_type == 'offer'
|
||||||
RETURN node._key
|
FILTER node.product_uuid != null
|
||||||
|
FILTER node.latitude != null AND node.longitude != null
|
||||||
|
LET dist = DISTANCE(node.latitude, node.longitude, @lat, @lon) / 1000
|
||||||
|
FILTER dist <= @radius_km
|
||||||
|
COLLECT product_uuid = node.product_uuid INTO offers
|
||||||
|
LET first_offer = FIRST(offers).node
|
||||||
|
RETURN {
|
||||||
|
uuid: product_uuid,
|
||||||
|
name: first_offer.product_name
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cursor = db.aql.execute(aql_offers, bind_vars={'product_uuid': product_uuid})
|
cursor = db.aql.execute(aql, bind_vars={'lat': lat, 'lon': lon, 'radius_km': radius_km})
|
||||||
offer_keys = list(cursor)
|
products = [ProductType(uuid=p['uuid'], name=p.get('name')) for p in cursor]
|
||||||
|
logger.info("Found %d products near hub %s", len(products), hub_uuid)
|
||||||
|
return products
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error finding offers for product: %s", e)
|
logger.error("Error getting products near hub: %s", e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not offer_keys:
|
def resolve_offers_to_hub(self, info, hub_uuid, product_uuid, limit=10):
|
||||||
logger.info("No offers found for product %s", product_uuid)
|
"""Get offers for a product with routes to hub."""
|
||||||
return []
|
|
||||||
|
|
||||||
# Find hubs reachable from these offer nodes
|
|
||||||
aql_hubs = """
|
|
||||||
LET offer_ids = @offer_ids
|
|
||||||
FOR offer_id IN offer_ids
|
|
||||||
FOR v, e, p IN 1..10 ANY CONCAT('nodes/', offer_id) GRAPH 'optovia_graph'
|
|
||||||
FILTER (v.node_type == 'logistics' OR v.node_type == null)
|
|
||||||
FILTER v.product_uuid == null
|
|
||||||
COLLECT hub_key = v._key INTO hubs
|
|
||||||
LET hub = FIRST(hubs).v
|
|
||||||
RETURN hub
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cursor = db.aql.execute(aql_hubs, bind_vars={'offer_ids': offer_keys})
|
|
||||||
hubs = list(cursor)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for hub in hubs:
|
|
||||||
if hub:
|
|
||||||
result.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'),
|
|
||||||
synced_at=hub.get('synced_at'),
|
|
||||||
transport_types=hub.get('transport_types') or [],
|
|
||||||
edges=[],
|
|
||||||
))
|
|
||||||
|
|
||||||
logger.info("Found %d hubs for product %s", len(result), product_uuid)
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Error finding hubs for product: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def resolve_find_supplier_product_hubs(self, info, supplier_uuid, product_uuid):
|
|
||||||
"""
|
|
||||||
Find hubs where this supplier can deliver this product.
|
|
||||||
Finds offer nodes matching both supplier and product, then finds reachable hubs.
|
|
||||||
"""
|
|
||||||
db = get_db()
|
|
||||||
ensure_graph()
|
|
||||||
|
|
||||||
# Find offer nodes with this supplier_uuid AND product_uuid
|
|
||||||
aql_offers = """
|
|
||||||
FOR node IN nodes
|
|
||||||
FILTER node.product_uuid == @product_uuid
|
|
||||||
FILTER node.supplier_uuid == @supplier_uuid
|
|
||||||
RETURN node._key
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cursor = db.aql.execute(
|
|
||||||
aql_offers,
|
|
||||||
bind_vars={'product_uuid': product_uuid, 'supplier_uuid': supplier_uuid}
|
|
||||||
)
|
|
||||||
offer_keys = list(cursor)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Error finding supplier offers: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
if not offer_keys:
|
|
||||||
logger.info("No offers found for supplier %s, product %s", supplier_uuid, product_uuid)
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Find hubs reachable from these offer nodes
|
|
||||||
aql_hubs = """
|
|
||||||
LET offer_ids = @offer_ids
|
|
||||||
FOR offer_id IN offer_ids
|
|
||||||
FOR v, e, p IN 1..10 ANY CONCAT('nodes/', offer_id) GRAPH 'optovia_graph'
|
|
||||||
FILTER (v.node_type == 'logistics' OR v.node_type == null)
|
|
||||||
FILTER v.product_uuid == null
|
|
||||||
COLLECT hub_key = v._key INTO hubs
|
|
||||||
LET hub = FIRST(hubs).v
|
|
||||||
RETURN hub
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cursor = db.aql.execute(aql_hubs, bind_vars={'offer_ids': offer_keys})
|
|
||||||
hubs = list(cursor)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for hub in hubs:
|
|
||||||
if hub:
|
|
||||||
result.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'),
|
|
||||||
synced_at=hub.get('synced_at'),
|
|
||||||
transport_types=hub.get('transport_types') or [],
|
|
||||||
edges=[],
|
|
||||||
))
|
|
||||||
|
|
||||||
logger.info("Found %d hubs for supplier %s product %s", len(result), supplier_uuid, product_uuid)
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Error finding supplier product hubs: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def resolve_find_offers_for_hub_by_product(self, info, hub_uuid, product_uuid, limit_sources=10):
|
|
||||||
"""
|
|
||||||
Find product offers that can be delivered to hub (with routes).
|
|
||||||
Same as find_product_routes but with business-oriented naming.
|
|
||||||
"""
|
|
||||||
return self.resolve_find_product_routes(
|
return self.resolve_find_product_routes(
|
||||||
info,
|
info,
|
||||||
product_uuid=product_uuid,
|
product_uuid=product_uuid,
|
||||||
to_uuid=hub_uuid,
|
to_uuid=hub_uuid,
|
||||||
limit_sources=limit_sources,
|
limit_sources=limit,
|
||||||
limit_routes=1,
|
limit_routes=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def resolve_delivery_to_hub(self, info, offer_uuid, hub_uuid):
|
||||||
|
"""Get delivery route from offer to hub."""
|
||||||
|
db = get_db()
|
||||||
|
nodes_col = db.collection('nodes')
|
||||||
|
offer = nodes_col.get(offer_uuid)
|
||||||
|
if not offer:
|
||||||
|
logger.info("Offer %s not found", offer_uuid)
|
||||||
|
return None
|
||||||
|
|
||||||
|
routes = self._build_routes(db, offer_uuid, hub_uuid, limit=1)
|
||||||
|
if not routes:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return ProductRouteOptionType(
|
||||||
|
source_uuid=offer_uuid,
|
||||||
|
source_name=offer.get('name'),
|
||||||
|
source_lat=offer.get('latitude'),
|
||||||
|
source_lon=offer.get('longitude'),
|
||||||
|
distance_km=routes[0].total_distance_km if routes else None,
|
||||||
|
routes=routes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user