Skip to content
Learn Agentic AI15 min read0 views

AI Agent for Hotel Revenue Management: Dynamic Pricing and Occupancy Optimization

Build an AI revenue management agent that implements dynamic pricing for hotels, forecasts demand, analyzes competitor rates, and optimizes occupancy through intelligent rate adjustments.

Why Hotels Need AI Revenue Management

Hotel rooms are the ultimate perishable inventory — an unsold room tonight generates zero revenue forever. Revenue management is the discipline of selling the right room to the right guest at the right price at the right time. Traditional revenue management relies on revenue managers manually adjusting rates based on experience and spreadsheets. An AI agent automates this process by continuously analyzing demand signals, competitor pricing, historical patterns, and booking velocity to optimize rates in real time.

The difference between good and poor revenue management is often 15 to 25 percent of total revenue — a make-or-break margin for hotel profitability.

Demand Forecasting Model

The foundation of revenue management is accurate demand forecasting. The agent needs to predict how many rooms will be booked for any future date.

from dataclasses import dataclass, field
from datetime import date, timedelta
from enum import Enum
import statistics

class DemandLevel(Enum):
    LOW = "low"
    MODERATE = "moderate"
    HIGH = "high"
    PEAK = "peak"

@dataclass
class DayForecast:
    target_date: date
    predicted_occupancy: float  # 0.0 to 1.0
    demand_level: DemandLevel
    confidence: float  # 0.0 to 1.0
    factors: list[str] = field(default_factory=list)

@dataclass
class HistoricalData:
    date: date
    occupancy: float
    avg_rate: float
    revenue: float
    bookings: int
    cancellations: int

def classify_demand(occupancy: float) -> DemandLevel:
    if occupancy >= 0.90:
        return DemandLevel.PEAK
    elif occupancy >= 0.75:
        return DemandLevel.HIGH
    elif occupancy >= 0.50:
        return DemandLevel.MODERATE
    return DemandLevel.LOW

def forecast_demand(
    target: date,
    historical: list[HistoricalData],
    events: list[dict],
) -> DayForecast:
    # Same day-of-week from past 8 weeks
    same_dow = [
        h for h in historical
        if h.date.weekday() == target.weekday()
    ]
    same_dow.sort(key=lambda h: abs((h.date - target).days))
    recent = same_dow[:8]

    if not recent:
        return DayForecast(target, 0.5, DemandLevel.MODERATE, 0.3)

    base_occupancy = statistics.mean(h.occupancy for h in recent)

    # Event boost
    factors = []
    event_boost = 0.0
    for event in events:
        event_start = event["start"]
        event_end = event["end"]
        if event_start <= target <= event_end:
            event_boost += event.get("demand_impact", 0.15)
            factors.append(f"Event: {event['name']}")

    # Weekend boost
    if target.weekday() in (4, 5):  # Friday, Saturday
        event_boost += 0.05
        factors.append("Weekend premium")

    predicted = min(1.0, base_occupancy + event_boost)
    confidence = min(0.95, 0.5 + len(recent) * 0.05)

    return DayForecast(
        target_date=target,
        predicted_occupancy=predicted,
        demand_level=classify_demand(predicted),
        confidence=confidence,
        factors=factors,
    )

Dynamic Pricing Engine

The pricing engine translates demand forecasts into optimal room rates.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

@dataclass
class RateStrategy:
    room_type: str
    base_rate: float
    min_rate: float
    max_rate: float

    def calculate_rate(self, forecast: DayForecast, days_until: int) -> float:
        # Demand multiplier
        demand_multipliers = {
            DemandLevel.LOW: 0.85,
            DemandLevel.MODERATE: 1.0,
            DemandLevel.HIGH: 1.25,
            DemandLevel.PEAK: 1.60,
        }
        demand_mult = demand_multipliers[forecast.demand_level]

        # Booking window multiplier: last-minute bookings pay more for high demand
        if days_until <= 1 and forecast.demand_level in (DemandLevel.HIGH, DemandLevel.PEAK):
            window_mult = 1.15
        elif days_until <= 3:
            window_mult = 1.05
        elif days_until >= 30:
            window_mult = 0.95  # Early bird discount
        else:
            window_mult = 1.0

        rate = self.base_rate * demand_mult * window_mult
        return round(max(self.min_rate, min(self.max_rate, rate)), 2)

@dataclass
class CompetitorRate:
    hotel_name: str
    room_type: str
    rate: float
    date: date

def adjust_for_competitors(
    proposed_rate: float,
    competitor_rates: list[CompetitorRate],
    positioning: str = "mid",  # budget, mid, premium
) -> float:
    if not competitor_rates:
        return proposed_rate
    avg_competitor = statistics.mean(r.rate for r in competitor_rates)

    positioning_targets = {
        "budget": 0.85,
        "mid": 1.0,
        "premium": 1.20,
    }
    target_ratio = positioning_targets.get(positioning, 1.0)
    competitive_rate = avg_competitor * target_ratio

    # Blend proposed and competitive rates (70% demand-driven, 30% competitive)
    blended = proposed_rate * 0.7 + competitive_rate * 0.3
    return round(blended, 2)

Building the Revenue Management Agent

from agents import Agent, function_tool

strategies = {
    "standard": RateStrategy("standard", 159.0, 99.0, 249.0),
    "deluxe": RateStrategy("deluxe", 229.0, 149.0, 379.0),
    "suite": RateStrategy("suite", 399.0, 249.0, 699.0),
}

historical_data: list[HistoricalData] = []
upcoming_events: list[dict] = []

@function_tool
def get_rate_recommendation(target_date: str, room_type: str) -> str:
    target = date.fromisoformat(target_date)
    days_until = (target - date.today()).days
    if days_until < 0:
        return "Cannot set rates for past dates."

    strategy = strategies.get(room_type)
    if not strategy:
        return f"Unknown room type: {room_type}. Options: {list(strategies.keys())}"

    forecast = forecast_demand(target, historical_data, upcoming_events)
    recommended_rate = strategy.calculate_rate(forecast, days_until)

    return (
        f"Date: {target_date} | Room: {room_type}\n"
        f"Demand: {forecast.demand_level.value} "
        f"(predicted {forecast.predicted_occupancy*100:.0f}% occupancy)\n"
        f"Factors: {', '.join(forecast.factors) if forecast.factors else 'None'}\n"
        f"Recommended rate: ${recommended_rate:.2f} "
        f"(base: ${strategy.base_rate:.2f})\n"
        f"Confidence: {forecast.confidence*100:.0f}%"
    )

@function_tool
def get_occupancy_forecast(start_date: str, days: int = 7) -> str:
    start = date.fromisoformat(start_date)
    lines = ["Date       | Demand   | Predicted Occ. | Factors"]
    lines.append("-" * 60)
    for i in range(days):
        target = start + timedelta(days=i)
        forecast = forecast_demand(target, historical_data, upcoming_events)
        factors_str = ", ".join(forecast.factors) if forecast.factors else "-"
        lines.append(
            f"{target.isoformat()} | {forecast.demand_level.value:8s} | "
            f"{forecast.predicted_occupancy*100:5.1f}%        | {factors_str}"
        )
    return "\n".join(lines)

@function_tool
def compare_competitor_rates(target_date: str, room_type: str) -> str:
    # In production, this queries rate shopping APIs
    competitors = [
        CompetitorRate("Hotel Alpha", room_type, 175.0, date.fromisoformat(target_date)),
        CompetitorRate("Hotel Beta", room_type, 162.0, date.fromisoformat(target_date)),
        CompetitorRate("Hotel Gamma", room_type, 189.0, date.fromisoformat(target_date)),
    ]
    avg_rate = statistics.mean(r.rate for r in competitors)
    lines = [f"Competitor rates for {room_type} on {target_date}:"]
    for c in competitors:
        lines.append(f"  {c.hotel_name}: ${c.rate:.2f}")
    lines.append(f"  Average: ${avg_rate:.2f}")
    return "\n".join(lines)

@function_tool
def set_rate(target_date: str, room_type: str, rate: float) -> str:
    strategy = strategies.get(room_type)
    if not strategy:
        return f"Unknown room type: {room_type}."
    if rate < strategy.min_rate or rate > strategy.max_rate:
        return (
            f"Rate ${rate:.2f} is outside allowed range "
            f"(${strategy.min_rate:.2f} - ${strategy.max_rate:.2f})."
        )
    return f"Rate for {room_type} on {target_date} set to ${rate:.2f}."

revenue_agent = Agent(
    name="Revenue Management Agent",
    instructions="""You are a hotel revenue management agent. Analyze demand
    forecasts, competitor rates, and booking patterns to recommend optimal
    room rates. Always explain the reasoning behind rate recommendations.
    Flag dates with unusual demand patterns. Never set rates outside the
    min/max guardrails without manager approval.""",
    tools=[get_rate_recommendation, get_occupancy_forecast,
           compare_competitor_rates, set_rate],
)

FAQ

How does the agent handle overbooking strategy?

Hotels routinely overbook by 5 to 10 percent to account for cancellations and no-shows. The agent factors historical cancellation rates into its occupancy forecast. When the predicted net occupancy (bookings minus expected cancellations) approaches 100 percent, the agent stops recommending rate decreases and instead switches to maximizing revenue per available room (RevPAR) by raising rates on remaining inventory.

What happens when a large event is announced that was not in historical data?

The agent accepts manual event entries that include the event name, dates, and estimated demand impact. When a new conference or festival is announced, the revenue manager inputs it and the agent immediately recalculates forecasts and rate recommendations for the affected dates. The agent also learns from the actual occupancy during the event to improve future predictions for similar events.

How does the agent balance occupancy vs. average daily rate?

The agent optimizes for RevPAR (Revenue Per Available Room), which is occupancy multiplied by average daily rate. Sometimes it is more profitable to sell fewer rooms at higher rates than to fill every room cheaply. The agent's demand-based multipliers are calibrated to maximize RevPAR rather than occupancy alone — during peak demand, it aggressively raises rates even if it means a few rooms go unsold.


#RevenueManagement #DynamicPricing #HotelAI #DemandForecasting #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.