Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from copy import deepcopy | |
| from typing import Any | |
| CATALOG_SPECS: list[dict[str, Any]] = [ | |
| { | |
| "slug": "trinity-bellwoods-townhouse", | |
| "title": "Trinity Bellwoods Townhouse", | |
| "city": "Toronto", | |
| "country": "Canada", | |
| "neighborhood": "Trinity Bellwoods", | |
| "host_id": 2, | |
| "price_per_night": 268, | |
| "cleaning_fee": 46, | |
| "service_fee": 30, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.91, | |
| "review_count": 76, | |
| "description": "A polished townhouse close to parks, bakeries, and the best stretch of west-end Toronto wandering.", | |
| "amenities": ["Wifi", "Patio", "Dedicated workspace", "Self check-in", "Washer", "Kitchen"], | |
| "house_rules": ["No parties", "Quiet hours after 10 PM", "No smoking"], | |
| "image_theme": "city_townhouse", | |
| }, | |
| { | |
| "slug": "ossington-studio-flat", | |
| "title": "Ossington Studio Flat", | |
| "city": "Toronto", | |
| "country": "Canada", | |
| "neighborhood": "Ossington", | |
| "host_id": 2, | |
| "price_per_night": 194, | |
| "cleaning_fee": 34, | |
| "service_fee": 24, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.84, | |
| "review_count": 59, | |
| "description": "A compact and bright studio near Toronto's restaurant-lined west side with a calm work setup and late-night snack access.", | |
| "amenities": ["Wifi", "Air conditioning", "Kitchen", "Coffee bar", "Laundry"], | |
| "house_rules": ["No smoking", "No parties", "Please remove shoes indoors"], | |
| "image_theme": "city_studio", | |
| }, | |
| { | |
| "slug": "roncesvalles-carriage-house", | |
| "title": "Roncesvalles Carriage House", | |
| "city": "Toronto", | |
| "country": "Canada", | |
| "neighborhood": "Roncesvalles", | |
| "host_id": 2, | |
| "price_per_night": 236, | |
| "cleaning_fee": 40, | |
| "service_fee": 29, | |
| "bedrooms": 2, | |
| "beds": 3, | |
| "baths": 1.0, | |
| "max_guests": 4, | |
| "rating": 4.92, | |
| "review_count": 68, | |
| "description": "A tucked-away carriage house near the lake with soft light, leafy streets, and a neighborhood feel that rewards longer walks.", | |
| "amenities": ["Wifi", "Patio", "Kitchen", "Washer", "Crib", "Self check-in"], | |
| "house_rules": ["No parties", "No smoking", "Quiet hours after 10 PM"], | |
| "image_theme": "city_garden", | |
| }, | |
| { | |
| "slug": "gastown-brick-loft", | |
| "title": "Gastown Brick Loft", | |
| "city": "Vancouver", | |
| "country": "Canada", | |
| "neighborhood": "Gastown", | |
| "host_id": 4, | |
| "price_per_night": 254, | |
| "cleaning_fee": 42, | |
| "service_fee": 31, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.88, | |
| "review_count": 71, | |
| "description": "An industrial loft with brick walls, big windows, and easy access to downtown mornings and waterfront evenings.", | |
| "amenities": ["Wifi", "Dedicated workspace", "Elevator", "Kitchen", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Building quiet hours after 10 PM"], | |
| "image_theme": "city_loft", | |
| }, | |
| { | |
| "slug": "mount-pleasant-balcony-suite", | |
| "title": "Mount Pleasant Balcony Suite", | |
| "city": "Vancouver", | |
| "country": "Canada", | |
| "neighborhood": "Mount Pleasant", | |
| "host_id": 4, | |
| "price_per_night": 221, | |
| "cleaning_fee": 38, | |
| "service_fee": 28, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.86, | |
| "review_count": 64, | |
| "description": "A design-forward suite near breweries, bike routes, and some of the city's best coffee stops.", | |
| "amenities": ["Wifi", "Balcony", "Kitchen", "Bike storage", "Air conditioning"], | |
| "house_rules": ["No smoking", "No parties", "Please respect neighbors in common areas"], | |
| "image_theme": "city_apartment", | |
| }, | |
| { | |
| "slug": "north-shore-outlook-cabin", | |
| "title": "North Shore Outlook Cabin", | |
| "city": "North Vancouver", | |
| "country": "Canada", | |
| "neighborhood": "Lynn Valley", | |
| "host_id": 4, | |
| "price_per_night": 279, | |
| "cleaning_fee": 47, | |
| "service_fee": 33, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.94, | |
| "review_count": 83, | |
| "description": "A cedar-lined cabin feel just outside the city, with forested views and quick access to mountain trails.", | |
| "amenities": ["Wifi", "Mountain views", "Parking", "Kitchen", "Indoor fireplace"], | |
| "house_rules": ["No parties", "No smoking", "Please lock doors when leaving for hikes"], | |
| "image_theme": "forest_cabin", | |
| }, | |
| { | |
| "slug": "mile-end-atelier-flat", | |
| "title": "Mile End Atelier Flat", | |
| "city": "Montreal", | |
| "country": "Canada", | |
| "neighborhood": "Mile End", | |
| "host_id": 3, | |
| "price_per_night": 186, | |
| "cleaning_fee": 33, | |
| "service_fee": 22, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.87, | |
| "review_count": 61, | |
| "description": "A bright artist-style apartment near bagels, bookstores, and a steady rhythm of neighborhood cafes.", | |
| "amenities": ["Wifi", "Kitchen", "Balcony", "Washer", "Dedicated workspace"], | |
| "house_rules": ["No smoking", "No parties", "Please recycle glass and paper"], | |
| "image_theme": "city_artful", | |
| }, | |
| { | |
| "slug": "old-montreal-stone-suite", | |
| "title": "Old Montreal Stone Suite", | |
| "city": "Montreal", | |
| "country": "Canada", | |
| "neighborhood": "Old Montreal", | |
| "host_id": 3, | |
| "price_per_night": 242, | |
| "cleaning_fee": 39, | |
| "service_fee": 28, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.83, | |
| "review_count": 57, | |
| "description": "A stone-walled suite close to river walks, galleries, and late dinners in the historic core.", | |
| "amenities": ["Wifi", "Air conditioning", "Kitchen", "Elevator", "Coffee bar"], | |
| "house_rules": ["No smoking", "Quiet hours after 10 PM", "No parties"], | |
| "image_theme": "city_suite", | |
| }, | |
| { | |
| "slug": "griffintown-modern-loft", | |
| "title": "Griffintown Modern Loft", | |
| "city": "Montreal", | |
| "country": "Canada", | |
| "neighborhood": "Griffintown", | |
| "host_id": 3, | |
| "price_per_night": 212, | |
| "cleaning_fee": 35, | |
| "service_fee": 26, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.81, | |
| "review_count": 49, | |
| "description": "A clean-lined loft near the canal with bike paths, gym access, and a polished city base feel.", | |
| "amenities": ["Wifi", "Gym", "Kitchen", "Air conditioning", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Building quiet hours after 10 PM"], | |
| "image_theme": "city_modern", | |
| }, | |
| { | |
| "slug": "west-queen-west-gallery-flat", | |
| "title": "West Queen West Gallery Flat", | |
| "city": "Toronto", | |
| "country": "Canada", | |
| "neighborhood": "West Queen West", | |
| "host_id": 2, | |
| "price_per_night": 228, | |
| "cleaning_fee": 37, | |
| "service_fee": 28, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.9, | |
| "review_count": 73, | |
| "description": "A gallery-inspired one-bedroom near independent shops, cocktail bars, and easy streetcar routes.", | |
| "amenities": ["Wifi", "Kitchen", "Dedicated workspace", "Air conditioning", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Please respect quiet hours after 10 PM"], | |
| "image_theme": "city_artful", | |
| }, | |
| { | |
| "slug": "queen-west-penthouse-nook", | |
| "title": "Queen West Penthouse Nook", | |
| "city": "Toronto", | |
| "country": "Canada", | |
| "neighborhood": "Queen West", | |
| "host_id": 2, | |
| "price_per_night": 312, | |
| "cleaning_fee": 52, | |
| "service_fee": 37, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 2.0, | |
| "max_guests": 4, | |
| "rating": 4.95, | |
| "review_count": 88, | |
| "description": "A top-floor retreat with skyline views, strong natural light, and room to settle into longer city stays.", | |
| "amenities": ["Wifi", "Balcony", "Dedicated workspace", "Kitchen", "Air conditioning", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Quiet hours after 10 PM"], | |
| "image_theme": "city_penthouse", | |
| }, | |
| { | |
| "slug": "tofino-cedar-surf-cabin", | |
| "title": "Tofino Cedar Surf Cabin", | |
| "city": "Tofino", | |
| "country": "Canada", | |
| "neighborhood": "Chesterman Beach", | |
| "host_id": 4, | |
| "price_per_night": 341, | |
| "cleaning_fee": 58, | |
| "service_fee": 40, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.0, | |
| "max_guests": 4, | |
| "rating": 4.97, | |
| "review_count": 105, | |
| "description": "A cedar hideaway near the beach with surf-rinse practicality, quiet interiors, and a proper storm-watching setup.", | |
| "amenities": ["Wifi", "Hot tub", "Outdoor shower", "Parking", "Kitchen", "Indoor fireplace"], | |
| "house_rules": ["No smoking", "No parties", "Please rinse sand before coming inside"], | |
| "image_theme": "coastal_cabin", | |
| }, | |
| { | |
| "slug": "tofino-harbor-house", | |
| "title": "Tofino Harbor House", | |
| "city": "Tofino", | |
| "country": "Canada", | |
| "neighborhood": "Town Center", | |
| "host_id": 4, | |
| "price_per_night": 298, | |
| "cleaning_fee": 52, | |
| "service_fee": 35, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.89, | |
| "review_count": 72, | |
| "description": "A harbor-adjacent stay for easy food runs, slow mornings, and nights that still feel close to the water.", | |
| "amenities": ["Wifi", "Kitchen", "Parking", "Washer", "Balcony"], | |
| "house_rules": ["No smoking", "No parties", "Quiet deck use after 10 PM"], | |
| "image_theme": "coastal_house", | |
| }, | |
| { | |
| "slug": "whistler-peak-chalet", | |
| "title": "Whistler Peak Chalet", | |
| "city": "Whistler", | |
| "country": "Canada", | |
| "neighborhood": "Creekside", | |
| "host_id": 4, | |
| "price_per_night": 389, | |
| "cleaning_fee": 62, | |
| "service_fee": 45, | |
| "bedrooms": 3, | |
| "beds": 4, | |
| "baths": 2.0, | |
| "max_guests": 6, | |
| "rating": 4.96, | |
| "review_count": 111, | |
| "description": "A mountain chalet set up for ski weekends, summer trails, and long dinners after a day outside.", | |
| "amenities": ["Wifi", "Hot tub", "Indoor fireplace", "Parking", "Ski storage", "Kitchen"], | |
| "house_rules": ["No smoking", "No parties", "Please store outdoor gear in the entry area"], | |
| "image_theme": "mountain_chalet", | |
| }, | |
| { | |
| "slug": "whistler-village-loft", | |
| "title": "Whistler Village Loft", | |
| "city": "Whistler", | |
| "country": "Canada", | |
| "neighborhood": "Whistler Village", | |
| "host_id": 4, | |
| "price_per_night": 324, | |
| "cleaning_fee": 49, | |
| "service_fee": 39, | |
| "bedrooms": 2, | |
| "beds": 3, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.9, | |
| "review_count": 85, | |
| "description": "A warm loft close to lifts, coffee stops, and the easy convenience of village weekends without a car.", | |
| "amenities": ["Wifi", "Kitchen", "Ski storage", "Indoor fireplace", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Please keep ski boots in the entry tray"], | |
| "image_theme": "mountain_loft", | |
| }, | |
| { | |
| "slug": "quebec-old-port-flat", | |
| "title": "Quebec Old Port Flat", | |
| "city": "Quebec City", | |
| "country": "Canada", | |
| "neighborhood": "Old Port", | |
| "host_id": 3, | |
| "price_per_night": 214, | |
| "cleaning_fee": 36, | |
| "service_fee": 25, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.88, | |
| "review_count": 66, | |
| "description": "A stone-and-light apartment for romantic old-city walks, bakeries, and quieter mornings before the crowds.", | |
| "amenities": ["Wifi", "Kitchen", "Coffee bar", "Air conditioning", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Respect building quiet hours after 10 PM"], | |
| "image_theme": "city_historic", | |
| }, | |
| { | |
| "slug": "quebec-hillside-suite", | |
| "title": "Quebec Hillside Suite", | |
| "city": "Quebec City", | |
| "country": "Canada", | |
| "neighborhood": "Montcalm", | |
| "host_id": 3, | |
| "price_per_night": 198, | |
| "cleaning_fee": 32, | |
| "service_fee": 22, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.82, | |
| "review_count": 44, | |
| "description": "A calm suite above the old city with leafy streets, museum access, and a lighter residential feel.", | |
| "amenities": ["Wifi", "Dedicated workspace", "Kitchen", "Washer", "Air conditioning"], | |
| "house_rules": ["No smoking", "No parties", "Please remove shoes indoors"], | |
| "image_theme": "city_suite", | |
| }, | |
| { | |
| "slug": "hudson-valley-orchard-house", | |
| "title": "Hudson Valley Orchard House", | |
| "city": "Hudson Valley", | |
| "country": "United States", | |
| "neighborhood": "Gardiner", | |
| "host_id": 3, | |
| "price_per_night": 356, | |
| "cleaning_fee": 59, | |
| "service_fee": 42, | |
| "bedrooms": 3, | |
| "beds": 4, | |
| "baths": 2.0, | |
| "max_guests": 6, | |
| "rating": 4.95, | |
| "review_count": 96, | |
| "description": "A quiet orchard-side house for firepit evenings, hiking weekends, and long-table meals with friends.", | |
| "amenities": ["Wifi", "Hot tub", "Fire pit", "Kitchen", "Parking", "Indoor fireplace"], | |
| "house_rules": ["No smoking", "No parties", "Please close gates after entering the property"], | |
| "image_theme": "country_house", | |
| }, | |
| { | |
| "slug": "catskills-ridge-cabin", | |
| "title": "Catskills Ridge Cabin", | |
| "city": "Catskills", | |
| "country": "United States", | |
| "neighborhood": "Phoenicia", | |
| "host_id": 3, | |
| "price_per_night": 334, | |
| "cleaning_fee": 54, | |
| "service_fee": 39, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.93, | |
| "review_count": 87, | |
| "description": "A dark-wood cabin with ridge views, a deck built for coffee, and enough quiet to feel fully reset.", | |
| "amenities": ["Wifi", "Mountain views", "Hot tub", "Indoor fireplace", "Kitchen", "Parking"], | |
| "house_rules": ["No smoking", "No parties", "Please secure outdoor cushions overnight"], | |
| "image_theme": "forest_cabin", | |
| }, | |
| { | |
| "slug": "palm-springs-sun-court", | |
| "title": "Palm Springs Sun Court", | |
| "city": "Palm Springs", | |
| "country": "United States", | |
| "neighborhood": "Movie Colony", | |
| "host_id": 3, | |
| "price_per_night": 344, | |
| "cleaning_fee": 56, | |
| "service_fee": 41, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 2.0, | |
| "max_guests": 4, | |
| "rating": 4.94, | |
| "review_count": 92, | |
| "description": "A bright desert home with a pool, citrus courtyard, and the easy glamour Palm Springs does best.", | |
| "amenities": ["Wifi", "Pool", "Outdoor dining", "Kitchen", "Parking", "Air conditioning"], | |
| "house_rules": ["No smoking", "No parties", "Outdoor music off after 9 PM"], | |
| "image_theme": "desert_house", | |
| }, | |
| { | |
| "slug": "palm-springs-courtyard-casita", | |
| "title": "Palm Springs Courtyard Casita", | |
| "city": "Palm Springs", | |
| "country": "United States", | |
| "neighborhood": "Warm Sands", | |
| "host_id": 3, | |
| "price_per_night": 267, | |
| "cleaning_fee": 43, | |
| "service_fee": 31, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.9, | |
| "review_count": 74, | |
| "description": "A smaller desert stay with a private courtyard, soft pink sunsets, and a great base for pool days and dinner out.", | |
| "amenities": ["Wifi", "Pool", "Patio", "Air conditioning", "Coffee bar"], | |
| "house_rules": ["No smoking", "No parties", "Please keep gates closed"], | |
| "image_theme": "desert_casita", | |
| }, | |
| { | |
| "slug": "brooklyn-brownstone-floor", | |
| "title": "Brooklyn Brownstone Floor", | |
| "city": "New York", | |
| "country": "United States", | |
| "neighborhood": "Fort Greene", | |
| "host_id": 2, | |
| "price_per_night": 362, | |
| "cleaning_fee": 58, | |
| "service_fee": 44, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.91, | |
| "review_count": 90, | |
| "description": "A full brownstone floor with high ceilings, a leafy street, and quick subway access into the city.", | |
| "amenities": ["Wifi", "Kitchen", "Dedicated workspace", "Air conditioning", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Please be mindful on the stoop late at night"], | |
| "image_theme": "city_brownstone", | |
| }, | |
| { | |
| "slug": "hudson-loft-retreat", | |
| "title": "Hudson Loft Retreat", | |
| "city": "Hudson", | |
| "country": "United States", | |
| "neighborhood": "Warren Street", | |
| "host_id": 3, | |
| "price_per_night": 246, | |
| "cleaning_fee": 39, | |
| "service_fee": 28, | |
| "bedrooms": 1, | |
| "beds": 1, | |
| "baths": 1.0, | |
| "max_guests": 2, | |
| "rating": 4.85, | |
| "review_count": 53, | |
| "description": "A relaxed loft near antiques, coffee, and train access, perfect for a quick upstate reset without a complicated plan.", | |
| "amenities": ["Wifi", "Kitchen", "Coffee bar", "Dedicated workspace", "Washer"], | |
| "house_rules": ["No smoking", "No parties", "Quiet hours after 10 PM"], | |
| "image_theme": "city_loft", | |
| }, | |
| { | |
| "slug": "county-vineyard-cottage", | |
| "title": "County Vineyard Cottage", | |
| "city": "Prince Edward County", | |
| "country": "Canada", | |
| "neighborhood": "Bloomfield", | |
| "host_id": 4, | |
| "price_per_night": 276, | |
| "cleaning_fee": 48, | |
| "service_fee": 33, | |
| "bedrooms": 2, | |
| "beds": 2, | |
| "baths": 1.0, | |
| "max_guests": 4, | |
| "rating": 4.9, | |
| "review_count": 69, | |
| "description": "A vineyard-adjacent cottage with easy county access, a generous deck, and all the ingredients for a slower weekend.", | |
| "amenities": ["Wifi", "Patio", "BBQ", "Kitchen", "Parking", "Fire pit"], | |
| "house_rules": ["No smoking", "No parties", "Please keep outdoor noise low after 10 PM"], | |
| "image_theme": "country_cottage", | |
| }, | |
| { | |
| "slug": "county-lakeview-bungalow", | |
| "title": "County Lakeview Bungalow", | |
| "city": "Prince Edward County", | |
| "country": "Canada", | |
| "neighborhood": "Wellington", | |
| "host_id": 4, | |
| "price_per_night": 288, | |
| "cleaning_fee": 50, | |
| "service_fee": 34, | |
| "bedrooms": 2, | |
| "beds": 3, | |
| "baths": 1.5, | |
| "max_guests": 4, | |
| "rating": 4.88, | |
| "review_count": 63, | |
| "description": "A calm bungalow close to water and county drives, with room for a friend weekend and unhurried mornings.", | |
| "amenities": ["Wifi", "Lake views", "Kitchen", "Parking", "Washer", "Patio"], | |
| "house_rules": ["No smoking", "No parties", "Please rinse sandy gear before entering"], | |
| "image_theme": "lake_house", | |
| }, | |
| ] | |
| IMAGE_THEMES: dict[str, list[dict[str, str]]] = { | |
| "city_townhouse": [ | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Townhouse living room"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Townhouse interior with large windows"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining area with warm wood tones"}, | |
| ], | |
| "city_studio": [ | |
| {"url": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bright studio apartment"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cozy bedroom corner"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Compact kitchen"}, | |
| ], | |
| "city_garden": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Garden-facing house exterior"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm living room with timber details"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cozy seating area by the window"}, | |
| ], | |
| "city_loft": [ | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Loft interior with high ceilings"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Open kitchen in loft space"}, | |
| {"url": "https://images.unsplash.com/photo-1448630360428-65456885c650?auto=format&fit=crop&w=1400&q=80", "alt_text": "Textured loft lounge area"}, | |
| ], | |
| "city_apartment": [ | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Apartment living room"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Apartment bedroom"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Window-side dining nook"}, | |
| ], | |
| "forest_cabin": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cabin exterior in the trees"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cabin living room with wood finishes"}, | |
| {"url": "https://images.unsplash.com/photo-1505692952047-1a78307da8f2?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm bedroom with forest feel"}, | |
| ], | |
| "city_artful": [ | |
| {"url": "https://images.unsplash.com/photo-1493809842364-78817add7ffb?auto=format&fit=crop&w=1400&q=80", "alt_text": "Artful apartment interior"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Reading corner by the window"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm textured living room"}, | |
| ], | |
| "city_suite": [ | |
| {"url": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?auto=format&fit=crop&w=1400&q=80", "alt_text": "Suite with clean lines"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Compact kitchen with warm wood"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bedroom with soft daylight"}, | |
| ], | |
| "city_modern": [ | |
| {"url": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?auto=format&fit=crop&w=1400&q=80", "alt_text": "Modern loft interior"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining area in a modern suite"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Open-plan kitchen"}, | |
| ], | |
| "city_penthouse": [ | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Penthouse living room"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining space with a view"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bedroom with city light"}, | |
| ], | |
| "coastal_cabin": [ | |
| {"url": "https://images.unsplash.com/photo-1505692952047-1a78307da8f2?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cedar cabin by the coast"}, | |
| {"url": "https://images.unsplash.com/photo-1448630360428-65456885c650?auto=format&fit=crop&w=1400&q=80", "alt_text": "Textured coastal interior"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm cabin lounge"}, | |
| ], | |
| "coastal_house": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "House near the water"}, | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Open coastal living room"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Soft bedroom overlooking the yard"}, | |
| ], | |
| "mountain_chalet": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Mountain chalet exterior"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Chalet living room with beams"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining area in mountain home"}, | |
| ], | |
| "mountain_loft": [ | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Loft with mountain textures"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining nook in loft"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bedroom with layered neutrals"}, | |
| ], | |
| "city_historic": [ | |
| {"url": "https://images.unsplash.com/photo-1493809842364-78817add7ffb?auto=format&fit=crop&w=1400&q=80", "alt_text": "Historic apartment with books and texture"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining space in old-city apartment"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Soft lounge corner"}, | |
| ], | |
| "country_house": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Country house exterior"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Long-table dining room"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cozy firelit seating area"}, | |
| ], | |
| "desert_house": [ | |
| {"url": "https://images.unsplash.com/photo-1505692952047-1a78307da8f2?auto=format&fit=crop&w=1400&q=80", "alt_text": "Desert house exterior"}, | |
| {"url": "https://images.unsplash.com/photo-1448630360428-65456885c650?auto=format&fit=crop&w=1400&q=80", "alt_text": "Minimal desert interior"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bright bedroom with desert palette"}, | |
| ], | |
| "desert_casita": [ | |
| {"url": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?auto=format&fit=crop&w=1400&q=80", "alt_text": "Casita interior"}, | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Courtyard-adjacent lounge area"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Desert bedroom corner"}, | |
| ], | |
| "city_brownstone": [ | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Brownstone living room"}, | |
| {"url": "https://images.unsplash.com/photo-1505693431207-565af06f8d4b?auto=format&fit=crop&w=1400&q=80", "alt_text": "Dining room in brownstone"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Brownstone bedroom"}, | |
| ], | |
| "country_cottage": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Country cottage exterior"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm cottage lounge"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cottage kitchen"}, | |
| ], | |
| "lake_house": [ | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Lakeside bungalow"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Living room with lake-house feel"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cozy sitting room with soft light"}, | |
| ], | |
| } | |
| COVER_IMAGE_POOL: list[dict[str, str]] = [ | |
| {"url": "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1400&q=80", "alt_text": "Warm living room with layered seating"}, | |
| {"url": "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1400&q=80", "alt_text": "Sunlit open-plan home"}, | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Welcoming home exterior with a private deck"}, | |
| {"url": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?auto=format&fit=crop&w=1400&q=80", "alt_text": "Polished apartment interior"}, | |
| {"url": "https://images.unsplash.com/photo-1464890100898-a385f744067f?auto=format&fit=crop&w=1400&q=80", "alt_text": "Bedroom corner with soft daylight"}, | |
| {"url": "https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=1400&q=80", "alt_text": "Compact kitchen with warm cabinetry"}, | |
| {"url": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=1400&q=80", "alt_text": "Exterior of a modern getaway home"}, | |
| {"url": "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?auto=format&fit=crop&w=1400&q=80", "alt_text": "Wood-lined lounge with cabin textures"}, | |
| {"url": "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=1400&q=80", "alt_text": "Cozy sitting area by a window"}, | |
| {"url": "https://images.unsplash.com/photo-1448630360428-65456885c650?auto=format&fit=crop&w=1400&q=80", "alt_text": "Minimal interior with warm textures"}, | |
| {"url": "https://images.unsplash.com/photo-1493809842364-78817add7ffb?auto=format&fit=crop&w=1400&q=80", "alt_text": "Artful apartment with books and plants"}, | |
| {"url": "https://images.unsplash.com/photo-1505692952047-1a78307da8f2?auto=format&fit=crop&w=1400&q=80", "alt_text": "Retreat-style exterior at golden hour"}, | |
| ] | |
| REVIEW_SNIPPETS = [ | |
| "Beautifully set up and even better in person.", | |
| "The neighborhood was easy to settle into and the home felt calm right away.", | |
| "Great natural light, smooth arrival, and the kind of place we would happily book again.", | |
| "Thoughtful details throughout and a very comfortable base for a few days away.", | |
| ] | |
| def _blocked_range(index: int) -> list[dict[str, str]]: | |
| month = 4 + (index % 4) | |
| start_day = 4 + (index * 3 % 18) | |
| end_day = start_day + 2 | |
| return [{"start": f"2026-{month:02d}-{start_day:02d}", "end": f"2026-{month:02d}-{end_day:02d}"}] | |
| def _build_images(theme: str, variant_seed: int = 0) -> list[dict[str, Any]]: | |
| image_set = IMAGE_THEMES[theme] | |
| offset = variant_seed % len(image_set) | |
| rotated_set = image_set[offset:] + image_set[:offset] | |
| cover_image = COVER_IMAGE_POOL[variant_seed % len(COVER_IMAGE_POOL)] | |
| ordered_images = [cover_image] + [image for image in rotated_set if image["url"] != cover_image["url"]] | |
| return [ | |
| { | |
| "url": image["url"], | |
| "alt_text": image["alt_text"], | |
| "display_order": order, | |
| } | |
| for order, image in enumerate(ordered_images[:3]) | |
| ] | |
| def _build_review(review_id: int, listing_id: int, user_id: int, rating: float, title: str, snippet: str) -> dict[str, Any]: | |
| return { | |
| "id": review_id, | |
| "listing_id": listing_id, | |
| "user_id": user_id, | |
| "rating": rating, | |
| "comment": f"{snippet} {title} made the trip feel easy.", | |
| "created_at": f"2026-02-{(review_id % 20) + 1:02d}T1{review_id % 10}:20:00", | |
| } | |
| def extend_seed_data(seed_data: dict[str, Any]) -> dict[str, Any]: | |
| if len(seed_data.get("listings", [])) >= 30: | |
| return seed_data | |
| expanded = deepcopy(seed_data) | |
| next_listing_id = max(item["id"] for item in expanded["listings"]) + 1 | |
| next_review_id = max(item["id"] for item in expanded["reviews"]) + 1 | |
| for index, spec in enumerate(CATALOG_SPECS): | |
| listing_id = next_listing_id + index | |
| listing_payload = { | |
| "id": listing_id, | |
| "slug": spec["slug"], | |
| "host_id": spec["host_id"], | |
| "title": spec["title"], | |
| "city": spec["city"], | |
| "country": spec["country"], | |
| "neighborhood": spec["neighborhood"], | |
| "price_per_night": spec["price_per_night"], | |
| "cleaning_fee": spec["cleaning_fee"], | |
| "service_fee": spec["service_fee"], | |
| "bedrooms": spec["bedrooms"], | |
| "beds": spec["beds"], | |
| "baths": spec["baths"], | |
| "max_guests": spec["max_guests"], | |
| "rating": spec["rating"], | |
| "review_count": spec["review_count"], | |
| "description": spec["description"], | |
| "amenities": spec["amenities"], | |
| "house_rules": spec["house_rules"], | |
| "blocked_ranges": _blocked_range(index), | |
| "images": _build_images(spec["image_theme"], index), | |
| } | |
| expanded["listings"].append(listing_payload) | |
| expanded["reviews"].append( | |
| _build_review( | |
| review_id=next_review_id, | |
| listing_id=listing_id, | |
| user_id=1 if index % 2 == 0 else 5, | |
| rating=round(min(spec["rating"], 5.0), 1), | |
| title=spec["title"], | |
| snippet=REVIEW_SNIPPETS[index % len(REVIEW_SNIPPETS)], | |
| ) | |
| ) | |
| next_review_id += 1 | |
| expanded["reviews"].append( | |
| _build_review( | |
| review_id=next_review_id, | |
| listing_id=listing_id, | |
| user_id=5 if index % 2 == 0 else 1, | |
| rating=round(min(spec["rating"] - 0.1, 5.0), 1), | |
| title=spec["title"], | |
| snippet=REVIEW_SNIPPETS[(index + 1) % len(REVIEW_SNIPPETS)], | |
| ) | |
| ) | |
| next_review_id += 1 | |
| return expanded | |