Skip to main content

Pricing, Fees & Revenue Rules Domain - Deep Dive Analysis

The Villa Life (TVL) Platform Specification Analysis Date: 2025-10-24 Analyst: Claude Code Deep-Dive Review


Executive Summary

This document provides a comprehensive deep-dive analysis of the Pricing, Fees & Revenue Rules domain for The Villa Life (TVL) platform. It synthesizes findings from the existing TVL specifications, industry research on dynamic pricing platforms, tax compliance systems, and competitive analysis of leading vacation rental management systems (Guesty, Hostaway).

Key Findings

  1. Current Specification Strengths:

    • Clean separation of pricing definition vs. application (RatePlan → Rules → Quote → Booking)
    • Multi-tenancy aware with org_id/account_id ownership
    • Future-ready architecture supporting complex revenue splits
    • Event-driven consistency model
  2. Critical Gaps Identified:

    • No tax calculation engine - this is a CRITICAL gap for compliance
    • Missing detailed fee type taxonomy
    • No channel-specific pricing override mechanism
    • Limited specification for rule evaluation priority algorithm
    • Incomplete quote line item breakdown structure
    • No tiered revenue split modeling
  3. Industry Benchmark Insights:

    • Dynamic pricing leaders (PriceLabs, Wheelhouse) process 10B+ data points daily
    • Tax compliance (Avalara MyLodgeTax) handles multi-jurisdictional complexity
    • Stripe Connect enables real-time multi-party revenue splits
    • Competitors (Guesty, Hostaway) offer built-in PriceOptimizer tools

Table of Contents

  1. Current Specification Analysis
  2. Comprehensive Schema Design
  3. Tax Calculation Engine Specification
  4. Fee Type Taxonomy
  5. Rule Evaluation Algorithm
  6. Quote Line Items Structure
  7. Revenue Splitting Patterns
  8. Gap Analysis vs Competitors
  9. Implementation Roadmap

1. Current Specification Analysis

1.1 Existing Domain Definition

Source: /mnt/c/GitHub/claude-test/prd/TVL Data and Domain Model Specification 2025-10-21 (1).md

Core Concepts Documented:

RatePlan

  • Purpose: Defines the set of pricing rules for a Space or Unit
  • Attributes: name, currency, status, valid_from/valid_to, channel (optional)
  • Ownership: (org_id, account_id, space_id[, unit_id])
  • Supports: Multiple simultaneous rate configurations (Standard, Promotional, Partner)

RateRule

  • Purpose: Determines base nightly price and modifiers
  • Supported Modifiers:
    • Season-based (e.g., High season Dec-Apr: +25%)
    • Length-of-stay (e.g., 7+ nights: -15%)
    • Lead time (e.g., 60+ days: -10%)
    • Day of week
  • Evaluation Order: Date match → stay length → lead time → channel override

FeeRule

  • Purpose: Additional charges applied per booking
  • Examples: Cleaning fee, service fee, resort fee, tax percentage
  • Attributes: type (fixed|percent), basis (per_stay|per_night|per_guest), applies_to (total|subtotal|room)

RevenueRule

  • Purpose: Defines revenue splits between Accounts
  • Examples:
    • Owner 80%, TVL 20% commission
    • Manager 10% override fee on owner share
  • Attributes: recipient_account_id, basis (gross|net|owner_share), percent_split, is_platform_fee

Quote

  • Purpose: Immutable snapshot of pricing at booking time
  • Contains: All resolved rates, fees, and revenue splits
  • Lifecycle: Immutable once converted to Booking

1.2 MVP Implementation (Villa-Only)

Documented Scope:

  1. Single RatePlan per Space ("Standard Rate")
  2. Flat nightly rate + optional seasonal multiplier
  3. One cleaning fee (fixed amount)
  4. Optional service fee (percent)
  5. Fixed revenue split: Owner 80%, TVL 20%
  6. Quote generated at checkout; frozen on confirmation

1.3 Identified Strengths

Strong Architectural Foundation:

  • Clean separation of concerns (definition vs. application)
  • Multi-tenant isolation via org_id
  • Event-driven consistency
  • Auditable and immutable quote snapshots

Scalability-Ready:

  • Supports multiple RatePlans per Space
  • Extensible rule types
  • Multi-party revenue splits
  • Channel-aware pricing

Financial Transparency:

  • Explicit fee and split recording
  • Centralized rate evaluation
  • Clear audit trail

1.4 Identified Gaps

CRITICAL: Tax Calculation Engine Missing

  • No tax rule modeling
  • No jurisdiction mapping (city, county, state, federal)
  • No distinction between taxable vs. non-taxable fees
  • No tax authority remittance tracking

Incomplete Fee Taxonomy

  • Limited to generic "cleaning fee" and "service fee"
  • No pet fees, resort fees, damage waiver, booking fees
  • No platform fees vs. pass-through fees distinction
  • No refundable vs. non-refundable fee classification

Rule Evaluation Algorithm Underspecified

  • Priority algorithm mentioned but not formalized
  • No conflict resolution rules
  • No composition rules (additive vs. override)
  • No channel-specific override mechanism

Quote Line Items Structure Incomplete

  • No detailed line item breakdown schema
  • No per-night rate breakdown
  • No tax line items
  • No fee categorization in quote

Revenue Split Limitations

  • Only flat percentage splits documented
  • No tiered commission structures (e.g., 15% on first $5K, 10% thereafter)
  • No minimum fee guarantees
  • No cap mechanisms

2. Comprehensive Schema Design

2.1 Core Tables

Table: rate_plans

CREATE TABLE rate_plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Ownership & Tenancy
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
space_id UUID NOT NULL REFERENCES spaces(id) ON DELETE CASCADE,
unit_id UUID REFERENCES units(id) ON DELETE CASCADE, -- Nullable for Space-level rates

-- Identification
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT,

-- Configuration
currency CHAR(3) NOT NULL DEFAULT 'USD', -- ISO 4217
status VARCHAR(50) NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'active', 'inactive', 'archived')),

-- Validity Period
valid_from DATE NOT NULL,
valid_to DATE, -- NULL = indefinite

-- Priority & Channel
priority INTEGER NOT NULL DEFAULT 100, -- Higher = more important
channel_id UUID REFERENCES channels(id), -- NULL = applies to all channels

-- Default Values
base_rate_minor INTEGER NOT NULL, -- Base nightly rate in minor units (cents)
min_rate_minor INTEGER, -- Minimum allowed rate
max_rate_minor INTEGER, -- Maximum allowed rate
min_stay_nights INTEGER DEFAULT 1,
max_stay_nights INTEGER,

-- Dynamic Pricing Integration
dynamic_pricing_enabled BOOLEAN DEFAULT FALSE,
dynamic_pricing_provider VARCHAR(100), -- 'pricelabs', 'wheelhouse', 'beyond', 'internal'
dynamic_pricing_config JSONB, -- Provider-specific settings

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id),
updated_by UUID REFERENCES users(id),
deleted_at TIMESTAMPTZ, -- Soft delete

-- Constraints
UNIQUE(org_id, space_id, slug),
CONSTRAINT valid_date_range CHECK (valid_to IS NULL OR valid_to > valid_from),
CONSTRAINT valid_rate_range CHECK (
(min_rate_minor IS NULL OR max_rate_minor IS NULL) OR
min_rate_minor <= max_rate_minor
)
);

CREATE INDEX idx_rate_plans_org_space ON rate_plans(org_id, space_id, status);
CREATE INDEX idx_rate_plans_validity ON rate_plans(valid_from, valid_to)
WHERE deleted_at IS NULL;
CREATE INDEX idx_rate_plans_channel ON rate_plans(channel_id)
WHERE channel_id IS NOT NULL;

Table: rate_rules

CREATE TABLE rate_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Relationship
rate_plan_id UUID NOT NULL REFERENCES rate_plans(id) ON DELETE CASCADE,

-- Rule Identification
name VARCHAR(255) NOT NULL,
rule_type VARCHAR(50) NOT NULL
CHECK (rule_type IN (
'base', -- Base rate
'seasonal', -- Season/date range modifier
'los', -- Length of stay discount
'dow', -- Day of week modifier
'lead_time', -- Early bird / last minute
'occupancy', -- Guest count modifier
'channel', -- Channel-specific override
'gap', -- Gap night filling
'last_minute', -- Last-minute discount
'orphan', -- Orphan night pricing
'custom' -- Custom rule
)),

-- Priority
priority INTEGER NOT NULL DEFAULT 100,

-- Condition Matching
conditions JSONB NOT NULL, -- Rule matching criteria
/*
Examples:
- Seasonal: {"start_date": "2025-12-15", "end_date": "2026-04-15"}
- LOS: {"min_nights": 7, "max_nights": null}
- Lead time: {"min_days_advance": 60}
- DOW: {"days": ["friday", "saturday"]}
- Occupancy: {"min_guests": 8, "max_guests": 12}
- Channel: {"channel_id": "uuid"}
*/

-- Adjustment
adjustment_type VARCHAR(50) NOT NULL
CHECK (adjustment_type IN ('fixed_amount', 'percentage', 'multiplier', 'set_value')),
adjustment_value NUMERIC(10, 4) NOT NULL,
adjustment_basis VARCHAR(50) NOT NULL DEFAULT 'base_rate'
CHECK (adjustment_basis IN ('base_rate', 'current_total', 'per_night', 'per_guest')),

-- Application
compound_mode VARCHAR(50) NOT NULL DEFAULT 'additive'
CHECK (compound_mode IN ('additive', 'multiplicative', 'override', 'max', 'min')),

-- Validity
is_active BOOLEAN NOT NULL DEFAULT TRUE,
valid_from DATE,
valid_to DATE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT,

CONSTRAINT valid_rule_dates CHECK (valid_to IS NULL OR valid_to >= valid_from)
);

CREATE INDEX idx_rate_rules_plan ON rate_rules(rate_plan_id, priority);
CREATE INDEX idx_rate_rules_type ON rate_rules(rule_type, is_active);
CREATE INDEX idx_rate_rules_validity ON rate_rules(valid_from, valid_to);

Table: fee_rules

CREATE TABLE fee_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Relationship
rate_plan_id UUID NOT NULL REFERENCES rate_plans(id) ON DELETE CASCADE,

-- Fee Identification
name VARCHAR(255) NOT NULL,
fee_type VARCHAR(100) NOT NULL
CHECK (fee_type IN (
-- Guest-Facing Fees
'cleaning',
'pet',
'extra_guest',
'resort',
'amenity',
'linen',
'damage_waiver',
'hot_tub',
'pool_heating',
'early_checkin',
'late_checkout',
'mid_stay_clean',

-- Platform/Service Fees
'booking_fee',
'service_fee',
'processing_fee',
'channel_commission',

-- Pass-Through Fees
'county_tax',
'city_tax',
'state_tax',
'federal_tax',
'tourism_tax',
'occupancy_tax',
'vat',
'gst',

-- Other
'deposit',
'security_deposit',
'custom'
)),

-- Amount Calculation
calculation_type VARCHAR(50) NOT NULL
CHECK (calculation_type IN ('fixed', 'percentage', 'per_night', 'per_guest', 'tiered')),
amount_minor INTEGER, -- For fixed amounts (in minor units)
percentage NUMERIC(5, 4), -- For percentage fees (e.g., 0.0500 = 5%)

-- Application Basis
basis VARCHAR(50) NOT NULL DEFAULT 'per_stay'
CHECK (basis IN ('per_stay', 'per_night', 'per_guest', 'per_adult', 'per_child')),
applies_to VARCHAR(50) NOT NULL DEFAULT 'subtotal'
CHECK (applies_to IN ('subtotal', 'total', 'room_rate', 'gross', 'taxable_amount')),

-- Tiered Structure (for tiered fees)
tiers JSONB, -- [{min: 0, max: 1000, rate: 50}, {min: 1000, max: null, rate: 30}]

-- Conditions
conditions JSONB, -- When this fee applies
/*
Examples:
- Extra guest fee: {"applies_when": "guests > base_occupancy", "base_occupancy": 6}
- Pet fee: {"requires_addon": "pet_friendly"}
- Hot tub: {"requires_amenity": "hot_tub_available"}
*/

-- Tax Treatment
is_taxable BOOLEAN NOT NULL DEFAULT FALSE,
tax_category VARCHAR(100), -- For tax jurisdiction mapping

-- Guest Display
is_mandatory BOOLEAN NOT NULL DEFAULT TRUE,
is_optional BOOLEAN NOT NULL DEFAULT FALSE,
is_refundable BOOLEAN NOT NULL DEFAULT FALSE,
display_name VARCHAR(255), -- Guest-facing name
description TEXT,

-- Platform Treatment
is_platform_revenue BOOLEAN NOT NULL DEFAULT FALSE, -- Does platform keep this fee?
is_passthrough BOOLEAN NOT NULL DEFAULT FALSE, -- Passed to 3rd party (e.g., taxes)
remittance_party VARCHAR(100), -- Who receives this fee (for passthrough)

-- Priority & Status
priority INTEGER NOT NULL DEFAULT 100,
is_active BOOLEAN NOT NULL DEFAULT TRUE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT
);

CREATE INDEX idx_fee_rules_plan ON fee_rules(rate_plan_id, priority);
CREATE INDEX idx_fee_rules_type ON fee_rules(fee_type, is_active);
CREATE INDEX idx_fee_rules_taxable ON fee_rules(is_taxable) WHERE is_taxable = TRUE;

Table: revenue_rules

CREATE TABLE revenue_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Relationship
rate_plan_id UUID NOT NULL REFERENCES rate_plans(id) ON DELETE CASCADE,

-- Revenue Split Configuration
name VARCHAR(255) NOT NULL,
recipient_account_id UUID NOT NULL REFERENCES accounts(id),
recipient_type VARCHAR(50) NOT NULL
CHECK (recipient_type IN ('owner', 'manager', 'platform', 'partner', 'channel', 'other')),

-- Split Calculation
split_type VARCHAR(50) NOT NULL
CHECK (split_type IN ('percentage', 'fixed_amount', 'tiered', 'remainder')),

-- Percentage Split
split_percentage NUMERIC(5, 4), -- 0.2000 = 20%

-- Fixed Amount Split
fixed_amount_minor INTEGER, -- Fixed fee per booking

-- Tiered Split
tiers JSONB, -- [{min_revenue: 0, max_revenue: 500000, rate: 0.20}, ...]

-- Basis
split_basis VARCHAR(50) NOT NULL DEFAULT 'gross'
CHECK (split_basis IN (
'gross', -- Total booking amount
'net', -- Gross minus fees
'subtotal', -- Room rate only
'owner_share', -- After platform commission
'platform_fees', -- Only platform fees
'guest_fees' -- Only guest-paid fees
)),

-- Minimum/Maximum Guarantees
min_amount_minor INTEGER, -- Minimum guaranteed payout
max_amount_minor INTEGER, -- Maximum cap on split

-- Priority & Application Order
priority INTEGER NOT NULL DEFAULT 100,
apply_order INTEGER NOT NULL DEFAULT 1, -- Order of application (1 = first)

-- Conditions
conditions JSONB, -- When this split applies
/*
Examples:
- Channel-specific: {"channel_id": "uuid"}
- Booking value tiers: {"min_booking_value": 100000} (in minor units)
- Season-based: {"season": "high"}
*/

-- Platform Treatment
is_platform_fee BOOLEAN NOT NULL DEFAULT FALSE,
is_passthrough BOOLEAN NOT NULL DEFAULT FALSE,

-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
valid_from DATE,
valid_to DATE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT,

CONSTRAINT valid_split_percentage CHECK (
split_percentage IS NULL OR
(split_percentage >= 0 AND split_percentage <= 1)
),
CONSTRAINT valid_revenue_dates CHECK (
valid_to IS NULL OR valid_to >= valid_from
)
);

CREATE INDEX idx_revenue_rules_plan ON revenue_rules(rate_plan_id, apply_order);
CREATE INDEX idx_revenue_rules_recipient ON revenue_rules(recipient_account_id);
CREATE INDEX idx_revenue_rules_validity ON revenue_rules(valid_from, valid_to);

2.2 Tax Tables (CRITICAL GAP ADDRESSED)

Table: tax_jurisdictions

CREATE TABLE tax_jurisdictions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Geographic Identification
jurisdiction_type VARCHAR(50) NOT NULL
CHECK (jurisdiction_type IN ('federal', 'state', 'county', 'city', 'district', 'special')),
jurisdiction_code VARCHAR(100) NOT NULL, -- FIPS code or similar
jurisdiction_name VARCHAR(255) NOT NULL,

-- Hierarchy
parent_jurisdiction_id UUID REFERENCES tax_jurisdictions(id),

-- Geographic Boundaries
country_code CHAR(2) NOT NULL, -- ISO 3166-1 alpha-2
state_code VARCHAR(10),
county_name VARCHAR(255),
city_name VARCHAR(255),
postal_codes TEXT[], -- Array of applicable ZIP/postal codes
geo_boundary GEOGRAPHY(POLYGON, 4326), -- PostGIS polygon for precise mapping

-- Tax Authority
tax_authority_name VARCHAR(255) NOT NULL,
tax_authority_contact JSONB, -- Contact details, website, filing info

-- Filing Requirements
filing_frequency VARCHAR(50), -- 'monthly', 'quarterly', 'annual'
filing_threshold_minor INTEGER, -- Minimum amount before filing required

-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
effective_from DATE NOT NULL,
effective_to DATE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT,

UNIQUE(jurisdiction_code, country_code)
);

CREATE INDEX idx_tax_jurisdictions_type ON tax_jurisdictions(jurisdiction_type, is_active);
CREATE INDEX idx_tax_jurisdictions_location ON tax_jurisdictions(country_code, state_code);
CREATE INDEX idx_tax_jurisdictions_postal ON tax_jurisdictions USING GIN(postal_codes);
CREATE INDEX idx_tax_jurisdictions_geo ON tax_jurisdictions USING GIST(geo_boundary);

Table: tax_rules

CREATE TABLE tax_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Jurisdiction
jurisdiction_id UUID NOT NULL REFERENCES tax_jurisdictions(id) ON DELETE CASCADE,

-- Tax Identification
tax_name VARCHAR(255) NOT NULL, -- "California State Transient Occupancy Tax"
tax_code VARCHAR(100), -- Official code/reference
tax_type VARCHAR(100) NOT NULL
CHECK (tax_type IN (
'occupancy_tax',
'lodging_tax',
'hotel_tax',
'tourism_tax',
'sales_tax',
'vat',
'gst',
'resort_tax',
'convention_tax',
'city_tax',
'county_tax',
'state_tax',
'custom'
)),

-- Rate Configuration
rate_type VARCHAR(50) NOT NULL
CHECK (rate_type IN ('percentage', 'fixed_per_night', 'fixed_per_stay', 'tiered')),
tax_rate NUMERIC(7, 6), -- 0.105000 = 10.5%
fixed_amount_minor INTEGER, -- For fixed taxes

-- Tiered Structure
tiers JSONB, -- For progressive tax rates

-- Applicability
applies_to VARCHAR(50) NOT NULL DEFAULT 'room_rate'
CHECK (applies_to IN ('room_rate', 'total_before_tax', 'gross_total', 'specific_fees')),
applies_to_fees TEXT[], -- Array of fee_types this tax applies to

-- Exemptions
exemption_rules JSONB, -- Conditions for tax exemption
/*
Examples:
- {"min_stay_nights": 30} -- Long-term stays exempt
- {"guest_type": "military"} -- Military exemption
- {"booking_purpose": "business"} -- Business exemption
*/

-- Calculation Rules
compound_taxes BOOLEAN NOT NULL DEFAULT FALSE, -- Tax on tax?
calculation_order INTEGER NOT NULL DEFAULT 1,
rounding_rule VARCHAR(50) DEFAULT 'nearest_cent'
CHECK (rounding_rule IN ('up', 'down', 'nearest_cent', 'nearest_dollar')),

-- Platform Responsibility
platform_collects BOOLEAN NOT NULL DEFAULT TRUE,
platform_remits BOOLEAN NOT NULL DEFAULT FALSE,
marketplace_facilitator_rule BOOLEAN DEFAULT FALSE, -- Does marketplace collect/remit?

-- Validity
is_active BOOLEAN NOT NULL DEFAULT TRUE,
effective_from DATE NOT NULL,
effective_to DATE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
regulation_reference TEXT, -- Link to official regulation
notes TEXT,

CONSTRAINT valid_tax_rate CHECK (
tax_rate IS NULL OR (tax_rate >= 0 AND tax_rate <= 1)
),
CONSTRAINT valid_tax_dates CHECK (
effective_to IS NULL OR effective_to >= effective_from
)
);

CREATE INDEX idx_tax_rules_jurisdiction ON tax_rules(jurisdiction_id, is_active);
CREATE INDEX idx_tax_rules_type ON tax_rules(tax_type, is_active);
CREATE INDEX idx_tax_rules_effective ON tax_rules(effective_from, effective_to);

Table: space_tax_mappings

CREATE TABLE space_tax_mappings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Space & Organization
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
space_id UUID NOT NULL REFERENCES spaces(id) ON DELETE CASCADE,

-- Tax Jurisdiction
jurisdiction_id UUID NOT NULL REFERENCES tax_jurisdictions(id),

-- Mapping Type
mapping_method VARCHAR(50) NOT NULL
CHECK (mapping_method IN ('address', 'postal_code', 'geo_coordinates', 'manual')),

-- Override
is_manual_override BOOLEAN DEFAULT FALSE,
override_reason TEXT,

-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
verified_at TIMESTAMPTZ,
verified_by UUID REFERENCES users(id),

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

UNIQUE(space_id, jurisdiction_id)
);

CREATE INDEX idx_space_tax_mappings_space ON space_tax_mappings(space_id, is_active);
CREATE INDEX idx_space_tax_mappings_jurisdiction ON space_tax_mappings(jurisdiction_id);

2.3 Quote Tables (Enhanced)

Table: quotes

CREATE TABLE quotes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Ownership & Context
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
space_id UUID NOT NULL REFERENCES spaces(id) ON DELETE CASCADE,
unit_id UUID REFERENCES units(id),

-- Rate Plan Reference
rate_plan_id UUID NOT NULL REFERENCES rate_plans(id),
rate_plan_snapshot JSONB NOT NULL, -- Frozen copy of rate plan at quote time

-- Stay Details
checkin_date DATE NOT NULL,
checkout_date DATE NOT NULL,
nights INTEGER NOT NULL,
guests INTEGER NOT NULL,
adults INTEGER,
children INTEGER,
pets INTEGER DEFAULT 0,

-- Currency
currency CHAR(3) NOT NULL DEFAULT 'USD',
exchange_rate NUMERIC(12, 6) DEFAULT 1.0, -- If multi-currency

-- Pricing Breakdown (All in minor units - cents)
subtotal_minor INTEGER NOT NULL, -- Sum of nightly rates before fees
fees_total_minor INTEGER NOT NULL, -- Sum of all fees (guest + platform)
taxes_total_minor INTEGER NOT NULL, -- Sum of all taxes
total_minor INTEGER NOT NULL, -- Grand total

-- Guest-Facing Amounts
guest_pays_minor INTEGER NOT NULL, -- What guest actually pays

-- Revenue Breakdown
owner_revenue_minor INTEGER, -- Owner's share
platform_revenue_minor INTEGER, -- Platform commission

-- Source & Channel
source VARCHAR(50) NOT NULL DEFAULT 'direct'
CHECK (source IN ('direct', 'partner', 'channel', 'manual', 'api')),
channel_id UUID REFERENCES channels(id),

-- Quote Metadata
quote_code VARCHAR(50) UNIQUE NOT NULL, -- Human-readable quote reference
status VARCHAR(50) NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'valid', 'expired', 'booked', 'cancelled', 'superseded')),

-- Validity
valid_until TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,

-- Conversion
booking_id UUID REFERENCES bookings(id), -- Set when converted to booking
converted_at TIMESTAMPTZ,

-- Special Conditions
discount_code VARCHAR(100),
promo_code VARCHAR(100),
special_requests TEXT,

-- Calculation Metadata
calculation_engine_version VARCHAR(50), -- For tracking quote algorithm version
calculation_duration_ms INTEGER, -- Performance tracking
rules_applied JSONB, -- Array of rule IDs applied

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id),

-- Constraints
CONSTRAINT valid_stay_dates CHECK (checkout_date > checkin_date),
CONSTRAINT valid_guests CHECK (guests > 0),
CONSTRAINT valid_nights CHECK (nights > 0),
CONSTRAINT valid_amounts CHECK (
total_minor = subtotal_minor + fees_total_minor + taxes_total_minor
)
);

CREATE INDEX idx_quotes_org_space ON quotes(org_id, space_id);
CREATE INDEX idx_quotes_dates ON quotes(checkin_date, checkout_date);
CREATE INDEX idx_quotes_status ON quotes(status, expires_at);
CREATE INDEX idx_quotes_code ON quotes(quote_code);
CREATE INDEX idx_quotes_booking ON quotes(booking_id) WHERE booking_id IS NOT NULL;

Table: quote_line_items

CREATE TABLE quote_line_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Quote
quote_id UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

-- Line Item Details
line_type VARCHAR(50) NOT NULL
CHECK (line_type IN (
'nightly_rate',
'fee',
'tax',
'discount',
'adjustment',
'deposit',
'credit'
)),

-- Item Identification
item_name VARCHAR(255) NOT NULL,
item_code VARCHAR(100), -- Reference code (e.g., fee_rule_id, tax_rule_id)
description TEXT,

-- Source Reference
rate_rule_id UUID REFERENCES rate_rules(id),
fee_rule_id UUID REFERENCES fee_rules(id),
tax_rule_id UUID REFERENCES tax_rules(id),

-- Quantity & Rate
quantity NUMERIC(10, 4) NOT NULL DEFAULT 1, -- e.g., 7 nights, 2 guests
unit_price_minor INTEGER, -- Price per unit (if applicable)

-- Amount (in minor units)
amount_minor INTEGER NOT NULL,

-- Calculation Details
calculation_basis VARCHAR(100), -- What this was calculated from
calculation_formula TEXT, -- Human-readable formula

-- Tax Treatment
is_taxable BOOLEAN NOT NULL DEFAULT FALSE,
taxable_amount_minor INTEGER,

-- Display
display_order INTEGER NOT NULL DEFAULT 100,
is_visible_to_guest BOOLEAN NOT NULL DEFAULT TRUE,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metadata JSONB -- Additional context
);

CREATE INDEX idx_quote_line_items_quote ON quote_line_items(quote_id, display_order);
CREATE INDEX idx_quote_line_items_type ON quote_line_items(line_type);
CREATE INDEX idx_quote_line_items_rule_refs ON quote_line_items(rate_rule_id, fee_rule_id, tax_rule_id);

Table: quote_daily_rates

CREATE TABLE quote_daily_rates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Quote
quote_id UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

-- Date
date DATE NOT NULL,
day_of_week VARCHAR(10) NOT NULL,

-- Rate (in minor units)
base_rate_minor INTEGER NOT NULL,
adjusted_rate_minor INTEGER NOT NULL,

-- Applied Rules
rules_applied JSONB, -- Array of {rule_id, rule_type, adjustment}

-- Display Order
night_number INTEGER NOT NULL, -- 1 = first night, 2 = second night, etc.

UNIQUE(quote_id, date)
);

CREATE INDEX idx_quote_daily_rates_quote ON quote_daily_rates(quote_id, night_number);

Table: quote_revenue_splits

CREATE TABLE quote_revenue_splits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Parent Quote
quote_id UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

-- Recipient
recipient_account_id UUID NOT NULL REFERENCES accounts(id),
recipient_type VARCHAR(50) NOT NULL,

-- Revenue Rule Reference
revenue_rule_id UUID REFERENCES revenue_rules(id),

-- Split Amount (in minor units)
split_amount_minor INTEGER NOT NULL,
split_percentage NUMERIC(5, 4),

-- Calculation Basis
basis_amount_minor INTEGER NOT NULL, -- Amount this split was calculated from
basis_type VARCHAR(50) NOT NULL,

-- Payout Timing
payout_timing VARCHAR(50) DEFAULT 'on_completion'
CHECK (payout_timing IN ('immediate', 'on_confirmation', 'on_checkin', 'on_completion', 'on_schedule')),

-- Display
display_order INTEGER NOT NULL DEFAULT 100,

-- Metadata
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_quote_revenue_splits_quote ON quote_revenue_splits(quote_id);
CREATE INDEX idx_quote_revenue_splits_recipient ON quote_revenue_splits(recipient_account_id);

3. Tax Calculation Engine Specification

3.1 Tax Engine Architecture

Components

┌─────────────────────────────────────────────────────────┐
│ TAX CALCULATION ENGINE │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. Jurisdiction Resolver │
│ └─> Space Address → Applicable Jurisdictions │
│ │
│ 2. Tax Rule Matcher │
│ └─> Booking Context → Applicable Tax Rules │
│ │
│ 3. Tax Calculator │
│ └─> Apply Rates → Calculate Line Items │
│ │
│ 4. Exemption Processor │
│ └─> Check Exemptions → Adjust Calculations │
│ │
│ 5. Compliance Tracker │
│ └─> Track Collection → Remittance Queue │
│ │
└─────────────────────────────────────────────────────────┘

3.2 Jurisdiction Resolution Algorithm

// Pseudocode for jurisdiction resolution
function resolveJurisdictions(space: Space): TaxJurisdiction[] {
const jurisdictions: TaxJurisdiction[] = [];

// 1. Start with federal/national level
const federal = findFederalJurisdiction(space.country_code);
if (federal && federal.has_applicable_taxes) {
jurisdictions.push(federal);
}

// 2. State/Province level
const state = findStateJurisdiction(space.country_code, space.state_code);
if (state && state.has_applicable_taxes) {
jurisdictions.push(state);
}

// 3. County level
const county = findCountyJurisdiction(
space.country_code,
space.state_code,
space.county_name
);
if (county && county.has_applicable_taxes) {
jurisdictions.push(county);
}

// 4. City/Municipal level
const city = findCityJurisdiction(
space.country_code,
space.state_code,
space.city_name,
space.postal_code
);
if (city && city.has_applicable_taxes) {
jurisdictions.push(city);
}

// 5. Special districts (tourism zones, etc.)
const special = findSpecialDistricts(
space.geo_coordinates,
space.postal_code
);
jurisdictions.push(...special);

// 6. Verify against manual overrides
const overrides = getManualTaxMappings(space.id);
if (overrides.length > 0) {
return overrides; // Manual mapping takes precedence
}

return jurisdictions;
}

3.3 Tax Calculation Workflow

// Tax calculation for a quote
function calculateTaxes(quote: Quote): TaxLineItem[] {
const space = getSpace(quote.space_id);
const jurisdictions = resolveJurisdictions(space);
const lineItems: TaxLineItem[] = [];

// Get applicable tax rules
for (const jurisdiction of jurisdictions) {
const taxRules = getTaxRules(jurisdiction, quote.checkin_date);

for (const rule of taxRules) {
// Check if tax applies
if (!doesTaxApply(rule, quote)) {
continue; // Skip if exempt
}

// Calculate taxable base
const taxableBase = calculateTaxableBase(rule, quote);

// Apply rate
let taxAmount = 0;
switch (rule.rate_type) {
case 'percentage':
taxAmount = taxableBase * rule.tax_rate;
break;
case 'fixed_per_night':
taxAmount = rule.fixed_amount_minor * quote.nights;
break;
case 'fixed_per_stay':
taxAmount = rule.fixed_amount_minor;
break;
case 'tiered':
taxAmount = calculateTieredTax(rule.tiers, taxableBase);
break;
}

// Apply rounding
taxAmount = roundTaxAmount(taxAmount, rule.rounding_rule);

// Create line item
lineItems.push({
jurisdiction_id: jurisdiction.id,
jurisdiction_name: jurisdiction.jurisdiction_name,
tax_rule_id: rule.id,
tax_name: rule.tax_name,
tax_type: rule.tax_type,
taxable_base_minor: taxableBase,
tax_rate: rule.tax_rate,
tax_amount_minor: taxAmount,
platform_collects: rule.platform_collects,
platform_remits: rule.platform_remits
});
}
}

// Sort by calculation_order for compound taxes
lineItems.sort((a, b) => a.calculation_order - b.calculation_order);

return lineItems;
}

3.4 Exemption Rules

-- Example exemption configurations in tax_rules.exemption_rules JSONB

-- Long-term stay exemption (30+ nights)
{
"exemption_type": "long_term_stay",
"min_nights": 30,
"full_exemption": true
}

-- Military personnel exemption
{
"exemption_type": "guest_status",
"guest_types": ["military", "veteran"],
"requires_verification": true,
"documentation_required": ["military_id", "orders"]
}

-- Business travel exemption
{
"exemption_type": "booking_purpose",
"purposes": ["business", "corporate"],
"requires_business_registration": true
}

-- Partial exemption for low-value stays
{
"exemption_type": "threshold",
"threshold_minor": 10000, // $100.00
"below_threshold_rate": 0.05, // Reduced 5% rate
"above_threshold_rate": 0.10 // Normal 10% rate
}

-- Senior citizen discount
{
"exemption_type": "guest_demographics",
"age_minimum": 65,
"discount_percentage": 0.50 // 50% reduction
}

3.5 Marketplace Facilitator Laws

// Determining collection responsibility
function determineCollectionResponsibility(
space: Space,
channel: Channel,
jurisdiction: TaxJurisdiction
): CollectionResponsibility {

const taxRule = getCurrentTaxRule(jurisdiction);

// Check if marketplace facilitator law applies
if (taxRule.marketplace_facilitator_rule) {
// Channel (e.g., Airbnb, VRBO) collects and remits
if (channel.is_marketplace_facilitator) {
return {
collector: 'channel',
remitter: 'channel',
platform_responsibility: 'none',
owner_responsibility: 'none'
};
}
}

// Default: Platform collects, determines remittance
if (taxRule.platform_collects) {
return {
collector: 'platform',
remitter: taxRule.platform_remits ? 'platform' : 'owner',
platform_responsibility: 'collect',
owner_responsibility: taxRule.platform_remits ? 'none' : 'remit'
};
}

// Owner-managed: Owner collects and remits
return {
collector: 'owner',
remitter: 'owner',
platform_responsibility: 'track',
owner_responsibility: 'collect_and_remit'
};
}

3.6 Integration with Avalara MyLodgeTax (Future)

// External tax service integration interface
interface TaxServiceProvider {
validateAddress(address: Address): AddressValidationResult;
resolveJurisdictions(address: Address): Jurisdiction[];
calculateTax(request: TaxCalculationRequest): TaxCalculationResponse;
submitReturn(return: TaxReturn): SubmissionResult;
}

class AvalaraTaxService implements TaxServiceProvider {
async calculateTax(request: TaxCalculationRequest): Promise<TaxCalculationResponse> {
// Call Avalara MyLodgeTax API
const response = await fetch('https://api.avalara.com/mylodge/v1/calculate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
property_address: request.space.address,
checkin_date: request.checkin_date,
checkout_date: request.checkout_date,
room_rate: request.subtotal,
fees: request.fees,
guests: request.guests
})
});

const data = await response.json();

return {
jurisdictions: data.jurisdictions,
tax_line_items: data.taxes.map(tax => ({
jurisdiction_name: tax.jurisdiction_name,
tax_name: tax.tax_name,
tax_rate: tax.rate,
taxable_amount: tax.taxable_amount,
tax_amount: tax.tax_amount,
remittance_required: tax.remittance_required
})),
total_tax_amount: data.total_tax
};
}
}

4. Fee Type Taxonomy

4.1 Complete Fee Classification

Guest-Facing Fees (Guest Pays)

Fee TypeTypical RangeBasisRefundableTaxableDescription
Cleaning Fee$50-$300Per staySometimesVaries by jurisdictionOne-time cleaning after checkout
Pet Fee$10-$50/night or $50-$150/stayPer night or per stayNoYesAdditional cleaning for pets
Extra Guest Fee$10-$50/guest/nightPer guest per nightYesYesCharge for guests beyond base occupancy
Resort Fee$20-$100/nightPer nightNoYesAccess to amenities (pool, gym, etc.)
Linen Fee$25-$75/stayPer stayNoYesLinen and towel service
Hot Tub Fee$25-$75/night or $100-$300/stayPer night or per stayNoYesHot tub heating and maintenance
Pool Heating Fee$50-$150/nightPer nightNoYesPool heating (seasonal)
Damage Waiver$5-$15/night or 5-10% of rentalPer night or percentageNoVariesInsurance alternative to security deposit
Early Check-in Fee$50-$150/occurrencePer occurrenceYes if not usedYesCheck-in before standard time
Late Checkout Fee$50-$150/occurrencePer occurrenceYes if not usedYesCheck-out after standard time
Mid-Stay Clean Fee$100-$200/cleanPer occurrenceYes if not usedYesAdditional cleaning during stay

Platform/Service Fees (Platform Revenue)

Fee TypeTypical RangeBasisDescription
Booking Fee$5-$50 or 3-5%Fixed or percentagePlatform booking service fee
Service Fee3-15%Percentage of subtotalPlatform service/technology fee
Processing Fee2.5-3.5%Percentage of totalCredit card/payment processing
Channel Commission3-15%PercentageChannel/OTA commission (e.g., Airbnb 3%, VRBO 5-15%)

Pass-Through Fees (Remitted to Third Parties)

Fee TypeTypical RangeBasisRemitted ToDescription
State Tax0-10%PercentageState revenue deptState transient occupancy tax
County Tax0-6%PercentageCounty tax authorityCounty lodging tax
City Tax0-8%PercentageCity tax authorityMunicipal hotel/lodging tax
Tourism Tax1-6%PercentageTourism authorityTourism development/marketing
Occupancy Tax5-15%PercentageMultiple authoritiesGeneral transient occupancy tax

Owner-Specific Fees

Fee TypeTypical RangeBasisDescription
Security Deposit$200-$2,000Per stayRefundable deposit for damages (held, not charged)
Deposit/Prepayment25-50% of totalPercentagePartial payment at booking

4.2 Fee Application Rules

Rule: Extra Guest Fees

{
"fee_type": "extra_guest",
"name": "Extra Guest Fee",
"calculation_type": "per_guest",
"amount_minor": 2500,
"basis": "per_night",
"conditions": {
"applies_when": "guests > base_occupancy",
"base_occupancy": 6,
"max_extra_guests": 4
},
"calculation_example": "If base_occupancy=6, guests=8, nights=5, fee = (8-6) * $25 * 5 = $250"
}

Rule: Pet Fees

{
"fee_type": "pet",
"name": "Pet Fee",
"calculation_type": "per_stay",
"amount_minor": 10000,
"basis": "per_stay",
"conditions": {
"requires_addon": "pet_friendly",
"max_pets": 2,
"per_pet_charge": true
},
"calculation_example": "If pets=2, fee = 2 * $100 = $200"
}

Rule: Tiered Service Fee

{
"fee_type": "service_fee",
"name": "Platform Service Fee",
"calculation_type": "tiered",
"basis": "per_stay",
"tiers": [
{
"min_amount_minor": 0,
"max_amount_minor": 100000,
"rate": 0.05
},
{
"min_amount_minor": 100000,
"max_amount_minor": 300000,
"rate": 0.03
},
{
"min_amount_minor": 300000,
"max_amount_minor": null,
"rate": 0.02
}
],
"calculation_example": "If subtotal=$2,500: ($1,000 * 5%) + ($2,000 * 3%) + ($500 * 2%) = $50 + $60 + $10 = $120"
}

4.3 Fee Display & Transparency

Guest-Facing Quote Display

┌─────────────────────────────────────────┐
│ Your Booking Summary │
├─────────────────────────────────────────┤
│ │
│ $500 x 7 nights $3,500 │
│ │
│ Fees: │
│ Cleaning fee $150 │
│ Pet fee (2 dogs) $200 │
│ Service fee (5%) $175 │
│ ─────────────────────────────────── │
│ Subtotal $4,025 │
│ │
│ Taxes: │
│ California State Tax (8%) $322 │
│ Santa Barbara County (6%) $242 │
│ City of Carpinteria (2%) $81 │
│ ─────────────────────────────────── │
│ Total taxes $645 │
│ │
│ TOTAL $4,670 │
│ │
│ Security Deposit (refundable) $500 │
│ (authorized, not charged) │
│ │
└─────────────────────────────────────────┘

5. Rule Evaluation Algorithm

5.1 Priority-Based Rule Resolution

Evaluation Order

1. RATE PLAN SELECTION
├─> Filter by validity (valid_from <= checkin, valid_to >= checkout)
├─> Filter by channel match
└─> Select highest priority active plan

2. RATE RULE EVALUATION
├─> Filter applicable rules (date range, LOS, conditions)
├─> Sort by priority (highest first)
└─> Apply in priority order with compound_mode handling

3. FEE RULE APPLICATION
├─> Filter mandatory fees (is_mandatory=true)
├─> Filter conditional fees (evaluate conditions)
├─> Sort by priority
└─> Calculate amounts

4. TAX CALCULATION
├─> Resolve jurisdictions
├─> Match tax rules
├─> Check exemptions
├─> Calculate in order (compound_taxes handling)
└─> Apply rounding

5. REVENUE SPLIT CALCULATION
├─> Filter active revenue rules
├─> Sort by apply_order
├─> Calculate splits sequentially
└─> Validate totals

5.2 Rate Rule Composition

Compound Mode: Additive

// Example: Base rate + seasonal modifier + LOS discount
const baseRate = 50000; // $500/night

// Rule 1: Seasonal (winter season +20%)
const seasonalAdjustment = baseRate * 0.20; // +$100
const afterSeasonal = baseRate + seasonalAdjustment; // $600

// Rule 2: 7-night discount (-15%)
const losDiscount = baseRate * 0.15; // -$75
const finalRate = afterSeasonal - losDiscount; // $525

// Both adjustments applied to base rate, then added/subtracted

Compound Mode: Multiplicative

// Example: Base rate with multiple percentage modifiers
const baseRate = 50000; // $500/night

// Rule 1: Seasonal multiplier (×1.20)
const afterSeasonal = baseRate * 1.20; // $600

// Rule 2: LOS multiplier (×0.85)
const finalRate = afterSeasonal * 0.85; // $510

// Each multiplier applies to the result of the previous

Compound Mode: Override

// Example: Channel-specific rate override
const baseRate = 50000; // $500/night

// Rule 1: High season adjustment (+$100)
let currentRate = baseRate + 10000; // $600

// Rule 2: Channel override (set to $550) - takes precedence
if (rule.compound_mode === 'override') {
currentRate = rule.fixed_value; // $550
}

// Override rules replace all previous calculations

5.3 Rule Conflict Resolution

function resolveRateRules(
ratePlan: RatePlan,
checkinDate: Date,
nights: number,
channel?: Channel
): AppliedRate {

// Get all applicable rules
const applicableRules = ratePlan.rate_rules
.filter(rule => isRuleApplicable(rule, checkinDate, nights, channel))
.sort((a, b) => b.priority - a.priority); // Highest priority first

let currentRate = ratePlan.base_rate_minor;
const appliedRules: RuleApplication[] = [];

for (const rule of applicableRules) {
// Check if rule should be applied based on previous rules
if (shouldSkipRule(rule, appliedRules)) {
continue;
}

// Apply the rule based on compound_mode
const previousRate = currentRate;

switch (rule.compound_mode) {
case 'override':
currentRate = calculateOverride(rule);
appliedRules.length = 0; // Clear previous rules
break;

case 'additive':
currentRate = calculateAdditive(currentRate, rule);
break;

case 'multiplicative':
currentRate = calculateMultiplicative(currentRate, rule);
break;

case 'max':
currentRate = Math.max(currentRate, calculateRuleValue(rule));
break;

case 'min':
currentRate = Math.min(currentRate, calculateRuleValue(rule));
break;
}

// Record the application
appliedRules.push({
rule_id: rule.id,
rule_type: rule.rule_type,
previous_rate: previousRate,
adjustment: currentRate - previousRate,
new_rate: currentRate
});

// Apply min/max constraints from rate plan
currentRate = Math.max(
ratePlan.min_rate_minor || 0,
Math.min(ratePlan.max_rate_minor || Infinity, currentRate)
);
}

return {
final_rate_minor: currentRate,
applied_rules: appliedRules,
calculation_notes: generateCalculationNotes(appliedRules)
};
}

5.4 Per-Night Rate Calculation

// Calculate rate for each night (handles day-of-week variations, etc.)
function calculateDailyRates(
ratePlan: RatePlan,
checkinDate: Date,
nights: number,
channel?: Channel
): DailyRate[] {

const dailyRates: DailyRate[] = [];

for (let i = 0; i < nights; i++) {
const date = addDays(checkinDate, i);
const dayOfWeek = getDayOfWeek(date);

// Get rules applicable to this specific date
const dateRules = ratePlan.rate_rules.filter(rule =>
isRuleApplicableForDate(rule, date, dayOfWeek, i, nights, channel)
);

// Calculate rate for this night
const nightRate = resolveRateRules(
{ ...ratePlan, rate_rules: dateRules },
date,
nights,
channel
);

dailyRates.push({
date: date,
day_of_week: dayOfWeek,
night_number: i + 1,
base_rate_minor: ratePlan.base_rate_minor,
adjusted_rate_minor: nightRate.final_rate_minor,
applied_rules: nightRate.applied_rules
});
}

return dailyRates;
}

5.5 Channel-Specific Pricing

// Channel override mechanism
function getChannelAdjustedRate(
baseRate: number,
ratePlan: RatePlan,
channel: Channel
): number {

// Check for channel-specific rate plan
const channelPlan = ratePlan.channel_overrides?.find(
override => override.channel_id === channel.id
);

if (channelPlan) {
return channelPlan.rate_minor;
}

// Check for channel-specific rate rules
const channelRule = ratePlan.rate_rules.find(
rule => rule.rule_type === 'channel' &&
rule.conditions.channel_id === channel.id
);

if (channelRule) {
return applyRuleAdjustment(baseRate, channelRule);
}

// Apply channel commission adjustment if needed
if (channel.commission_rate && ratePlan.absorb_commission) {
// Increase rate to absorb channel commission
return baseRate / (1 - channel.commission_rate);
}

return baseRate;
}

6. Quote Line Items Structure

6.1 Line Item Hierarchy

Quote
├─ Subtotal (Sum of nightly rates)
│ ├─ Night 1: $500 (Base: $450, Weekend: +$50)
│ ├─ Night 2: $500
│ ├─ Night 3: $500
│ ├─ Night 4: $500
│ ├─ Night 5: $500
│ ├─ Night 6: $450 (Base: $450)
│ └─ Night 7: $450
│ └─ SUBTOTAL: $3,400

├─ Fees
│ ├─ Cleaning Fee: $150 (mandatory, per stay)
│ ├─ Pet Fee: $200 (2 dogs × $100)
│ ├─ Service Fee: $170 (5% of $3,400)
│ └─ FEES TOTAL: $520

├─ Taxes
│ ├─ CA State Tax: $313.60 (8% of $3,920)
│ ├─ County Tax: $235.20 (6% of $3,920)
│ └─ City Tax: $78.40 (2% of $3,920)
│ └─ TAXES TOTAL: $627.20

├─ Adjustments/Discounts
│ └─ 7-Night Discount: -$170 (-5%)

└─ GRAND TOTAL: $4,377.20

6.2 Quote JSON Structure

{
"quote_id": "quo_abc123xyz",
"quote_code": "TVL-2025-10-24-0001",
"status": "valid",
"created_at": "2025-10-24T10:30:00Z",
"valid_until": "2025-10-26T10:30:00Z",

"stay_details": {
"space_id": "spa_xyz",
"space_name": "Beachfront Villa Azul",
"checkin_date": "2026-01-15",
"checkout_date": "2026-01-22",
"nights": 7,
"guests": 8,
"adults": 6,
"children": 2,
"pets": 2
},

"currency": "USD",

"breakdown": {
"subtotal": {
"amount_minor": 340000,
"amount_display": "$3,400.00",
"description": "7 nights accommodation",
"daily_rates": [
{
"date": "2026-01-15",
"day_of_week": "Friday",
"night_number": 1,
"base_rate_minor": 45000,
"adjustments": [
{"type": "weekend", "amount_minor": 5000, "description": "Weekend rate"}
],
"final_rate_minor": 50000
}
// ... additional nights
]
},

"fees": {
"total_minor": 52000,
"total_display": "$520.00",
"line_items": [
{
"fee_type": "cleaning",
"name": "Cleaning Fee",
"description": "Post-checkout professional cleaning",
"amount_minor": 15000,
"amount_display": "$150.00",
"is_mandatory": true,
"is_taxable": true,
"is_refundable": false
},
{
"fee_type": "pet",
"name": "Pet Fee",
"description": "Pet cleaning and maintenance (2 dogs)",
"calculation": "2 dogs × $100",
"amount_minor": 20000,
"amount_display": "$200.00",
"is_mandatory": false,
"is_taxable": true,
"is_refundable": false
},
{
"fee_type": "service_fee",
"name": "Platform Service Fee",
"description": "Booking and support services",
"calculation": "5% of $3,400",
"amount_minor": 17000,
"amount_display": "$170.00",
"is_mandatory": true,
"is_taxable": false,
"is_refundable": false
}
]
},

"taxes": {
"total_minor": 62720,
"total_display": "$627.20",
"taxable_base_minor": 392000,
"taxable_base_display": "$3,920.00",
"line_items": [
{
"jurisdiction_name": "State of California",
"jurisdiction_type": "state",
"tax_type": "occupancy_tax",
"tax_name": "California Transient Occupancy Tax",
"tax_rate": 0.08,
"taxable_amount_minor": 392000,
"tax_amount_minor": 31360,
"tax_amount_display": "$313.60",
"platform_collects": true,
"platform_remits": false
},
{
"jurisdiction_name": "Santa Barbara County",
"jurisdiction_type": "county",
"tax_type": "county_tax",
"tax_name": "County Lodging Tax",
"tax_rate": 0.06,
"taxable_amount_minor": 392000,
"tax_amount_minor": 23520,
"tax_amount_display": "$235.20",
"platform_collects": true,
"platform_remits": false
},
{
"jurisdiction_name": "City of Carpinteria",
"jurisdiction_type": "city",
"tax_type": "city_tax",
"tax_name": "City Tourism Tax",
"tax_rate": 0.02,
"taxable_amount_minor": 392000,
"tax_amount_minor": 7840,
"tax_amount_display": "$78.40",
"platform_collects": true,
"platform_remits": true
}
]
},

"discounts": {
"total_minor": -17000,
"total_display": "-$170.00",
"line_items": [
{
"discount_type": "los",
"name": "7-Night Stay Discount",
"description": "5% discount for weekly stays",
"amount_minor": -17000,
"amount_display": "-$170.00"
}
]
}
},

"totals": {
"subtotal_minor": 340000,
"fees_minor": 52000,
"discounts_minor": -17000,
"taxable_base_minor": 392000,
"taxes_minor": 62720,
"grand_total_minor": 437720,

"subtotal_display": "$3,400.00",
"fees_display": "$520.00",
"discounts_display": "-$170.00",
"taxes_display": "$627.20",
"grand_total_display": "$4,377.20"
},

"revenue_splits": [
{
"recipient_account_id": "acc_owner_123",
"recipient_type": "owner",
"recipient_name": "Casa Azul LLC",
"split_percentage": 0.80,
"basis_amount_minor": 392000,
"split_amount_minor": 313600,
"split_amount_display": "$3,136.00",
"payout_timing": "on_completion"
},
{
"recipient_account_id": "acc_platform_001",
"recipient_type": "platform",
"recipient_name": "The Villa Life",
"split_percentage": 0.20,
"basis_amount_minor": 392000,
"split_amount_minor": 78400,
"split_amount_display": "$784.00",
"payout_timing": "immediate"
}
],

"metadata": {
"rate_plan_id": "rp_standard_001",
"rate_plan_name": "Standard Rate",
"rules_applied": [
"rate_rule_base",
"rate_rule_weekend",
"fee_rule_cleaning",
"fee_rule_pet",
"fee_rule_service",
"tax_rule_ca_state",
"tax_rule_sb_county",
"tax_rule_carpinteria",
"revenue_rule_owner_80",
"revenue_rule_platform_20"
],
"calculation_engine_version": "1.0.0",
"calculation_duration_ms": 45
}
}

7. Revenue Splitting Patterns

7.1 Simple Percentage Split

Pattern: Owner 80%, Platform 20%

-- Revenue Rule 1: Owner
INSERT INTO revenue_rules (
rate_plan_id,
name,
recipient_account_id,
recipient_type,
split_type,
split_percentage,
split_basis,
priority,
apply_order
) VALUES (
'rp_001',
'Owner Revenue Share',
'acc_owner_123',
'owner',
'percentage',
0.80,
'net', -- After platform fees
100,
1
);

-- Revenue Rule 2: Platform
INSERT INTO revenue_rules (
rate_plan_id,
name,
recipient_account_id,
recipient_type,
split_type,
split_percentage,
split_basis,
priority,
apply_order
) VALUES (
'rp_001',
'Platform Commission',
'acc_platform_001',
'platform',
'percentage',
0.20,
'net',
100,
2
);

-- Calculation:
-- Subtotal: $3,400
-- Fees (non-platform): $150 (cleaning) + $200 (pet) = $350
-- Platform fee: $170 (5% service fee) - kept by platform
-- Net = $3,400 + $350 = $3,750
-- Owner: $3,750 × 80% = $3,000
-- Platform commission: $3,750 × 20% = $750
-- Platform total: $750 + $170 (service fee) = $920

7.2 Tiered Commission Structure

Pattern: Sliding scale based on booking value

INSERT INTO revenue_rules (
rate_plan_id,
name,
recipient_account_id,
recipient_type,
split_type,
tiers,
split_basis
) VALUES (
'rp_002',
'Tiered Platform Commission',
'acc_platform_001',
'platform',
'tiered',
'[
{
"min_revenue_minor": 0,
"max_revenue_minor": 50000,
"rate": 0.20,
"description": "20% on first $500"
},
{
"min_revenue_minor": 50000,
"max_revenue_minor": 200000,
"rate": 0.15,
"description": "15% on $500-$2,000"
},
{
"min_revenue_minor": 200000,
"max_revenue_minor": null,
"rate": 0.10,
"description": "10% on amounts over $2,000"
}
]'::jsonb,
'gross'
);

-- Calculation for $3,400 booking:
-- Tier 1: $500 × 20% = $100
-- Tier 2: $1,500 × 15% = $225
-- Tier 3: $1,400 × 10% = $140
-- Total platform commission: $465
-- Owner receives: $3,400 - $465 = $2,935

7.3 Multi-Party Split (Owner + Manager + Platform)

Pattern: Three-way split with manager override

-- Rule 1: Owner (70%)
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_account_id, recipient_type,
split_type, split_percentage, split_basis, apply_order
) VALUES (
'rp_003', 'Owner Revenue', 'acc_owner_456', 'owner',
'percentage', 0.70, 'gross', 1
);

-- Rule 2: Property Manager (15%)
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_account_id, recipient_type,
split_type, split_percentage, split_basis, apply_order
) VALUES (
'rp_003', 'Manager Fee', 'acc_manager_789', 'manager',
'percentage', 0.15, 'gross', 2
);

-- Rule 3: Platform (15%)
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_account_id, recipient_type,
split_type, split_percentage, split_basis, apply_order
) VALUES (
'rp_003', 'Platform Commission', 'acc_platform_001', 'platform',
'percentage', 0.15, 'gross', 3
);

-- Calculation for $3,400 booking:
-- Owner: $3,400 × 70% = $2,380
-- Manager: $3,400 × 15% = $510
-- Platform: $3,400 × 15% = $510
-- Total: $3,400

7.4 Minimum Guarantee Split

Pattern: Owner gets at least $X, platform takes remainder

-- Rule 1: Owner with minimum guarantee
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_account_id, recipient_type,
split_type, split_percentage, split_basis,
min_amount_minor, apply_order
) VALUES (
'rp_004', 'Owner Revenue', 'acc_owner_999', 'owner',
'percentage', 0.75, 'gross',
250000, -- $2,500 minimum guaranteed
1
);

-- Rule 2: Platform takes remainder
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_account_id, recipient_type,
split_type, split_basis, apply_order
) VALUES (
'rp_004', 'Platform Commission', 'acc_platform_001', 'platform',
'remainder', 'gross', 2
);

-- Calculation examples:
-- Example 1: $4,000 booking
-- Owner: max($4,000 × 75%, $2,500) = $3,000
-- Platform: $4,000 - $3,000 = $1,000
--
-- Example 2: $2,000 booking (below threshold)
-- Owner: max($2,000 × 75%, $2,500) = $2,500 (minimum kicks in)
-- Platform: $2,000 - $2,500 = -$500 (platform takes loss)

7.5 Channel-Specific Splits

Pattern: Different commission rates per channel

-- Direct bookings: Lower commission
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_type,
split_type, split_percentage,
conditions, apply_order
) VALUES (
'rp_005', 'Platform Commission (Direct)', 'platform',
'percentage', 0.10,
'{"channel_type": "direct"}'::jsonb, 1
);

-- Airbnb: Higher commission (absorb their 3% fee)
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_type,
split_type, split_percentage,
conditions, apply_order
) VALUES (
'rp_005', 'Platform Commission (Airbnb)', 'platform',
'percentage', 0.23, -- 20% + 3% Airbnb fee
'{"channel_id": "ch_airbnb_001"}'::jsonb, 2
);

-- VRBO: Even higher commission
INSERT INTO revenue_rules (
rate_plan_id, name, recipient_type,
split_type, split_percentage,
conditions, apply_order
) VALUES (
'rp_005', 'Platform Commission (VRBO)', 'platform',
'percentage', 0.28, -- 20% + 8% VRBO fee
'{"channel_id": "ch_vrbo_001"}'::jsonb, 3
);

7.6 Stripe Connect Integration

// Example: Creating split payments with Stripe Connect
async function processBookingPayment(
booking: Booking,
quote: Quote
): Promise<PaymentResult> {

const revenueSplits = quote.revenue_splits;

// Create a payment intent with transfer_data for automatic splits
const paymentIntent = await stripe.paymentIntents.create({
amount: quote.grand_total_minor,
currency: quote.currency.toLowerCase(),
customer: booking.guest_stripe_customer_id,

// Transfer to owner account
transfer_data: {
destination: getStripeConnectedAccountId(
revenueSplits.find(s => s.recipient_type === 'owner')
),
amount: revenueSplits.find(
s => s.recipient_type === 'owner'
).split_amount_minor
},

// Platform keeps the commission
application_fee_amount: revenueSplits.find(
s => s.recipient_type === 'platform'
).split_amount_minor,

metadata: {
booking_id: booking.id,
quote_id: quote.id,
org_id: quote.org_id
}
});

return {
payment_intent_id: paymentIntent.id,
status: paymentIntent.status,
splits_applied: revenueSplits
};
}

8. Gap Analysis vs Competitors

8.1 TVL vs Guesty Pricing Features

FeatureTVL (Current)GuestyGap Analysis
Base Rate Management✅ Supported✅ Supported✅ At parity
Seasonal Rates✅ Via rate rules✅ Calendar-based✅ At parity
LOS Discounts✅ Via rate rules✅ Built-in✅ At parity
Day-of-Week Pricing✅ Via rate rules✅ Built-in✅ At parity
Last-Minute Discounts⚠️ Manual rule✅ Automated⚠️ Need automation
Gap Night Pricing❌ Not specified✅ AutomatedGAP
Orphan Night Handling❌ Not specified✅ AutomatedGAP
Dynamic Pricing AI⚠️ Placeholder✅ PriceOptimizer ML⚠️ Integration needed
Channel-Specific Rates✅ Supported✅ Supported✅ At parity
Multi-Currency⚠️ Single currency✅ Multi-currency⚠️ Need FX support
Tax CalculationNOT SPECIFIED✅ IntegratedCRITICAL GAP
Fee Management⚠️ Basic types✅ Comprehensive⚠️ Need taxonomy
Revenue Splits✅ Supported✅ Supported✅ At parity
Promotions/Coupons❌ Not specified✅ Built-inGAP
Rate Calendar ViewNot specified✅ Visual calendar⚠️ UI/UX gap
Bulk Rate UpdatesNot specified✅ Batch operations⚠️ Ops tooling gap
Rate History/Audit⚠️ Basic audit✅ Full versioning⚠️ Need enhancement

8.2 TVL vs Hostaway Pricing Features

FeatureTVL (Current)HostawayGap Analysis
Base Rate Management✅ Supported✅ Supported✅ At parity
Seasonal Pricing✅ Supported✅ Supported✅ At parity
LOS Discounts✅ Supported✅ Supported✅ At parity
Dynamic Pricing⚠️ Placeholder✅ 3rd-party integration⚠️ Need partnerships
Smart Pricing❌ Not specified✅ Via add-onsGAP
Last-Minute Rules⚠️ Manual✅ Automated⚠️ Automation needed
Derived Rates❌ Not specified✅ SupportedGAP
Rate Templates❌ Not specified✅ Reusable templatesGAP
Channel Mapping✅ Supported✅ Supported✅ At parity
Tax AutomationNOT SPECIFIED✅ IntegratedCRITICAL GAP
Fee Customization⚠️ Limited✅ Extensive⚠️ Need expansion
Commission Tracking✅ Supported✅ Supported✅ At parity
Rate Sync SpeedNot specified✅ Real-time⚠️ Performance spec needed
Conflict DetectionNot specified✅ Automated alerts⚠️ Need validation

8.3 Feature Priority Matrix

┌────────────────────────────────────────────────────────┐
│ │
│ HIGH PRIORITY QUADRANTS │
│ IMPACT │
│ ↑ │
│ │ ┌─────────────────┬─────────────────┐ │
│ │ │ │ │ │
│ │ │ CRITICAL │ IMPORTANT │ │
│ │ │ MUST HAVE │ SHOULD HAVE │ │
│ │ │ │ │ │
│ │ │ • Tax Engine │ • Gap Nights │ │
│ │ │ • Fee Taxonomy │ • Orphan Nights │ │
│ │ │ • Rate Audit │ • Promo Codes │ │
│ │ │ │ • Multi-Currency│ │
│ │ │ │ • Rate Templates│ │
│ │ ├─────────────────┼─────────────────┤ │
│ │ │ │ │ │
│ │ │ NICE TO HAVE │ LOW PRIORITY │ │
│ │ │ LATER │ BACKLOG │ │
│ │ │ │ │ │
│ │ │ • Visual │ • Advanced ML │ │
│ │ │ Calendar UI │ • Predictive │ │
│ │ │ • Bulk Updates │ Analytics │ │
│ │ │ │ │ │
│ │ └─────────────────┴─────────────────┘ │
│ │ │
│ LOW EFFORT/COMPLEXITY → HIGH │
│ │
└────────────────────────────────────────────────────────┘

8.4 Competitive Strengths of TVL Architecture

Areas Where TVL Excels:

  1. Multi-Tenancy Architecture

    • Competitors: Single-tenant or weak isolation
    • TVL: True multi-tenant with org_id/account_id
    • Advantage: Scales to white-label marketplaces
  2. Revenue Split Flexibility

    • Competitors: Fixed commission models
    • TVL: Tiered, multi-party, conditional splits
    • Advantage: Supports complex business models
  3. Rule-Based Extensibility

    • Competitors: Hard-coded pricing logic
    • TVL: JSONB conditions, priority system
    • Advantage: No code deploys for new rules
  4. Immutable Quote Snapshots

    • Competitors: Quotes update with rate changes
    • TVL: Immutable pricing guarantee
    • Advantage: Audit compliance, price guarantees
  5. Event-Driven Architecture

    • Competitors: Synchronous updates
    • TVL: Event bus for consistency
    • Advantage: Scalability, resilience

9. Implementation Roadmap

9.1 Phase 1: MVP Foundation (Weeks 1-4)

Goal: Deliver villa-only pricing with basic tax support

Week 1-2: Core Schema Implementation

  • ✅ Implement rate_plans, rate_rules, fee_rules, revenue_rules tables
  • ✅ Implement quotes and quote_line_items tables
  • ✅ Seed base roles and permissions
  • ✅ Create indexes and constraints

Week 3-4: Basic Tax Support

  • ⚠️ Implement tax_jurisdictions, tax_rules, space_tax_mappings
  • ⚠️ Build jurisdiction resolver (US-only, basic mapping)
  • ⚠️ Implement simple percentage tax calculator
  • ⚠️ Add tax line items to quotes

Deliverables:

  • Working rate calculation engine
  • Quote generation with line items
  • Basic tax calculation (percentage-based)
  • Revenue split calculation

MVP Limitations:

  • US-only tax support
  • Simple percentage taxes only
  • Manual jurisdiction mapping
  • No dynamic pricing integration

9.2 Phase 2: Tax Engine Enhancement (Weeks 5-8)

Goal: Production-ready tax compliance

Week 5-6: Multi-Jurisdiction Tax

  • Implement hierarchical jurisdiction resolution
  • Add tiered tax rate support
  • Build exemption rule processor
  • Implement compound tax calculation
  • Add marketplace facilitator law handling

Week 7-8: Tax Authority Integration Prep

  • Design Avalara MyLodgeTax integration interface
  • Build tax calculation abstraction layer
  • Implement tax remittance tracking
  • Create tax reporting tables
  • Build tax filing queue

Deliverables:

  • Multi-jurisdictional tax calculation
  • Exemption rule support
  • Tax remittance tracking
  • Avalara integration-ready

9.3 Phase 3: Advanced Pricing Features (Weeks 9-12)

Goal: Competitive feature parity

Week 9-10: Dynamic Pricing Integration

  • Design dynamic pricing provider interface
  • Implement PriceLabs API integration
  • Build rate override/approval workflow
  • Add dynamic pricing audit trail

Week 11-12: Advanced Rules

  • Implement gap night pricing
  • Add orphan night handling
  • Build last-minute automation
  • Add promotion/coupon system

Deliverables:

  • Dynamic pricing integration (PriceLabs)
  • Gap and orphan night automation
  • Promotions and discounts
  • Rate templates

9.4 Phase 4: Scale & Optimize (Weeks 13-16)

Goal: Enterprise-grade performance

Week 13-14: Performance

  • Implement rate caching layer
  • Build bulk rate update operations
  • Add conflict detection
  • Optimize quote generation (<100ms)

Week 15-16: Operational Tooling

  • Build rate calendar UI
  • Add rate history/audit viewer
  • Create bulk operations admin tools
  • Implement rate conflict resolver

Deliverables:

  • Sub-100ms quote generation
  • Bulk rate operations
  • Admin tooling
  • Rate conflict detection

9.5 Future Enhancements (Post-MVP)

Phase 5: International Expansion

  • Multi-currency support
  • VAT/GST handling
  • International tax compliance
  • FX rate management

Phase 6: AI/ML Features

  • Predictive pricing models
  • Demand forecasting
  • Competitor rate monitoring
  • Automated rate optimization

Phase 7: Enterprise Features

  • White-label marketplace support
  • Partner revenue sharing
  • Multi-brand rate management
  • Enterprise SLA guarantees

10. Technical Specifications

10.1 API Endpoints

Quote Generation API

POST /api/v1/quotes
Content-Type: application/json
Authorization: Bearer {token}

{
"space_id": "spa_xyz123",
"checkin_date": "2026-01-15",
"checkout_date": "2026-01-22",
"guests": 8,
"adults": 6,
"children": 2,
"pets": 2,
"channel_id": null,
"promo_code": null,
"special_requests": "Early check-in if possible"
}

Response 200 OK:
{
"quote": {
"id": "quo_abc123",
"quote_code": "TVL-2025-10-24-0001",
"status": "valid",
"valid_until": "2025-10-26T10:30:00Z",
"totals": {
"subtotal_minor": 340000,
"fees_minor": 52000,
"taxes_minor": 62720,
"grand_total_minor": 437720,
"currency": "USD"
},
"breakdown": { /* full breakdown */ }
}
}

Rate Plan Management API

POST /api/v1/rate-plans
Content-Type: application/json
Authorization: Bearer {token}

{
"org_id": "org_123",
"account_id": "acc_456",
"space_id": "spa_789",
"name": "Summer 2026 Rate",
"currency": "USD",
"base_rate_minor": 50000,
"valid_from": "2026-06-01",
"valid_to": "2026-09-01",
"rate_rules": [
{
"rule_type": "seasonal",
"name": "Peak Summer",
"priority": 100,
"conditions": {
"start_date": "2026-07-01",
"end_date": "2026-08-15"
},
"adjustment_type": "percentage",
"adjustment_value": 0.20
}
],
"fee_rules": [
{
"fee_type": "cleaning",
"name": "Cleaning Fee",
"calculation_type": "fixed",
"amount_minor": 15000
}
]
}

10.2 Performance Requirements

OperationTargetMax Acceptable
Quote Generation<100ms<500ms
Rate Calculation<50ms<200ms
Tax Calculation<50ms<200ms
Revenue Split Calculation<20ms<100ms
Quote Retrieval<20ms<50ms
Rate Plan Update<200ms<500ms
Bulk Rate Import1000 rates/sec100 rates/sec

10.3 Data Retention & Archival

Data TypeRetentionArchival Policy
Rate PlansIndefiniteSoft delete only
Rate RulesIndefiniteHistorical versioning
Quotes7 yearsArchive to cold storage after 2 years
Quote Line Items7 yearsArchive with parent quote
Tax Calculations7 yearsRegulatory compliance requirement
Revenue Splits10 yearsFinancial audit requirement
Audit Events10 yearsCompliance requirement

10.4 Security & Compliance

Access Control

  • Rate plan modifications require pricing.edit permission
  • Quote generation requires booking.create permission
  • Tax configuration requires admin or finance_admin role
  • Revenue rule changes require dual approval (future)

Audit Requirements

  • All rate changes logged with user_id, timestamp, before/after state
  • Quote calculations logged with rule version and inputs
  • Tax calculations include jurisdiction resolution audit trail
  • Revenue splits include calculation basis and applied rules

Data Privacy

  • Guest PII not stored in pricing tables
  • Quote history anonymized after 2 years (guest_id nulled)
  • Tax jurisdiction data is public information (no privacy concerns)

11. Conclusion & Recommendations

11.1 Summary of Findings

Current State Assessment:

  • ✅ Strong architectural foundation with multi-tenancy, rule-based pricing, and event-driven consistency
  • ⚠️ Pricing logic well-specified but lacks operational details
  • Critical gap: No tax calculation engine specified
  • ⚠️ Fee taxonomy is incomplete
  • ⚠️ Rule evaluation algorithm needs formalization

11.2 Critical Recommendations

IMMEDIATE (Pre-Launch Blockers)

  1. Tax Calculation Engine - CRITICAL

    • Priority: P0 - Blocking MVP launch
    • Effort: 3-4 weeks
    • Recommendation: Implement basic multi-jurisdiction tax engine (Phase 2 roadmap)
    • Risk: Legal/compliance liability without tax support
  2. Fee Type Taxonomy - HIGH

    • Priority: P1 - Required for MVP
    • Effort: 1 week
    • Recommendation: Implement full fee_type enum and standardize naming
  3. Quote Line Item Structure - HIGH

    • Priority: P1 - Required for transparency
    • Effort: 1 week
    • Recommendation: Implement detailed line item breakdown

SHORT-TERM (Post-MVP Critical)

  1. Avalara MyLodgeTax Integration

    • Priority: P1 - Scale requirement
    • Effort: 2-3 weeks
    • Recommendation: Replace manual tax rules with Avalara API
  2. Gap Night & Orphan Night Automation

    • Priority: P2 - Competitive feature
    • Effort: 2 weeks
    • Recommendation: Implement automated pricing adjustments
  3. Dynamic Pricing Integration (PriceLabs)

    • Priority: P2 - Revenue optimization
    • Effort: 3 weeks
    • Recommendation: Partner integration via API

MEDIUM-TERM (Scale & Optimize)

  1. Multi-Currency Support

    • Priority: P2 - International expansion
    • Effort: 3-4 weeks
    • Recommendation: Add currency conversion and FX rate management
  2. Promotion & Coupon System

    • Priority: P3 - Marketing feature
    • Effort: 2 weeks
    • Recommendation: Extend discount system

11.3 Risk Mitigation

Risk 1: Tax Compliance Liability

  • Mitigation: Implement basic tax engine in Phase 1 (manual jurisdiction mapping)
  • Long-term: Avalara integration for automation and accuracy

Risk 2: Competitive Feature Gap

  • Mitigation: Prioritize gap night automation and dynamic pricing integration
  • Long-term: Build ML-based rate optimization

Risk 3: Performance at Scale

  • Mitigation: Implement quote caching and rate materialization
  • Long-term: Distributed caching layer (Redis)

11.4 Success Metrics

Launch Readiness (MVP):

  • ✅ Quote generation <500ms (target <100ms)
  • ✅ Tax calculation for all US properties
  • ✅ Revenue split calculation accuracy 100%
  • ✅ Zero quote calculation errors

Post-Launch (3 months):

  • Quote generation <100ms (p95)
  • Tax accuracy 99.9% (validated vs. Avalara)
  • Dynamic pricing adoption >50% of properties
  • Revenue optimization +10% vs. static pricing

Scale Metrics (12 months):

  • Support 10,000+ properties
  • 100,000+ quotes generated/month
  • Multi-currency support (5+ currencies)
  • International tax compliance (3+ countries)

Appendix A: Schema DDL Summary

Complete DDL for all pricing domain tables is provided in Section 2 above. Key tables:

  1. rate_plans - Core pricing configuration
  2. rate_rules - Dynamic pricing modifiers
  3. fee_rules - Additional charges
  4. revenue_rules - Multi-party splits
  5. tax_jurisdictions - Geographic tax authorities
  6. tax_rules - Tax rate configurations
  7. space_tax_mappings - Property tax jurisdiction mapping
  8. quotes - Pricing snapshots
  9. quote_line_items - Detailed breakdown
  10. quote_daily_rates - Per-night rates
  11. quote_revenue_splits - Revenue distribution

Appendix B: Industry Research Sources

  1. Dynamic Pricing Platforms:

  2. Tax Compliance:

    • Avalara MyLodgeTax (https://avalara.com/mylodgetax) - Multi-jurisdiction automation
    • State-by-state lodging tax guides
    • Marketplace facilitator law tracker
  3. Payment Processing:

  4. Competitors:


Appendix C: Glossary

  • Base Rate: Default nightly rate before modifiers
  • Compound Mode: How multiple rules combine (additive, multiplicative, override)
  • Fee Rule: Additional charges beyond room rate
  • Jurisdiction: Geographic area with tax authority
  • LOS: Length of Stay
  • Minor Units: Currency smallest unit (cents for USD)
  • Orphan Night: Single unbookable night between bookings
  • Gap Night: Short period between bookings (2-3 nights)
  • Rate Plan: Complete pricing configuration for a property
  • Rate Rule: Conditional modifier to base rate (seasonal, LOS, etc.)
  • Revenue Split: How booking revenue is divided among parties
  • Taxable Base: Amount on which taxes are calculated
  • Tiered Pricing: Progressive rate structure based on value/quantity

Document End

This analysis represents a comprehensive deep-dive into the Pricing, Fees & Revenue Rules domain for The Villa Life platform, incorporating industry research, competitive analysis, and detailed technical specifications to address identified gaps and provide a clear implementation roadmap.