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
- 
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
 
- 
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
 
- 
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
- Current Specification Analysis
- Comprehensive Schema Design
- Tax Calculation Engine Specification
- Fee Type Taxonomy
- Rule Evaluation Algorithm
- Quote Line Items Structure
- Revenue Splitting Patterns
- Gap Analysis vs Competitors
- 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:
- Single RatePlan per Space ("Standard Rate")
- Flat nightly rate + optional seasonal multiplier
- One cleaning fee (fixed amount)
- Optional service fee (percent)
- Fixed revenue split: Owner 80%, TVL 20%
- 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 Type | Typical Range | Basis | Refundable | Taxable | Description | 
|---|---|---|---|---|---|
| Cleaning Fee | $50-$300 | Per stay | Sometimes | Varies by jurisdiction | One-time cleaning after checkout | 
| Pet Fee | $10-$50/night or $50-$150/stay | Per night or per stay | No | Yes | Additional cleaning for pets | 
| Extra Guest Fee | $10-$50/guest/night | Per guest per night | Yes | Yes | Charge for guests beyond base occupancy | 
| Resort Fee | $20-$100/night | Per night | No | Yes | Access to amenities (pool, gym, etc.) | 
| Linen Fee | $25-$75/stay | Per stay | No | Yes | Linen and towel service | 
| Hot Tub Fee | $25-$75/night or $100-$300/stay | Per night or per stay | No | Yes | Hot tub heating and maintenance | 
| Pool Heating Fee | $50-$150/night | Per night | No | Yes | Pool heating (seasonal) | 
| Damage Waiver | $5-$15/night or 5-10% of rental | Per night or percentage | No | Varies | Insurance alternative to security deposit | 
| Early Check-in Fee | $50-$150/occurrence | Per occurrence | Yes if not used | Yes | Check-in before standard time | 
| Late Checkout Fee | $50-$150/occurrence | Per occurrence | Yes if not used | Yes | Check-out after standard time | 
| Mid-Stay Clean Fee | $100-$200/clean | Per occurrence | Yes if not used | Yes | Additional cleaning during stay | 
Platform/Service Fees (Platform Revenue)
| Fee Type | Typical Range | Basis | Description | 
|---|---|---|---|
| Booking Fee | $5-$50 or 3-5% | Fixed or percentage | Platform booking service fee | 
| Service Fee | 3-15% | Percentage of subtotal | Platform service/technology fee | 
| Processing Fee | 2.5-3.5% | Percentage of total | Credit card/payment processing | 
| Channel Commission | 3-15% | Percentage | Channel/OTA commission (e.g., Airbnb 3%, VRBO 5-15%) | 
Pass-Through Fees (Remitted to Third Parties)
| Fee Type | Typical Range | Basis | Remitted To | Description | 
|---|---|---|---|---|
| State Tax | 0-10% | Percentage | State revenue dept | State transient occupancy tax | 
| County Tax | 0-6% | Percentage | County tax authority | County lodging tax | 
| City Tax | 0-8% | Percentage | City tax authority | Municipal hotel/lodging tax | 
| Tourism Tax | 1-6% | Percentage | Tourism authority | Tourism development/marketing | 
| Occupancy Tax | 5-15% | Percentage | Multiple authorities | General transient occupancy tax | 
Owner-Specific Fees
| Fee Type | Typical Range | Basis | Description | 
|---|---|---|---|
| Security Deposit | $200-$2,000 | Per stay | Refundable deposit for damages (held, not charged) | 
| Deposit/Prepayment | 25-50% of total | Percentage | Partial 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
| Feature | TVL (Current) | Guesty | Gap 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 | ✅ Automated | ❌ GAP | 
| Orphan Night Handling | ❌ Not specified | ✅ Automated | ❌ GAP | 
| 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 Calculation | ❌ NOT SPECIFIED | ✅ Integrated | ❌ CRITICAL GAP | 
| Fee Management | ⚠️ Basic types | ✅ Comprehensive | ⚠️ Need taxonomy | 
| Revenue Splits | ✅ Supported | ✅ Supported | ✅ At parity | 
| Promotions/Coupons | ❌ Not specified | ✅ Built-in | ❌ GAP | 
| Rate Calendar View | Not specified | ✅ Visual calendar | ⚠️ UI/UX gap | 
| Bulk Rate Updates | Not specified | ✅ Batch operations | ⚠️ Ops tooling gap | 
| Rate History/Audit | ⚠️ Basic audit | ✅ Full versioning | ⚠️ Need enhancement | 
8.2 TVL vs Hostaway Pricing Features
| Feature | TVL (Current) | Hostaway | Gap 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-ons | ❌ GAP | 
| Last-Minute Rules | ⚠️ Manual | ✅ Automated | ⚠️ Automation needed | 
| Derived Rates | ❌ Not specified | ✅ Supported | ❌ GAP | 
| Rate Templates | ❌ Not specified | ✅ Reusable templates | ❌ GAP | 
| Channel Mapping | ✅ Supported | ✅ Supported | ✅ At parity | 
| Tax Automation | ❌ NOT SPECIFIED | ✅ Integrated | ❌ CRITICAL GAP | 
| Fee Customization | ⚠️ Limited | ✅ Extensive | ⚠️ Need expansion | 
| Commission Tracking | ✅ Supported | ✅ Supported | ✅ At parity | 
| Rate Sync Speed | Not specified | ✅ Real-time | ⚠️ Performance spec needed | 
| Conflict Detection | Not 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:
- 
Multi-Tenancy Architecture - Competitors: Single-tenant or weak isolation
- TVL: True multi-tenant with org_id/account_id
- Advantage: Scales to white-label marketplaces
 
- 
Revenue Split Flexibility - Competitors: Fixed commission models
- TVL: Tiered, multi-party, conditional splits
- Advantage: Supports complex business models
 
- 
Rule-Based Extensibility - Competitors: Hard-coded pricing logic
- TVL: JSONB conditions, priority system
- Advantage: No code deploys for new rules
 
- 
Immutable Quote Snapshots - Competitors: Quotes update with rate changes
- TVL: Immutable pricing guarantee
- Advantage: Audit compliance, price guarantees
 
- 
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_rulestables
- ✅ Implement quotesandquote_line_itemstables
- ✅ 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
| Operation | Target | Max 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 Import | 1000 rates/sec | 100 rates/sec | 
10.3 Data Retention & Archival
| Data Type | Retention | Archival Policy | 
|---|---|---|
| Rate Plans | Indefinite | Soft delete only | 
| Rate Rules | Indefinite | Historical versioning | 
| Quotes | 7 years | Archive to cold storage after 2 years | 
| Quote Line Items | 7 years | Archive with parent quote | 
| Tax Calculations | 7 years | Regulatory compliance requirement | 
| Revenue Splits | 10 years | Financial audit requirement | 
| Audit Events | 10 years | Compliance requirement | 
10.4 Security & Compliance
Access Control
- Rate plan modifications require pricing.editpermission
- Quote generation requires booking.createpermission
- Tax configuration requires adminorfinance_adminrole
- 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)
- 
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
 
- 
Fee Type Taxonomy - HIGH - Priority: P1 - Required for MVP
- Effort: 1 week
- Recommendation: Implement full fee_type enum and standardize naming
 
- 
Quote Line Item Structure - HIGH - Priority: P1 - Required for transparency
- Effort: 1 week
- Recommendation: Implement detailed line item breakdown
 
SHORT-TERM (Post-MVP Critical)
- 
Avalara MyLodgeTax Integration - Priority: P1 - Scale requirement
- Effort: 2-3 weeks
- Recommendation: Replace manual tax rules with Avalara API
 
- 
Gap Night & Orphan Night Automation - Priority: P2 - Competitive feature
- Effort: 2 weeks
- Recommendation: Implement automated pricing adjustments
 
- 
Dynamic Pricing Integration (PriceLabs) - Priority: P2 - Revenue optimization
- Effort: 3 weeks
- Recommendation: Partner integration via API
 
MEDIUM-TERM (Scale & Optimize)
- 
Multi-Currency Support - Priority: P2 - International expansion
- Effort: 3-4 weeks
- Recommendation: Add currency conversion and FX rate management
 
- 
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:
- rate_plans- Core pricing configuration
- rate_rules- Dynamic pricing modifiers
- fee_rules- Additional charges
- revenue_rules- Multi-party splits
- tax_jurisdictions- Geographic tax authorities
- tax_rules- Tax rate configurations
- space_tax_mappings- Property tax jurisdiction mapping
- quotes- Pricing snapshots
- quote_line_items- Detailed breakdown
- quote_daily_rates- Per-night rates
- quote_revenue_splits- Revenue distribution
Appendix B: Industry Research Sources
- 
Dynamic Pricing Platforms: - PriceLabs (https://pricelabs.co) - 35,000+ users, 300,000+ listings
- Wheelhouse (https://usewheelhouse.com) - Data-driven + rule-based hybrid
- Beyond Pricing (https://beyondpricing.com) - Real-time consumer demand data
 
- 
Tax Compliance: - Avalara MyLodgeTax (https://avalara.com/mylodgetax) - Multi-jurisdiction automation
- State-by-state lodging tax guides
- Marketplace facilitator law tracker
 
- 
Payment Processing: - Stripe Connect (https://stripe.com/connect) - Multi-party payouts
- Revenue splitting best practices
 
- 
Competitors: - Guesty (https://guesty.com) - PriceOptimizer ML tool
- Hostaway (https://hostaway.com) - Third-party dynamic pricing integration
 
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.