Project: Restaurant Recommendation System
Author: Ayush Saxena
Date: January 2026
Version: 1.0
This document catalogs edge cases, error scenarios, and handling strategies for the restaurant recommendation system. Proper edge case handling is critical for production reliability.
Scenario: User has 0 order history
Behavior:
- Cannot use collaborative filtering (no interaction data)
- Must rely on content-based + contextual
Handling:
if user_order_count == 0:
# Trigger onboarding flow
if user_completed_onboarding:
# Use preferences from onboarding
recommendations = cold_start_handler.onboarding_recommend(
user_preferences,
n=10
)
else:
# Fallback to popular restaurants
recommendations = cold_start_handler.popular_recommend(n=10)User Experience:
- Show onboarding prompt (3 questions)
- Allow skip option (defaults to popular)
- Clear messaging: "Help us personalize your experience"
Scenario: User hasn't ordered in 90+ days
Issues:
- Preferences may have changed
- Historical data less relevant
- Recency score approaches zero
Handling:
if days_since_last_order > 90:
# Reduce weight of historical preferences
cf_weight = 0.2 # Down from 0.4
cb_weight = 0.5 # Up from 0.35
context_weight = 0.3 # Up from 0.25
# Add "Welcome back!" messaging
add_notification("We've updated recommendations based on what's popular now")Alternative Approach:
- Treat as "semi-cold start"
- Blend old preferences with current trends
Scenario: User has 200+ orders
Issues:
- Interaction matrix dominated by this user
- May have ordered from 100+ restaurants
- Difficult to find "new" recommendations
Handling:
if user_order_count > 100:
# Increase novelty weight
novelty_boost = 1.5
# Expand "new restaurant" definition
new_threshold_days = 180 # Haven't ordered in 6 months = "new"
# Prioritize discovery over familiarity
familiar_ratio = 0.2 # 20% familiar, 80% newUser Experience:
- Badge: "Foodie Explorer"
- Section: "Hidden Gems You Haven't Tried"
- Explicit novelty messaging
Scenario: User only orders from 1 cuisine type (e.g., only North Indian)
Issues:
- All recommendations become same cuisine
- No diversity
- Filter bubble
Handling:
if user_cuisine_concentration > 0.9: # 90%+ from one cuisine
# Force diversity
max_per_cuisine = 2 # Down from 3
# Add "You might also like" section
recommendations += get_adjacent_cuisines(
primary_cuisine,
n=3
)
# Soften explanations
# Instead of: "You love North Indian"
# Use: "Based on your taste, you might enjoy..."Adjacent Cuisines Logic:
cuisine_similarity = {
'North Indian': ['South Indian', 'Punjabi', 'Mughlai'],
'Chinese': ['Thai', 'Japanese', 'Pan-Asian'],
'Italian': ['Continental', 'Mediterranean', 'Pizza']
}Scenario: User says "vegetarian" but orders from non-veg restaurants
Issues:
- Profile doesn't match behavior
- Which signal to trust?
Handling:
if stated_preference != behavioral_preference:
# Trust behavior over stated preference
effective_preference = behavioral_preference
# Suggest updating profile
show_notification(
"We noticed you often order from non-veg restaurants. "
"Update your dietary preference for better recommendations?"
)Priority Hierarchy:
- Recent behavior (last 10 orders)
- Long-term behavior (last 6 months)
- Stated preference (profile)
Scenario: Restaurant just joined platform, 0 orders
Issues:
- No collaborative signal
- No user ratings yet
- Cold start for restaurant
Handling:
if restaurant_order_count == 0:
# Use content-based features only
score = content_based_score(restaurant, user)
# Add "new restaurant" boost (exploration)
if restaurant_days_on_platform < 30:
score *= 1.2 # 20% boost
add_badge("New on Platform")Gradual Integration:
- Week 1: Show to 5% of compatible users
- Week 2-4: Increase to 20% if ratings positive
- Month 2+: Full integration if avg rating ≥3.5
Scenario: Restaurant has avg rating <3.0
Issues:
- Poor user experience if recommended
- Guardrail metric: Maintain quality
Handling:
# Hard filter: Never recommend below 3.0
if restaurant_avg_rating < 3.0:
exclude_from_recommendations = True
# Soft filter: Penalize 3.0-3.5
if 3.0 <= restaurant_avg_rating < 3.5:
score *= 0.7 # 30% penaltyException:
- If user has historically ordered from this restaurant (personal preference)
- Allow but add warning: "Rating has dropped since your last order"
Scenario: Restaurant is closed (maintenance, holidays, peak hours)
Issues:
- User frustration if recommended
- Wasted recommendation slot
Handling:
# Real-time availability check (if API available)
if not restaurant.is_accepting_orders():
exclude_from_recommendations = True
# Fallback: Use operating hours
current_time = datetime.now().time()
if not restaurant.is_open_at(current_time):
exclude_from_recommendations = TrueFuture Enhancement:
- "Opens at 5 PM" badge for closed restaurants
- "Save for later" option
Scenario: Restaurant is 15+ km away
Issues:
- Long delivery time (>60 min)
- High delivery fees
- Cold food
Handling:
if distance_km > 10:
# Hard filter for portfolio version
exclude_from_recommendations = True
# Production: Soft filter with warning
if 10 < distance_km < 15:
score *= 0.5 # Heavy penalty
add_warning("Longer delivery time expected")Context-Dependent:
- Late night (1-3 AM): Relax distance constraint (few options)
- Peak hours: Tighten constraint (traffic delays)
Scenario: Restaurant only has 3 menu items
Issues:
- May not satisfy all users
- Limited options for dietary restrictions
Handling:
if restaurant_menu_item_count < 5:
# Add badge: "Specialty Restaurant"
# Lower weight slightly
score *= 0.9Scenario: Model throws exception or times out
Issues:
- User sees error or blank screen
- Lost revenue opportunity
Handling:
try:
recommendations = hybrid_model.recommend(
user_id=user_id,
n=10,
timeout=2.0 # 2 second timeout
)
except Exception as e:
log_error(f"Model failure for user {user_id}: {e}")
# Fallback: Popular restaurants
recommendations = fallback_recommender.popular_restaurants(
user_location=user_location,
n=10
)
# Add subtle notice
# "Showing popular restaurants in your area"Fallback Hierarchy:
- Cached recommendations (pre-computed)
- Popular restaurants (by location)
- Random sample (last resort)
Scenario: Model returns 10 restaurants, all with score <0.3
Issues:
- Low confidence predictions
- May not satisfy user
Handling:
if max(recommendation_scores) < 0.3:
# Blend with popular restaurants
model_recs = recommendations[:5] # Top 5 from model
popular_recs = get_popular(n=5) # Top 5 popular
final_recs = interleave(model_recs, popular_recs)Scenario: All model recommendations filtered out (distance, rating, etc.)
Issues:
- Empty recommendation list
- User sees "No restaurants found"
Handling:
recommendations = apply_filters(candidate_restaurants)
if len(recommendations) == 0:
# Relax filters progressively
# Step 1: Relax distance (10km → 15km)
recommendations = apply_filters(
candidate_restaurants,
max_distance=15
)
# Step 2: Relax rating (3.5 → 3.0)
if len(recommendations) == 0:
recommendations = apply_filters(
candidate_restaurants,
max_distance=15,
min_rating=3.0
)
# Step 3: Show popular (no filters)
if len(recommendations) == 0:
recommendations = get_popular_restaurants(n=10)
add_message("Showing popular restaurants (some may be far)")Scenario: Model returns same restaurant multiple times
Issues:
- Wasted recommendation slots
- Looks like a bug
Handling:
# Deduplicate before returning
recommendations = list(dict.fromkeys(recommendations)) # Preserves order
# If <10 after deduplication, fill with next-best
if len(recommendations) < 10:
additional = get_next_best_recommendations(
exclude=recommendations,
n=10 - len(recommendations)
)
recommendations.extend(additional)Scenario: User's GPS coordinates are (0, 0) or out of service area
Issues:
- Can't calculate distance
- Can't provide location-based recommendations
Handling:
if not is_valid_location(user_lat, user_lon):
# Use last known location
if user_has_previous_location:
user_location = get_last_known_location(user_id)
else:
# Use city center as default
user_location = get_city_center(user_city)
add_message("Using your city location. Enable GPS for better recommendations.")Scenario: User orders at 3 AM
Issues:
- Most restaurants closed
- Limited options
- Different food preferences (late-night cravings)
Handling:
if 2 <= current_hour <= 5: # 2 AM - 5 AM
# Filter: Only show 24-hour restaurants
restaurants = filter_24_hour(restaurants)
# Boost late-night cuisine
cuisine_boost = {
'Fast Food': 1.5,
'Street Food': 1.4,
'Chinese': 1.3
}
# Relax quality threshold (fewer options)
min_rating = 3.0 # Down from 3.5Scenario: Heavy rain, storm warning, or extreme heat
Issues:
- Delivery delays
- Restaurant closures
- Safety concerns
Handling:
if weather_alert_level == "severe":
# Add prominent warning
show_banner(
"Weather Alert: Deliveries may be delayed or unavailable. "
"Check with restaurant before ordering."
)
# Penalize outdoor/street food
if restaurant_type in ['Street Food', 'Food Truck']:
score *= 0.5
# Boost reliable chains
if restaurant_is_chain:
score *= 1.2Scenario: Diwali, Holi, Christmas, or local festival
Issues:
- Many restaurants closed
- Different food preferences (festive meals)
- High demand, longer delivery times
Handling:
if is_festival_day(current_date):
# Boost festival-appropriate cuisines
if festival == "Diwali":
cuisine_boost = {
'Desserts': 2.0,
'North Indian': 1.5,
'South Indian': 1.4
}
# Show "Open during festival" badge
if restaurant_is_open:
add_badge("Open Today")
# Set expectations
add_message("Festival rush: Deliveries may take longer than usual")Scenario: User profile incomplete (no dietary preference, no favorite cuisine)
Handling:
# Use safe defaults
user_features = {
'dietary_preference': user.dietary_preference or 'no_preference',
'favorite_cuisine': user.favorite_cuisine or get_most_popular_cuisine(),
'price_sensitivity': user.price_sensitivity or 'medium'
}Scenario: Restaurant has rating=4.5 but 0 reviews
Handling:
# Data validation on ingestion
if restaurant_avg_rating > 0 and restaurant_total_reviews == 0:
log_warning(f"Inconsistent data for {restaurant_id}")
# Don't use rating until reviews accumulate
restaurant_avg_rating = None
use_content_based_only = TrueScenario: Order value = ₹50,000 (catering order) or delivery time = 300 min
Handling:
# Remove outliers before model training
orders = orders[
(orders['order_value'] >= 100) &
(orders['order_value'] <= 2000) &
(orders['delivery_time'] >= 15) &
(orders['delivery_time'] <= 90)
]Scenario: Platform wants to promote high-margin restaurants
Conflict: Business goal vs user satisfaction
Handling (Ethical Approach):
# DO NOT boost based on commission
# Maintain user trust and long-term retention
# Optional: Paid placement section (clearly labeled)
# "Sponsored" section separate from recommendationsWhy This Matters:
- User trust is paramount
- Short-term revenue gain < long-term reputation damage
- Recommendations should optimize for user, not business
Scenario: Restaurant only has 5 delivery slots left
Issues:
- If recommended to many users, won't fulfill
- User frustration
Handling (Future Enhancement):
# Real-time inventory check
if restaurant_available_slots < 10:
# Reduce recommendation frequency
show_probability *= (restaurant_available_slots / 10)
# Fallback for MVP: Not implemented
# Requires real-time restaurant API integrationScenario: User flagged for fraudulent behavior
Handling:
if user_is_banned:
# Don't provide service
return error_response("Account suspended. Contact support.")
if user_is_flagged_for_fraud:
# Provide service but log activity
log_fraud_attempt(user_id, activity="recommendation_request")
# Return recommendations (don't reveal detection)
return recommendationsScenario: User views recommendations, then orders from a different screen, then returns
Issues:
- Recommendations may now include just-ordered restaurant
- Seems like a mistake
Handling:
# Check session state before displaying
recent_orders = get_orders_in_last_n_minutes(user_id, n=5)
# Exclude from recommendations
recommendations = [
r for r in recommendations
if r.restaurant_id not in recent_orders
]Scenario: User rapidly refreshes page, triggering multiple recommendation calls
Issues:
- Wasted computation
- Potential race conditions
Handling:
# Request deduplication
request_key = f"recs:{user_id}:{context_hash}"
# Check if request in progress
if redis.exists(f"processing:{request_key}"):
# Return cached or wait
return wait_for_completion(request_key)
# Set processing flag
redis.setex(f"processing:{request_key}", ttl=5, value=1)
try:
recommendations = compute_recommendations()
cache_recommendations(recommendations)
finally:
redis.delete(f"processing:{request_key}")Scenario: User exercises "right to be forgotten"
Handling:
def delete_user_data(user_id):
# Delete personal data
delete_from_db(f"users WHERE user_id = {user_id}")
delete_from_db(f"orders WHERE user_id = {user_id}")
# Anonymize in analytics
anonymize_user_id(user_id)
# Remove from recommendation models
remove_from_interaction_matrix(user_id)
# Flag for model re-training
schedule_model_retrain()Scenario: User is under 18
Issues:
- Different content policies
- Parental consent requirements
Handling:
if user_age < 18:
# Don't recommend alcohol-serving restaurants
restaurants = exclude_alcohol_license(restaurants)
# Require parental consent for account
if not parental_consent_given:
prompt_for_consent()class RecommendationError(Exception):
"""Base exception for recommendation system"""
pass
class UserNotFoundError(RecommendationError):
"""User doesn't exist in system"""
fallback = "popular_restaurants"
class ModelTimeoutError(RecommendationError):
"""Model took too long to respond"""
fallback = "cached_recommendations"
class NoRecommendationsError(RecommendationError):
"""No restaurants match criteria"""
fallback = "relax_filters"
class DataQualityError(RecommendationError):
"""Data validation failed"""
fallback = "skip_invalid_records"def safe_recommend(user_id, context, n=10):
"""
Fault-tolerant recommendation with automatic fallbacks
"""
try:
# Primary: ML model
return hybrid_model.recommend(user_id, context, n)
except ModelTimeoutError:
# Fallback 1: Cached recommendations
cached = get_cached_recommendations(user_id)
if cached:
return apply_context(cached, context)[:n]
except (UserNotFoundError, NoRecommendationsError):
# Fallback 2: Popular restaurants
return popular_recommender(user_location, n)
except Exception as e:
# Fallback 3: Random (last resort)
log_critical_error(f"All fallbacks failed: {e}")
return random_restaurants(n)
finally:
# Always log
log_recommendation_attempt(user_id, success=True)def test_cold_start_user():
user = create_user(order_count=0)
recs = recommender.recommend(user.id)
assert len(recs) == 10
assert all(r.rating >= 3.5 for r in recs)
def test_invalid_location():
user = create_user(location=(0, 0))
recs = recommender.recommend(user.id)
assert len(recs) == 10 # Should not crash
def test_model_timeout():
with mock.patch('model.predict', side_effect=TimeoutError):
recs = recommender.recommend(user.id)
assert len(recs) == 10 # Fallback should work✅ New user (cold start)
✅ Model failure/timeout
✅ Invalid location
✅ No recommendations after filtering
✅ Inactive user (stale data)
✅ Restaurant temporarily closed
✅ Extreme weather
✅ Data quality issues
Document Owner: Ayush Saxena
Last Updated: January 2026
Review Cycle: Quarterly or post-incident