Skip to main content
Glama
financial_calculator.py7.2 kB
"""Calculate financial reports from reservation data. This module provides a workaround for the unavailable Hostaway financial reports API by calculating revenue, expenses, and profitability from reservation data. """ from datetime import datetime from typing import Any, ClassVar class FinancialCalculator: """Calculate financial metrics from reservation data.""" # Booking statuses that represent confirmed revenue REVENUE_STATUSES: ClassVar[set[str]] = { "confirmed", "checked_in", "checked_out", "completed", } # Booking statuses that should be excluded EXCLUDED_STATUSES: ClassVar[set[str]] = { "cancelled", "declined", "pending", "inquiry", "inquiryNotPossible", "modified", # Modified bookings may have incorrect totals } @staticmethod def calculate_financial_report( reservations: list[dict[str, Any]], start_date: str, end_date: str, ) -> dict[str, Any]: """Calculate financial report from reservation data. Args: reservations: List of reservation dictionaries start_date: Report start date (YYYY-MM-DD) end_date: Report end date (YYYY-MM-DD) Returns: Financial report with revenue, expenses, and metrics """ # Parse dates for filtering start = datetime.strptime(start_date, "%Y-%m-%d").date() end = datetime.strptime(end_date, "%Y-%m-%d").date() # Filter reservations by date range and status filtered_reservations = [] for res in reservations: # Skip if status is excluded status = res.get("status", "").lower() if status in FinancialCalculator.EXCLUDED_STATUSES: continue # Check if arrival or departure date is in range arrival_str = res.get("arrivalDate") departure_str = res.get("departureDate") if not arrival_str or not departure_str: continue try: arrival = datetime.strptime(arrival_str, "%Y-%m-%d").date() departure = datetime.strptime(departure_str, "%Y-%m-%d").date() # Include if any part of the stay overlaps with the report period if arrival <= end and departure >= start: filtered_reservations.append(res) except (ValueError, TypeError): continue # Skip reservations with invalid dates # Calculate metrics total_revenue = 0.0 total_cleaning_fees = 0.0 total_channel_commission = 0.0 total_hostaway_commission = 0.0 total_taxes = 0.0 total_nights = 0 total_bookings = len(filtered_reservations) revenue_by_channel: dict[str, float] = {} currency = "USD" # Default currency for res in filtered_reservations: # Get currency (use first non-null currency found) if res.get("currency") and currency == "USD": currency = res.get("currency", "USD") # Revenue total_price = float(res.get("totalPrice") or 0) total_revenue += total_price # Fees and commissions cleaning_fee = float(res.get("cleaningFee") or 0) total_cleaning_fees += cleaning_fee channel_commission = float(res.get("channelCommissionAmount") or 0) total_channel_commission += channel_commission hostaway_commission = float(res.get("hostawayCommissionAmount") or 0) total_hostaway_commission += hostaway_commission tax_amount = float(res.get("taxAmount") or 0) total_taxes += tax_amount # Nights nights = int(res.get("nights") or 0) total_nights += nights # Revenue by channel channel_name = res.get("channelName", "Unknown") revenue_by_channel[channel_name] = ( revenue_by_channel.get(channel_name, 0.0) + total_price ) # Calculate derived metrics total_expenses = total_channel_commission + total_hostaway_commission net_income = total_revenue - total_expenses average_daily_rate = total_revenue / total_nights if total_nights > 0 else 0.0 average_booking_value = total_revenue / total_bookings if total_bookings > 0 else 0.0 # Build report structure matching expected format return { "period": { "startDate": start_date, "endDate": end_date, }, "currency": currency, "revenue": { "totalRevenue": round(total_revenue, 2), "cleaningFees": round(total_cleaning_fees, 2), "taxes": round(total_taxes, 2), "byChannel": { channel: round(amount, 2) for channel, amount in revenue_by_channel.items() }, }, "expenses": { "totalExpenses": round(total_expenses, 2), "channelCommissions": round(total_channel_commission, 2), "hostawayCommissions": round(total_hostaway_commission, 2), }, "profitability": { "netIncome": round(net_income, 2), "profitMargin": round( (net_income / total_revenue * 100) if total_revenue > 0 else 0.0, 2 ), }, "metrics": { "totalBookings": total_bookings, "totalNights": total_nights, "averageDailyRate": round(average_daily_rate, 2), "averageBookingValue": round(average_booking_value, 2), }, "dataSource": "calculated_from_reservations", "note": "This report is calculated from reservation data as the Hostaway financial reports API is not available for your account.", } @staticmethod def calculate_property_financials( reservations: list[dict[str, Any]], property_id: int, start_date: str, end_date: str, ) -> dict[str, Any]: """Calculate financial report for a specific property. Args: reservations: List of all reservation dictionaries property_id: Property/listing ID to filter by start_date: Report start date (YYYY-MM-DD) end_date: Report end date (YYYY-MM-DD) Returns: Property-specific financial report """ # Filter to specific property property_reservations = [ res for res in reservations if res.get("listingMapId") == property_id ] # Calculate using standard logic report = FinancialCalculator.calculate_financial_report( property_reservations, start_date, end_date, ) # Add property-specific fields if property_reservations: report["listingId"] = property_id report["listingName"] = property_reservations[0].get("listingName", "Unknown") return report

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/darrentmorgan/hostaway-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server