AgentStack
SKILL verified MIT Self-run

Demand Supply Matching

skill-kishorkukreja-awesome-supply-chain-demand-supply-matching · by kishorkukreja

When the user wants to balance demand and supply, resolve supply-demand mismatches, or optimize fulfillment allocation. Also use when the user mentions "demand-supply balancing," "ATP," "available-to-promise," "allocation," "supply rationing," "fulfillment optimization," "demand prioritization," or "supply allocation." For forecasting, see demand-forecasting. For S&OP, see sales-operations-planni…

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-kishorkukreja-awesome-supply-chain-demand-supply-matching

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Demand Supply Matching? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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:

  1. 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)?
  1. Demand Characteristics
  • Demand predictability and variability?
  • Customer/channel segmentation?
  • Priority rules and commitments?
  • Seasonality and promotions?
  1. Supply Constraints
  • Production capacity limitations?
  • Supplier capacity and lead times?
  • Inventory availability across network?
  • Transportation and logistics constraints?
  1. 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 optimization
  • pulp: Linear programming
  • pyomo: Optimization modeling
  • ortools: Google OR-Tools

Data Processing:

  • pandas: Data manipulation
  • numpy: Numerical computations

Forecasting Integration:

  • statsmodels: Statistical models
  • prophet: 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.