When the user wants to optimize seasonal planning, manage seasonal buy decisions, or plan for seasonal demand. Also use when the user mentions "seasonal planning," "seasonal buy," "holiday planning," "back-to-school," "spring/fall collection," "seasonal inventory," "peak season," or "seasonal assortment." For demand forecasting, see demand-forecasting. For retail allocation, see retail-allocation.
You are an expert in retail seasonal planning and merchandise buying. Your goal is to help retailers plan seasonal assortments, optimize buy quantities, manage seasonal inventory, and execute successful seasonal transitions while balancing sales maximization with markdown risk.
Before planning seasonal buys, understand:
Business Context
Financial Targets
Product Mix
Historical Data Available
Pre-Season (Weeks -12 to 0)
Early Season (Weeks 1-4)
Peak Season (Weeks 5-8)
Late Season (Weeks 9-12)
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from scipy import stats
class SeasonalBuyOptimizer:
"""
Optimize seasonal buy quantities
Balance:
- Under-buying: Lost sales (stockouts)
- Over-buying: Markdowns and excess inventory
"""
def __init__(self, season_config):
"""
Parameters:
- season_config: Season parameters (length, targets, costs)
"""
self.season = season_config
def calculate_optimal_buy(self, sku_forecast, unit_cost, retail_price,
markdown_rate=0.50, stockout_cost_multiplier=1.5):
"""
Calculate optimal buy quantity using newsvendor model
Classic single-period inventory problem
"""
# Expected demand
mean_demand = sku_forecast['mean']
std_demand = sku_forecast['std']
# Profit margins
full_price_margin = retail_price - unit_cost
markdown_price = retail_price * (1 - markdown_rate)
markdown_margin = markdown_price - unit_cost
# Cost of under-stocking (lost profit + goodwill)
cost_understocking = full_price_margin * stockout_cost_multiplier
# Cost of over-stocking (forced markdown or liquidation)
cost_overstocking = unit_cost - markdown_price
# Critical ratio (newsvendor)
critical_ratio = cost_understocking / (cost_understocking + cost_overstocking)
# Optimal order quantity (quantile of demand distribution)
optimal_quantity = stats.norm.ppf(critical_ratio, mean_demand, std_demand)
# Calculate expected profit at optimal quantity
expected_sales = self._expected_sales(optimal_quantity, mean_demand, std_demand)
expected_markdowns = max(0, optimal_quantity - expected_sales)
expected_revenue = (expected_sales * retail_price +
expected_markdowns * markdown_price)
expected_cost = optimal_quantity * unit_cost
expected_profit = expected_revenue - expected_cost
# Service level (fill rate)
service_level = stats.norm.cdf(optimal_quantity, mean_demand, std_demand)
return {
'optimal_buy_quantity': round(optimal_quantity, 0),
'expected_demand': mean_demand,
'demand_std': std_demand,
'expected_sales': round(expected_sales, 0),
'expected_markdowns': round(expected_markdowns, 0),
'expected_profit': round(expected_profit, 2),
'service_level': round(service_level * 100, 1),
'markdown_rate': markdown_rate * 100,
'critical_ratio': round(critical_ratio, 3)
}
def _expected_sales(self, quantity, mean, std):
"""
Calculate expected sales given quantity
Accounts for potential stockouts
"""
# E[Sales] = E[min(Demand, Quantity)]
# Using normal distribution approximation
if std == 0:
return min(quantity, mean)
z = (quantity - mean) / std
expected_sales = mean * stats.norm.cdf(z) + std * stats.norm.pdf(z)
return min(expected_sales, quantity)
def optimize_assortment_mix(self, product_options, total_budget,
category_constraints=None):
"""
Optimize product mix within budget
Select which products to buy and in what quantities
"""
n_products = len(product_options)
# Objective: Maximize total expected profit
def objective(quantities):
total_profit = 0
for i, qty in enumerate(quantities):
product = product_options.iloc[i]
# Calculate profit for this quantity
result = self.calculate_optimal_buy(
sku_forecast={'mean': product['forecast_mean'],
'std': product['forecast_std']},
unit_cost=product['unit_cost'],
retail_price=product['retail_price']
)
# Adjust for actual quantity vs. optimal
if qty > 0:
# Approximate profit at this quantity
profit_at_qty = result['expected_profit'] * (qty / result['optimal_buy_quantity'])
total_profit += profit_at_qty
return -total_profit # Negative for minimization
# Constraints
def budget_constraint(quantities):
total_cost = sum(
quantities[i] * product_options.iloc[i]['unit_cost']
for i in range(n_products)
)
return total_budget - total_cost
constraints = [{'type': 'ineq', 'fun': budget_constraint}]
# Bounds (non-negative quantities)
bounds = [(0, product_options.iloc[i]['max_quantity']) for i in range(n_products)]
# Initial guess (proportional to forecast)
x0 = np.array([
min(product_options.iloc[i]['forecast_mean'],
product_options.iloc[i]['max_quantity'])
for i in range(n_products)
]) * 0.8 # Start conservative
# Optimize
result = minimize(objective, x0, method='SLSQP',
bounds=bounds, constraints=constraints)
optimal_quantities = result.x
# Build result dataframe
results = []
for i, qty in enumerate(optimal_quantities):
product = product_options.iloc[i]
if qty > 5: # Only include products with meaningful quantity
buy_analysis = self.calculate_optimal_buy(
sku_forecast={'mean': product['forecast_mean'],
'std': product['forecast_std']},
unit_cost=product['unit_cost'],
retail_price=product['retail_price']
)
results.append({
'sku': product['sku'],
'category': product['category'],
'buy_quantity': round(qty, 0),
'unit_cost': product['unit_cost'],
'retail_price': product['retail_price'],
'total_cost': round(qty * product['unit_cost'], 2),
'expected_profit': buy_analysis['expected_profit'],
'expected_markdown_rate': buy_analysis['markdown_rate']
})
results_df = pd.DataFrame(results)
return results_df, result
def calculate_open_to_buy(self, sales_plan, beginning_inventory,
on_order, target_end_inventory,
markdown_receipts=0):
"""
Calculate open-to-buy (OTB) budget
OTB = Sales Plan + Target End Inv - Beginning Inv - On Order + Markdowns
"""
otb = (
sales_plan +
target_end_inventory -
beginning_inventory -
on_order +
markdown_receipts
)
return {
'sales_plan': sales_plan,
'beginning_inventory': beginning_inventory,
'on_order': on_order,
'target_end_inventory': target_end_inventory,
'markdown_receipts': markdown_receipts,
'open_to_buy': otb,
'otb_pct_of_sales': (otb / sales_plan * 100) if sales_plan > 0 else 0
}
# Example usage
season_config = {
'season_name': 'Fall 2024',
'start_date': '2024-08-01',
'end_date': '2024-11-30',
'weeks': 16
}
optimizer = SeasonalBuyOptimizer(season_config)
# Single SKU optimization
sku_forecast = {'mean': 500, 'std': 150}
buy_decision = optimizer.calculate_optimal_buy(
sku_forecast=sku_forecast,
unit_cost=25,
retail_price=60,
markdown_rate=0.50
)
print("Optimal Buy Analysis:")
print(f" Optimal quantity: {buy_decision['optimal_buy_quantity']}")
print(f" Expected sales: {buy_decision['expected_sales']}")
print(f" Expected markdowns: {buy_decision['expected_markdowns']}")
print(f" Expected profit: ${buy_decision['expected_profit']:,.2f}")
print(f" Service level: {buy_decision['service_level']:.1f}%")
# Assortment optimization
product_options = pd.DataFrame({
'sku': [f'SKU{i:03d}' for i in range(1, 21)],
'category': np.random.choice(['Tops', 'Bottoms', 'Dresses'], 20),
'forecast_mean': np.random.uniform(200, 800, 20),
'forecast_std': np.random.uniform(50, 200, 20),
'unit_cost': np.random.uniform(15, 40, 20),
'retail_price': np.random.uniform(40, 100, 20),
'max_quantity': 1000
})
assortment, optimization_result = optimizer.optimize_assortment_mix(
product_options,
total_budget=150000
)
print(f"\nOptimized Assortment (Budget: $150K):")
print(f"Products selected: {len(assortment)}")
print(f"Total cost: ${assortment['total_cost'].sum():,.0f}")
print(f"Expected total profit: ${assortment['expected_profit'].sum():,.0f}")
class SeasonalDemandForecaster:
"""
Forecast seasonal demand patterns
Accounts for:
- Historical seasonal trends
- Year-over-year growth
- Fashion trends and newness
- Weather impacts
"""
def __init__(self, historical_data):
"""
Parameters:
- historical_data: Historical sales by week/season
columns: ['season', 'year', 'week', 'sales', 'category']
"""
self.history = historical_data
def forecast_seasonal_curve(self, season, category):
"""
Create seasonal sales curve
Shows expected % of season sales by week
"""
# Get historical data for this season
season_history = self.history[
(self.history['season'] == season) &
(self.history['category'] == category)
]
if len(season_history) == 0:
# Use generic curve
return self._generic_seasonal_curve()
# Average sales by week across years
weekly_avg = season_history.groupby('week')['sales'].mean()
total_season_sales = weekly_avg.sum()
# Calculate % distribution
weekly_pct = (weekly_avg / total_season_sales * 100).to_dict()
# Smooth the curve
weeks = sorted(weekly_pct.keys())
smoothed_pct = {}
for week in weeks:
# 3-week moving average
nearby_weeks = [w for w in weeks if abs(w - week) <= 1]
smoothed_pct[week] = np.mean([weekly_pct[w] for w in nearby_weeks])
return smoothed_pct
def _generic_seasonal_curve(self):
"""Generic seasonal curve (normal distribution)"""
weeks = range(1, 17) # 16-week season
peak_week = 6 # Peak in week 6
curve = {}
total = 0
for week in weeks:
# Normal distribution centered at peak
sales = np.exp(-((week - peak_week) ** 2) / 20)
curve[week] = sales
total += sales
# Convert to percentages
for week in weeks:
curve[week] = curve[week] / total * 100
return curve
def forecast_total_season_sales(self, season, category, last_year_sales,
growth_rate=0.05, trend_factor=1.0):
"""
Forecast total season sales
Based on:
- Last year performance
- Expected growth rate
- Category trends
"""
# Base forecast: last year + growth
base_forecast = last_year_sales * (1 + growth_rate)
# Adjust for trends
adjusted_forecast = base_forecast * trend_factor
return {
'season': season,
'category': category,
'last_year_sales': last_year_sales,
'growth_rate': growth_rate * 100,
'trend_factor': trend_factor,
'forecasted_sales': adjusted_forecast
}
def allocate_forecast_to_skus(self, total_forecast, sku_mix):
"""
Allocate total forecast to individual SKUs
Based on:
- Historical performance (for carry-overs)
- Analogous products (for new items)
- Price point distribution
"""
sku_forecasts = []
for idx, sku in sku_mix.iterrows():
if sku['is_new']:
# New item: use analog performance
forecast_pct = sku['analog_sales_pct']
else:
# Carry-over: use historical
forecast_pct = sku['historical_sales_pct']
# Adjust for price point appeal
price_adjustment = sku.get('price_elasticity', 1.0)
sku_forecast = total_forecast * (forecast_pct / 100) * price_adjustment
# Add uncertainty (standard deviation)
sku_std = sku_forecast * 0.30 # 30% coefficient of variation
sku_forecasts.append({
'sku': sku['sku'],
'forecast_mean': sku_forecast,
'forecast_std': sku_std,
'is_new': sku['is_new'],
'confidence': 'Low' if sku['is_new'] else 'High'
})
return pd.DataFrame(sku_forecasts)
def simulate_season(self, initial_inventory, seasonal_curve,
total_forecast, n_simulations=1000):
"""
Monte Carlo simulation of season performance
Accounts for demand uncertainty
"""
results = []
for sim in range(n_simulations):
# Simulate demand with uncertainty
weekly_demand_pct = seasonal_curve.copy()
# Add random variation
for week in weekly_demand_pct.keys():
noise = np.random.normal(1.0, 0.15) # 15% noise
weekly_demand_pct[week] *= noise
# Normalize back to 100%
total_pct = sum(weekly_demand_pct.values())
weekly_demand_pct = {k: v/total_pct*100 for k, v in weekly_demand_pct.items()}
# Simulate season
inventory = initial_inventory
total_sales = 0
total_stockouts = 0
for week, pct in sorted(weekly_demand_pct.items()):
weekly_demand = total_forecast * (pct / 100)
# Sales limited by inventory
weekly_sales = min(weekly_demand, inventory)
stockout = max(0, weekly_demand - inventory)
inventory -= weekly_sales
total_sales += weekly_sales
total_stockouts += stockout
# Calculate metrics
sell_through_rate = (total_sales / initial_inventory * 100) if initial_inventory > 0 else 0
stockout_rate = (total_stockouts / total_forecast * 100) if total_forecast > 0 else 0
leftover_inventory = inventory
results.append({
'simulation': sim,
'total_sales': total_sales,
'sell_through_rate': sell_through_rate,
'stockout_rate': stockout_rate,
'leftover_inventory': leftover_inventory
})
results_df = pd.DataFrame(results)
# Summary statistics
summary = {
'mean_sell_through': results_df['sell_through_rate'].mean(),
'p10_sell_through': results_df['sell_through_rate'].quantile(0.10),
'p50_sell_through': results_df['sell_through_rate'].quantile(0.50),
'p90_sell_through': results_df['sell_through_rate'].quantile(0.90),
'mean_stockout_rate': results_df['stockout_rate'].mean(),
'mean_leftover': results_df['leftover_inventory'].mean()
}
return results_df, summary
# Example
historical_data = pd.DataFrame({
'season': ['Fall'] * 48,
'year': [2021, 2021, 2022, 2022, 2023, 2023] * 8,
'week': sorted(list(range(1, 9)) * 6),
'sales': np.random.uniform(5000, 15000, 48),
'category': 'Sweaters'
})
forecaster = SeasonalDemandForecaster(historical_data)
# Get seasonal curve
curve = forecaster.forecast_seasonal_curve('Fall', 'Sweaters')
print("Seasonal Curve (% of total sales by week):")
for week, pct in sorted(curve.items())[:8]:
print(f" Week {week}: {pct:.1f}%")
# Forecast total season
season_forecast = forecaster.forecast_total_season_sales(
season='Fall',
category='Sweaters',
last_year_sales=500000,
growth_rate=0.08,
trend_factor=1.1
)
print(f"\nForecast total season sales: ${season_forecast['forecasted_sales']:,.0f}")
# Simulate season
simulation_results, summary = forecaster.simulate_season(
initial_inventory=5000,
seasonal_curve=curve,
total_forecast=season_forecast['forecasted_sales'] / 50, # Per SKU
n_simulations=1000
)
print(f"\nSeason Simulation Results:")
print(f" Mean sell-through: {summary['mean_sell_through']:.1f}%")
print(f" P10/P50/P90 sell-through: {summary['p10_sell_through']:.1f}% / {summary['p50_sell_through']:.1f}% / {summary['p90_sell_through']:.1f}%")
print(f" Mean stockout rate: {summary['mean_stockout_rate']:.1f}%")
class InSeasonManager:
"""
Manage in-season performance
React to actual performance vs. plan
"""
def __init__(self, season_plan):
self.plan = season_plan
def identify_chase_opportunities(self, actual_sales, weeks_elapsed,
current_inventory):
"""
Identify products to chase (reorder)
Chase when:
- Selling faster than planned
- Current inventory insufficient for season
- Vendor lead time allows
"""
opportunities = []
for sku, sales in actual_sales.items():
plan_sales = self.plan.get(sku, {}).get('total_plan', 0)
weeks_remaining = self.plan['season_weeks'] - weeks_elapsed
if weeks_elapsed == 0:
continue
# Calculate sell-through rate
weekly_rate = sales / weeks_elapsed
projected_total_sales = weekly_rate * self.plan['season_weeks']
# Compare to plan
vs_plan_pct = (projected_total_sales / plan_sales - 1) * 100 if plan_sales > 0 else 0
# Check inventory sufficiency
inventory_remaining = current_inventory.get(sku, 0)
projected_remaining_sales = weekly_rate * weeks_remaining
if vs_plan_pct > 20 and inventory_remaining < projected_remaining_sales:
# Chase opportunity
chase_qty = projected_remaining_sales - inventory_remaining
# Check if lead time allows
vendor_lead_time = self.plan.get(sku, {}).get('lead_time_weeks', 8)
if weeks_remaining > vendor_lead_time + 2: # Buffer
opportunities.append({
'sku': sku,
'vs_plan': vs_plan_pct,
'projected_total_sales': projected_total_sales,
'inventory_remaining': inventory_remaining,
'recommended_chase_qty': round(chase_qty, 0),
'urgency': 'High' if weeks_remaining < vendor_lead_time + 4 else 'Medium'
})
return pd.DataFrame(opportunities)
def identify_markdown_candidates(self, actual_sales, weeks_elapsed,
current_inventory, target_str=0.75):
"""
Identify products needing markdown
Markdown when:
- Selling slower than planned
- Risk of excess inventory at season end
"""
candidates = []
weeks_remaining = self.plan['season_weeks'] - weeks_elapsed
for sku, sales in actual_sales.items():
initial_buy = self.plan.get(sku, {}).get('buy_quantity', 0)
inventory_remaining = current_inventory.get(sku, 0)
if initial_buy == 0:
continue
# Current sell-through rate
current_str = (initial_buy - inventory_remaining) / initial_buy
# Projected final sell-through
if weeks_elapsed > 0:
weekly_rate = sales / weeks_elapsed
projected_additional_sales = weekly_rate * weeks_remaining
projected_final_str = (sales + projected_additional_sales) / initial_buy
else:
projected_final_str = 0
# If projected STR < target, need markdown
if projected_final_str < target_str and inventory_remaining > 0:
# Recommend markdown depth based on urgency
if projected_final_str < 0.50:
recommended_markdown = 40
elif projected_final_str < 0.60:
recommended_markdown = 30
else:
recommended_markdown = 20
candidates.append({
'sku': sku,
'current_str': round(current_str * 100, 1),
'projected_str': round(projected_final_str * 100, 1),
'inventory_remaining': inventory_remaining,
'recommended_markdown': recommended_markdown,
'urgency': 'High' if projected_final_str < 0.50 else 'Medium'
})
return pd.DataFrame(candidates)
def calculate_season_health_score(self, actual_sales, weeks_elapsed,
current_inventory):
"""
Calculate overall season health score (0-100)
Factors:
- Sales vs. plan
- Sell-through rate
- Markdown rate
- Inventory balance
"""
total_plan_sales = sum(sku.get('total_plan', 0) for sku in self.plan.values() if isinstance(sku, dict))
total_actual_sales = sum(actual_sales.values())
# Sales attainment
if total_plan_sales > 0:
sales_attainment = total_actual_sales / (total_plan_sales * weeks_elapsed / self.plan['season_weeks'])
else:
sales_attainment = 0
sales_score = min(sales_attainment * 50, 50) # Max 50 points
# Sell-through rate
total_initial_buy = sum(sku.get('buy_quantity', 0) for sku in self.plan.values() if isinstance(sku, dict))
total_current_inv = sum(current_inventory.values())
if total_initial_buy > 0:
current_str = (total_initial_buy - total_current_inv) / total_initial_buy
else:
current_str = 0
# Target STR at this point in season
target_str_now = weeks_elapsed / self.plan['season_weeks'] * 0.80 # 80% by end
str_score = min(current_str / target_str_now * 30, 30) if target_str_now > 0 else 0
# Inventory balance (not too much, not too little)
weeks_remaining = self.plan['season_weeks'] - weeks_elapsed
weekly_run_rate = total_actual_sales / weeks_elapsed if weeks_elapsed > 0 else 0
weeks_of_supply = total_current_inv / weekly_run_rate if weekly_run_rate > 0 else 999
if 0.8 * weeks_remaining <= weeks_of_supply <= 1.2 * weeks_remaining:
balance_score = 20 # Perfect balance
else:
balance_score = max(0, 20 - abs(weeks_of_supply - weeks_remaining) * 2)
total_score = sales_score + str_score + balance_score
return {
'health_score': round(total_score, 1),
'sales_attainment': round(sales_attainment * 100, 1),
'sell_through_rate': round(current_str * 100, 1),
'weeks_of_supply': round(weeks_of_supply, 1),
'interpretation': self._interpret_health_score(total_score)
}
def _interpret_health_score(self, score):
"""Interpret health score"""
if score >= 80:
return 'Excellent - on track for strong season'
elif score >= 65:
return 'Good - minor adjustments needed'
elif score >= 50:
return 'Fair - action required'
else:
return 'Poor - aggressive intervention needed'
# Example
season_plan = {
'season_weeks': 16,
'SKU001': {'total_plan': 10000, 'buy_quantity': 8000, 'lead_time_weeks': 6},
'SKU002': {'total_plan': 15000, 'buy_quantity': 12000, 'lead_time_weeks': 8},
'SKU003': {'total_plan': 5000, 'buy_quantity': 5000, 'lead_time_weeks': 6}
}
manager = InSeasonManager(season_plan)
# Week 6 performance
actual_sales = {'SKU001': 4500, 'SKU002': 4000, 'SKU003': 1200}
current_inventory = {'SKU001': 2500, 'SKU002': 7000, 'SKU003': 3500}
weeks_elapsed = 6
# Identify chase opportunities
chase_opps = manager.identify_chase_opportunities(
actual_sales, weeks_elapsed, current_inventory
)
print("Chase Opportunities:")
print(chase_opps)
# Identify markdown candidates
markdown_candidates = manager.identify_markdown_candidates(
actual_sales, weeks_elapsed, current_inventory, target_str=0.75
)
print("\nMarkdown Candidates:")
print(markdown_candidates)
# Season health score
health = manager.calculate_season_health_score(
actual_sales, weeks_elapsed, current_inventory
)
print(f"\nSeason Health Score: {health['health_score']:.1f}/100")
print(f"Interpretation: {health['interpretation']}")
print(f"Sales attainment: {health['sales_attainment']:.1f}%")
print(f"Sell-through rate: {health['sell_through_rate']:.1f}%")
Optimization:
scipy.optimize: Newsvendor optimizationpulp, pyomo: Linear programming for assortmentnumpy: Numerical computationsForecasting:
statsmodels: Time series analysisprophet: Seasonal forecastingpandas: Data manipulationSimulation:
numpy.random: Monte Carlo simulationscipy.stats: Statistical distributionsPlanning Systems:
Specialized Tools:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Executive Summary:
Financial Plan:
| Metric | Target |
|---|---|
| Total buy at cost | $4.2M |
| Total buy at retail | $10.5M |
| Initial markup (IMU) | 62% |
| Sales plan | $9.2M |
| Sell-through target | 88% |
| Markdown budget | 4% of sales ($368K) |
| Maintained margin | 58% |
| Gross profit | $5.3M |
Category Mix:
| Category | Buy $ | Buy % | SKU Count | Avg Price | Strategy |
|---|---|---|---|---|---|
| Outerwear | $1.2M | 29% | 65 | $125 | Core + fashion, focus on trend colors |
| Sweaters | $980K | 23% | 95 | $68 | Basics with fashion accents |
| Dresses | $750K | 18% | 80 | $95 | Fashion-forward, limited quantities |
| Tops | $620K | 15% | 110 | $45 | High volume, core basics |
| Bottoms | $480K | 11% | 55 | $78 | Denim focus, seasonal colors |
| Accessories | $170K | 4% | 20 | $35 | Impulse items, high margin |
New vs. Carry-Over:
| Type | Buy $ | Buy % | Risk Level | Strategy |
|---|---|---|---|---|
| Carry-over (proven) | $2.5M | 60% | Low | Core basics, repeat winners |
| New items | $1.7M | 40% | High | Fashion, test quantities |
Weekly Receipt Flow:
| Week | Receipt $ | Cum % | Focus |
|---|---|---|---|
| Week -2 | $420K | 10% | Core basics early |
| Week 0 | $840K | 30% | Launch assortment |
| Week 2 | $630K | 45% | Fill-in and fashion |
| Week 4 | $420K | 55% | Fresh arrivals |
| Week 6-8 | $890K | 76% | Peak season support |
| Week 10+ | $1M | 100% | Late season, limited items |
Risk Assessment:
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Warm weather (delayed season start) | Medium | High | Conservative initial buy, chase plans ready |
| New product performance | High | Medium | Test quantities, monitor week 1 closely |
| Vendor delays | Low | High | Air freight budget, alternate suppliers |
| Competitive pricing | Medium | Medium | Markdown budget, price match capability |
Success Metrics:
| Metric | Target | Week 4 Check | Week 8 Check | Season End |
|---|---|---|---|---|
| Sales vs. plan | 100% | ≥90% | ≥95% | ≥97% |
| Sell-through rate | 88% | ≥30% | ≥60% | ≥85% |
| Markdown rate | <4% | 0% | <2% | <5% |
| Gross margin | 58% | 62% | 60% | ≥57% |
Action Plan:
| Week | Action | Owner |
|---|---|---|
| Week -4 | Final assortment review, POs placed | Buyer |
| Week -2 | First receipts, allocation to stores | Allocator |
| Week 0 | Season launch, marketing campaign | Marketing |
| Week 1 | Monitor early reads, identify trends | Planner |
| Week 4 | Chase order decisions for winners | Buyer |
| Week 8 | First markdown evaluation | Planner |
| Week 12 | Aggressive clearance markdowns | Buyer |
If you need more context: