Warehouse Management in Python — Deep Dive
Building warehouse management logic in Python means solving spatial optimization problems, integrating with barcode/RFID hardware, and serving real-time decisions to workers on the floor. This guide covers the technical implementations behind slotting, pick routing, wave planning, and inventory reconciliation.
Warehouse layout modeling
Before optimizing anything, model the physical warehouse as a graph:
from dataclasses import dataclass
import networkx as nx
@dataclass
class Location:
loc_id: str
aisle: int
bay: int
level: int
zone: str
x: float # meters from origin
y: float
def build_warehouse_graph(locations: list[Location], aisle_width: float = 3.0) -> nx.Graph:
"""Build a traversal graph where nodes are pick locations and edges are walkable paths."""
G = nx.Graph()
for loc in locations:
G.add_node(loc.loc_id, pos=(loc.x, loc.y), zone=loc.zone)
# Connect locations within the same aisle sequentially
by_aisle = {}
for loc in locations:
by_aisle.setdefault(loc.aisle, []).append(loc)
for aisle, locs in by_aisle.items():
locs.sort(key=lambda l: l.y)
for i in range(len(locs) - 1):
dist = abs(locs[i].y - locs[i+1].y)
G.add_edge(locs[i].loc_id, locs[i+1].loc_id, weight=dist)
# Connect aisle ends (cross-aisles at front and back)
aisle_ends = {}
for aisle, locs in by_aisle.items():
aisle_ends[aisle] = (locs[0], locs[-1])
sorted_aisles = sorted(aisle_ends.keys())
for i in range(len(sorted_aisles) - 1):
a1, a2 = sorted_aisles[i], sorted_aisles[i+1]
# Front cross-aisle
G.add_edge(aisle_ends[a1][0].loc_id, aisle_ends[a2][0].loc_id, weight=aisle_width)
# Back cross-aisle
G.add_edge(aisle_ends[a1][1].loc_id, aisle_ends[a2][1].loc_id, weight=aisle_width)
return G
Slotting optimization
Velocity-based slotting assigns the fastest-moving SKUs to the most accessible locations (closest to the packing area, at pick-height level):
import pandas as pd
from scipy.optimize import linear_sum_assignment
def optimize_slotting(
sku_velocity: pd.DataFrame, # columns: sku, picks_per_week
locations: pd.DataFrame, # columns: loc_id, distance_to_pack, ergonomic_score
) -> pd.DataFrame:
"""Assign SKUs to locations minimizing total weighted travel distance."""
# Rank SKUs by velocity (highest first)
skus = sku_velocity.sort_values("picks_per_week", ascending=False).reset_index(drop=True)
# Rank locations by desirability (closest and most ergonomic first)
locs = locations.sort_values(["distance_to_pack", "ergonomic_score"]).reset_index(drop=True)
n = min(len(skus), len(locs))
# Build cost matrix: high-velocity SKU in far location = high cost
cost_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
cost_matrix[i][j] = skus.iloc[i]["picks_per_week"] * locs.iloc[j]["distance_to_pack"]
row_ind, col_ind = linear_sum_assignment(cost_matrix)
assignments = []
for r, c in zip(row_ind, col_ind):
assignments.append({"sku": skus.iloc[r]["sku"], "loc_id": locs.iloc[c]["loc_id"]})
return pd.DataFrame(assignments)
For warehouses with 10,000+ SKUs and locations, the Hungarian algorithm in linear_sum_assignment solves in seconds. Re-run monthly as velocity patterns shift.
Pick-path routing
Given a set of locations to visit, find the shortest walking path through the warehouse graph:
def optimal_pick_path(
graph: nx.Graph,
pick_locations: list[str],
start: str = "PACK_STATION",
) -> tuple[list[str], float]:
"""Solve TSP on warehouse graph for a pick list."""
nodes = [start] + pick_locations
# Build distance sub-matrix using shortest paths
dist = {}
for i, u in enumerate(nodes):
lengths = nx.single_source_dijkstra_path_length(graph, u, weight="weight")
for j, v in enumerate(nodes):
dist[(i, j)] = lengths.get(v, float("inf"))
# Nearest-neighbor heuristic + 2-opt improvement
n = len(nodes)
visited = [False] * n
path = [0]
visited[0] = True
for _ in range(n - 1):
last = path[-1]
nearest = min(
(j for j in range(n) if not visited[j]),
key=lambda j: dist[(last, j)],
)
path.append(nearest)
visited[nearest] = True
# 2-opt improvement
improved = True
while improved:
improved = False
for i in range(1, len(path) - 1):
for j in range(i + 1, len(path)):
old_cost = dist[(path[i-1], path[i])] + dist[(path[j-1], path[j])]
new_cost = dist[(path[i-1], path[j-1])] + dist[(path[i], path[j])]
if new_cost < old_cost:
path[i:j] = reversed(path[i:j])
improved = True
total_dist = sum(dist[(path[i], path[i+1])] for i in range(len(path) - 1))
ordered_locations = [nodes[i] for i in path]
return ordered_locations, total_dist
For batch picking (multiple orders in one trip), merge the pick lists and solve once, then separate items by order at the sorting station.
Wave planning
Wave planning groups orders into batches that release together. Objectives:
- Meet carrier cutoff times (e.g., FedEx pickup at 3 PM).
- Balance workload across zones.
- Maximize batch-pick efficiency (orders sharing many SKUs go in the same wave).
from collections import defaultdict
def plan_waves(
orders: list[dict], # each: {order_id, items: [sku, ...], carrier, cutoff_time}
max_wave_size: int = 50,
max_waves: int = 10,
) -> list[list[str]]:
"""Group orders into waves by carrier cutoff and SKU overlap."""
# Sort by cutoff urgency
orders.sort(key=lambda o: o["cutoff_time"])
waves = []
current_wave = []
current_skus = set()
for order in orders:
order_skus = set(order["items"])
overlap = len(order_skus & current_skus) / max(1, len(order_skus))
if len(current_wave) >= max_wave_size:
waves.append([o["order_id"] for o in current_wave])
current_wave = []
current_skus = set()
current_wave.append(order)
current_skus |= order_skus
if current_wave:
waves.append([o["order_id"] for o in current_wave])
return waves[:max_waves]
Production systems use more sophisticated clustering (K-Medoids on SKU overlap distance) and constraint programming to respect zone capacity limits.
Real-time inventory tracking
A FastAPI service handles barcode scan events from handheld devices:
from fastapi import FastAPI
from pydantic import BaseModel
from datetime import datetime
app = FastAPI()
class ScanEvent(BaseModel):
location_id: str
sku: str
quantity: int
event_type: str # "receive", "pick", "move", "count"
worker_id: str
timestamp: datetime
@app.post("/scan")
async def process_scan(event: ScanEvent):
if event.event_type == "receive":
await update_inventory(event.location_id, event.sku, event.quantity)
elif event.event_type == "pick":
await update_inventory(event.location_id, event.sku, -event.quantity)
elif event.event_type == "move":
# Move triggers two updates: remove from source, add to destination
pass
elif event.event_type == "count":
await reconcile_count(event.location_id, event.sku, event.quantity)
return {"status": "ok", "processed_at": datetime.utcnow()}
Events stream into a PostgreSQL inventory table and a Redis cache for sub-second lookups during pick-path computation.
Measuring improvement
Before/after metrics for a Python-augmented WMS:
| Metric | Before (manual) | After (Python-optimized) |
|---|---|---|
| Pick rate (lines/hour) | 85 | 130 |
| Walk distance per order | 180 m | 110 m |
| Slotting review frequency | Annual | Monthly |
| Inventory accuracy | 96.5% | 99.2% |
| Wave planning time | 45 min (manual) | 2 min (automated) |
Pitfalls
- Static slotting — velocity changes seasonally. A product that was fast-moving six months ago may now be a slow mover occupying prime real estate.
- Ignoring congestion — the shortest path means nothing if three pickers converge on the same aisle. Stagger wave releases or add congestion penalties to the routing graph.
- Over-engineering for small warehouses — a 500-location warehouse does not need a full graph solver. Simple zone rules and manual slotting reviews often suffice.
- Barcode scan failures — if workers skip scans when busy, inventory accuracy degrades quickly. Design the system to flag missing scan sequences.
The one thing to remember: Python warehouse management combines spatial graph modeling for pick routing, assignment optimization for slotting, wave planning for batch efficiency, and real-time scan processing to turn warehouse operations from intuition-driven to data-driven.
See Also
- Python Adaptive Learning Systems How Python builds learning apps that adjust to each student like a personal tutor who knows exactly what you need next.
- Python Airflow Learn Airflow as a timetable manager that makes sure data tasks run in the right order every day.
- Python Altair Learn Altair through the idea of drawing charts by describing rules, not by hand-placing every visual element.
- Python Automated Grading How Python grades homework and exams automatically, from simple answer keys to understanding written essays.
- Python Batch Vs Stream Processing Batch processing is like doing laundry once a week; stream processing is like a self-cleaning shirt that cleans itself constantly.