AI Restaurant Menu Optimization System
Menu engineering with AI is not just "remove underperforming dishes." The system analyzes profitability, popularity, seasonality, prep time, kitchen impact, and forecasts demand for procurement. McDonald's and Starbucks use ML for quarterly menu reviews.
Menu Analysis: Stars/Puzzles/Plowhorses/Dogs Matrix
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from anthropic import Anthropic
import json
class MenuEngineeringAnalyzer:
"""Classic menu engineering matrix + ML extensions"""
def classify_menu_items(self, sales_data: pd.DataFrame) -> pd.DataFrame:
"""
Boston Consulting Group matrix for menu:
- Stars: high margin + high popularity → keep, promote
- Puzzles: high margin + low popularity → rename/reposition
- Plowhorses: low margin + high popularity → reduce costs
- Dogs: low margin + low popularity → remove
"""
df = sales_data.copy()
# Normalized metrics
median_popularity = df['orders_count'].median()
median_margin = df['contribution_margin_pct'].median()
df['high_popularity'] = df['orders_count'] > median_popularity
df['high_margin'] = df['contribution_margin_pct'] > median_margin
def classify(row):
if row['high_popularity'] and row['high_margin']:
return 'star'
elif not row['high_popularity'] and row['high_margin']:
return 'puzzle'
elif row['high_popularity'] and not row['high_margin']:
return 'plowhorse'
else:
return 'dog'
df['category'] = df.apply(classify, axis=1)
# Additional metrics
df['revenue_share'] = df['total_revenue'] / df['total_revenue'].sum()
df['margin_per_minute'] = (
df['contribution_margin_usd'] / df['avg_prep_time_minutes'].clip(1)
)
return df.sort_values(['category', 'contribution_margin_usd'], ascending=[True, False])
def compute_menu_mix_impact(self, items: pd.DataFrame) -> dict:
"""What happens to revenue when menu changes"""
stars = items[items['category'] == 'star']
dogs = items[items['category'] == 'dog']
puzzles = items[items['category'] == 'puzzle']
return {
'stars_revenue_share': stars['revenue_share'].sum(),
'dogs_revenue_share': dogs['revenue_share'].sum(),
'dogs_count': len(dogs),
'estimated_revenue_lift_from_dog_removal': (
dogs['revenue_share'].sum() * 0.6 # 60% orders shift to stars
),
'puzzles_reposition_opportunity': len(puzzles)
}
class DemandForecastForMenu:
"""Demand forecasting for menu items for procurement"""
def __init__(self):
self.models = {}
def train_item_model(self, item_id: str, sales_history: pd.DataFrame):
"""Forecast model for specific dish"""
if len(sales_history) < 60:
return
features = self._build_features(sales_history)
y = sales_history['orders_count']
self.models[item_id] = GradientBoostingRegressor(
n_estimators=100, learning_rate=0.1, random_state=42
)
self.models[item_id].fit(features, y)
def _build_features(self, df: pd.DataFrame) -> pd.DataFrame:
return pd.DataFrame({
'weekday': df['date'].dt.weekday,
'month': df['date'].dt.month,
'is_weekend': (df['date'].dt.weekday >= 5).astype(int),
'is_holiday': df.get('is_holiday', 0),
'temperature': df.get('temperature_c', 15),
'is_raining': df.get('is_raining', 0),
'lag_7d': df['orders_count'].shift(7).fillna(0),
'lag_14d': df['orders_count'].shift(14).fillna(0),
'rolling_mean_7d': df['orders_count'].rolling(7).mean().fillna(0),
'special_event': df.get('special_event', 0),
}).fillna(0)
def forecast_week(self, item_id: str,
next_7_days: pd.DataFrame) -> dict:
"""Forecast orders for week for inventory management"""
if item_id not in self.models:
return {'error': 'No model trained'}
features = self._build_features(next_7_days)
daily_forecast = self.models[item_id].predict(features).clip(0)
return {
'item_id': item_id,
'daily_forecast': daily_forecast.round().astype(int).tolist(),
'total_week': int(daily_forecast.sum()),
'peak_day': next_7_days['date'].iloc[daily_forecast.argmax()].strftime('%A'),
'confidence': 'high' if item_id in self.models else 'low'
}
class MenuAIAdvisor:
"""LLM consultant for menu optimization"""
def __init__(self):
self.llm = Anthropic()
def generate_optimization_report(self, menu_analysis: pd.DataFrame,
restaurant_concept: str,
season: str) -> str:
"""Report with menu optimization recommendations"""
stars = menu_analysis[menu_analysis['category'] == 'star'][['item_name', 'revenue_share']].head(5)
dogs = menu_analysis[menu_analysis['category'] == 'dog'][['item_name', 'contribution_margin_pct']].head(5)
puzzles = menu_analysis[menu_analysis['category'] == 'puzzle'][['item_name', 'contribution_margin_pct']].head(3)
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=500,
messages=[{
"role": "user",
"content": f"""You're a restaurant consultant. Provide menu optimization recommendations.
Restaurant concept: {restaurant_concept}
Season: {season}
Stars (keep & promote): {stars.to_dict('records')}
Dogs (consider removing): {dogs.to_dict('records')}
Puzzles (reposition/rename): {puzzles.to_dict('records')}
Provide specific recommendations:
1. Which dogs to remove and why
2. How to reposition puzzle items (name changes, placement, description)
3. How to leverage stars better
4. 2-3 seasonal items to consider adding
5. Pricing adjustments for plowhorses
Be specific. 3-4 paragraphs."""
}]
)
return response.content[0].text
def suggest_new_items(self, current_menu: list[str],
trending_ingredients: list[str],
cuisine_type: str) -> list[dict]:
"""Suggestions for new dishes based on trends"""
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=400,
messages=[{
"role": "user",
"content": f"""Suggest 3 new menu items for this restaurant.
Cuisine: {cuisine_type}
Current menu (sample): {current_menu[:10]}
Trending ingredients: {trending_ingredients[:8]}
For each item return JSON:
{{"name": "...", "description": "...", "main_ingredients": [...], "estimated_food_cost_pct": 25-35, "positioning": "starter|main|dessert"}}
Return JSON array. Suggest items that complement the current menu."""
}]
)
try:
return json.loads(response.content[0].text)
except Exception:
return []
Regular AI menu analysis (quarterly) increases contribution margin by 3-6 percentage points. Removing bottom-10% "dog" category items rarely reduces revenue by more than 2%—guests switch to alternatives. Demand forecasting reduces food waste by 15-25%.







