hotel-inventory-management
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHotel Inventory Management
酒店库存管理
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).
您是酒店库存管理与收益优化领域的专家。您的目标是通过优化客房分配、动态定价、分销渠道管理和容量规划,在平衡入住率与平均每日房价(ADR)的同时,帮助酒店实现收益最大化。
Initial Assessment
初始评估
Before optimizing hotel inventory, understand:
-
Property Characteristics
- Property type? (luxury, midscale, budget, resort, limited service)
- Number of rooms and room types?
- Location and market segment? (urban, resort, airport, suburban)
- Seasonal patterns?
-
Current Performance
- Occupancy rate and trends?
- Average daily rate (ADR)?
- Revenue per available room (RevPAR)?
- Booking pace and lead times?
-
Distribution Channels
- Direct bookings? (website, phone, walk-in)
- OTAs? (Booking.com, Expedia, etc.)
- GDS? (Amadeus, Sabre, Travelport)
- Corporate contracts and groups?
-
Objectives & Constraints
- Primary goals? (maximize revenue, occupancy, ADR)
- Competitive position?
- Brand standards or restrictions?
- Technology systems in place? (PMS, RMS, CRS)
在优化酒店库存之前,需了解以下信息:
-
酒店属性特征
- 酒店类型?(奢华型、中端型、经济型、度假型、有限服务型)
- 客房数量及房型?
- 地理位置与细分市场?(城市、度假地、机场周边、郊区)
- 季节性规律?
-
当前运营表现
- 入住率及趋势?
- 平均每日房价(ADR)?
- 每可用客房收入(RevPAR)?
- 预订节奏与提前预订时长?
-
分销渠道
- 直订渠道?(官网、电话、到店预订)
- 在线旅行社(OTA)?(Booking.com、Expedia等)
- 全球分销系统(GDS)?(Amadeus、Sabre、Travelport)
- 企业协议与团队预订?
-
目标与约束条件
- 核心目标?(收益最大化、入住率最大化、ADR最大化)
- 市场竞争地位?
- 品牌标准或限制?
- 已部署的技术系统?(PMS、RMS、CRS)
Hotel Inventory Framework
酒店库存管理框架
Revenue Management Fundamentals
收益管理基础
Key Metrics:
- ADR (Average Daily Rate): Total room revenue / Rooms sold
- Occupancy: Rooms sold / Rooms available
- RevPAR (Revenue Per Available Room): Total room revenue / Rooms available = ADR × Occupancy
- TRevPAR (Total RevPAR): Total property revenue / Rooms available
Revenue Optimization Equation:
Total Revenue = Σ (Rate_i × Rooms_Sold_i) across all segments/channelsGoal: Maximize Total Revenue subject to capacity constraints
关键指标:
- ADR(Average Daily Rate):客房总营收 / 售出客房数
- 入住率:售出客房数 / 可用客房数
- RevPAR(Revenue Per Available Room):客房总营收 / 可用客房数 = ADR × 入住率
- TRevPAR(Total RevPAR):酒店总营收 / 可用客房数
收益优化公式:
Total Revenue = Σ (Rate_i × Rooms_Sold_i) across all segments/channels目标:在容量约束下实现总收益最大化
Dynamic Pricing Optimization
动态定价优化
Price Elasticity-Based Pricing
基于价格弹性的定价
python
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
}python
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
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}")
undefinedoptimizer = 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}")
undefinedMulti-Day Pricing Optimization
多日定价优化
python
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
}python
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
}Overbooking Optimization
超售优化
Optimal Overbooking Level
最优超售水平
python
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
}python
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
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}")
---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}")
---Distribution Channel Management
分销渠道管理
Channel Mix Optimization
渠道组合优化
python
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
}python
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
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}")
---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}")
---Demand Forecasting
需求预测
Hotel Demand Forecasting
酒店需求预测
python
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)
}python
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)
}Group Booking Optimization
团队预订优化
Group vs. Transient Mix
团队与散客组合优化
python
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])
}python
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])
}Tools & Libraries
工具与库
Python Libraries
Python库
Optimization:
- : Linear programming
PuLP - : General optimization
scipy.optimize - : Convex optimization
cvxpy
Forecasting & ML:
- : Machine learning
scikit-learn - : Time series forecasting
prophet - : Statistical models
statsmodels - : Gradient boosting
xgboost
Data Analysis:
- ,
pandas: Data manipulationnumpy - ,
matplotlib: Visualizationseaborn
优化类:
- :线性规划
PuLP - :通用优化
scipy.optimize - :凸优化
cvxpy
预测与机器学习类:
- :机器学习
scikit-learn - :时间序列预测
prophet - :统计模型
statsmodels - :梯度提升
xgboost
数据分析类:
- ,
pandas:数据处理numpy - ,
matplotlib:数据可视化seaborn
Commercial Software
商业软件
Revenue Management Systems (RMS):
- IDeaS: Market-leading RMS
- Duetto: Cloud-based revenue strategy
- Rainmaker: Revenue management software
- Atomize: AI-powered pricing
Property Management Systems (PMS):
- Oracle Opera: Enterprise PMS
- Cloudbeds: Cloud PMS for independents
- Mews: Modern cloud PMS
- Protel: European market leader
Channel Management:
- SiteMinder: Distribution platform
- TravelClick: Channel connectivity
- Synxis (Sabre): CRS and distribution
Business Intelligence:
- STR (CoStar): Competitive benchmarking
- Kalibri Labs: Revenue optimization analytics
- OTA Insight: Market intelligence
收益管理系统(RMS):
- IDeaS:市场领先的RMS
- Duetto:云原生收益策略系统
- Rainmaker:收益管理软件
- Atomize:AI驱动的定价系统
酒店管理系统(PMS):
- Oracle Opera:企业级PMS
- Cloudbeds:面向独立酒店的云PMS
- Mews:现代化云PMS
- Protel:欧洲市场领先者
渠道管理:
- SiteMinder:分销平台
- TravelClick:渠道连接系统
- Synxis (Sabre):CRS与分销系统
商业智能:
- STR (CoStar):竞争基准分析
- Kalibri Labs:收益优化分析
- OTA Insight:市场情报
Common Challenges & Solutions
常见挑战与解决方案
Challenge: Rate Parity Issues
挑战:价格一致性问题
Problem:
- OTAs showing lower rates than brand.com
- Rate parity violations
- Brand reputation damage
Solutions:
- Rate shopping tools and monitoring
- Dynamic BAR (Best Available Rate) management
- Direct booking incentives (member rates, perks)
- Contract enforcement with OTAs
- Strategic rate positioning
问题:
- OTA显示的价格低于品牌官网
- 违反价格一致性条款
- 损害品牌声誉
解决方案:
- 价格监控工具
- 动态BAR(最优可用房价)管理
- 直订激励(会员价、额外福利)
- 与OTA的合同执行
- 战略性定价定位
Challenge: Demand Volatility
挑战:需求波动
Problem:
- Unpredictable booking patterns
- Last-minute cancellations
- Seasonal extremes
Solutions:
- Advanced demand forecasting (ML)
- Flexible cancellation policies with pricing tiers
- Dynamic pricing (hourly updates)
- Minimum length-of-stay restrictions
- Closed-to-arrival restrictions
问题:
- 预订模式不可预测
- 临时取消订单
- 季节性需求极端波动
解决方案:
- 高级需求预测(机器学习)
- 灵活的取消政策搭配定价层级
- 动态定价(每小时更新)
- 最短入住时长限制
- 特定日期不接受入住限制
Challenge: Distribution Cost Control
挑战:分销成本控制
Problem:
- High OTA commissions (15-25%)
- Acquisition costs rising
- Profitability pressure
Solutions:
- Direct booking campaigns (lower CAC)
- Loyalty program incentives
- Meta-search bidding optimization
- Strategic OTA partnerships
- Commission negotiation
问题:
- OTA佣金过高(15-25%)
- 用户获取成本上升
- 盈利能力承压
解决方案:
- 直订推广活动(降低用户获取成本)
- 忠诚度计划激励
- 元搜索竞价优化
- 战略性OTA合作
- 佣金谈判
Challenge: Group Block Management
挑战:团队预留客房管理
Problem:
- Group attrition (pickup less than contracted)
- Opportunity cost of holding rooms
- Complex group contracts
Solutions:
- Dynamic group pricing
- Attrition clauses and penalties
- Phased release of unsold group rooms
- Group revenue displacement analysis
- Automated group block management
问题:
- 团队预订未达预期(实际入住数少于合同数)
- 预留客房的机会成本
- 复杂的团队合同
解决方案:
- 动态团队定价
- 未达入住数条款与处罚
- 分阶段释放未售出的团队预留客房
- 团队收益替代分析
- 自动化团队预留客房管理
Output Format
输出格式
Hotel Revenue Management Report
酒店收益管理报告
Executive Summary:
- Property performance overview
- Key optimization opportunities
- Revenue improvement potential
- Strategic recommendations
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:
- Increase direct booking share from 28% to 35% (save $45K/month in commissions)
- Implement dynamic pricing updates every 4 hours (vs. daily)
- Launch spring promotion: Book 3 nights, get 20% off 4th night
- Renegotiate OTA contracts for improved commission rates
- Optimize group strategy: Accept corporate groups at $165+ only
执行摘要:
- 酒店运营表现概述
- 关键优化机会
- 收益提升潜力
- 战略建议
运营指标(当月至今):
| 指标 | 当前值 | 去年同期 | 差异 | 目标值 |
|---|---|---|---|---|
| 入住率 | 78.5% | 75.2% | +3.3个百分点 | 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 |
预订节奏(未来30天):
| 日期 | 已售客房数 | 入住率 | ADR | 状态 | 建议 |
|---|---|---|---|---|---|
| 2026-03-15 | 165 | 82.5% | $195 | 良好 | 维持当前价格 |
| 2026-03-16 | 192 | 96.0% | $210 | 强劲 | 价格上调$10 |
| 2026-03-17 | 145 | 72.5% | $175 | 疲软 | 推出促销活动 |
渠道表现:
| 渠道 | 已售客房数 | 占比 | 总营收 | 佣金 | 净营收 | 净ADR |
|---|---|---|---|---|---|---|
| 直订(品牌官网) | 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 |
| 其他 | 130 | 4% | $19,500 | $0 | $19,500 | $150 |
| 总计 | 3,000 | 100% | $554,150 | $56,427 | $497,723 | $166 |
定价建议:
| 房型 | 当前价格 | 建议价格 | 预期影响 |
|---|---|---|---|
| 标准间 | $175 | $185 | 每日营收增加$1,200 |
| 豪华间 | $225 | $235 | 每日营收增加$600 |
| 套房 | $350 | $365 | 每日营收增加$300 |
战略行动:
- 将直订占比从28%提升至35%(每月节省$45,000佣金)
- 改为每4小时更新一次动态定价(原每日更新)
- 推出春季促销活动:预订3晚,第4晚享8折
- 重新协商OTA合同以获得更优惠的佣金率
- 优化团队策略:仅接受报价$165及以上的企业团队预订
Questions to Ask
需询问的问题
If you need more context:
- What type of hotel property? (luxury, midscale, limited service, resort)
- How many rooms and what room types?
- What's the current occupancy, ADR, and RevPAR?
- What's the competitive set and market positioning?
- What distribution channels are used?
- What systems are in place? (PMS, RMS, channel manager)
- What are the key challenges? (occupancy, rate, distribution costs)
如需更多背景信息,请询问:
- 酒店类型是什么?(奢华型、中端型、有限服务型、度假型)
- 客房数量及房型有哪些?
- 当前的入住率、ADR和RevPAR是多少?
- 竞争环境与市场定位如何?
- 使用哪些分销渠道?
- 已部署哪些系统?(PMS、RMS、渠道管理器)
- 核心挑战是什么?(入住率、定价、分销成本)
Related Skills
相关技能
- tour-operations: For tour operator and package management
- airline-cargo-optimization: For airline operations
- hospitality-procurement: For hotel purchasing and procurement
- demand-forecasting: For advanced forecasting techniques
- dynamic-pricing: For pricing optimization
- optimization-modeling: For advanced optimization
- seasonal-planning: For seasonal demand management
- tour-operations:旅游运营商与套餐管理
- airline-cargo-optimization:航空运营
- hospitality-procurement:酒店采购
- demand-forecasting:高级预测技术
- dynamic-pricing:定价优化
- optimization-modeling:高级优化建模
- seasonal-planning:季节性需求管理