| name | seasonal-planning |
| description | 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. |
Seasonal Planning
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.
Initial Assessment
Before planning seasonal buys, understand:
-
Business Context
- What retail category? (apparel, home, toys, etc.)
- What season? (spring, summer, fall, holiday, back-to-school)
- Season length? (weeks of selling season)
- Historical seasonal performance? (sales, sell-through, markdowns)
-
Financial Targets
- Season sales target? (revenue goal)
- Target gross margin? (initial markup, markdown budget)
- Inventory turn goals?
- Open-to-buy budget?
- Cash flow constraints?
-
Product Mix
- Carry-over vs. new products? (% of each)
- Core basics vs. fashion/trend items?
- Price point distribution? (good/better/best)
- SKU count target?
- Vendor/supplier lead times?
-
Historical Data Available
- Past season sales by week?
- Sell-through rates by category/style?
- Markdown rates and timing?
- Stockout frequency?
- Weather impacts?
Seasonal Planning Framework
Season Phases
Pre-Season (Weeks -12 to 0)
- Trend forecasting and market research
- Assortment planning (styles, colors, sizes)
- Buy planning and vendor negotiations
- Allocation planning
- Marketing campaign planning
Early Season (Weeks 1-4)
- Initial receipts and allocation
- Monitor early sell-through
- Identify fast/slow movers
- Adjust future orders (if possible)
- Replenishment decisions
Peak Season (Weeks 5-8)
- Peak sales volume
- Maintain in-stock on winners
- Begin markdown planning for slow movers
- Chase orders for hot items
- Maximize full-price selling
Late Season (Weeks 9-12)
- Aggressive markdowns to clear
- Minimize leftover inventory
- Transition space to next season
- Pack-away vs. liquidation decisions
- Post-season analysis
Buy Planning & Optimization
Seasonal Buy Quantity Optimization
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
"""
mean_demand = sku_forecast['mean']
std_demand = sku_forecast['std']
full_price_margin = retail_price - unit_cost
markdown_price = retail_price * (1 - markdown_rate)
markdown_margin = markdown_price - unit_cost
cost_understocking = full_price_margin * stockout_cost_multiplier
cost_overstocking = unit_cost - markdown_price
critical_ratio = cost_understocking / (cost_understocking + cost_overstocking)
optimal_quantity = stats.norm.ppf(critical_ratio, mean_demand, std_demand)
expected_sales = ._expected_sales(optimal_quantity, mean_demand, std_demand)
expected_markdowns = (, 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 = stats.norm.cdf(optimal_quantity, mean_demand, std_demand)
{
: (optimal_quantity, ),
: mean_demand,
: std_demand,
: (expected_sales, ),
: (expected_markdowns, ),
: (expected_profit, ),
: (service_level * , ),
: markdown_rate * ,
: (critical_ratio, )
}
():
std == :
(quantity, mean)
z = (quantity - mean) / std
expected_sales = mean * stats.norm.cdf(z) + std * stats.norm.pdf(z)
(expected_sales, quantity)
():
n_products = (product_options)
():
total_profit =
i, qty (quantities):
product = product_options.iloc[i]
result = .calculate_optimal_buy(
sku_forecast={: product[],
: product[]},
unit_cost=product[],
retail_price=product[]
)
qty > :
profit_at_qty = result[] * (qty / result[])
total_profit += profit_at_qty
-total_profit
():
total_cost = (
quantities[i] * product_options.iloc[i][]
i (n_products)
)
total_budget - total_cost
constraints = [{: , : budget_constraint}]
bounds = [(, product_options.iloc[i][]) i (n_products)]
x0 = np.array([
(product_options.iloc[i][],
product_options.iloc[i][])
i (n_products)
]) *
result = minimize(objective, x0, method=,
bounds=bounds, constraints=constraints)
optimal_quantities = result.x
results = []
i, qty (optimal_quantities):
product = product_options.iloc[i]
qty > :
buy_analysis = .calculate_optimal_buy(
sku_forecast={: product[],
: product[]},
unit_cost=product[],
retail_price=product[]
)
results.append({
: product[],
: product[],
: (qty, ),
: product[],
: product[],
: (qty * product[], ),
: buy_analysis[],
: buy_analysis[]
})
results_df = pd.DataFrame(results)
results_df, result
():
otb = (
sales_plan +
target_end_inventory -
beginning_inventory -
on_order +
markdown_receipts
)
{
: sales_plan,
: beginning_inventory,
: on_order,
: target_end_inventory,
: markdown_receipts,
: otb,
: (otb / sales_plan * ) sales_plan >
}
season_config = {
: ,
: ,
: ,
:
}
optimizer = SeasonalBuyOptimizer(season_config)
sku_forecast = {: , : }
buy_decision = optimizer.calculate_optimal_buy(
sku_forecast=sku_forecast,
unit_cost=,
retail_price=,
markdown_rate=
)
()
()
()
()
()
()
product_options = pd.DataFrame({
: [ i (, )],
: np.random.choice([, , ], ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
: np.random.uniform(, , ),
:
})
assortment, optimization_result = optimizer.optimize_assortment_mix(
product_options,
total_budget=
)
()
()
()
()
Seasonal Forecasting
Seasonal Demand Modeling
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
"""
season_history = self.history[
(self.history['season'] == season) &
(self.history['category'] == category)
]
if len(season_history) == 0:
return self._generic_seasonal_curve()
weekly_avg = season_history.groupby('week')['sales'].mean()
total_season_sales = weekly_avg.sum()
weekly_pct = (weekly_avg / total_season_sales * 100).to_dict()
weeks = sorted(weekly_pct.keys())
smoothed_pct = {}
for week in weeks:
nearby_weeks = [w w weeks (w - week) <= ]
smoothed_pct[week] = np.mean([weekly_pct[w] w nearby_weeks])
smoothed_pct
():
weeks = (, )
peak_week =
curve = {}
total =
week weeks:
sales = np.exp(-((week - peak_week) ** ) / )
curve[week] = sales
total += sales
week weeks:
curve[week] = curve[week] / total *
curve
():
base_forecast = last_year_sales * ( + growth_rate)
adjusted_forecast = base_forecast * trend_factor
{
: season,
: category,
: last_year_sales,
: growth_rate * ,
: trend_factor,
: adjusted_forecast
}
():
sku_forecasts = []
idx, sku sku_mix.iterrows():
sku[]:
forecast_pct = sku[]
:
forecast_pct = sku[]
price_adjustment = sku.get(, )
sku_forecast = total_forecast * (forecast_pct / ) * price_adjustment
sku_std = sku_forecast *
sku_forecasts.append({
: sku[],
: sku_forecast,
: sku_std,
: sku[],
: sku[]
})
pd.DataFrame(sku_forecasts)
():
results = []
sim (n_simulations):
weekly_demand_pct = seasonal_curve.copy()
week weekly_demand_pct.keys():
noise = np.random.normal(, )
weekly_demand_pct[week] *= noise
total_pct = (weekly_demand_pct.values())
weekly_demand_pct = {k: v/total_pct* k, v weekly_demand_pct.items()}
inventory = initial_inventory
total_sales =
total_stockouts =
week, pct (weekly_demand_pct.items()):
weekly_demand = total_forecast * (pct / )
weekly_sales = (weekly_demand, inventory)
stockout = (, weekly_demand - inventory)
inventory -= weekly_sales
total_sales += weekly_sales
total_stockouts += stockout
sell_through_rate = (total_sales / initial_inventory * ) initial_inventory >
stockout_rate = (total_stockouts / total_forecast * ) total_forecast >
leftover_inventory = inventory
results.append({
: sim,
: total_sales,
: sell_through_rate,
: stockout_rate,
: leftover_inventory
})
results_df = pd.DataFrame(results)
summary = {
: results_df[].mean(),
: results_df[].quantile(),
: results_df[].quantile(),
: results_df[].quantile(),
: results_df[].mean(),
: results_df[].mean()
}
results_df, summary
historical_data = pd.DataFrame({
: [] * ,
: [, , , , , ] * ,
: (((, )) * ),
: np.random.uniform(, , ),
:
})
forecaster = SeasonalDemandForecaster(historical_data)
curve = forecaster.forecast_seasonal_curve(, )
()
week, pct (curve.items())[:]:
()
season_forecast = forecaster.forecast_total_season_sales(
season=,
category=,
last_year_sales=,
growth_rate=,
trend_factor=
)
()
simulation_results, summary = forecaster.simulate_season(
initial_inventory=,
seasonal_curve=curve,
total_forecast=season_forecast[] / ,
n_simulations=
)
()
()
()
()
In-Season Management
Chase & Markdown Strategy
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
weekly_rate = sales / weeks_elapsed
projected_total_sales = weekly_rate * self.plan['season_weeks']
vs_plan_pct = (projected_total_sales / plan_sales - 1) * 100 if plan_sales > 0 else 0
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_qty = projected_remaining_sales - inventory_remaining
vendor_lead_time = .plan.get(sku, {}).get(, )
weeks_remaining > vendor_lead_time + :
opportunities.append({
: sku,
: vs_plan_pct,
: projected_total_sales,
: inventory_remaining,
: (chase_qty, ),
: weeks_remaining < vendor_lead_time +
})
pd.DataFrame(opportunities)
():
candidates = []
weeks_remaining = .plan[] - weeks_elapsed
sku, sales actual_sales.items():
initial_buy = .plan.get(sku, {}).get(, )
inventory_remaining = current_inventory.get(sku, )
initial_buy == :
current_str = (initial_buy - inventory_remaining) / initial_buy
weeks_elapsed > :
weekly_rate = sales / weeks_elapsed
projected_additional_sales = weekly_rate * weeks_remaining
projected_final_str = (sales + projected_additional_sales) / initial_buy
:
projected_final_str =
projected_final_str < target_str inventory_remaining > :
projected_final_str < :
recommended_markdown =
projected_final_str < :
recommended_markdown =
:
recommended_markdown =
candidates.append({
: sku,
: (current_str * , ),
: (projected_final_str * , ),
: inventory_remaining,
: recommended_markdown,
: projected_final_str <
})
pd.DataFrame(candidates)
():
total_plan_sales = (sku.get(, ) sku .plan.values() (sku, ))
total_actual_sales = (actual_sales.values())
total_plan_sales > :
sales_attainment = total_actual_sales / (total_plan_sales * weeks_elapsed / .plan[])
:
sales_attainment =
sales_score = (sales_attainment * , )
total_initial_buy = (sku.get(, ) sku .plan.values() (sku, ))
total_current_inv = (current_inventory.values())
total_initial_buy > :
current_str = (total_initial_buy - total_current_inv) / total_initial_buy
:
current_str =
target_str_now = weeks_elapsed / .plan[] *
str_score = (current_str / target_str_now * , ) target_str_now >
weeks_remaining = .plan[] - weeks_elapsed
weekly_run_rate = total_actual_sales / weeks_elapsed weeks_elapsed >
weeks_of_supply = total_current_inv / weekly_run_rate weekly_run_rate >
* weeks_remaining <= weeks_of_supply <= * weeks_remaining:
balance_score =
:
balance_score = (, - (weeks_of_supply - weeks_remaining) * )
total_score = sales_score + str_score + balance_score
{
: (total_score, ),
: (sales_attainment * , ),
: (current_str * , ),
: (weeks_of_supply, ),
: ._interpret_health_score(total_score)
}
():
score >= :
score >= :
score >= :
:
season_plan = {
: ,
: {: , : , : },
: {: , : , : },
: {: , : , : }
}
manager = InSeasonManager(season_plan)
actual_sales = {: , : , : }
current_inventory = {: , : , : }
weeks_elapsed =
chase_opps = manager.identify_chase_opportunities(
actual_sales, weeks_elapsed, current_inventory
)
()
(chase_opps)
markdown_candidates = manager.identify_markdown_candidates(
actual_sales, weeks_elapsed, current_inventory, target_str=
)
()
(markdown_candidates)
health = manager.calculate_season_health_score(
actual_sales, weeks_elapsed, current_inventory
)
()
()
()
()
Tools & Libraries
Python Libraries
Optimization:
scipy.optimize: Newsvendor optimization
pulp, pyomo: Linear programming for assortment
numpy: Numerical computations
Forecasting:
statsmodels: Time series analysis
prophet: Seasonal forecasting
pandas: Data manipulation
Simulation:
numpy.random: Monte Carlo simulation
scipy.stats: Statistical distributions
Commercial Software
Planning Systems:
- Blue Yonder (JDA) Assortment: Seasonal planning and optimization
- o9 Solutions: Digital planning platform
- Oracle Retail Merchandise Planning: Seasonal merchandise planning
- SAP IBP: Integrated business planning
- RELEX Solutions: Seasonal demand planning
Specialized Tools:
- Armonia: Retail planning suite
- TXT Retail: Fashion planning
- APTOS Merchandise Lifecycle Management: Seasonal planning
Common Challenges & Solutions
Challenge: Forecasting New Products
Problem:
- No historical data
- High uncertainty
- Risk of over/under buying
Solutions:
- Analog product approach
- Test markets / pilot stores
- Start conservative, chase winners
- Use product attributes (price, color, style)
- Market research and trend analysis
- Multiple scenarios (optimistic/realistic/conservative)
Challenge: Weather Dependency
Problem:
- Unseasonable weather impacts sales
- Hard to predict
- Risk management
Solutions:
- Weather-based contingency plans
- Flexible vendor agreements
- Geographic diversification
- Pack-away programs (hold for next year)
- Transfer between climates
- Quick markdown response
Challenge: Late Vendor Deliveries
Problem:
- Receipts arrive late
- Miss selling window
- Forced markdowns
Solutions:
- Air freight contingencies
- Vendor scorecards and penalties
- Multiple sourcing
- Buffer lead times in planning
- Early production starts
- Substitute product strategies
Challenge: Balancing Newness vs. Basics
Problem:
- Fashion/trend items riskier
- Basics boring but reliable
- Need both for assortment
Solutions:
- 70/30 or 60/40 ratio (basics/fashion)
- Test fashion in limited quantities
- Fast fashion model (short lead times)
- Core basics with fashion colors
- Clear newness every season
- Price segmentation (basics lower, fashion higher)
Challenge: End-of-Season Clearance
Problem:
- Leftover inventory
- Deep markdowns hurt margins
- Storage costs
Solutions:
- Aggressive early markdowns
- Pack-away for next year (if feasible)
- Outlet store distribution
- Liquidation companies
- Donation (tax benefit)
- Improved planning to reduce leftovers
Output Format
Seasonal Planning Report
Executive Summary:
- Season: Fall 2024 (August - November)
- Total buy plan: $4.2M at cost ($10.5M retail)
- Target sales: $9.2M (88% sell-through at full price)
- Target margin: 62% IMU, 58% maintained margin
- SKU count: 425 SKUs across 8 categories
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 |
Questions to Ask
If you need more context:
- What season are you planning? (spring, fall, holiday, etc.)
- What's the season length? (weeks of selling)
- What was last year's performance? (sales, sell-through, markdowns)
- What's your sales target for this season?
- What's your open-to-buy budget?
- What % is new vs. carry-over merchandise?
- What are your vendor lead times?
- What's your target markdown rate?
- What categories/product types are included?
Related Skills
- demand-forecasting: Demand forecasting methodologies
- retail-allocation: Store allocation optimization
- markdown-optimization: Markdown strategy and timing
- inventory-optimization: Safety stock and inventory management
- retail-replenishment: In-season replenishment
- planogram-optimization: Space planning for seasonal sets
- supply-chain-analytics: Performance metrics and tracking