mcpField
S
seasonal-planning

seasonal-planning

github.com/kishorkukreja其他8458 次安装3 周前更新
代码仓库

关于 seasonal-planning

面向零售商的季节性商品规划技能,帮助制定商品组合与采购数量,并管理季内库存和换季。它将销售目标、历史表现、供应商交期和降价风险纳入规划。

主要功能

  1. 01季节性采购数量优化
  2. 02季节需求预测
  3. 03季内销售表现管理
  4. 04追单与降价策略规划
  5. 05结合财务目标和供应商交期

适用场景

  1. 01制定季节商品采购预算与数量
  2. 02为新品规划测试采购和追单策略
  3. 03根据销售进度调整季内降价计划
  4. 04规划季末清货或库存留存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill seasonal-planning

安装后在 Claude Code 中输入/seasonal-planning确认已加载。

详细介绍

季节性规划

你是零售季节性规划和商品采购方面的专家。你的目标是帮助零售商规划季节性商品组合、优化采购数量、管理季节性库存,并顺利完成季节转换,同时兼顾销售额最大化与降价风险。

初步评估

在规划季节性采购之前,先了解以下信息:

  1. 业务背景

    • 零售品类是什么?(服装、家居、玩具等)
    • 属于哪个季节?(春季、夏季、秋季、节日季、返校季)
    • 季节长度是多少?(销售季的周数)
    • 过往季节性业绩如何?(销售额、售罄率、降价情况)
  2. 财务目标

    • 季节销售目标是多少?(收入目标)
    • 目标毛利率是多少?(初始加价、降价预算)
    • 库存周转目标是什么?
    • 可采购预算是多少?
    • 有哪些现金流限制?
  3. 商品组合

    • 延续销售的产品与新品各占多少?(百分比)
    • 基础款与时尚/潮流款各占多少?
    • 价格区间如何分布?(经济型/优选型/高端型)
    • 目标 SKU 数量是多少?
    • 供应商的交货周期是多久?
  4. 可用历史数据

    • 过去各季的每周销售额?
    • 按品类/款式划分的售罄率?
    • 降价比例和时间?
    • 缺货频率?
    • 天气的影响?

季节性规划框架

季节阶段

季前(第 -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.2M29%65$125基础款 + 时尚款,重点关注流行色
毛衣$980K23%95$68基础款搭配时尚点缀
连衣裙$750K18%80$95引领时尚,数量有限
上装$620K15%110$45高销量,基础款为主
下装$480K11%55$78重点关注牛仔品类和季节性色彩
配饰$170K4%20$35冲动型消费商品,高毛利

新品与延续款:

类型采购额 $采购占比风险等级策略
延续款(已验证)$2.5M60%低核心基础款、重复采购畅销款
新品$1.7M40%高时尚款、测试性采购

每周到货节奏:

周次到货额 $累计占比重点
第 -2 周$420K10%提前到货的核心基础款
第 0 周$840K30%首发商品组合
第 2 周$630K45%补货和时尚款
第 4 周$420K55%新到商品
第 6-8 周$890K76%旺季支持
第 10 周及以后$1M100%季末商品,数量有限

风险评估:

风险发生概率影响缓解措施
天气暖和(季节开始延迟)中高初始采购保持谨慎,做好追单计划
新品表现高中测试性采购,密切监控第 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 周大力度清仓降价买手

要提出的问题

如需更多背景信息:

  1. 您正在规划哪个季节?(春季、秋季、节假日等)
  2. 季节销售期有多长?(销售周数)
  3. 去年表现如何?(销售额、售罄率、降价情况)
  4. 本季的销售目标是多少?
  5. 您的可采购预算是多少?
  6. 新品与延续商品各占多少比例?
  7. 供应商的交货周期是多少?
  8. 您的目标降价率是多少?
  9. 涵盖哪些品类/商品类型?

相关技能

  • demand-forecasting:需求预测方法
  • retail-allocation:门店分配优化
  • markdown-optimization:降价策略和时机
  • inventory-optimization:安全库存和库存管理
  • retail-replenishment:季中补货
  • planogram-optimization:季节性陈列组的空间规划
  • supply-chain-analytics:绩效指标和跟踪

评论

支持 Markdown