skills/hotel-inventory-management/SKILL.md
When the user wants to optimize hotel room inventory, manage rate strategies, or improve revenue management. Also use when the user mentions "hotel revenue management," "room allocation," "yield management," "dynamic pricing," "overbooking optimization," "distribution channel management," or "hotel capacity planning." For tour operations, see tour-operations. For hospitality procurement, see hospitality-procurement.
npx skillsauth add kishorkukreja/awesome-supply-chain hotel-inventory-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in hotel inventory management and revenue optimization. Your goal is to help maximize hotel revenue through optimal room allocation, dynamic pricing, distribution channel management, and capacity planning while balancing occupancy and average daily rate (ADR).
Before optimizing hotel inventory, understand:
Property Characteristics
Current Performance
Distribution Channels
Objectives & Constraints
Key Metrics:
Revenue Optimization Equation:
Total Revenue = Σ (Rate_i × Rooms_Sold_i) across all segments/channels
Goal: Maximize Total Revenue subject to capacity constraints
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from datetime import datetime, timedelta
class HotelPricingOptimizer:
"""
Optimize hotel room pricing based on demand forecasts and price elasticity
"""
def __init__(self, total_rooms, room_types):
self.total_rooms = total_rooms
self.room_types = room_types # {type: count}
def optimize_pricing(self, date, demand_forecast, competitor_prices,
price_elasticity=-1.5, cost_per_room=30):
"""
Optimize room rates to maximize revenue
Parameters:
- date: date for pricing
- demand_forecast: dict of {room_type: unconstrained_demand}
- competitor_prices: dict of {room_type: competitor_avg_price}
- price_elasticity: price sensitivity (typically -1.0 to -2.0)
- cost_per_room: marginal cost (cleaning, amenities, etc.)
"""
from scipy.optimize import minimize
# Objective function: maximize revenue
def objective(prices):
"""
Revenue = Σ (Price × Quantity_Demanded)
Demand model: Q = Q0 × (P/P0)^elasticity
"""
total_revenue = 0
for i, room_type in enumerate(self.room_types.keys()):
price = prices[i]
base_demand = demand_forecast[room_type]
base_price = competitor_prices[room_type]
# Demand as function of price (power law)
price_ratio = price / base_price
demand = base_demand * (price_ratio ** price_elasticity)
# Constrained by available rooms
rooms_available = self.room_types[room_type]
rooms_sold = min(demand, rooms_available)
total_revenue += price * rooms_sold
return -total_revenue # Negative for minimization
# Initial guess: competitor prices
initial_prices = [competitor_prices[rt] for rt in self.room_types.keys()]
# Bounds: cost + margin to maximum luxury price
bounds = [(cost_per_room + 20, 1000) for _ in self.room_types]
# Optimize
result = minimize(objective, initial_prices, method='L-BFGS-B',
bounds=bounds)
optimal_prices = result.x
# Calculate expected occupancy and revenue
metrics = {}
total_rooms_sold = 0
total_revenue = 0
for i, room_type in enumerate(self.room_types.keys()):
price = optimal_prices[i]
base_demand = demand_forecast[room_type]
base_price = competitor_prices[room_type]
demand = base_demand * ((price / base_price) ** price_elasticity)
rooms_sold = min(demand, self.room_types[room_type])
total_rooms_sold += rooms_sold
revenue = price * rooms_sold
total_revenue += revenue
metrics[room_type] = {
'optimal_price': price,
'expected_rooms_sold': rooms_sold,
'revenue': revenue,
'occupancy': rooms_sold / self.room_types[room_type]
}
return {
'optimal_prices': dict(zip(self.room_types.keys(), optimal_prices)),
'room_type_metrics': metrics,
'total_occupancy': total_rooms_sold / self.total_rooms,
'total_revenue': total_revenue,
'revpar': total_revenue / self.total_rooms
}
# Example usage
optimizer = HotelPricingOptimizer(
total_rooms=200,
room_types={
'Standard': 120,
'Deluxe': 60,
'Suite': 20
}
)
demand_forecast = {
'Standard': 100,
'Deluxe': 50,
'Suite': 15
}
competitor_prices = {
'Standard': 120,
'Deluxe': 180,
'Suite': 300
}
result = optimizer.optimize_pricing(
date='2026-07-15',
demand_forecast=demand_forecast,
competitor_prices=competitor_prices,
price_elasticity=-1.3
)
print(f"Optimal Prices: {result['optimal_prices']}")
print(f"Expected Occupancy: {result['total_occupancy']:.1%}")
print(f"Expected RevPAR: ${result['revpar']:.2f}")
def optimize_length_of_stay_pricing(arrival_date, room_inventory, demand_by_los,
horizon_days=30):
"""
Optimize pricing for different length-of-stay (LOS) combinations
Parameters:
- arrival_date: starting date
- room_inventory: available rooms by date
- demand_by_los: dict of {length_of_stay: demand_curve}
- horizon_days: planning horizon
"""
from pulp import *
prob = LpProblem("LOS_Pricing", LpMaximize)
# Variables: rooms sold for each LOS starting each day
x = {} # x[arrival_day, los]: rooms sold
for day in range(horizon_days):
for los in [1, 2, 3, 4, 5, 6, 7, 14]: # Common LOS values
if day + los <= horizon_days:
x[day, los] = LpVariable(f"Rooms_{day}_{los}", lowBound=0)
# Price for each LOS (decision variables or fixed)
# For simplicity, use demand-based pricing
prices = {1: 150, 2: 140, 3: 135, 4: 130, 5: 125, 6: 120, 7: 115, 14: 100}
# Objective: maximize revenue
total_revenue = lpSum([x[day, los] * prices[los] * los
for day in range(horizon_days)
for los in [1, 2, 3, 4, 5, 6, 7, 14]
if (day, los) in x])
prob += total_revenue
# Constraints
# Room availability each night
for night in range(horizon_days):
# All reservations occupying this night
occupancy = lpSum([x[day, los]
for day in range(max(0, night - 13), night + 1)
for los in [1, 2, 3, 4, 5, 6, 7, 14]
if (day, los) in x and day + los > night])
prob += occupancy <= room_inventory[night]
# Demand constraints (can't sell more than demand)
for day in range(horizon_days):
for los in [1, 2, 3, 4, 5, 6, 7, 14]:
if (day, los) in x:
prob += x[day, los] <= demand_by_los.get(los, {}).get(day, 0)
# Solve
prob.solve(PULP_CBC_CMD(msg=0))
# Extract results
bookings = []
for (day, los), var in x.items():
if var.varValue > 0.1:
bookings.append({
'arrival_day': day,
'length_of_stay': los,
'rooms_booked': var.varValue,
'total_revenue': var.varValue * prices[los] * los
})
return {
'total_revenue': value(prob.objective),
'bookings': pd.DataFrame(bookings),
'avg_los': sum([b['length_of_stay'] * b['rooms_booked'] for b in bookings]) /
sum([b['rooms_booked'] for b in bookings]) if bookings else 0
}
def calculate_optimal_overbooking(no_show_probability, cancellation_probability,
walk_cost, revenue_per_room):
"""
Calculate optimal overbooking level to maximize expected profit
Balances risk of denied boarding (walk cost) vs. lost revenue from no-shows
Parameters:
- no_show_probability: P(guest doesn't arrive)
- cancellation_probability: P(guest cancels)
- walk_cost: cost of walking a guest (relocation + compensation)
- revenue_per_room: revenue per room per night
"""
from scipy.stats import binom
rooms_available = 100
# Calculate expected profit for different overbooking levels
results = []
for overbook in range(0, 21): # Test 0-20 rooms overbooked
total_bookings = rooms_available + overbook
expected_profit = 0
# Simulate different no-show scenarios
for shows_up in range(0, total_bookings + 1):
# Probability of this many guests showing up
prob_shows = binom.pmf(shows_up, total_bookings,
1 - no_show_probability)
if shows_up <= rooms_available:
# All guests accommodated
profit = shows_up * revenue_per_room
else:
# Need to walk guests
walks = shows_up - rooms_available
profit = (rooms_available * revenue_per_room -
walks * walk_cost)
expected_profit += prob_shows * profit
results.append({
'overbook_level': overbook,
'expected_profit': expected_profit,
'expected_walks': max(0, overbook * (1 - no_show_probability) - 0)
})
results_df = pd.DataFrame(results)
optimal = results_df.loc[results_df['expected_profit'].idxmax()]
return {
'optimal_overbook_level': int(optimal['overbook_level']),
'expected_profit': optimal['expected_profit'],
'profit_improvement': optimal['expected_profit'] -
results_df.iloc[0]['expected_profit'],
'all_results': results_df
}
# Example
result = calculate_optimal_overbooking(
no_show_probability=0.05, # 5% no-show rate
cancellation_probability=0.03,
walk_cost=250, # Cost to relocate + compensate
revenue_per_room=150
)
print(f"Optimal overbooking: {result['optimal_overbook_level']} rooms")
print(f"Expected profit improvement: ${result['profit_improvement']:,.0f}")
def optimize_channel_mix(channels, demand_by_channel, commission_rates,
total_rooms=200):
"""
Optimize allocation across distribution channels
Parameters:
- channels: list of channel names
- demand_by_channel: dict of {channel: demand_at_each_price_point}
- commission_rates: dict of {channel: commission_rate}
- total_rooms: total available rooms
"""
from pulp import *
prob = LpProblem("Channel_Mix", LpMaximize)
# Variables: rooms allocated to each channel
allocation = {}
for channel in channels:
allocation[channel] = LpVariable(f"Alloc_{channel}",
lowBound=0,
upBound=total_rooms)
# Prices by channel (BAR - Best Available Rate variations)
prices = {
'Direct': 180,
'OTA_A': 180,
'OTA_B': 175,
'GDS': 185,
'Corporate': 150,
'Group': 130
}
# Objective: maximize net revenue after commissions
net_revenue = []
for channel in channels:
gross_price = prices[channel]
commission = commission_rates.get(channel, 0)
net_price = gross_price * (1 - commission)
net_revenue.append(allocation[channel] * net_price)
prob += lpSum(net_revenue)
# Constraints
# Total allocation cannot exceed inventory
prob += lpSum([allocation[channel] for channel in channels]) <= total_rooms
# Channel-specific demand caps
for channel in channels:
max_demand = demand_by_channel.get(channel, total_rooms)
prob += allocation[channel] <= max_demand
# Strategic constraints
# Maintain minimum direct bookings (brand.com)
prob += allocation.get('Direct', 0) >= total_rooms * 0.25 # At least 25% direct
# Solve
prob.solve(PULP_CBC_CMD(msg=0))
# Results
channel_allocation = {}
for channel in channels:
rooms_allocated = allocation[channel].varValue
gross_price = prices[channel]
commission = commission_rates.get(channel, 0)
net_price = gross_price * (1 - commission)
channel_allocation[channel] = {
'rooms': rooms_allocated,
'gross_revenue': rooms_allocated * gross_price,
'commission': rooms_allocated * gross_price * commission,
'net_revenue': rooms_allocated * net_price,
'share': rooms_allocated / total_rooms * 100
}
return {
'total_net_revenue': value(prob.objective),
'channel_allocation': channel_allocation,
'blended_adr': sum([alloc['gross_revenue']
for alloc in channel_allocation.values()]) / total_rooms
}
# Example
channels = ['Direct', 'OTA_A', 'OTA_B', 'GDS', 'Corporate', 'Group']
demand_by_channel = {
'Direct': 60,
'OTA_A': 80,
'OTA_B': 70,
'GDS': 40,
'Corporate': 50,
'Group': 30
}
commission_rates = {
'Direct': 0.00, # No commission
'OTA_A': 0.18, # 18% commission
'OTA_B': 0.15,
'GDS': 0.10,
'Corporate': 0.00, # Negotiated rate
'Group': 0.00
}
result = optimize_channel_mix(channels, demand_by_channel, commission_rates)
print(f"Total net revenue: ${result['total_net_revenue']:,.0f}")
print(f"Blended ADR: ${result['blended_adr']:.2f}")
for channel, data in result['channel_allocation'].items():
print(f" {channel}: {data['rooms']:.0f} rooms ({data['share']:.1f}%), "
f"Net revenue: ${data['net_revenue']:,.0f}")
def forecast_hotel_demand(historical_bookings, events_calendar,
competitors_data, economic_indicators):
"""
Forecast hotel demand using multiple signals
Factors:
- Historical patterns (seasonality, day of week)
- Events and conferences
- Competitor pricing and availability
- Economic indicators
- Booking pace
"""
from sklearn.ensemble import RandomForestRegressor
import pandas as pd
# Prepare features
df = historical_bookings.copy()
# Time features
df['day_of_week'] = df['date'].dt.dayofweek
df['month'] = df['date'].dt.month
df['day_of_month'] = df['date'].dt.day
df['is_weekend'] = (df['day_of_week'] >= 5).astype(int)
# Lead time (days before arrival)
df['booking_lead_time'] = (df['date'] - df['booking_date']).dt.days
# Seasonal indicators
df['is_high_season'] = df['month'].isin([6, 7, 8, 12]).astype(int)
# Events
df = df.merge(events_calendar, on='date', how='left')
df['has_major_event'] = df['event_type'].notna().astype(int)
# Competitor data
df = df.merge(competitors_data[['date', 'avg_competitor_price',
'competitor_occupancy']], on='date', how='left')
# Price index (own price vs market)
df['price_index'] = df['adr'] / df['avg_competitor_price']
# Lag features
df['demand_lag_7'] = df['demand'].shift(7)
df['demand_lag_28'] = df['demand'].shift(28)
df['demand_rolling_7'] = df['demand'].rolling(7).mean()
# Drop NaN
df = df.dropna()
# Features
feature_cols = ['day_of_week', 'month', 'is_weekend', 'booking_lead_time',
'is_high_season', 'has_major_event', 'price_index',
'competitor_occupancy', 'demand_lag_7', 'demand_rolling_7']
X = df[feature_cols]
y = df['demand']
# Train model
model = RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42)
model.fit(X, y)
# Feature importance
importance = pd.DataFrame({
'feature': feature_cols,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
return {
'model': model,
'feature_importance': importance,
'train_r2': model.score(X, y)
}
def optimize_group_transient_mix(group_requests, transient_forecast,
rooms_available, dates):
"""
Optimize whether to accept group bookings vs. hold for transient
Parameters:
- group_requests: list of {group_id, rooms, nights, rate_offered}
- transient_forecast: expected transient demand and rates
- rooms_available: available rooms by date
- dates: list of dates to optimize
"""
from pulp import *
prob = LpProblem("Group_Transient", LpMaximize)
# Variables: accept group (binary)
accept_group = {}
for g, group in enumerate(group_requests):
accept_group[g] = LpVariable(f"Accept_Group_{g}", cat='Binary')
# Expected transient revenue (placeholder for optimization)
transient_revenue = {}
for date in dates:
transient_revenue[date] = LpVariable(f"Transient_Rev_{date}", lowBound=0)
# Objective: maximize total revenue
group_revenue = lpSum([accept_group[g] * group['rooms'] *
len(group['dates']) * group['rate_offered']
for g in range(len(group_requests))])
total_transient = lpSum([transient_revenue[date] for date in dates])
prob += group_revenue + total_transient
# Constraints
# Room availability by date
for date in dates:
# Group rooms consumed
group_rooms = lpSum([accept_group[g] * group['rooms']
for g, group in enumerate(group_requests)
if date in group['dates']])
# Transient rooms
transient_rooms = transient_revenue[date] / transient_forecast[date]['expected_rate']
prob += group_rooms + transient_rooms <= rooms_available[date]
# Transient revenue based on remaining capacity
for date in dates:
# Simple model: transient revenue = rooms × rate
# Limited by forecasted demand
prob += transient_revenue[date] <= \
transient_forecast[date]['expected_demand'] * \
transient_forecast[date]['expected_rate']
# Solve
prob.solve(PULP_CBC_CMD(msg=0))
# Extract decisions
accepted_groups = []
for g, group in enumerate(group_requests):
if accept_group[g].varValue > 0.5:
accepted_groups.append({
'group_id': group['group_id'],
'rooms': group['rooms'],
'nights': len(group['dates']),
'rate': group['rate_offered'],
'revenue': group['rooms'] * len(group['dates']) * group['rate_offered']
})
return {
'total_revenue': value(prob.objective),
'accepted_groups': accepted_groups,
'group_revenue': sum([g['revenue'] for g in accepted_groups]),
'transient_revenue': value(prob.objective) -
sum([g['revenue'] for g in accepted_groups])
}
Optimization:
PuLP: Linear programmingscipy.optimize: General optimizationcvxpy: Convex optimizationForecasting & ML:
scikit-learn: Machine learningprophet: Time series forecastingstatsmodels: Statistical modelsxgboost: Gradient boostingData Analysis:
pandas, numpy: Data manipulationmatplotlib, seaborn: VisualizationRevenue Management Systems (RMS):
Property Management Systems (PMS):
Channel Management:
Business Intelligence:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Executive Summary:
Performance Metrics (Month-to-Date):
| Metric | Current | Last Year | Variance | Target | |--------|---------|-----------|----------|--------| | Occupancy | 78.5% | 75.2% | +3.3 pts | 80% | | ADR | $185.50 | $178.20 | +4.1% | $190 | | RevPAR | $145.62 | $134.01 | +8.7% | $152 | | TRevPAR | $198.25 | $185.50 | +6.9% | $205 |
Booking Pace (Next 30 Days):
| Date | Rooms Sold | Occupancy | ADR | Status | Recommendation | |------|------------|-----------|-----|--------|----------------| | 2026-03-15 | 165 | 82.5% | $195 | Good | Hold rate | | 2026-03-16 | 192 | 96.0% | $210 | Strong | Increase rate +$10 | | 2026-03-17 | 145 | 72.5% | $175 | Soft | Promotional push |
Channel Performance:
| Channel | Rooms Sold | Mix % | Gross Revenue | Commission | Net Revenue | Net ADR | |---------|------------|-------|---------------|------------|-------------|---------| | Direct (brand.com) | 850 | 28% | $165,750 | $0 | $165,750 | $195 | | OTA A | 980 | 33% | $176,400 | $31,752 | $144,648 | $148 | | OTA B | 620 | 21% | $108,500 | $16,275 | $92,225 | $149 | | GDS | 420 | 14% | $84,000 | $8,400 | $75,600 | $180 | | Other | 130 | 4% | $19,500 | $0 | $19,500 | $150 | | Total | 3,000 | 100% | $554,150 | $56,427 | $497,723 | $166 |
Pricing Recommendations:
| Room Type | Current Rate | Recommended Rate | Expected Impact | |-----------|--------------|------------------|-----------------| | Standard | $175 | $185 | +$1,200/day revenue | | Deluxe | $225 | $235 | +$600/day revenue | | Suite | $350 | $365 | +$300/day revenue |
Strategic Actions:
If you need more context:
documentation
When the user wants to optimize yard operations, manage trailer parking, or improve dock door utilization. Also use when the user mentions "yard management," "trailer tracking," "yard jockey," "drop trailer program," "trailer pool," "dock scheduling," or "gate management." For cross-dock operations, see cross-docking. For warehouse design, see warehouse-design.
tools
When the user wants to optimize workforce scheduling, create shift plans, or balance labor demand. Also use when the user mentions "staff scheduling," "labor planning," "shift optimization," "crew scheduling," "roster optimization," or "employee scheduling." For task assignment, see task-assignment-problem. For wave planning labor, see wave-planning-optimization.
testing
When the user wants to optimize pick wave planning, schedule warehouse operations, or improve order fulfillment efficiency. Also use when the user mentions "wave management," "batch picking," "pick wave scheduling," "order release optimization," "wave design," or "pick wave strategy." For order batching, see order-batching-optimization. For workforce scheduling, see workforce-scheduling.
testing
When the user wants to optimize warehouse slot assignments, improve pick efficiency, or design warehouse layouts. Also use when the user mentions "slotting optimization," "slot assignment," "ABC slotting," "pick path optimization," "storage location assignment," "warehouse layout optimization," or "forward pick locations." For picker routing, see picker-routing-optimization. For warehouse design, see warehouse-design.