hotel-inventory-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hotel 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:
  1. 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?
  2. Current Performance
    • Occupancy rate and trends?
    • Average daily rate (ADR)?
    • Revenue per available room (RevPAR)?
    • Booking pace and lead times?
  3. Distribution Channels
    • Direct bookings? (website, phone, walk-in)
    • OTAs? (Booking.com, Expedia, etc.)
    • GDS? (Amadeus, Sabre, Travelport)
    • Corporate contracts and groups?
  4. Objectives & Constraints
    • Primary goals? (maximize revenue, occupancy, ADR)
    • Competitive position?
    • Brand standards or restrictions?
    • Technology systems in place? (PMS, RMS, CRS)

在优化酒店库存之前,需了解以下信息:
  1. 酒店属性特征
    • 酒店类型?(奢华型、中端型、经济型、度假型、有限服务型)
    • 客房数量及房型?
    • 地理位置与细分市场?(城市、度假地、机场周边、郊区)
    • 季节性规律?
  2. 当前运营表现
    • 入住率及趋势?
    • 平均每日房价(ADR)?
    • 每可用客房收入(RevPAR)?
    • 预订节奏与提前预订时长?
  3. 分销渠道
    • 直订渠道?(官网、电话、到店预订)
    • 在线旅行社(OTA)?(Booking.com、Expedia等)
    • 全球分销系统(GDS)?(Amadeus、Sabre、Travelport)
    • 企业协议与团队预订?
  4. 目标与约束条件
    • 核心目标?(收益最大化、入住率最大化、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/channels
Goal: 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}")
undefined
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}")
undefined

Multi-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:
  • PuLP
    : Linear programming
  • scipy.optimize
    : General optimization
  • cvxpy
    : Convex optimization
Forecasting & ML:
  • scikit-learn
    : Machine learning
  • prophet
    : Time series forecasting
  • statsmodels
    : Statistical models
  • xgboost
    : Gradient boosting
Data Analysis:
  • pandas
    ,
    numpy
    : Data manipulation
  • matplotlib
    ,
    seaborn
    : Visualization
优化类:
  • 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):
MetricCurrentLast YearVarianceTarget
Occupancy78.5%75.2%+3.3 pts80%
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):
DateRooms SoldOccupancyADRStatusRecommendation
2026-03-1516582.5%$195GoodHold rate
2026-03-1619296.0%$210StrongIncrease rate +$10
2026-03-1714572.5%$175SoftPromotional push
Channel Performance:
ChannelRooms SoldMix %Gross RevenueCommissionNet RevenueNet ADR
Direct (brand.com)85028%$165,750$0$165,750$195
OTA A98033%$176,400$31,752$144,648$148
OTA B62021%$108,500$16,275$92,225$149
GDS42014%$84,000$8,400$75,600$180
Other1304%$19,500$0$19,500$150
Total3,000100%$554,150$56,427$497,723$166
Pricing Recommendations:
Room TypeCurrent RateRecommended RateExpected Impact
Standard$175$185+$1,200/day revenue
Deluxe$225$235+$600/day revenue
Suite$350$365+$300/day revenue
Strategic Actions:
  1. Increase direct booking share from 28% to 35% (save $45K/month in commissions)
  2. Implement dynamic pricing updates every 4 hours (vs. daily)
  3. Launch spring promotion: Book 3 nights, get 20% off 4th night
  4. Renegotiate OTA contracts for improved commission rates
  5. 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-1516582.5%$195良好维持当前价格
2026-03-1619296.0%$210强劲价格上调$10
2026-03-1714572.5%$175疲软推出促销活动
渠道表现:
渠道已售客房数占比总营收佣金净营收净ADR
直订(品牌官网)85028%$165,750$0$165,750$195
OTA A98033%$176,400$31,752$144,648$148
OTA B62021%$108,500$16,275$92,225$149
GDS42014%$84,000$8,400$75,600$180
其他1304%$19,500$0$19,500$150
总计3,000100%$554,150$56,427$497,723$166
定价建议:
房型当前价格建议价格预期影响
标准间$175$185每日营收增加$1,200
豪华间$225$235每日营收增加$600
套房$350$365每日营收增加$300
战略行动:
  1. 将直订占比从28%提升至35%(每月节省$45,000佣金)
  2. 改为每4小时更新一次动态定价(原每日更新)
  3. 推出春季促销活动:预订3晚,第4晚享8折
  4. 重新协商OTA合同以获得更优惠的佣金率
  5. 优化团队策略:仅接受报价$165及以上的企业团队预订

Questions to Ask

需询问的问题

If you need more context:
  1. What type of hotel property? (luxury, midscale, limited service, resort)
  2. How many rooms and what room types?
  3. What's the current occupancy, ADR, and RevPAR?
  4. What's the competitive set and market positioning?
  5. What distribution channels are used?
  6. What systems are in place? (PMS, RMS, channel manager)
  7. What are the key challenges? (occupancy, rate, distribution costs)

如需更多背景信息,请询问:
  1. 酒店类型是什么?(奢华型、中端型、有限服务型、度假型)
  2. 客房数量及房型有哪些?
  3. 当前的入住率、ADR和RevPAR是多少?
  4. 竞争环境与市场定位如何?
  5. 使用哪些分销渠道?
  6. 已部署哪些系统?(PMS、RMS、渠道管理器)
  7. 核心挑战是什么?(入住率、定价、分销成本)

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:季节性需求管理