第03章:多门店智能订货系统——告别拍脑袋订货

第03章:多门店智能订货系统——告别拍脑袋订货


3.1 餐饮订货的三个永恒问题

每个餐饮店长每天早上都要回答三个问题:

今天来多少人?
每道菜备多少?
万一备多了怎么办?

这三个问题的本质是信息不对称:订货时掌握的信息太少,历史数据、天气预报、竞品动态、促销活动——这些变量如果有系统整合起来,订货准确率能提升30%以上。


3.2 智能订货的数据来源

智能订货系统需要整合四类数据:

数据源一:历史销售数据(核心基础)

-- 过去90天的销售明细
SELECT
    DATE(order_time) as order_date,
    dish_id,
    dish_name,
    SUM(quantity) as total_qty,
    COUNT(DISTINCT ticket_id) as order_count,
    AVG(unit_price) as avg_price
FROM order_details
WHERE store_id = :store_id
  AND order_time >= NOW() - INTERVAL '90 days'
GROUP BY DATE(order_time), dish_id, dish_name
ORDER BY order_date DESC

数据源二:天气数据(关键变量)

天气对餐饮的影响是真实且可量化的:

天气 堂食影响 外卖影响
下雨/暴雨 -20%~-40% +30%
晴天 +10% -5%
降温(<10°C) +15%(火锅品类) +10%
升温(>30°C) -10%(热菜) +20%
# 从天气预报API获取未来3天的数据
WEATHER_API = "https://api.open-meteo.com/v1/forecast"

def get_weather_forecast(lat, lon, days=3):
    params = {
        "latitude": lat,
        "longitude": lon,
        "daily": "temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode",
        "forecast_days": days
    }
    response = requests.get(WEATHER_API, params=params)
    return response.json()

数据源三:节假日与事件日历

SPECIAL_DATES = {
    "2026-04-04": {"name": "清明节", "impact": "周末效应", "multiplier": 1.3},
    "2026-05-01": {"name": "劳动节", "impact": "节假日效应", "multiplier": 1.5},
    "2026-06-01": {"name": "端午节", "impact": "节假日效应", "multiplier": 1.4},
    "2026-09-15": {"name": "中秋节", "impact": "节假日效应", "multiplier": 1.6},
}

数据源四:竞品动态(手动输入)

# 店长每天在飞书机器人输入
def input_competition_info(store_id, info):
    """
    竞品动态:谁在做活动、折扣力度
    """
    with get_pg_conn() as conn:
        cur = conn.cursor()
        cur.execute("""
            INSERT INTO competition_events (store_id, event_type, description, impact_days)
            VALUES (%s, %s, %s, %s)
        """, (store_id, info["type"], info["desc"], info.get("impact_days", 3)))
        conn.commit()

3.3 智能订货算法

基础订货量公式

建议订货量 = 基础量 × 天气系数 × 节假日系数 × 竞品影响系数 × 安全库存系数

基础量 = 过去90天同类菜日均销量 × 备货天数
def calculate_order_recommendation(store_id, dish_id, order_days=3):
    """
    计算智能订货建议
    """
    # 1. 基础量:过去90天日均销量
    avg_daily_sales = get_avg_daily_sales(store_id, dish_id, days=90)

    # 2. 天气系数
    weather = get_weather_forecast(STORE_LAT[store_id], STORE_LON[store_id])
    weather_multiplier = calc_weather_multiplier(weather, get_dish_category(dish_id))

    # 3. 节假日系数
    holiday_multiplier = get_holiday_multiplier(order_days)

    # 4. 竞品影响系数
    competition_multiplier = get_competition_multiplier(store_id)

    # 5. 计算建议量
    recommended_qty = (
        avg_daily_sales
        * order_days
        * weather_multiplier
        * holiday_multiplier
        * competition_multiplier
    )

    # 6. 安全库存(增加20%Buffer)
    recommended_qty = recommended_qty * 1.2

    # 7. 对接供应链(按箱/按件取整)
    package_size = get_dish_package_size(dish_id)  # 比如小龙虾按斤称
    final_qty = round(recommended_qty)

    return {
        "dish_id": dish_id,
        "recommended_qty": final_qty,
        "avg_daily_sales": round(avg_daily_sales, 1),
        "weather_factor": weather_multiplier,
        "holiday_factor": holiday_multiplier,
        "safety_stock": round(recommended_qty * 0.2),
        "confidence": calc_confidence(avg_daily_sales, days=90)  # 置信度
    }

3.4 每日订货建议报告

每天早晨6点,系统自动生成订货建议,推送到店长飞书:

def generate_daily_order_report(store_id):
    """
    生成当日订货建议报告
    """
    report_lines = []
    report_lines.append(f"📋 {get_store_name(store_id)} 订货建议单")
    report_lines.append(f"📅 日期:{datetime.now().strftime('%Y-%m-%d')}")
    report_lines.append(f"🌤️ 天气:{get_today_weather_desc(store_id)}")
    report_lines.append("")

    # 按分类输出
    categories = get_dish_categories()
    total_value = 0

    for category in categories:
        dishes = get_dishes_by_category(category)
        if not dishes:
            continue

        report_lines.append(f"━━━ {category} ━━━")

        for dish in dishes:
            rec = calculate_order_recommendation(store_id, dish["id"], order_days=3)

            if rec["recommended_qty"] == 0:
                continue

            emoji = "🔴" if rec["confidence"] < 0.6 else ("🟡" if rec["confidence"] < 0.8 else "🟢")

            report_lines.append(
                f"{emoji} {dish['name']}: "
                f"建议备货 {rec['recommended_qty']}{dish['unit']} "
                f"(日均{rec['avg_daily_sales']}{dish['unit']}, "
                f"置信度{int(rec['confidence']*100)}%)"
            )

        report_lines.append("")

    # 汇总
    report_lines.append(f"📊 预估备货总额:¥{total_value:.0f}")
    report_lines.append(f"⚠️ 低置信度菜品:{', '.join(get_low_confidence_dishes(store_id))}")
    report_lines.append("📌 以上为系统建议,最终以店长判断为准")

    return "\n".join(report_lines)


def send_order_report_to_store(store_id):
    """发送到店长飞书"""
    report = generate_daily_order_report(store_id)
    store = get_store(store_id)
    feishu_client.send_message(
        chat_id=store["manager_feishu_id"],
        msg_type="text",
        content=report
    )

3.5 损耗追踪与反馈闭环

订货质量需要持续追踪,损耗数据反过来优化订货模型:

CREATE TABLE daily_waste_log (
    id SERIAL PRIMARY KEY,
    store_id INTEGER,
    dish_id INTEGER,
    date DATE,
    opening_stock DECIMAL(10,2),     -- 今日开货量
    sold DECIMAL(10,2),             -- 实际销量
    waste DECIMAL(10,2),            -- 损耗量
    waste_rate DECIMAL(5,2),        -- 损耗率
    reason VARCHAR(200),            -- 损耗原因
    created_at TIMESTAMP DEFAULT NOW()
);
def log_waste_and_feedback(store_id, dish_id, waste_data):
    """
    每日打烊后录入损耗数据,反馈到订货模型
    """
    waste_rate = waste_data["waste"] / waste_data["opening_stock"]

    with get_pg_conn() as conn:
        cur = conn.cursor()
        cur.execute("""
            INSERT INTO daily_waste_log
            (store_id, dish_id, date, opening_stock, sold, waste, waste_rate, reason)
            VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
        """, (
            store_id, dish_id,
            datetime.now().date(),
            waste_data["opening_stock"],
            waste_data["sold"],
            waste_data["waste"],
            waste_rate,
            waste_data.get("reason", "")
        ))
        conn.commit()

    # 如果损耗率>15%,触发预警
    if waste_rate > 0.15:
        send_waste_alert(store_id, dish_id, waste_rate)

    # 损耗数据反哺订货模型:增加该菜品历史销量的置信度
    update_order_model_confidence(store_id, dish_id, waste_data)

落地动作

  1. 导出过去30天的菜品销售数据(收银系统可导出Excel)
  2. 在PG创建 dishesdaily_waste_logorder_recommendations 三张表
  3. 录入第一批菜品基础数据(分类、单位、供应商)
  4. 运行一次订货算法,看输出结果是否合理
  5. 配置每天早晨6点的定时推送任务(飞书)
  6. 设计损耗录入流程(建议:打烊盘点时由店长录入)