Install
$ agentstack add skill-kishorkukreja-awesome-supply-chain-demand-supply-matching ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Demand-Supply Matching
You are an expert in demand-supply matching and fulfillment optimization. Your goal is to help organizations effectively balance demand and supply, optimize allocation decisions, and maximize customer service while minimizing costs and inefficiencies.
Initial Assessment
Before implementing demand-supply matching, understand:
- Business Context
- What's driving matching needs? (supply constraints, demand volatility, growth)
- Current matching process and pain points?
- Service level objectives by customer/channel?
- Cost of mismatches (shortages vs. excess)?
- Demand Characteristics
- Demand predictability and variability?
- Customer/channel segmentation?
- Priority rules and commitments?
- Seasonality and promotions?
- Supply Constraints
- Production capacity limitations?
- Supplier capacity and lead times?
- Inventory availability across network?
- Transportation and logistics constraints?
- Complexity Factors
- Multi-plant, multi-DC network?
- Global vs. regional operations?
- Product complexity (BOM depth, variants)?
- Perishability or shelf-life issues?
Demand-Supply Matching Framework
Matching Scenarios
1. Unconstrained (Supply > Demand)
- Fulfill all demand
- Optimize fulfillment source
- Minimize costs
- Manage excess inventory
2. Constrained (Demand > Supply)
- Allocate limited supply
- Prioritize customers/orders
- Balance service and revenue
- Communicate shortfalls
3. Balanced (Supply ≈ Demand)
- Optimize matching
- Buffer for uncertainty
- Monitor closely
- Prepare for swings
Allocation Strategies
1. First-Come, First-Served (FCFS)
- Simple, fair
- No prioritization
- May not optimize value
- Risk of strategic orders
2. Pro-Rata Allocation
- Proportional to demand
- Fair across customers
- Doesn't consider customer value
- Simple to implement
3. Priority-Based Allocation
- Strategic customer priority
- Contract commitments first
- Revenue or margin optimization
- May strain low-priority customers
4. Optimization-Based Allocation
- Maximize objective (revenue, margin, service)
- Consider constraints
- Most complex
- Best financial outcome
Available-to-Promise (ATP) Logic
ATP Calculation
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class ATPEngine:
"""Available-to-Promise calculation engine"""
def __init__(self):
self.inventory_positions = {}
self.supply_schedule = []
self.demand_schedule = []
self.allocations = []
def add_inventory_position(self, location, product_id, on_hand, in_transit=0):
"""
Add current inventory position
on_hand: Physical inventory
in_transit: Confirmed receipts en route
"""
key = (location, product_id)
self.inventory_positions[key] = {
'location': location,
'product_id': product_id,
'on_hand': on_hand,
'in_transit': in_transit,
'available': on_hand, # Available = on_hand - allocated
'allocated': 0
}
def add_supply(self, location, product_id, quantity, receipt_date):
"""Add future supply (production, purchases, transfers)"""
self.supply_schedule.append({
'location': location,
'product_id': product_id,
'quantity': quantity,
'receipt_date': receipt_date,
'type': 'supply'
})
def add_demand(self, location, product_id, quantity, required_date, priority=5):
"""
Add demand requirement
priority: 1 (highest) to 10 (lowest)
"""
self.demand_schedule.append({
'location': location,
'product_id': product_id,
'quantity': quantity,
'required_date': required_date,
'priority': priority,
'type': 'demand',
'allocated_quantity': 0,
'status': 'Pending'
})
def calculate_atp(self, location, product_id, horizon_days=30):
"""
Calculate Available-to-Promise for a product-location
Returns ATP by time period
"""
key = (location, product_id)
if key not in self.inventory_positions:
return None
# Starting position
current_available = self.inventory_positions[key]['on_hand']
# Get relevant supply and demand
supplies = [s for s in self.supply_schedule
if s['location'] == location and s['product_id'] == product_id]
demands = [d for d in self.demand_schedule
if d['location'] == location and d['product_id'] == product_id]
# Sort by date
supplies.sort(key=lambda x: x['receipt_date'])
demands.sort(key=lambda x: x['required_date'])
# Calculate ATP by period
today = datetime.now().date()
atp_schedule = []
for day_offset in range(horizon_days + 1):
target_date = today + timedelta(days=day_offset)
# Add supplies arriving on or before this date
supplies_today = sum(s['quantity'] for s in supplies
if s['receipt_date'] 0:
# Allocate as much as possible
allocated = min(quantity_needed, available)
# Update available inventory
self.inventory_positions[key]['available'] -= allocated
self.inventory_positions[key]['allocated'] += allocated
# Update demand
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == quantity_needed else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'priority': demand['priority'],
'requested_quantity': quantity_needed,
'allocated_quantity': allocated,
'shortfall': quantity_needed - allocated
})
return pd.DataFrame(allocations)
def _allocate_fcfs(self):
"""Allocate supply first-come, first-served (by date)"""
# Sort by required date
sorted_demands = sorted(self.demand_schedule, key=lambda x: x['required_date'])
allocations = []
for demand in sorted_demands:
location = demand['location']
product_id = demand['product_id']
quantity_needed = demand['quantity']
key = (location, product_id)
if key not in self.inventory_positions:
continue
available = self.inventory_positions[key]['available']
if available > 0:
allocated = min(quantity_needed, available)
self.inventory_positions[key]['available'] -= allocated
self.inventory_positions[key]['allocated'] += allocated
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == quantity_needed else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'requested_quantity': quantity_needed,
'allocated_quantity': allocated,
'shortfall': quantity_needed - allocated
})
return pd.DataFrame(allocations)
def _allocate_pro_rata(self):
"""Allocate supply proportionally across all demands"""
# Group demands by location and product
demand_groups = {}
for demand in self.demand_schedule:
key = (demand['location'], demand['product_id'])
if key not in demand_groups:
demand_groups[key] = []
demand_groups[key].append(demand)
allocations = []
for key, demands in demand_groups.items():
location, product_id = key
if key not in self.inventory_positions:
continue
available = self.inventory_positions[key]['available']
total_demand = sum(d['quantity'] for d in demands)
if total_demand == 0:
continue
# Calculate allocation ratio
allocation_ratio = min(1.0, available / total_demand)
for demand in demands:
allocated = int(demand['quantity'] * allocation_ratio)
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == demand['quantity'] else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'priority': demand.get('priority', 5),
'requested_quantity': demand['quantity'],
'allocated_quantity': allocated,
'allocation_ratio': allocation_ratio,
'shortfall': demand['quantity'] - allocated
})
self.inventory_positions[key]['allocated'] = sum(a['allocated_quantity'] for a in allocations if a['location'] == location and a['product_id'] == product_id)
self.inventory_positions[key]['available'] = available - self.inventory_positions[key]['allocated']
return pd.DataFrame(allocations)
def generate_shortage_report(self):
"""Generate report of unfulfilled demands"""
shortages = []
for demand in self.demand_schedule:
if demand['status'] in ['Pending', 'Partial']:
shortfall = demand['quantity'] - demand['allocated_quantity']
shortages.append({
'location': demand['location'],
'product_id': demand['product_id'],
'required_date': demand['required_date'],
'priority': demand['priority'],
'requested_quantity': demand['quantity'],
'allocated_quantity': demand['allocated_quantity'],
'shortfall': shortfall,
'status': demand['status']
})
return pd.DataFrame(shortages) if shortages else pd.DataFrame()
# Example ATP and allocation
atp_engine = ATPEngine()
# Add inventory positions
atp_engine.add_inventory_position('DC1', 'PROD001', on_hand=1000, in_transit=500)
atp_engine.add_inventory_position('DC2', 'PROD001', on_hand=800, in_transit=300)
# Add future supply
atp_engine.add_supply('DC1', 'PROD001', 500, datetime.now().date() + timedelta(days=10))
atp_engine.add_supply('DC1', 'PROD001', 700, datetime.now().date() + timedelta(days=20))
# Add demands with priorities
atp_engine.add_demand('DC1', 'PROD001', 600, datetime.now().date() + timedelta(days=5), priority=1) # High priority
atp_engine.add_demand('DC1', 'PROD001', 400, datetime.now().date() + timedelta(days=7), priority=3)
atp_engine.add_demand('DC1', 'PROD001', 500, datetime.now().date() + timedelta(days=12), priority=2)
atp_engine.add_demand('DC1', 'PROD001', 300, datetime.now().date() + timedelta(days=15), priority=5) # Low priority
# Calculate ATP
atp = atp_engine.calculate_atp('DC1', 'PROD001', horizon_days=30)
print("Available-to-Promise Schedule:")
print(atp[atp['day_offset'] % 5 == 0][['day_offset', 'atp', 'cumulative_supply', 'cumulative_demand']]) # Show every 5 days
# Allocate supply (priority-based)
print("\n\nPriority-Based Allocation:")
allocations = atp_engine.allocate_supply(allocation_method='priority')
print(allocations[['product_id', 'required_date', 'priority', 'requested_quantity', 'allocated_quantity', 'shortfall']])
# Generate shortage report
shortages = atp_engine.generate_shortage_report()
print("\n\nShortage Report:")
if not shortages.empty:
print(shortages[['product_id', 'required_date', 'priority', 'shortfall', 'status']])
else:
print("No shortages - all demands fulfilled")
Optimization-Based Allocation
Multi-Objective Allocation Optimization
from scipy.optimize import linprog
import numpy as np
class OptimizedAllocation:
"""Optimization-based demand-supply matching"""
def optimize_allocation(self, supply_data, demand_data, customer_priorities):
"""
Optimize allocation to maximize weighted service level
supply_data: dict {location: {product: quantity}}
demand_data: list of dicts with customer, product, quantity, location
customer_priorities: dict {customer: weight} (1-10, higher = more important)
"""
# Build optimization problem
# Decision variables: allocation[i] for each demand i
n_demands = len(demand_data)
# Objective: Maximize weighted fulfillment
# Higher priority customers get more weight
c = [] # Coefficients for objective (negative because linprog minimizes)
for demand in demand_data:
customer = demand['customer']
weight = customer_priorities.get(customer, 5) # Default weight 5
c.append(-weight * demand['quantity']) # Negative for maximization
c = np.array(c)
# Constraints: Supply constraints
# Sum of allocations for each product-location 0 else 100
allocation_results.append({
'customer': demand['customer'],
'product': demand['product'],
'location': demand['location'],
'requested': demand['quantity'],
'allocated': allocated,
'fulfillment_rate': round(fulfillment_rate, 1),
'priority_weight': customer_priorities.get(demand['customer'], 5)
})
return {
'success': True,
'allocations': pd.DataFrame(allocation_results),
'objective_value': -result.fun, # Negative because we minimized negative
'solver_status': result.message
}
else:
return {
'success': False,
'message': result.message
}
# Example optimization
optimizer = OptimizedAllocation()
# Supply availability
supply = {
'DC1': {'PROD001': 1000, 'PROD002': 800},
'DC2': {'PROD001': 600, 'PROD002': 500}
}
# Demands
demands = [
{'customer': 'CUST_A', 'product': 'PROD001', 'location': 'DC1', 'quantity': 600},
{'customer': 'CUST_B', 'product': 'PROD001', 'location': 'DC1', 'quantity': 500},
{'customer': 'CUST_C', 'product': 'PROD001', 'location': 'DC1', 'quantity': 400},
{'customer': 'CUST_A', 'product': 'PROD002', 'location': 'DC1', 'quantity': 300},
{'customer': 'CUST_D', 'product': 'PROD002', 'location': 'DC1', 'quantity': 700},
]
# Customer priorities (1-10, 10 = highest)
priorities = {
'CUST_A': 10, # Strategic customer
'CUST_B': 7,
'CUST_C': 5,
'CUST_D': 3
}
# Optimize
result = optimizer.optimize_allocation(supply, demands, priorities)
if result['success']:
print("Optimized Allocation:")
print(result['allocations'])
print(f"\nObjective Value (Weighted Fulfillment): {result['objective_value']:.2f}")
else:
print(f"Optimization failed: {result['message']}")
Tools & Libraries
Python Libraries
Optimization:
scipy.optimize: Linear and non-linear optimizationpulp: Linear programmingpyomo: Optimization modelingortools: Google OR-Tools
Data Processing:
pandas: Data manipulationnumpy: Numerical computations
Forecasting Integration:
statsmodels: Statistical modelsprophet: Forecasting
Commercial Software
Planning & Allocation:
- **
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kishorkukreja
- Source: kishorkukreja/awesome-supply-chain
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.