x

Agent-03 库存预测与补货

版本: v1.0
概述: 基于历史销量预测库存需求,生成智能补货建议

1. Agent职责

维度 说明
名称 inventory-forecast-agent
执行时间 每日 06:00
推送渠道 企业微信 / 邮件
接收人 采购主管 / 仓库主管 / 店长

核心能力

能力 说明
安全库存 正态分布法,Z×σ×√LT
缺货预测 未来7/14/30天缺货风险
智能补货 含MOQ约束的最优补货量
滞销识别 零销售/库龄/清理优先级
品类关联 Apriori算法搭配销售推荐
季节性预测 Holt-Winters指数平滑

2. 安全库存计算

正态分布法公式

安全库存(SS)= Z × σ × √(LT)

其中:
- Z = 服务水平系数(95%→1.65, 97.5%→1.96, 99%→2.33)
- σ = 需求标准差(日均销量的波动)
- LT = 补货提前期(天)
- 再订货点(ROP) = 日均销量 × 补货提前期 + 安全库存

安全库存SQL

WITH daily_sales AS (
    SELECT 
        product_id,
        toDate(sale_date) AS sale_date,
        SUM(quantity) AS daily_qty
    FROM sales_order_detail
    WHERE sale_date >= today() - 90
    GROUP BY product_id, toDate(sale_date)
),
sales_stats AS (
    SELECT 
        product_id,
        AVG(daily_qty) AS avg_daily_sales,
        stddevPop(daily_qty) AS demand_stddev
    FROM daily_sales
    GROUP BY product_id
),
lead_times AS (
    SELECT product_id, AVG(lead_time_days) AS avg_lead_time
    FROM supplier_lead_times
    GROUP BY product_id
),
safe_stock_calc AS (
    SELECT 
        s.product_id,
        p.product_name,
        s.avg_daily_sales,
        s.demand_stddev,
        l.avg_lead_time,
        -- 安全库存 97.5%服务水平
        ROUND(1.96 * s.demand_stddev * sqrt(l.avg_lead_time)) AS safe_stock_975,
        -- 再订货点
        ROUND(s.avg_daily_sales * l.avg_lead_time + 1.96 * s.demand_stddev * sqrt(l.avg_lead_time)) AS reorder_point
    FROM sales_stats s
    LEFT JOIN lead_times l ON s.product_id = l.product_id
    JOIN product_master p ON s.product_id = p.product_id
)
SELECT *, GREATEST(0, reorder_point + safe_stock_975 - current_stock) AS suggested_order_qty
FROM safe_stock_calc
ORDER BY suggested_order_qty DESC;

3. 缺货预测SQL

WITH future_demand AS (
    SELECT 
        product_id,
        SUM(CASE WHEN sale_date >= today() AND sale_date < today() + 7 THEN quantity ELSE 0 END) AS demand_7d,
        AVG(CASE WHEN sale_date >= today() - 7 THEN daily_qty ELSE NULL END) AS avg_daily_7d
    FROM (
        SELECT product_id, toDate(sale_date) AS sale_date, SUM(quantity) AS quantity, SUM(quantity)/7 AS daily_qty
        FROM sales_order_detail
        WHERE sale_date >= today() - 30
        GROUP BY product_id, toDate(sale_date)
    )
    GROUP BY product_id
)
SELECT 
    p.product_code,
    p.product_name,
    i.current_stock,
    f.demand_7d,
    f.avg_daily_7d,
    ROUND(i.current_stock / NULLIF(f.avg_daily_7d, 0)) AS stock_days_remaining,
    CASE 
        WHEN i.current_stock <= f.demand_7d THEN '🔴 7天内缺货'
        WHEN i.current_stock <= f.demand_7d * 2 THEN '🟠 14天内缺货'
        ELSE '🟢 库存充足'
    END AS stockout_risk
FROM product_master p
LEFT JOIN inventory_current i ON p.product_id = i.product_id
LEFT JOIN future_demand f ON p.product_id = f.product_id
WHERE i.current_stock <= f.demand_7d * 2 OR i.current_stock = 0
ORDER BY i.current_stock ASC;

4. 智能补货计算

核心算法

def calculate_reorder_quantity(
    current_stock, avg_daily_sales, demand_stddev,
    lead_time, safety_factor=1.96, moq=10, eoq=100, max_stock=None
):
    safety_stock = safety_factor * demand_stddev * (lead_time ** 0.5)
    reorder_point = avg_daily_sales * lead_time + safety_stock
    available_stock = current_stock  # 简化模型

    if available_stock <= reorder_point:
        target_stock = avg_daily_sales * lead_time * 2 + safety_stock
        if max_stock:
            target_stock = min(target_stock, max_stock)
        theoretical_qty = target_stock - available_stock
        order_qty = max(moq, math.ceil(theoretical_qty / eoq) * eoq)
    else:
        order_qty = 0

    return {
        'need_reorder': available_stock <= reorder_point,
        'safety_stock': round(safety_stock, 2),
        'reorder_point': round(reorder_point, 2),
        'suggested_qty': math.ceil(order_qty),
        'urgency': 'high' if available_stock < safety_stock else 'medium'
    }

5. 滞销识别SQL

SELECT 
    p.product_code,
    p.product_name,
    i.current_stock,
    i.current_stock_value,
    today() - toDate(i.last_out_date) AS days_since_last_sale,
    today() - toDate(i.last_in_date) AS days_in_stock,
    CASE 
        WHEN days_since_last_sale > 180 THEN '🆕 死库存'
        WHEN days_in_stock > 365 THEN '⚠️ 超龄库存'
        WHEN days_since_last_sale > 90 THEN '🔴 长期滞销'
        WHEN days_in_stock > 90 THEN '🟡 积压'
        ELSE '🟢 正常'
    END AS slow_moving_type,
    ROUND(days_in_stock * 0.3 + days_since_last_sale * 0.4, 0) AS clearance_priority_score
FROM inventory_current i
JOIN product_master p ON i.product_id = p.product_id
WHERE i.current_stock > 0
  AND (days_since_last_sale > 90 OR days_in_stock > 180)
ORDER BY clearance_priority_score DESC
LIMIT 50;

6. 品类关联分析(Apriori)

-- 经常一起购买的商品组合
WITH order_items AS (
    SELECT 
        order_no,
        groupArray(product_id) AS product_ids
    FROM (
        SELECT DISTINCT order_no, product_id
        FROM sales_order_detail
        WHERE order_date >= today() - 90
    )
    GROUP BY order_no
)
SELECT 
    p1.product_name AS item1,
    p2.product_name AS item2,
    COUNT(*) AS co_occurrence_count,
    ROUND(COUNT(*) / (SELECT count() FROM order_items) * 100, 2) AS support_pct,
    ROUND(COUNT(*) / (SELECT count() FROM order_items WHERE has(product_ids, p1.product_id)) * 100, 2) AS confidence_pct,
    CASE WHEN confidence_pct >= 50 THEN '⭐⭐⭐ 强关联' WHEN confidence_pct >= 30 THEN '⭐⭐ 中关联' ELSE '⭐ 弱关联' END AS strength
FROM order_items oi
ARRAY JOIN product_ids AS p1_id, product_ids AS p2_id
JOIN product_master p1 ON p1_id = p1.product_id
JOIN product_master p2 ON p2_id = p2.product_id
WHERE p1_id < p2_id
GROUP BY p1.product_name, p2.product_name
HAVING co_occurrence_count >= 10
ORDER BY confidence_pct DESC
LIMIT 20;

7. 季节性预测(Holt-Winters)

-- 月度季节性指数
WITH monthly_sales AS (
    SELECT 
        toStartOfMonth(sale_date) AS month,
        SUM(total_amount) AS monthly_sales
    FROM sales_order
    WHERE sale_date >= addMonths(today(), -24)
      AND order_status NOT IN ('cancelled')
    GROUP BY toStartOfMonth(sale_date)
),
seasonal_index AS (
    SELECT 
        toMonth(month) AS month_num,
        AVG(monthly_sales) AS avg_sales,
        ROUND(AVG(monthly_sales) / (SELECT AVG(monthly_sales) FROM monthly_sales), 3) AS seasonal_index
    FROM monthly_sales
    GROUP BY toMonth(month)
)
SELECT 
    month_num,
    CASE month_num
        WHEN 1 THEN '1月' WHEN 2 THEN '2月' WHEN 3 THEN '3月'
        WHEN 4 THEN '4月' WHEN 5 THEN '5月' WHEN 6 THEN '6月'
        WHEN 7 THEN '7月' WHEN 8 THEN '8月' WHEN 9 THEN '9月'
        WHEN 10 THEN '10月' WHEN 11 THEN '11月' WHEN 12 THEN '12月'
    END AS month_label,
    seasonal_index,
    CASE WHEN seasonal_index > 1.2 THEN '🔴 旺季' WHEN seasonal_index < 0.8 THEN '🔵 淡季' ELSE '🟡 平季' END AS season_type
FROM seasonal_index
ORDER BY month_num;
Left-click: follow link, Right-click: select node, Scroll: zoom
x