Supply Chain Optimization
Optimize supply chain operations with Geode for route planning, inventory tracking, and real-time logistics analytics.
Overview
Supply chain networks are inherently graph-structured with warehouses, distribution centers, suppliers, and customers connected by transportation routes. Geode provides efficient path finding, network analysis, and real-time tracking capabilities.
Key Capabilities
- Route Optimization: Find shortest/cheapest paths for deliveries
- Inventory Tracking: Monitor stock levels across network
- Supplier Network Analysis: Identify dependencies and risks
- Real-Time Logistics: Track shipments and update ETAs
- Bottleneck Detection: Find congestion points in network
- Multi-Modal Transport: Optimize across truck/rail/ship/air
Use Case Scenarios
1. Last-Mile Delivery Optimization
Challenge: Minimize delivery time and cost for final leg to customers
Solution: Model delivery network with:
- Warehouses and distribution centers
- Customer locations
- Routes with distance, time, traffic
- Vehicle capacity constraints
Benefits:
- Reduced delivery times (15-30%)
- Lower fuel costs (10-20%)
- Improved customer satisfaction
- Dynamic route adjustments
2. Multi-Tier Supplier Network
Challenge: Manage complex supplier dependencies and risks
Solution: Build supplier graph with:
- Suppliers at multiple tiers
- Component dependencies
- Lead times and capacities
- Geographic risks
Benefits:
- Identify single points of failure
- Assess cascade impact
- Optimize supplier mix
- Plan contingencies
3. Warehouse Network Optimization
Challenge: Determine optimal warehouse locations and inventory levels
Solution: Model warehouse network:
- Warehouse locations and capacities
- Customer demand patterns
- Transportation costs
- Service level requirements
Benefits:
- Minimize total logistics costs
- Reduce inventory holding
- Improve service levels
- Optimize warehouse placement
Data Model
Network Entities
CREATE GRAPH SupplyChainNetwork;
USE SupplyChainNetwork;
-- Facilities
CREATE
(:Warehouse {
id: 'wh_1',
name: 'Seattle Distribution Center',
location: point({latitude: 47.6062, longitude: -122.3321}),
capacity: 100000,
current_inventory: 75000
}),
(:Warehouse {
id: 'wh_2',
name: 'Portland Hub',
location: point({latitude: 45.5152, longitude: -122.6784}),
capacity: 50000,
current_inventory: 30000
}),
(:Supplier {
id: 'sup_1',
name: 'Component Manufacturer Inc',
location: point({latitude: 37.7749, longitude: -122.4194}),
lead_time_days: 7,
reliability: 0.95
}),
(:Customer {
id: 'cust_1',
name: 'ABC Retail',
location: point({latitude: 47.6101, longitude: -122.2015}),
demand_weekly: 500
});
Routes and Costs
-- Transportation routes
MATCH (wh1:Warehouse {id: 'wh_1'}), (wh2:Warehouse {id: 'wh_2'})
CREATE (wh1)-[:ROUTE {
distance_km: 280,
time_hours: 3.5,
cost_per_unit: 0.50,
mode: 'truck',
capacity: 5000
}]->(wh2);
MATCH (wh:Warehouse {id: 'wh_1'}), (cust:Customer {id: 'cust_1'})
CREATE (wh)-[:ROUTE {
distance_km: 15,
time_hours: 0.5,
cost_per_unit: 0.25,
mode: 'van',
capacity: 100
}]->(cust);
-- Supplier relationships
MATCH (sup:Supplier {id: 'sup_1'}), (wh:Warehouse {id: 'wh_1'})
CREATE (sup)-[:SUPPLIES {
lead_time_days: 7,
min_order_quantity: 1000,
cost_per_unit: 5.00,
reliability: 0.95
}]->(wh);
Inventory and Products
-- Products
CREATE
(:Product {
id: 'prod_1',
sku: 'WIDGET-001',
name: 'Standard Widget',
unit_cost: 5.00,
unit_price: 15.00,
weight_kg: 0.5
}),
(:Product {
id: 'prod_2',
sku: 'GADGET-001',
name: 'Premium Gadget',
unit_cost: 20.00,
unit_price: 60.00,
weight_kg: 2.0
});
-- Inventory at locations
MATCH (wh:Warehouse {id: 'wh_1'}), (prod:Product {id: 'prod_1'})
CREATE (wh)-[:STOCKS {
quantity: 5000,
reorder_point: 1000,
reorder_quantity: 2000,
last_updated: timestamp()
}]->(prod);
Implementation Guide
Step 1: Route Optimization
Shortest Path (Distance)
-- Find shortest route from warehouse to customer
MATCH (wh:Warehouse {id: 'wh_1'}), (cust:Customer {id: 'cust_1'}),
path = shortestPath((wh)-[:ROUTE*]->(cust))
RETURN [n IN nodes(path) | n.name] AS route,
reduce(dist = 0, r IN relationships(path) | dist + r.distance_km) AS total_distance,
reduce(time = 0, r IN relationships(path) | time + r.time_hours) AS total_time;
Cheapest Path (Cost)
-- Find cheapest route using Dijkstra
CALL graph.dijkstra('SupplyChainNetwork', {
start_node: {id: 'wh_1'},
end_node: {id: 'cust_1'},
relationship_type: 'ROUTE',
weight_property: 'cost_per_unit',
minimize: true
})
YIELD path, cost
RETURN [n IN nodes(path) | n.name] AS route,
cost AS total_cost,
length(path) AS hops;
Multi-Constraint Optimization
-- Find route optimizing multiple factors
MATCH (wh:Warehouse {id: 'wh_1'}), (cust:Customer {id: 'cust_1'}),
path = (wh)-[:ROUTE*1..5]->(cust)
WITH path,
reduce(dist = 0, r IN relationships(path) | dist + r.distance_km) AS distance,
reduce(cost = 0, r IN relationships(path) | cost + r.cost_per_unit) AS cost,
reduce(time = 0, r IN relationships(path) | time + r.time_hours) AS time
WHERE time <= 24 -- Delivery within 24 hours
RETURN [n IN nodes(path) | n.name] AS route,
distance,
cost,
time,
(cost * 0.5 + time * 0.3 + distance * 0.2) AS composite_score
ORDER BY composite_score ASC
LIMIT 1;
Step 2: Inventory Management
Stock Level Monitoring
-- Find low inventory items
MATCH (loc:Warehouse)-[stock:STOCKS]->(prod:Product)
WHERE stock.quantity < stock.reorder_point
RETURN loc.name AS warehouse,
prod.sku,
prod.name,
stock.quantity AS current_stock,
stock.reorder_point,
stock.reorder_quantity AS suggested_order
ORDER BY (stock.reorder_point - stock.quantity) DESC;
Inventory Optimization
-- Calculate optimal stock distribution
MATCH (wh:Warehouse)-[stock:STOCKS]->(prod:Product)
OPTIONAL MATCH (wh)-[:ROUTE]->(cust:Customer)
WITH wh, prod, stock.quantity AS current_stock,
sum(cust.demand_weekly) AS weekly_demand
RETURN wh.name,
prod.sku,
current_stock,
weekly_demand,
CASE
WHEN current_stock < weekly_demand * 2 THEN 'CRITICAL'
WHEN current_stock < weekly_demand * 4 THEN 'LOW'
ELSE 'ADEQUATE'
END AS stock_status,
(weekly_demand * 4 - current_stock) AS recommended_reorder;
Step 3: Supplier Network Analysis
Identify Critical Suppliers
-- Find suppliers with high impact (many downstream customers)
MATCH (sup:Supplier)-[:SUPPLIES*1..3]->(node)
WITH sup, count(DISTINCT node) AS reach
ORDER BY reach DESC
RETURN sup.name,
sup.reliability,
reach AS network_reach,
CASE
WHEN sup.reliability < 0.8 THEN 'HIGH_RISK'
WHEN reach > 10 THEN 'CRITICAL'
ELSE 'NORMAL'
END AS risk_level;
Supply Chain Risk Assessment
-- Find single points of failure
MATCH path = (sup:Supplier)-[:SUPPLIES*]->(wh:Warehouse)-[:ROUTE*]->(cust:Customer)
WITH sup, cust, count(DISTINCT path) AS alternative_paths
WHERE alternative_paths = 1
RETURN sup.name AS critical_supplier,
cust.name AS affected_customer,
'SINGLE_PATH' AS risk_type;
-- Geographic concentration risk
MATCH (sup:Supplier)
WITH sup.location.latitude AS lat, sup.location.longitude AS lon,
count(sup) AS supplier_count
WHERE supplier_count > 5
RETURN point({latitude: lat, longitude: lon}) AS concentration_point,
supplier_count,
'GEOGRAPHIC_CONCENTRATION' AS risk_type;
Step 4: Real-Time Tracking
Shipment Tracking
-- Model shipments
CREATE
(:Shipment {
id: 'ship_1',
order_id: 'ord_12345',
status: 'in_transit',
created: timestamp(),
estimated_delivery: timestamp() + (24 * 60 * 60 * 1000)
});
-- Track shipment location
MATCH (ship:Shipment {id: 'ship_1'}), (current_loc:Warehouse {id: 'wh_2'})
CREATE (ship)-[:CURRENT_LOCATION {
timestamp: timestamp(),
next_hop: 'wh_1'
}]->(current_loc);
-- Query shipment status
MATCH (ship:Shipment {id: 'ship_1'})-[:CURRENT_LOCATION]->(loc)
OPTIONAL MATCH path = (loc)-[:ROUTE*]->(destination)
RETURN ship.status,
loc.name AS current_location,
ship.estimated_delivery,
length(path) AS remaining_hops;
ETA Calculation
-- Calculate updated ETA based on current location
MATCH (ship:Shipment {id: 'ship_1'})-[:CURRENT_LOCATION]->(current:Warehouse)
MATCH (destination:Customer {id: 'cust_1'}),
path = shortestPath((current)-[:ROUTE*]->(destination))
WITH ship, path,
reduce(time = 0, r IN relationships(path) | time + r.time_hours) AS remaining_hours
RETURN ship.id,
ship.estimated_delivery AS original_eta,
timestamp() + (remaining_hours * 60 * 60 * 1000) AS updated_eta,
(timestamp() + (remaining_hours * 60 * 60 * 1000) - ship.estimated_delivery) / (60 * 60 * 1000) AS delay_hours;
Step 5: Network Optimization
Bottleneck Detection
-- Find routes with highest traffic
MATCH ()-[route:ROUTE]->()
WITH route, route.capacity AS capacity,
route.current_load AS load
WHERE load > capacity * 0.8
RETURN startNode(route).name AS from_location,
endNode(route).name AS to_location,
load,
capacity,
(load * 1.0 / capacity) AS utilization
ORDER BY utilization DESC;
Betweenness Centrality (Critical Nodes)
-- Find critical warehouses in network
CALL graph.betweennessCentrality('SupplyChainNetwork', {
relationship_type: 'ROUTE',
normalized: true
})
YIELD node, score
WHERE node:Warehouse
RETURN node.name AS warehouse,
node.capacity,
score AS criticality_score
ORDER BY score DESC;
Step 6: Demand Forecasting Integration
-- Update demand based on historical patterns
MATCH (cust:Customer)-[:ORDERED]->(order:Order)
WHERE order.timestamp > timestamp() - (90 * 24 * 60 * 60 * 1000) -- Last 90 days
WITH cust, sum(order.quantity) AS total_ordered, count(order) AS order_count
SET cust.demand_weekly = (total_ordered * 1.0 / 13); -- 90 days ≈ 13 weeks
-- Adjust inventory based on forecasted demand
MATCH (wh:Warehouse)-[stock:STOCKS]->(prod:Product)
MATCH (wh)-[:ROUTE]->(cust:Customer)
WITH wh, prod, stock, sum(cust.demand_weekly) AS forecast_demand
SET stock.reorder_point = forecast_demand * 2,
stock.reorder_quantity = forecast_demand * 4;
Advanced Patterns
Multi-Modal Transportation
-- Model different transport modes
MATCH (origin:Warehouse {id: 'wh_1'}), (dest:Warehouse {id: 'wh_3'})
CREATE (origin)-[:ROUTE {
mode: 'truck',
distance_km: 500,
time_hours: 8,
cost_per_unit: 1.00,
carbon_kg: 50
}]->(dest);
CREATE (origin)-[:ROUTE {
mode: 'rail',
distance_km: 550,
time_hours: 12,
cost_per_unit: 0.60,
carbon_kg: 20
}]->(dest);
-- Optimize for cost vs. speed vs. sustainability
MATCH (origin:Warehouse {id: 'wh_1'}), (dest:Warehouse {id: 'wh_3'}),
path = (origin)-[:ROUTE*]->(dest)
WITH path,
reduce(cost = 0, r IN relationships(path) | cost + r.cost_per_unit) AS total_cost,
reduce(time = 0, r IN relationships(path) | time + r.time_hours) AS total_time,
reduce(carbon = 0, r IN relationships(path) | carbon + r.carbon_kg) AS total_carbon
RETURN [r IN relationships(path) | r.mode] AS transport_modes,
total_cost,
total_time,
total_carbon,
(total_cost * 0.4 + total_time * 0.3 + total_carbon * 0.3) AS sustainability_score
ORDER BY sustainability_score ASC;
Vehicle Routing Problem (VRP)
# Python client - Solve VRP with capacity constraints
from geode_client import Client
client = Client(host="geode.example.com", port=3141)
async def solve_vrp(conn, warehouse_id, customer_ids, vehicle_capacity):
"""Solve VRP for multiple deliveries from one warehouse"""
# Get distances between all points
query = """
MATCH (wh:Warehouse {id: $wh_id})
UNWIND $cust_ids AS cust_id
MATCH (cust:Customer {id: cust_id})
MATCH path = shortestPath((wh)-[:ROUTE*]->(cust))
RETURN cust_id,
reduce(dist = 0, r IN relationships(path) | dist + r.distance_km) AS distance,
cust.demand_weekly AS demand
"""
page, _ = await conn.query(query, {
'wh_id': warehouse_id,
'cust_ids': customer_ids
})
distance_map = {row["cust_id"].raw_value: row["distance"].raw_value for row in page.rows}
demand_map = {row["cust_id"].raw_value: row["demand"].raw_value for row in page.rows}
def get_distance(_from_id, to_id):
return distance_map.get(to_id, float("inf"))
def get_demand(cust_id):
return demand_map.get(cust_id, 0)
# Greedy nearest neighbor with capacity constraints
routes = []
current_route = []
current_capacity = 0
unvisited = set(customer_ids)
while unvisited:
if not current_route:
next_cust = min(unvisited, key=lambda c: get_distance(warehouse_id, c))
else:
last_cust = current_route[-1]
next_cust = min(unvisited, key=lambda c: get_distance(last_cust, c))
demand = get_demand(next_cust)
if current_capacity + demand <= vehicle_capacity:
current_route.append(next_cust)
current_capacity += demand
unvisited.remove(next_cust)
else:
routes.append(current_route)
current_route = []
current_capacity = 0
if current_route:
routes.append(current_route)
return routes
# async with client.connection() as conn:
# routes = await solve_vrp(conn, "wh_1", ["c1", "c2"], 200)
Dynamic Rerouting
-- Handle route disruption (e.g., road closure)
MATCH ()-[disrupted:ROUTE {id: 'route_123'}]->()
SET disrupted.available = false,
disrupted.reason = 'road_closure',
disrupted.until = timestamp() + (48 * 60 * 60 * 1000);
-- Find alternative routes
MATCH (origin:Warehouse {id: 'wh_1'}), (dest:Customer {id: 'cust_1'}),
path = (origin)-[:ROUTE*]->(dest)
WHERE ALL(r IN relationships(path) WHERE r.available = true)
WITH path,
reduce(dist = 0, r IN relationships(path) | dist + r.distance_km) AS distance,
reduce(cost = 0, r IN relationships(path) | cost + r.cost_per_unit) AS cost
RETURN [n IN nodes(path) | n.name] AS alternative_route,
distance,
cost
ORDER BY cost ASC
LIMIT 3;
Performance Optimization
Spatial Indexing
-- Create spatial index for location-based queries
CREATE INDEX location_spatial_idx ON Warehouse(location) USING spatial;
CREATE INDEX customer_location_idx ON Customer(location) USING spatial;
-- Find nearby warehouses
MATCH (wh:Warehouse)
WHERE point.distance(wh.location, point({latitude: 47.6062, longitude: -122.3321})) < 50000 -- 50km
RETURN wh.name, wh.location;
Route Caching
# Cache frequently used routes
from geode_client import Client
client = Client(host="geode.example.com", port=3141)
route_cache = {}
async def get_shortest_path(conn, origin_id, dest_id):
"""Cache shortest paths between common locations"""
cache_key = (origin_id, dest_id)
if cache_key in route_cache:
return route_cache[cache_key]
page, _ = await conn.query("""
MATCH (o {id: $origin}), (d {id: $dest}),
path = shortestPath((o)-[:ROUTE*]->(d))
RETURN path
""", {'origin': origin_id, 'dest': dest_id})
path = page.rows[0]["path"].raw_value if page.rows else None
route_cache[cache_key] = path
return path
# async with client.connection() as conn:
# path = await get_shortest_path(conn, "wh_1", "cust_42")
Materialized Views
-- Pre-compute common routes
MATCH (wh:Warehouse)-[r:ROUTE*1..3]->(cust:Customer)
WITH wh, cust,
reduce(dist = 0, rel IN r | dist + rel.distance_km) AS total_distance,
reduce(cost = 0, rel IN r | cost + rel.cost_per_unit) AS total_cost
CREATE (wh)-[:PRECOMPUTED_ROUTE {
distance: total_distance,
cost: total_cost,
updated: timestamp()
}]->(cust);
-- Query precomputed routes (much faster)
MATCH (wh:Warehouse {id: 'wh_1'})-[route:PRECOMPUTED_ROUTE]->(cust:Customer)
RETURN cust.name, route.distance, route.cost
ORDER BY route.cost ASC;
Metrics and KPIs
Operational Metrics
-- Average delivery time
MATCH ()-[route:ROUTE]->()
RETURN avg(route.time_hours) AS avg_delivery_time;
-- Network utilization
MATCH ()-[route:ROUTE]->()
WITH route,
CASE WHEN route.current_load IS NOT NULL
THEN route.current_load * 1.0 / route.capacity
ELSE 0
END AS utilization
RETURN avg(utilization) AS avg_network_utilization,
max(utilization) AS max_utilization,
count(CASE WHEN utilization > 0.8 THEN 1 END) AS congested_routes;
-- Inventory turnover
MATCH (wh:Warehouse)-[stock:STOCKS]->(prod:Product)
WITH prod, sum(stock.quantity) AS total_stock
MATCH (ord:Order)-[:CONTAINS]->(prod)
WHERE ord.timestamp > timestamp() - (30 * 24 * 60 * 60 * 1000)
WITH prod, total_stock, sum(ord.quantity) AS monthly_sales
RETURN prod.sku,
monthly_sales * 12.0 / total_stock AS annual_turnover;
Cost Analysis
-- Total logistics cost
MATCH (order:Order)-[:SHIPPED_VIA]->(route:ROUTE)
WHERE order.timestamp > timestamp() - (30 * 24 * 60 * 60 * 1000)
RETURN sum(order.quantity * route.cost_per_unit) AS monthly_logistics_cost;
-- Cost per delivery
MATCH (order:Order)-[:DELIVERED_TO]->(cust:Customer)
WHERE order.timestamp > timestamp() - (30 * 24 * 60 * 60 * 1000)
WITH order, sum(order.logistics_cost) AS delivery_cost
RETURN avg(delivery_cost) AS avg_cost_per_delivery,
percentile_cont(delivery_cost, 0.5) AS median_cost,
percentile_cont(delivery_cost, 0.95) AS p95_cost;
Best Practices
1. Model Granularity
- Start coarse, refine as needed: Begin with major hubs, add detail later
- Balance detail vs. performance: Too many nodes slow queries
- Use aggregation: Group small customers into zones
2. Weight Selection
- Normalize weights: Ensure comparable scales (distance vs. cost)
- Time-varying weights: Update traffic patterns by time of day
- Multi-objective: Combine cost, time, reliability, sustainability
3. Data Quality
- Update regularly: Route conditions change (traffic, weather)
- Validate constraints: Capacity, lead times, availability
- Track exceptions: Monitor deviations from plan
Complete Example: E-Commerce Fulfillment
-- Create fulfillment network
CREATE
(:FulfillmentCenter {id: 'fc_1', name: 'East Coast FC', capacity: 200000}),
(:FulfillmentCenter {id: 'fc_2', name: 'West Coast FC', capacity: 150000}),
(:FulfillmentCenter {id: 'fc_3', name: 'Midwest FC', capacity: 100000});
-- Customer orders
CREATE
(:Order {id: 'ord_1', customer_zip: '98101', quantity: 5, priority: 'standard'}),
(:Order {id: 'ord_2', customer_zip: '94102', quantity: 10, priority: 'express'}),
(:Order {id: 'ord_3', customer_zip: '60601', quantity: 3, priority: 'standard'});
-- Route orders to optimal fulfillment center
MATCH (fc:FulfillmentCenter), (ord:Order)
MATCH (fc)-[stock:STOCKS]->(prod:Product)
WHERE stock.quantity >= ord.quantity
WITH fc, ord,
point.distance(
fc.location,
geocode(ord.customer_zip)
) AS distance
ORDER BY ord.priority DESC, distance ASC
WITH ord, head(collect(fc)) AS optimal_fc
CREATE (ord)-[:FULFILLED_BY]->(optimal_fc);
-- Generate pick lists
MATCH (fc:FulfillmentCenter {id: 'fc_1'})<-[:FULFILLED_BY]-(ord:Order)
RETURN ord.id,
ord.customer_zip,
ord.quantity,
ord.priority
ORDER BY ord.priority DESC, ord.id;
Next Steps
- Graph Algorithms - Shortest path, network flow
- Performance Tuning - Optimize large-scale logistics queries
- Real-Time Analytics - Live tracking and alerts
- Spatial Queries - Geographic search optimization
References
- Data Model Guide - Modeling transportation networks
- GQL Guide - Path queries and aggregations
- Dijkstra Tutorial - Weighted shortest paths
License: Apache License 2.0 Copyright: 2024-2025 CodePros Last Updated: January 2026