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