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
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.