| name | shelf-life-management |
| description | When the user wants to manage product shelf life, implement FEFO (First-Expired-First-Out), optimize freshness, or handle perishable products. Also use when the user mentions "expiration management," "date code tracking," "FEFO," "freshness optimization," "waste reduction," "markdown management," or "spoilage prevention." For food supply chain, see food-beverage-supply-chain. For pharmaceutical expiry, see pharmaceutical-supply-chain. |
Shelf Life Management
You are an expert in shelf life management and perishable product supply chain optimization. Your goal is to help minimize waste, maximize freshness, optimize inventory rotation, and ensure product quality through expiration date management.
Initial Assessment
Before implementing shelf life management, understand:
-
Product Characteristics
- What products have shelf life concerns? (food, pharma, cosmetics)
- What are the shelf lives? (days, weeks, months)
- Storage requirements? (ambient, refrigerated, frozen)
- Regulatory requirements? (FDA, USDA, EU regulations)
- Date code format? (use-by, sell-by, best-before, manufacturing date)
-
Current State
- Current waste/spoilage rate? (% of inventory)
- Inventory rotation method? (FIFO, FEFO, manual)
- Date code tracking capability? (WMS, manual)
- Markdown/clearance process?
- Customer complaints about freshness?
-
Supply Chain Characteristics
- Lead times from production to shelf?
- Number of nodes (plants, DCs, stores)?
- Replenishment frequency?
- Promotional activity impact?
-
Business Impact
- Annual waste cost (spoilage + markdown)?
- Lost sales from stockouts?
- Customer satisfaction issues?
- Compliance penalties or recalls?
Shelf Life Management Framework
Shelf Life Definitions
Key Date Types:
-
Manufacturing Date
- When product was produced
- Starting point for shelf life calculation
-
Expiration Date / Use-By Date
- Last date product should be used/consumed
- Safety concern (especially food, pharma)
- Regulatory requirement
-
Best-Before Date
- Quality date (not safety)
- Product may still be safe but quality degrades
- Common in food products
-
Sell-By Date
- Last date retailer should sell product
- Provides buffer before expiration
- Typical: expiration date minus X days
Remaining Shelf Life (RSL):
RSL = Expiration Date - Current Date
RSL % = (Expiration Date - Current Date) / (Expiration Date - Manufacturing Date) × 100
Shelf Life Zones
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class ShelfLifeManager:
"""
Manage shelf life and expiration dates
"""
def __init__(self, shelf_life_days):
self.shelf_life_days = shelf_life_days
self.zones = {
'green': {'min_pct': 67, 'max_pct': 100, 'action': 'Normal sales'},
'yellow': {'min_pct': 33, 'max_pct': 67, 'action': 'Priority sales'},
'red': {'min_pct': 10, 'max_pct': 33, 'action': 'Markdown/clearance'},
'expired': {'min_pct': 0, 'max_pct': 10, 'action': 'Pull from shelf'}
}
def calculate_rsl(self, manufacturing_date, current_date=None):
"""Calculate remaining shelf life"""
if current_date is :
current_date = datetime.now()
(manufacturing_date, ):
manufacturing_date = pd.to_datetime(manufacturing_date)
(current_date, ):
current_date = pd.to_datetime(current_date)
expiration_date = manufacturing_date + timedelta(days=.shelf_life_days)
rsl_days = (expiration_date - current_date).days
rsl_pct = (rsl_days / .shelf_life_days) *
{
: manufacturing_date,
: expiration_date,
: current_date,
: (, rsl_days),
: (, rsl_pct),
: rsl_days <=
}
():
zone_name, zone_info .zones.items():
zone_info[] <= rsl_pct < zone_info[]:
{
: zone_name,
: zone_info[]
}
{: , : }
():
current_date = datetime.now()
inventory_df[] = inventory_df[].apply(
x: .calculate_rsl(x, current_date)
)
inventory_df[] = inventory_df[].apply( x: x[])
inventory_df[] = inventory_df[].apply( x: x[])
inventory_df[] = inventory_df[].apply(
x: x[]
)
inventory_df[] = inventory_df[].apply( x: x[])
inventory_df[] = inventory_df[].apply(.classify_zone)
inventory_df[] = inventory_df[].apply( x: x[])
inventory_df[] = inventory_df[].apply( x: x[])
zone_summary = inventory_df.groupby().agg({
: ,
:
}).rename(columns={: })
expiring_soon = inventory_df[
(inventory_df[] <= ) &
(inventory_df[] > )
]
expired_inventory = inventory_df[inventory_df[] == ]
report = {
: inventory_df[].(),
: (inventory_df),
: zone_summary,
: {
: expiring_soon[].(),
: (expiring_soon),
: expiring_soon[[, , , , ]]
},
: {
: expired_inventory[].(),
: (expired_inventory),
: expired_inventory[[, , , , ]]
}
}
report
manager = ShelfLifeManager(shelf_life_days=)
inventory = pd.DataFrame({
: [, , , , ],
: [, , , , ],
: [
datetime.now() - timedelta(days=),
datetime.now() - timedelta(days=),
datetime.now() - timedelta(days=),
datetime.now() - timedelta(days=),
datetime.now() - timedelta(days=)
],
: [, , , , ],
: [, , , , ]
})
report = manager.generate_shelf_life_report(inventory)
()
(report[])
()
()
FEFO (First-Expired-First-Out) Implementation
FEFO Allocation Logic
class FEFOInventoryManager:
"""
Implement FEFO (First-Expired-First-Out) inventory allocation
"""
def __init__(self, inventory_df):
"""
Initialize with inventory
Parameters:
- inventory_df: DataFrame with columns ['sku', 'lot', 'expiration_date',
'quantity', 'location']
"""
self.inventory = inventory_df.copy()
def allocate_order(self, sku, quantity_needed, location=None,
min_rsl_days=None):
"""
Allocate inventory using FEFO logic
Parameters:
- sku: product SKU
- quantity_needed: quantity to allocate
- location: preferred location (None = any)
- min_rsl_days: minimum remaining shelf life (customer requirement)
Returns:
- allocation list of lots
"""
available = self.inventory[
(self.inventory['sku'] == sku) &
(self.inventory['quantity'] > 0)
].copy()
if location:
available = available[available['location'] == location]
if min_rsl_days:
current_date = datetime.now()
available = available[
(available['expiration_date'] - current_date).dt.days >= min_rsl_days
]
available = available.sort_values('expiration_date')
allocation = []
remaining_need = quantity_needed
for idx, row available.iterrows():
remaining_need <= :
allocate_qty = (remaining_need, row[])
allocation.append({
: sku,
: row[],
: row[],
: row[],
: allocate_qty,
: (row[] - datetime.now()).days
})
remaining_need -= allocate_qty
.inventory.loc[idx, ] -= allocate_qty
allocated_qty = (a[] a allocation)
shortage = quantity_needed - allocated_qty
{
: allocation,
: allocated_qty,
: shortage,
: allocated_qty / quantity_needed quantity_needed >
}
():
summary = .inventory.groupby([, ]).agg({
: ,
: ,
: [, ]
})
summary
inventory = pd.DataFrame({
: [, , , ],
: [, , , ],
: pd.to_datetime([
,
,
,
]),
: [, , , ],
: [, , , ]
})
fefo = FEFOInventoryManager(inventory)
order = fefo.allocate_order(
sku=,
quantity_needed=,
location=,
min_rsl_days=
)
()
alloc order[]:
(
)
()
()
Waste Reduction Strategies
Dynamic Markdown Optimization
import numpy as np
from scipy.optimize import minimize_scalar
def optimize_markdown_timing(current_rsl_days, regular_price, cost,
demand_elasticity=-2.0):
"""
Optimize when to markdown product to minimize waste
Parameters:
- current_rsl_days: remaining shelf life
- regular_price: normal selling price
- cost: product cost
- demand_elasticity: price elasticity of demand
Returns:
- optimal markdown timing and price
"""
def expected_profit(markdown_day):
"""Calculate expected profit if markdown starts on given day"""
days_full_price = min(markdown_day, current_rsl_days)
days_markdown = max(0, current_rsl_days - markdown_day)
daily_demand_full = 10
markdown_pct = min(0.5, days_markdown / current_rsl_days)
markdown_price = regular_price * (1 - markdown_pct)
demand_lift = (markdown_pct / 0.5) ** (-demand_elasticity)
daily_demand_markdown = daily_demand_full * demand_lift
sales_full_price = days_full_price * daily_demand_full * regular_price
sales_markdown = days_markdown * daily_demand_markdown * markdown_price
units_sold = (days_full_price * daily_demand_full +
days_markdown * daily_demand_markdown)
total_cost = units_sold * cost
profit = sales_full_price + sales_markdown - total_cost
waste = (, - units_sold)
waste_cost = waste * cost
profit - waste_cost
result = minimize_scalar(
x: -expected_profit(x),
bounds=(, current_rsl_days),
method=
)
optimal_day = (result.x)
optimal_profit = -result.fun
markdown_pct = (, (current_rsl_days - optimal_day) / current_rsl_days)
{
: optimal_day,
: optimal_day,
: markdown_pct * ,
: regular_price * ( - markdown_pct),
: optimal_profit
}
markdown_strategy = optimize_markdown_timing(
current_rsl_days=,
regular_price=,
cost=,
demand_elasticity=-
)
()
()
()
Waste Tracking and Analysis
class WasteAnalyzer:
"""
Track and analyze waste from expiration
"""
def __init__(self):
self.waste_records = []
def record_waste(self, waste_data):
"""Record waste event"""
self.waste_records.append(waste_data)
def analyze_waste(self):
"""Analyze waste patterns"""
if not self.waste_records:
return None
df = pd.DataFrame(self.waste_records)
analysis = {
'total_waste_units': df['quantity'].sum(),
'total_waste_value': (df['quantity'] * df['unit_cost']).sum(),
'waste_by_sku': df.groupby('sku').agg({
'quantity': 'sum',
'unit_cost': lambda x: (df.loc[x.index, 'quantity'] * x).sum()
}),
'waste_by_location': df.groupby('location')['quantity'].sum(),
'waste_by_reason': df.groupby('reason')['quantity'].sum(),
'avg_rsl_at_waste': df[].mean()
}
analysis[] = analysis[].nlargest(, )
df.columns:
analysis[] = (
df[].() / df[].() *
)
analysis
():
df = pd.DataFrame(.waste_records)
drivers = {}
overstock_waste = df[df[] == ]
drivers[] = {
: (overstock_waste) / (df) * ,
: (overstock_waste[] * overstock_waste[]).()
}
long_lt_waste = df[df[] > ]
drivers[] = {
: (long_lt_waste) / (df) * ,
: (long_lt_waste[] * long_lt_waste[]).()
}
forecast_error_waste = df[df[].() > ]
drivers[] = {
: (forecast_error_waste) / (df) * ,
: (forecast_error_waste[] *
forecast_error_waste[]).()
}
rotation_waste = df[df[] == ]
drivers[] = {
: (rotation_waste) / (df) * ,
: (rotation_waste[] * rotation_waste[]).()
}
drivers
analyzer = WasteAnalyzer()
analyzer.record_waste({
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
:
})
analysis = analyzer.analyze_waste()
drivers = analyzer.identify_waste_drivers()
()
()
()
driver, data drivers.items():
()
Freshness Optimization
Supplier Selection Based on Age
def select_supplier_by_freshness(suppliers, demand, min_rsl_required):
"""
Select suppliers to maximize freshness
Parameters:
- suppliers: list of suppliers with available product and RSL
- demand: total demand to fulfill
- min_rsl_required: minimum RSL acceptable
Returns:
- optimal supplier selection
"""
from pulp import *
prob = LpProblem("Freshness_Optimization", LpMaximize)
x = LpVariable.dicts("Quantity",
[s['supplier_id'] for s in suppliers],
lowBound=0,
cat='Continuous')
objective = lpSum([
x[s['supplier_id']] * s['rsl_days']
for s in suppliers
])
prob += objective
prob += lpSum([x[s['supplier_id']] for s in suppliers]) >= demand
for s in suppliers:
prob += x[s['supplier_id']] <= s['available_quantity']
for s in suppliers:
if s['rsl_days'] < min_rsl_required:
prob += x[s['supplier_id']] == 0
prob.solve(PULP_CBC_CMD(msg=))
results = []
s suppliers:
qty = x[s[]].varValue
qty > :
results.append({
: s[],
: qty,
: s[],
: qty * s[]
})
total_qty = (r[] r results)
weighted_rsl = (r[] * r[] r results) / total_qty
{
: results,
: total_qty,
: weighted_rsl,
: (r[] r results)
}
suppliers = [
{
: ,
: ,
: ,
:
},
{
: ,
: ,
: ,
:
},
{
: ,
: ,
: ,
:
}
]
result = select_supplier_by_freshness(
suppliers=suppliers,
demand=,
min_rsl_required=
)
()
alloc result[]:
(
)
()
Regulatory Compliance
Date Code Management
class DateCodeManager:
"""
Manage date codes and regulatory compliance
"""
def __init__(self, date_format='%Y%m%d'):
self.date_format = date_format
def parse_date_code(self, date_code, code_type='manufacturing'):
"""
Parse date code to datetime
Common formats:
- YYYYMMDD: 20250115
- YYMMDD: 250115
- Julian: 25015 (year + day of year)
"""
if len(date_code) == 8:
return datetime.strptime(date_code, '%Y%m%d')
elif len(date_code) == 6:
return datetime.strptime(date_code, '%y%m%d')
elif len(date_code) == 5:
year = int('20' + date_code[:2])
day_of_year = int(date_code[2:])
return datetime(year, 1, 1) + timedelta(days=day_of_year - 1)
else:
raise ValueError(f"Unknown date code format: {date_code}")
def validate_date_code(self, date_code, product_type=):
:
parsed_date = .parse_date_code(date_code)
:
{: , : }
current_date = datetime.now()
parsed_date > current_date:
{: , : }
max_age_days = {
: ,
: ,
: ,
: ,
:
}
age_days = (current_date - parsed_date).days
max_age = max_age_days.get(product_type, )
age_days > max_age:
{
: ,
:
}
{: , : parsed_date, : age_days}
():
(manufacturing_date, ):
manufacturing_date = .parse_date_code(manufacturing_date)
expiration_date = manufacturing_date + timedelta(days=shelf_life_days)
sell_by_date = expiration_date - timedelta(days=sell_by_buffer_days)
{
: manufacturing_date,
: expiration_date,
: sell_by_date,
: shelf_life_days
}
manager = DateCodeManager()
date_info = manager.parse_date_code()
()
validation = manager.validate_date_code(, product_type=)
()
expiry = manager.calculate_expiration_date(
manufacturing_date=,
shelf_life_days=,
sell_by_buffer_days=
)
()
()
Tools & Technologies
Shelf Life Management Software
Warehouse Management Systems (WMS) with FEFO:
- Manhattan Associates WMS: Advanced FEFO and lot tracking
- Blue Yonder WMS: Shelf life management
- SAP EWM: Extended warehouse management with expiry
- Oracle WMS: Date code and FEFO support
- HighJump WMS: Perishables management
Specialized Solutions:
- FoodLogiQ: Food traceability and date code management
- Trace Register: Supply chain traceability
- rfxcel: Serialization and expiry tracking
- FreshSurety: Shelf life and temperature monitoring
- ZestIOT: Real-time freshness monitoring
Markdown Optimization:
- Revionics: Price and markdown optimization (Oracle)
- Pricefx: Dynamic pricing with expiry
- PROS: AI-driven markdown optimization
Python Libraries
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
from pulp import *
from scipy.optimize import minimize, minimize_scalar
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
Common Challenges & Solutions
Challenge: High Waste Rate
Problem:
- 5-10% of inventory expires
- Significant cost impact
- Lost revenue
Solutions:
- Implement FEFO rigorously
- Reduce order quantities (more frequent orders)
- Improve demand forecasting
- Dynamic safety stock (reduce as expiration approaches)
- Markdown earlier and more aggressively
- Donate near-expiry (tax benefit, goodwill)
Challenge: Inconsistent Date Code Formats
Problem:
- Suppliers use different formats
- Manual tracking error-prone
- Compliance risk
Solutions:
- Standardize date code format across suppliers
- Automated date code parsing (OCR, barcode)
- Validation at receiving
- WMS integration
- Master data management
Challenge: Customer Freshness Requirements
Problem:
- Retailers require 75% minimum RSL
- Limits usable inventory
- Increases waste at DC
Solutions:
- Negotiate RSL requirements
- Price incentives for lower RSL
- Fast replenishment to stores
- Allocate fresher stock to demanding customers
- Use older stock for promotions
Challenge: Multi-Echelon Complexity
Problem:
- DCs hold aging inventory
- Stores also have freshness requirements
- Difficult to optimize across network
Solutions:
- Network-wide visibility of RSL
- Centralized allocation (freshest to furthest)
- Dynamic routing based on expiry
- Cross-docking for fast movers
- DC bypass for fresh products
Output Format
Shelf Life Performance Report
Executive Summary:
- Total Inventory: 500,000 units
- Waste Rate: 3.2% (down from 5.1% last year)
- Waste Value: $320,000 annually
- Average RSL at Sale: 68%
- Compliance: 100% (no expired products sold)
Expiration Summary:
| Zone | Units | % of Total | Action Required |
|---|
| Green (>67% RSL) | 350,000 | 70% | Normal sales |
| Yellow (33-67% RSL) | 100,000 | 20% | Priority outbound |
| Red (10-33% RSL) | 45,000 | 9% | Markdown now |
| Expired (<10% RSL) | 5,000 | 1% | Pull immediately |
Expiring in Next 30 Days:
| SKU | Location | Quantity | Exp Date | RSL Days | Action |
|---|
| SKU_A | DC1 | 2,500 | 2025-02-15 | 15 | 30% markdown |
| SKU_B | DC2 | 1,200 | 2025-02-10 | 10 | 50% markdown |
| SKU_C | DC1 | 800 | 2025-02-05 | 5 | Pull/donate |
Waste Analysis:
| Category | Waste Units | Value | % of Total Waste |
|---|
| Overstock | 8,000 | $160,000 | 50% |
| Forecast Error | 4,000 | $80,000 | 25% |
| Long Lead Time | 3,000 | $60,000 | 18.75% |
| Improper Rotation | 1,000 | $20,000 | 6.25% |
Recommendations:
- Implement automated FEFO allocation (reduce rotation errors)
- Reduce order quantities for SKU_A, SKU_B (high waste items)
- Earlier markdown trigger for slow movers (Red zone → markdown at 40% RSL)
- Partner with food bank for donation program
- Negotiate extended RSL requirements with retailers
Questions to Ask
If you need more context:
- What products have shelf life concerns? Shelf life duration?
- Current waste/spoilage rate and cost?
- Do you have FEFO capability in WMS?
- What are customer RSL requirements?
- Date code tracking and format?
- Markdown process and timing?
- Multi-echelon network or single location?
- Regulatory requirements (FDA, USDA, etc.)?
Related Skills
- inventory-optimization: For safety stock with expiration constraints
- demand-forecasting: To reduce overstock and waste
- warehouse-slotting-optimization: For FEFO-friendly slotting
- food-beverage-supply-chain: For perishable product supply chain
- pharmaceutical-supply-chain: For drug expiry management
- markdown-optimization: For price optimization of expiring products
- quality-management: For quality control and compliance
- replenishment-strategy: For optimal reorder policies with expiry