关于 seasonal-planning
面向零售商的季节性商品规划技能,帮助制定商品组合与采购数量,并管理季内库存和换季。它将销售目标、历史表现、供应商交期和降价风险纳入规划。
主要功能
- 01季节性采购数量优化
- 02季节需求预测
- 03季内销售表现管理
- 04追单与降价策略规划
- 05结合财务目标和供应商交期
适用场景
- 01制定季节商品采购预算与数量
- 02为新品规划测试采购和追单策略
- 03根据销售进度调整季内降价计划
- 04规划季末清货或库存留存
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill seasonal-planning安装后在 Claude Code 中输入/seasonal-planning确认已加载。
详细介绍
季节性规划
你是零售季节性规划和商品采购方面的专家。你的目标是帮助零售商规划季节性商品组合、优化采购数量、管理季节性库存,并顺利完成季节转换,同时兼顾销售额最大化与降价风险。
初步评估
在规划季节性采购之前,先了解以下信息:
-
业务背景
- 零售品类是什么?(服装、家居、玩具等)
- 属于哪个季节?(春季、夏季、秋季、节日季、返校季)
- 季节长度是多少?(销售季的周数)
- 过往季节性业绩如何?(销售额、售罄率、降价情况)
-
财务目标
- 季节销售目标是多少?(收入目标)
- 目标毛利率是多少?(初始加价、降价预算)
- 库存周转目标是什么?
- 可采购预算是多少?
- 有哪些现金流限制?
-
商品组合
- 延续销售的产品与新品各占多少?(百分比)
- 基础款与时尚/潮流款各占多少?
- 价格区间如何分布?(经济型/优选型/高端型)
- 目标 SKU 数量是多少?
- 供应商的交货周期是多久?
-
可用历史数据
- 过去各季的每周销售额?
- 按品类/款式划分的售罄率?
- 降价比例和时间?
- 缺货频率?
- 天气的影响?
季节性规划框架
季节阶段
季前(第 -12 至 0 周)
- 趋势预测和市场调研
- 商品组合规划(款式、颜色、尺码)
- 采购规划和供应商谈判
- 配货规划
- 营销活动规划
季初(第 1 至 4 周)
- 首批到货和配货
- 监测早期售罄情况
- 识别畅销品和滞销品
- 调整后续订单(如可行)
- 制定补货决策
销售高峰期(第 5 至 8 周)
- 销售量达到高峰
- 确保畅销品有货
- 开始为滞销品制定降价计划
- 为热销品追加订单
- 最大化正价销售
季末(第 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}%")
工具与库
Python 库
优化:
scipy.optimize:新保闻商模型优化pulp、pyomo:用于商品组合规划的线性规划numpy:数值计算
预测:
statsmodels:时间序列分析prophet:季节性预测pandas:数据处理
模拟:
numpy.random:蒙特卡洛模拟scipy.stats:统计分布
商业软件
规划系统:
- Blue Yonder (JDA) Assortment:季节性规划与优化
- o9 Solutions:数字化规划平台
- Oracle Retail Merchandise Planning:季节性商品规划
- SAP IBP:集成业务规划
- RELEX Solutions:季节性需求规划
专业工具:
- Armonia:零售规划套件
- TXT Retail:时尚规划
- APTOS Merchandise Lifecycle Management:季节性规划
常见挑战与解决方案
挑战:预测新品
问题:
- 没有历史数据
- 不确定性高
- 存在采购过多或过少的风险
解决方案:
- 采用类似产品法
- 开展试销市场/试点门店
- 保守起步,及时追加畅销品
- 利用产品属性(价格、颜色、款式)
- 开展市场调研和趋势分析
- 制定多种情景(乐观/现实/保守)
挑战:天气依赖性
问题:
- 反常天气会影响销售
- 难以预测
- 需要管理风险
解决方案:
- 制定基于天气的应急计划
- 与供应商签订灵活协议
- 实现地域多元化
- 实施收库存留待来年销售计划
- 在不同气候区域之间调拨
- 及时快速降价
挑战:供应商延迟交货
问题:
- 到货延迟
- 错过销售窗口
- 被迫降价
解决方案:
- 准备空运应急方案
- 供应商评分卡和罚则
- 多源采购
- 在规划中预留缓冲交货时间
- 提前开始生产
- 制定替代产品策略
挑战:平衡新品与基础款
问题:
- 时尚/潮流单品风险较高
- 基础款平淡但可靠
- 商品组合需要兼顾两者
解决方案:
- 按 70/30 或 60/40 的比例搭配(基础款/时尚款)
- 少量试销时尚款
- 快时尚模式(交货周期短)
- 基础核心款搭配时尚色彩
- 每季明确推出新品
- 价格分层(基础款价格较低,时尚款价格较高)
挑战:季末清仓
问题:
- 库存积压
- 大幅降价影响利润率
- 仓储成本
解决方案:
- 尽早大幅降价
- 留存库存供来年销售(如可行)
- 分销至奥特莱斯门店
- 通过清算公司处理
- 捐赠(可享受税收优惠)
- 改进规划,减少剩余库存
输出格式
季节规划报告
执行摘要:
- 季节:2024 年秋季(8 月 - 11 月)
- 总采购计划:成本价 $4.2M(零售价 $10.5M)
- 目标销售额:$9.2M(按全价计算的售罄率为 88%)
- 目标毛利率:初始加价率(IMU)62%,维持毛利率 58%
- SKU 数量:8 个品类共 425 个 SKU
财务计划:
| 指标 | 目标 |
|---|---|
| 成本价总采购额 | $4.2M |
| 零售价总采购额 | $10.5M |
| 初始加价率(IMU) | 62% |
| 销售计划 | $9.2M |
| 目标售罄率 | 88% |
| 降价预算 | 销售额的 4%($368K) |
| 维持毛利率 | 58% |
| 毛利 | $5.3M |
品类组合:
| 品类 | 采购额 $ | 采购占比 | SKU 数量 | 平均价格 | 策略 |
|---|---|---|---|---|---|
| 外套 | $1.2M | 29% | 65 | $125 | 基础款 + 时尚款,重点关注流行色 |
| 毛衣 | $980K | 23% | 95 | $68 | 基础款搭配时尚点缀 |
| 连衣裙 | $750K | 18% | 80 | $95 | 引领时尚,数量有限 |
| 上装 | $620K | 15% | 110 | $45 | 高销量,基础款为主 |
| 下装 | $480K | 11% | 55 | $78 | 重点关注牛仔品类和季节性色彩 |
| 配饰 | $170K | 4% | 20 | $35 | 冲动型消费商品,高毛利 |
新品与延续款:
| 类型 | 采购额 $ | 采购占比 | 风险等级 | 策略 |
|---|---|---|---|---|
| 延续款(已验证) | $2.5M | 60% | 低 | 核心基础款、重复采购畅销款 |
| 新品 | $1.7M | 40% | 高 | 时尚款、测试性采购 |
每周到货节奏:
| 周次 | 到货额 $ | 累计占比 | 重点 |
|---|---|---|---|
| 第 -2 周 | $420K | 10% | 提前到货的核心基础款 |
| 第 0 周 | $840K | 30% | 首发商品组合 |
| 第 2 周 | $630K | 45% | 补货和时尚款 |
| 第 4 周 | $420K | 55% | 新到商品 |
| 第 6-8 周 | $890K | 76% | 旺季支持 |
| 第 10 周及以后 | $1M | 100% | 季末商品,数量有限 |
风险评估:
| 风险 | 发生概率 | 影响 | 缓解措施 |
|---|---|---|---|
| 天气暖和(季节开始延迟) | 中 | 高 | 初始采购保持谨慎,做好追单计划 |
| 新品表现 | 高 | 中 | 测试性采购,密切监控第 1 周表现 |
| 供应商延迟 | 低 | 高 | 预留空运预算,准备替代供应商 |
| 竞争性定价 | 中 | 中 | 预留降价预算,具备价格匹配能力 |
成功指标:
| 指标 | 目标 | 第 4 周检查 | 第 8 周检查 | 季末 |
|---|---|---|---|---|
| 销售额与计划对比 | 100% | ≥90% | ≥95% | ≥97% |
| 售罄率 | 88% | ≥30% | ≥60% | ≥85% |
| 降价率 | <4% | 0% | <2% | <5% |
| 毛利率 | 58% | 62% | 60% | ≥57% |
行动计划:
| 周次 | 行动 | 负责人 |
|---|---|---|
| 第 -4 周 | 最终审核商品组合,提交采购订单 | 买手 |
| 第 -2 周 | 首批到货,分配至门店 | 配货员 |
| 第 0 周 | 季节上线,开展营销活动 | 营销团队 |
| 第 1 周 | 监控初期表现,识别趋势 | 规划员 |
| 第 4 周 | 决定是否为畅销款追加订单 | 买手 |
| 第 8 周 | 首次评估降价情况 | 规划员 |
| 第 12 周 | 大力度清仓降价 | 买手 |
要提出的问题
如需更多背景信息:
- 您正在规划哪个季节?(春季、秋季、节假日等)
- 季节销售期有多长?(销售周数)
- 去年表现如何?(销售额、售罄率、降价情况)
- 本季的销售目标是多少?
- 您的可采购预算是多少?
- 新品与延续商品各占多少比例?
- 供应商的交货周期是多少?
- 您的目标降价率是多少?
- 涵盖哪些品类/商品类型?
相关技能
- demand-forecasting:需求预测方法
- retail-allocation:门店分配优化
- markdown-optimization:降价策略和时机
- inventory-optimization:安全库存和库存管理
- retail-replenishment:季中补货
- planogram-optimization:季节性陈列组的空间规划
- supply-chain-analytics:绩效指标和跟踪
评论
支持 Markdown