mcpField
M
markdown-optimization

markdown-optimization

github.com/kishorkukreja营销与增长8451 次安装3 周前更新
代码仓库

关于 markdown-optimization

面向零售商的降价优化技能,用于为滞销库存制定价格与清货策略。内容涉及需求弹性、降价时机、多 SKU 决策和门店级调整。

主要功能

  1. 01评估库存与业务约束
  2. 02分析价格弹性与需求
  3. 03安排动态降价时机
  4. 04优化多 SKU 降价方案
  5. 05制定门店级降价策略

适用场景

  1. 01为季末或滞销库存规划降价
  2. 02在清货目标与利润约束间权衡
  3. 03按门店库存制定不同价格调整

安装

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

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

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

详细介绍

Markdown 优化

你是零售 markdown 优化和清仓定价策略方面的专家。你的目标是帮助零售商通过数据驱动的定价决策,最大限度地回收滞销库存的收入、尽量减少 markdown 成本,并加快售罄。

初步评估

在优化 markdown 前,请先了解:

  1. 库存状况

    • 哪些库存需要 markdown?(积压库存、季节性商品、滞销商品)
    • 各 SKU/门店当前的现有库存数量是多少?
    • 库存库龄是多少?(库存周数)
    • 原始成本和零售价是多少?
    • 历史售罄率是多少?
  2. 业务背景

    • 当前 markdown 率是多少?(占销售额的比例)
    • 目标 markdown 率是多少?
    • 有哪些利润率限制?(可接受的最低利润率)
    • 季节性因素如何?(季末压力)
    • 竞争定价环境如何?
  3. 定价灵活性

    • 谁负责定价?(集中管理、区域管理、门店管理)
    • markdown 频率如何?(每周、每月、持续进行)
    • markdown 调价规则是什么?(按 10%、20%、30% 递增)
    • 折扣有最低/最高限额吗?
    • 价格能否个性化?(动态定价、基于忠诚度的定价)
  4. 渠道与竞争

    • 是单一渠道还是全渠道?
    • 线上定价有多大灵活性?
    • 是否跟踪竞争对手的定价?
    • 是否有折扣店用于清仓?
    • 有哪些清算方式?(批发商、捐赠)

Markdown 优化框架

Markdown 策略基础

关键目标:

  1. 最大限度地回收收入:转为清仓销售前,尽可能以高价售出
  2. 加快售罄:在库存过时前将其清空
  3. 尽量减少 markdown 金额:降低 markdown 总支出
  4. 维护品牌形象:避免过度打折
  5. 优化时机:平衡提前 markdown 与延后 markdown

Markdown 生命周期:

Full Price → First Markdown (10-25%) → Second Markdown (30-50%) →
Final Clearance (50-75%) → Liquidation/Donation

决策框架:

  • 何时进行 markdown? 监控售罄率和库存可供周数
  • markdown 幅度是多少? 根据价格弹性和紧迫程度确定
  • 哪些 SKU 需要 markdown? 按库龄、数量和利润率确定优先级
  • 哪些门店需要 markdown? 按门店分别执行,还是全连锁统一执行

价格弹性与需求建模

价格-需求关系
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt

class PriceElasticityAnalyzer:
    """
    Analyze price elasticity of demand for markdown optimization

    Measure how demand changes with price reductions
    """

    def __init__(self, historical_pricing_data):
        """
        Parameters:
        - historical_pricing_data: DataFrame with historical price/sales
          columns: ['sku', 'date', 'price', 'units_sold', 'original_price']
        """
        self.data = historical_pricing_data

    def calculate_price_elasticity(self, sku):
        """
        Calculate price elasticity of demand

        Elasticity = % change in quantity / % change in price

        E > 1: Elastic (demand very sensitive to price)
        E = 1: Unit elastic
        E < 1: Inelastic (demand not very sensitive)
        """

        sku_data = self.data[self.data['sku'] == sku].copy()

        if len(sku_data) < 10:
            return {'error': 'Insufficient data', 'elasticity': None}

        # Calculate discount percentage
        sku_data['discount_pct'] = (
            (sku_data['original_price'] - sku_data['price']) /
            sku_data['original_price'] * 100
        )

        # Log transformation for elasticity calculation
        sku_data['log_price'] = np.log(sku_data['price'])
        sku_data['log_quantity'] = np.log(sku_data['units_sold'] + 1)

        # Linear regression: log(Q) = a + b * log(P)
        # b is the price elasticity
        X = sku_data[['log_price']].values
        y = sku_data['log_quantity'].values

        model = LinearRegression()
        model.fit(X, y)

        elasticity = model.coef_[0]
        r_squared = model.score(X, y)

        return {
            'sku': sku,
            'elasticity': abs(elasticity),  # Report as positive
            'r_squared': r_squared,
            'interpretation': self._interpret_elasticity(abs(elasticity)),
            'model': model
        }

    def _interpret_elasticity(self, elasticity):
        """Interpret elasticity value"""

        if elasticity > 2:
            return 'Highly elastic - very price sensitive'
        elif elasticity > 1:
            return 'Elastic - price sensitive'
        elif elasticity > 0.5:
            return 'Moderately elastic'
        else:
            return 'Inelastic - not very price sensitive'

    def estimate_demand_at_price(self, sku, target_price):
        """
        Estimate demand at a specific price point

        Uses fitted elasticity model
        """

        elasticity_result = self.calculate_price_elasticity(sku)

        if elasticity_result.get('error'):
            return None

        model = elasticity_result['model']
        log_price = np.log(target_price)

        log_quantity_pred = model.predict([[log_price]])[0]
        estimated_quantity = np.exp(log_quantity_pred) - 1

        return max(0, estimated_quantity)

    def find_optimal_markdown(self, sku, cost, current_price, current_inventory,
                             weeks_remaining=8):
        """
        Find optimal markdown to maximize revenue recovery

        Balances:
        - Higher prices = higher margin but slower sell-through
        - Lower prices = faster sell-through but lower margin
        """

        elasticity_result = self.calculate_price_elasticity(sku)

        if elasticity_result.get('error'):
            return {'error': 'Cannot optimize without elasticity data'}

        # Test different price points
        price_range = np.linspace(cost * 1.1, current_price, 20)  # Don't go below cost
        results = []

        for test_price in price_range:
            # Estimate weekly demand at this price
            estimated_weekly_demand = self.estimate_demand_at_price(sku, test_price)

            # Estimate total units sold over remaining weeks
            estimated_total_sold = min(
                estimated_weekly_demand * weeks_remaining,
                current_inventory
            )

            # Calculate revenue
            revenue = estimated_total_sold * test_price

            # Calculate margin
            margin = test_price - cost
            margin_pct = margin / test_price * 100 if test_price > 0 else 0

            # Calculate weeks to sell out
            if estimated_weekly_demand > 0:
                weeks_to_sellout = current_inventory / estimated_weekly_demand
            else:
                weeks_to_sellout = 999

            results.append({
                'price': test_price,
                'discount_pct': (current_price - test_price) / current_price * 100,
                'estimated_weekly_demand': estimated_weekly_demand,
                'estimated_total_sold': estimated_total_sold,
                'revenue': revenue,
                'margin_pct': margin_pct,
                'weeks_to_sellout': weeks_to_sellout
            })

        results_df = pd.DataFrame(results)

        # Find optimal price (maximize revenue)
        optimal_idx = results_df['revenue'].idxmax()
        optimal = results_df.iloc[optimal_idx]

        return {
            'current_price': current_price,
            'optimal_price': optimal['price'],
            'recommended_discount': optimal['discount_pct'],
            'expected_revenue': optimal['revenue'],
            'expected_sellthrough': optimal['estimated_total_sold'] / current_inventory * 100,
            'weeks_to_sellout': optimal['weeks_to_sellout'],
            'all_scenarios': results_df
        }

# Example usage
np.random.seed(42)

# Generate sample historical data
dates = pd.date_range('2024-01-01', periods=50, freq='W')
historical_data = []

for week in range(50):
    # Simulate price elasticity: lower prices → higher sales
    base_price = 100
    discount = min(week * 2, 60)  # Increasing discounts over time
    price = base_price * (1 - discount/100)

    # Demand increases as price decreases (elasticity ~1.5)
    base_demand = 20
    price_factor = (base_price / price) ** 1.5
    units_sold = base_demand * price_factor + np.random.normal(0, 5)
    units_sold = max(0, units_sold)

    historical_data.append({
        'sku': 'SKU123',
        'date': dates[week],
        'price': price,
        'units_sold': units_sold,
        'original_price': base_price
    })

historical_df = pd.DataFrame(historical_data)

# Analyze elasticity
analyzer = PriceElasticityAnalyzer(historical_df)
elasticity = analyzer.calculate_price_elasticity('SKU123')

print(f"Price Elasticity: {elasticity['elasticity']:.2f}")
print(f"Interpretation: {elasticity['interpretation']}")
print(f"R-squared: {elasticity['r_squared']:.2f}")

# Find optimal markdown
optimization = analyzer.find_optimal_markdown(
    sku='SKU123',
    cost=40,
    current_price=80,
    current_inventory=500,
    weeks_remaining=6
)

print(f"\nCurrent price: ${optimization['current_price']:.2f}")
print(f"Optimal price: ${optimization['optimal_price']:.2f}")
print(f"Recommended discount: {optimization['recommended_discount']:.1f}%")
print(f"Expected revenue: ${optimization['expected_revenue']:,.0f}")
print(f"Expected sell-through: {optimization['expected_sellthrough']:.1f}%")

Markdown 时机优化

动态 Markdown 排期
class MarkdownScheduler:
    """
    Optimize markdown timing to maximize revenue recovery

    Decides when to markdown based on:
    - Current sell-through rate
    - Time remaining in season
    - Inventory level
    - Competitive pressure
    """

    def __init__(self, season_end_date, current_date):
        self.season_end = pd.to_datetime(season_end_date)
        self.current_date = pd.to_datetime(current_date)
        self.weeks_remaining = (self.season_end - self.current_date).days / 7

    def calculate_sell_through_rate(self, units_sold, initial_inventory, weeks_elapsed):
        """
        Calculate sell-through rate (STR)

        STR = units sold / initial inventory
        Weekly STR = STR / weeks
        """

        if initial_inventory == 0:
            return 0

        str_total = units_sold / initial_inventory * 100
        str_weekly = str_total / weeks_elapsed if weeks_elapsed > 0 else 0

        return {
            'total_str': str_total,
            'weekly_str': str_weekly,
            'units_sold': units_sold,
            'initial_inventory': initial_inventory
        }

    def should_markdown(self, sku_data):
        """
        Decide if SKU should be marked down now

        Factors:
        - Sell-through rate below target
        - Weeks of supply too high
        - End of season approaching
        """

        current_inventory = sku_data['current_inventory']
        weekly_sales_rate = sku_data['weekly_sales_rate']
        target_str = sku_data.get('target_str', 70)  # Target 70% sell-through

        # Calculate current trajectory
        projected_str = (weekly_sales_rate * self.weeks_remaining) / sku_data['initial_inventory'] * 100

        # Calculate weeks of supply
        if weekly_sales_rate > 0:
            weeks_of_supply = current_inventory / weekly_sales_rate
        else:
            weeks_of_supply = 999

        # Decision rules
        reasons = []
        should_md = False

        # Rule 1: Projected sell-through below target
        if projected_str < target_str:
            should_md = True
            reasons.append(f"Projected STR ({projected_str:.1f}%) below target ({target_str}%)")

        # Rule 2: Excessive weeks of supply
        if weeks_of_supply > self.weeks_remaining * 1.5:
            should_md = True
            reasons.append(f"Weeks of supply ({weeks_of_supply:.1f}) exceeds season ({self.weeks_remaining:.1f})")

        # Rule 3: End of season urgency
        if self.weeks_remaining < 4 and projected_str < 80:
            should_md = True
            reasons.append(f"Season ending soon ({self.weeks_remaining:.1f} weeks) with low STR")

        # Rule 4: No sales momentum
        if weekly_sales_rate < sku_data['initial_inventory'] * 0.01:  # <1% per week
            should_md = True
            reasons.append("Very slow sales rate")

        return {
            'should_markdown': should_md,
            'reasons': reasons,
            'projected_str': projected_str,
            'weeks_of_supply': weeks_of_supply,
            'weeks_remaining': self.weeks_remaining,
            'urgency': 'High' if self.weeks_remaining < 4 else 'Medium' if self.weeks_remaining < 8 else 'Low'
        }

    def recommend_markdown_depth(self, sku_data):
        """
        Recommend markdown percentage

        Deeper discounts for:
        - Lower sell-through
        - More weeks of supply
        - End of season
        - Low price elasticity (need big discount to move)
        """

        decision = self.should_markdown(sku_data)

        if not decision['should_markdown']:
            return {
                'recommended_markdown': 0,
                'reason': 'On track, no markdown needed'
            }

        weeks_of_supply = decision['weeks_of_supply']
        projected_str = decision['projected_str']
        elasticity = sku_data.get('price_elasticity', 1.0)

        # Base markdown on severity
        if projected_str < 40 or weeks_of_supply > 20:
            base_markdown = 50  # Aggressive
        elif projected_str < 55 or weeks_of_supply > 15:
            base_markdown = 30  # Moderate
        else:
            base_markdown = 20  # Conservative

        # Adjust for elasticity
        if elasticity < 0.8:  # Inelastic
            base_markdown *= 1.3  # Need bigger discount
        elif elasticity > 1.5:  # Elastic
            base_markdown *= 0.8  # Smaller discount works

        # Adjust for urgency
        if self.weeks_remaining < 3:
            base_markdown *= 1.4

        # Round to standard increments
        markdown_increments = [10, 20, 25, 30, 40, 50, 60, 70]
        recommended_markdown = min(markdown_increments, key=lambda x: abs(x - base_markdown))

        return {
            'recommended_markdown': recommended_markdown,
            'reason': f"Projected STR {projected_str:.1f}%, {weeks_of_supply:.1f} weeks supply",
            'urgency': decision['urgency']
        }

    def create_markdown_cadence(self, sku_data, markdown_stages=[20, 40, 60]):
        """
        Create multi-stage markdown plan

        Progressive markdowns over time
        """

        weeks_left = self.weeks_remaining
        cadence = []

        # Determine if we need aggressive or conservative cadence
        decision = self.should_markdown(sku_data)

        if decision['urgency'] == 'High':
            # Fast cadence - markdown every 1-2 weeks
            week_intervals = [0, 1, 3]
        elif decision['urgency'] == 'Medium':
            # Moderate cadence - markdown every 2-3 weeks
            week_intervals = [0, 2, 5]
        else:
            # Slow cadence - markdown every 3-4 weeks
            week_intervals = [0, 3, 7]

        for i, (markdown_pct, week_offset) in enumerate(zip(markdown_stages, week_intervals)):
            if week_offset < weeks_left:
                markdown_date = self.current_date + pd.Timedelta(weeks=week_offset)

                cadence.append({
                    'stage': i + 1,
                    'markdown_date': markdown_date,
                    'markdown_pct': markdown_pct,
                    'price': sku_data['original_price'] * (1 - markdown_pct/100),
                    'weeks_from_now': week_offset
                })

        return cadence

# Example
scheduler = MarkdownScheduler(
    season_end_date='2024-09-30',
    current_date='2024-08-15'
)

sku_data = {
    'sku': 'SKU456',
    'initial_inventory': 1000,
    'current_inventory': 720,
    'weekly_sales_rate': 25,
    'original_price': 89.99,
    'target_str': 75,
    'price_elasticity': 1.3
}

# Should we markdown?
decision = scheduler.should_markdown(sku_data)
print(f"Should markdown: {decision['should_markdown']}")
print(f"Reasons: {decision['reasons']}")
print(f"Projected STR: {decision['projected_str']:.1f}%")
print(f"Urgency: {decision['urgency']}")

# Recommend markdown depth
recommendation = scheduler.recommend_markdown_depth(sku_data)
print(f"\nRecommended markdown: {recommendation['recommended_markdown']}%")
print(f"Reason: {recommendation['reason']}")

# Create markdown cadence
cadence = scheduler.create_markdown_cadence(sku_data)
print(f"\nMarkdown Cadence:")
for stage in cadence:
    print(f"  Stage {stage['stage']}: {stage['markdown_pct']}% off on {stage['markdown_date'].date()} (${stage['price']:.2f})")

Markdown 优化模型

多 SKU Markdown 优化
from scipy.optimize import minimize, LinearConstraint

class MarkdownOptimizer:
    """
    Optimize markdowns across multiple SKUs

    Maximize total revenue while meeting clearance targets
    """

    def __init__(self, skus_data, constraints):
        """
        Parameters:
        - skus_data: DataFrame with SKU information
          columns: ['sku', 'inventory', 'cost', 'current_price', 'elasticity',
                   'weekly_demand_base']
        - constraints: Dict with budget/target constraints
        """
        self.skus = skus_data
        self.constraints = constraints

    def estimate_demand_at_discount(self, base_demand, elasticity, discount_pct):
        """
        Estimate demand at a given discount level

        Uses price elasticity to project demand lift
        """

        # Demand increases as price decreases
        # New_Demand = Base_Demand * (1 / (1 - discount))^elasticity
        if discount_pct >= 100:
            return base_demand * 10  # Arbitrary large number

        price_ratio = 1 / (1 - discount_pct/100)
        demand_multiplier = price_ratio ** elasticity

        return base_demand * demand_multiplier

    def calculate_revenue(self, discount_percentages):
        """
        Calculate total revenue for given discount strategy

        Parameters:
        - discount_percentages: Array of discount % for each SKU
        """

        total_revenue = 0

        for i, row in self.skus.iterrows():
            discount_pct = discount_percentages[i]

            # New price after discount
            new_price = row['current_price'] * (1 - discount_pct/100)

            # Estimated demand at this price
            estimated_demand = self.estimate_demand_at_discount(
                row['weekly_demand_base'],
                row['elasticity'],
                discount_pct
            )

            # Units sold (capped at inventory)
            units_sold = min(estimated_demand * 8, row['inventory'])  # 8 weeks

            # Revenue
            revenue = units_sold * new_price

            total_revenue += revenue

        return total_revenue

    def optimize_markdowns(self, max_avg_discount=40, min_clearance_rate=70):
        """
        Find optimal markdown strategy

        Constraints:
        - Average discount across SKUs <= max_avg_discount
        - Achieve min_clearance_rate sell-through
        """

        n_skus = len(self.skus)

        # Objective: Maximize revenue (minimize negative revenue)
        def objective(discounts):
            return -self.calculate_revenue(discounts)

        # Constraints
        constraints_list = []

        # Constraint 1: Average discount <= max
        def avg_discount_constraint(discounts):
            return max_avg_discount - np.mean(discounts)

        constraints_list.append({
            'type': 'ineq',
            'fun': avg_discount_constraint
        })

        # Constraint 2: Achieve minimum clearance rate
        def clearance_constraint(discounts):
            total_cleared = 0
            total_inventory = 0

            for i, row in self.skus.iterrows():
                discount_pct = discounts[i]
                new_price = row['current_price'] * (1 - discount_pct/100)

                estimated_demand = self.estimate_demand_at_discount(
                    row['weekly_demand_base'],
                    row['elasticity'],
                    discount_pct
                )

                units_sold = min(estimated_demand * 8, row['inventory'])
                total_cleared += units_sold
                total_inventory += row['inventory']

            clearance_rate = total_cleared / total_inventory * 100
            return clearance_rate - min_clearance_rate

        constraints_list.append({
            'type': 'ineq',
            'fun': clearance_constraint
        })

        # Bounds: 0% to 70% discount
        bounds = [(0, 70) for _ in range(n_skus)]

        # Initial guess: uniform discount
        x0 = np.full(n_skus, 30.0)

        # Optimize
        result = minimize(
            objective,
            x0,
            method='SLSQP',
            bounds=bounds,
            constraints=constraints_list
        )

        optimal_discounts = result.x

        # Calculate results
        results = []
        for i, row in self.skus.iterrows():
            discount = optimal_discounts[i]
            new_price = row['current_price'] * (1 - discount/100)

            estimated_demand = self.estimate_demand_at_discount(
                row['weekly_demand_base'],
                row['elasticity'],
                discount
            )

            units_sold = min(estimated_demand * 8, row['inventory'])
            revenue = units_sold * new_price
            clearance_rate = units_sold / row['inventory'] * 100

            results.append({
                'sku': row['sku'],
                'current_price': row['current_price'],
                'optimal_discount': discount,
                'new_price': new_price,
                'estimated_units_sold': units_sold,
                'revenue': revenue,
                'clearance_rate': clearance_rate
            })

        results_df = pd.DataFrame(results)

        return results_df, result

# Example
skus_data = pd.DataFrame({
    'sku': [f'SKU{i}' for i in range(1, 11)],
    'inventory': np.random.randint(100, 1000, 10),
    'cost': np.random.uniform(20, 50, 10),
    'current_price': np.random.uniform(50, 150, 10),
    'elasticity': np.random.uniform(0.8, 2.0, 10),
    'weekly_demand_base': np.random.uniform(5, 30, 10)
})

optimizer = MarkdownOptimizer(skus_data, {})

# Optimize
optimal_strategy, optimization_result = optimizer.optimize_markdowns(
    max_avg_discount=35,
    min_clearance_rate=75
)

print("Optimal Markdown Strategy:")
print(optimal_strategy[['sku', 'optimal_discount', 'new_price', 'estimated_units_sold', 'clearance_rate']])
print(f"\nTotal revenue: ${optimal_strategy['revenue'].sum():,.0f}")
print(f"Average discount: {optimal_strategy['optimal_discount'].mean():.1f}%")
print(f"Average clearance rate: {optimal_strategy['clearance_rate'].mean():.1f}%")

门店级 Markdown 决策

本地化 Markdown 策略:

class StoreMarkdownManager:
    """
    Manage store-level markdown decisions

    Some stores need different markdown strategies than others
    """

    def __init__(self, stores_data):
        self.stores = stores_data

    def should_markdown_locally(self, sku, store_id, chain_markdown_pct=0):
        """
        Decide if store should markdown locally vs. follow chain

        Factors:
        - Local inventory level (excess?)
        - Local sell-through performance
        - Local competitive pressure
        """

        store = self.stores[self.stores['store_id'] == store_id].iloc[0]

        # Get SKU-specific data at store
        sku_inventory = store.get(f'{sku}_inventory', 0)
        sku_weekly_sales = store.get(f'{sku}_weekly_sales', 0)
        weeks_of_supply = sku_inventory / sku_weekly_sales if sku_weekly_sales > 0 else 999

        # Decision criteria
        local_markdown_needed = False
        reasons = []

        # Criterion 1: Excessive local inventory
        chain_avg_wos = 8  # Example chain average
        if weeks_of_supply > chain_avg_wos * 1.5:
            local_markdown_needed = True
            reasons.append(f"Excess inventory: {weeks_of_supply:.1f} weeks (chain avg: {chain_avg_wos})")

        # Criterion 2: Poor local performance
        chain_avg_str = 65  # Example
        local_str = store.get(f'{sku}_str', 50)
        if local_str < chain_avg_str * 0.8:
            local_markdown_needed = True
            reasons.append(f"Low STR: {local_str:.1f}% (chain avg: {chain_avg_str}%)")

        # Criterion 3: Competitive pressure
        if store.get('high_competition', False):
            local_markdown_needed = True
            reasons.append("High local competitive pressure")

        # Recommend local markdown depth
        if local_markdown_needed:
            # Go deeper than chain markdown
            recommended_markdown = max(chain_markdown_pct + 10, 30)
        else:
            # Follow chain markdown
            recommended_markdown = chain_markdown_pct

        return {
            'local_markdown_needed': local_markdown_needed,
            'reasons': reasons,
            'recommended_markdown': recommended_markdown,
            'weeks_of_supply': weeks_of_supply,
            'follow_chain': not local_markdown_needed
        }

    def identify_stores_for_clearance_transfer(self, sku, min_inventory=50):
        """
        Identify stores with excess inventory to transfer to outlet/clearance stores

        Better than heavy markdowns in regular stores
        """

        stores_with_excess = []

        for idx, store in self.stores.iterrows():
            sku_inventory = store.get(f'{sku}_inventory', 0)
            sku_weekly_sales = store.get(f'{sku}_weekly_sales', 0.1)

            if sku_inventory > min_inventory:
                weeks_of_supply = sku_inventory / sku_weekly_sales

                if weeks_of_supply > 15:  # Way too much
                    stores_with_excess.append({
                        'store_id': store['store_id'],
                        'inventory': sku_inventory,
                        'weeks_of_supply': weeks_of_supply,
                        'recommended_transfer_qty': int(sku_inventory * 0.5)  # Transfer half
                    })

        return pd.DataFrame(stores_with_excess)

# Example
stores_data = pd.DataFrame({
    'store_id': [f'S{i:03d}' for i in range(1, 21)],
    'high_competition': np.random.choice([True, False], 20, p=[0.3, 0.7])
})

# Add SKU-specific data
for idx, row in stores_data.iterrows():
    stores_data.at[idx, 'SKU001_inventory'] = np.random.randint(50, 300)
    stores_data.at[idx, 'SKU001_weekly_sales'] = np.random.randint(5, 25)
    stores_data.at[idx, 'SKU001_str'] = np.random.uniform(40, 80)

manager = StoreMarkdownManager(stores_data)

# Check if specific store needs local markdown
local_decision = manager.should_markdown_locally('SKU001', 'S001', chain_markdown_pct=20)
print(f"Store S001 - Local markdown needed: {local_decision['local_markdown_needed']}")
print(f"Recommended: {local_decision['recommended_markdown']}%")
print(f"Reasons: {local_decision['reasons']}")

# Find stores for clearance transfer
transfer_candidates = manager.identify_stores_for_clearance_transfer('SKU001')
print(f"\nStores with excess inventory for transfer:")
print(transfer_candidates)

工具与库

Python 库

优化:

  • scipy.optimize:用于价格优化的非线性优化
  • pulp、pyomo:数学规划
  • cvxpy:凸优化

机器学习:

  • scikit-learn:用于价格弹性分析的回归
  • xgboost:价格-需求建模
  • statsmodels:统计分析

数据处理:

  • pandas:数据处理
  • numpy:数值计算
商业软件

定价与 Markdown:

  • Revionics:价格优化和 markdown 管理
  • Blue Yonder (JDA) Price:AI 驱动的定价
  • Oracle Retail Price Optimization:企业级 markdown 优化
  • Competera:动态定价平台
  • Pricefx:云定价软件

分析:

  • Tableau、Power BI:Markdown 分析与可视化
  • SAP Analytics:企业级分析

常见挑战与解决方案

挑战:蚕食风险

问题:

  • Markdown 会蚕食全价销售
  • 顾客会等到打折时再购买
  • 全价售罄率下降

解决方案:

  • 定向 markdown(针对特定门店或有限地点)
  • 不要广泛宣传
  • 通过忠诚度计划提供个性化折扣
  • 优先 markdown 库龄最长的库存
  • 将商品转移至折扣店,而不是在店内 markdown
  • 设置独立的清仓专区
挑战:大幅打折损害品牌形象

问题:

  • 过度打折会损害品牌形象
  • 让顾客养成等促销的习惯
  • 与竞争对手陷入价格战

解决方案:

  • 制定策略性折扣上限(最高 40-50%)
  • 通过奥特莱斯/清仓店提供大幅折扣
  • 选择捐赠/清算,而不是大幅打折
  • 改进采购,减少打折
  • 专注于原价售罄
  • 按高端与高性价比分层产品
挑战:价格弹性数据不足

问题:

  • 新产品没有打折历史数据
  • 无法估算最佳折扣幅度
  • 靠猜测会导致决策不佳

解决方案:

  • 使用类似产品作为参照
  • 在试点门店测试不同折扣
  • 参考行业价格弹性基准
  • 以中等弹性(1.0-1.5)作为起点
  • 根据产品属性使用机器学习
  • 随着时间推移建立价格弹性数据库
挑战:全渠道定价复杂

问题:

  • 线上与门店价格不同
  • 顾客困惑并提出投诉
  • 出现先到店看货、再网上购买/先线上研究、再到店购买的行为

解决方案:

  • 各渠道统一定价(首选)
  • 设置线上专属清仓专区
  • 提供价格匹配保证
  • 按区域制定线上价格
  • 清晰说明定价政策
  • 利用忠诚度计划实现个性化,而不是全面打折
挑战:折扣时机不当

问题:

  • 过早打折 → 流失原价销售
  • 过晚打折 → 无法清理库存
  • 两种错误都会造成高昂代价

解决方案:

  • 监控售罄率阈值
  • 设置自动折扣触发条件
  • 采用多阶段折扣节奏
  • 根据气候差异调整各地区的时机
  • 监控竞争情况
  • 进行季后分析,以改进下一年

输出格式

Markdown 优化报告

执行摘要:

  • 面临风险的库存总额:零售价值 820 万美元(42,000 件)
  • 当前降价率:销售额的 28%
  • 目标降价率:<22%
  • 推荐的降价策略:循序渐进、精准施策
  • 预计降价成本:180 万美元(库存价值的 22%)
  • 预计收入回收:640 万美元(78%)

SKU 级降价建议:

SKU类别库存供应周数当前售罄率建议措施折扣 %新价格预计收入
SKU001外套8501842%立即降价40%$59.99$41K
SKU002连衣裙1,2002238%立即降价50%$39.99$38K
SKU003上衣6501258%第 3 周降价30%$34.99$18K
SKU004下装420868%持续监测,暂不采取措施0%$49.99$21K
SKU005配饰2,1002535%调拨至折扣店60%$15.99$27K

各 SKU 降价节奏:

SKU阶段 1阶段 2阶段 3清仓
SKU001立减 30%(现在)立减 50%(第 3 周)立减 60%(第 6 周)清仓处理剩余商品
SKU002立减 40%(现在)立减 60%(第 2 周)立减 70%(第 5 周)捐赠剩余商品
SKU003立减 20%(第 2 周)立减 40%(第 5 周)--

门店级差异化方案:

门店集群连锁统一降价本地调整原因
高客流城市门店按连锁统一方案执行无售罄进度符合预期
郊区购物中心门店按连锁统一方案执行额外加深 10% 折扣库存过剩,竞争压力大
小型门店按连锁统一方案执行调拨至大型门店空间有限,数量较少
折扣店大幅降价立减 60-70%清仓目的地

财务影响:

指标未优化优化后改善情况
总收入回收$5.8M$6.4M+$600K
降价成本$2.4M (29%)$1.8M (22%)-$600K
清售件数28,000 (67%)34,000 (81%)+14 个百分点
清售所需周数12 周8 周-4 周

实施计划:

周次行动SKU 数量预期影响
第 1 周第一轮降价(立减 30-40%)15 个 SKU清售 8,000 件,收入 $480K
第 2 周第二轮降价(立减 20-30%)12 个 SKU清售 6,000 件,收入 $340K
第 3 周加大滞销商品折扣8 个 SKU清售 5,000 件,收入 $180K
第 4-6 周将过剩库存调拨至折扣店5 个 SKU清售 10,000 件,收入 $250K
第 7-8 周最终清仓(立减 60-70%)所有剩余商品清售 5,000 件,收入 $120K

需要询问的问题

如果需要更多背景信息:

  1. 哪些库存需要降价?(库龄、数量、类别)
  2. 当前降价率是多少?目标是多少?
  3. 有多少时间?(季末、剩余周数)
  4. 当前售罄率是多少?
  5. 是否有历史价格弹性数据?
  6. 可以如何控制降价?(频率、幅度、门店级别)
  7. 是否有折扣店用于清仓?
  8. 竞争对手采取哪些降价做法?
  9. 是否有利润率限制?(可接受的最低利润率)

相关技能

  • retail-allocation:初始分配,尽量减少未来降价
  • demand-forecasting:预测不同价位的需求
  • retail-replenishment:制定补货策略,避免库存过剩
  • seasonal-planning:规划季节性采购,降低降价风险
  • promotional-planning:比较促销策略与降价策略
  • inventory-optimization:管理库存,尽量减少降价
  • supply-chain-analytics:降价绩效指标

评论

支持 Markdown