Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| user_role_context_handler.py - Handle different user roles and rental models | |
| Supports: Airbnb (host/guest), African rentals (landlord/renter/tenant) | |
| """ | |
| import logging | |
| from typing import Dict, Tuple, Optional | |
| from enum import Enum | |
| import re | |
| logger = logging.getLogger(__name__) | |
| class RentalModel(Enum): | |
| """Different rental models""" | |
| AIRBNB = "airbnb" # Short-stay, host/guest model | |
| AFRICAN_RENTAL = "african" # Long-term rent, landlord/tenant model | |
| ROOMMATE = "roommate" # Room sharing in existing space | |
| MIXED = "mixed" # Both types possible | |
| UNKNOWN = "unknown" | |
| class UserRole: | |
| """Handle different user roles across rental models""" | |
| # Airbnb roles | |
| AIRBNB_HOST = "airbnb_host" | |
| AIRBNB_GUEST = "airbnb_guest" | |
| # African rental roles | |
| LANDLORD = "landlord" | |
| RENTER = "renter" | |
| TENANT = "tenant" # Alias for renter | |
| # Roommate roles | |
| HOMEOWNER_SEEKING_ROOMMATE = "homeowner_seeking_roommate" # Has space, looking for roommate | |
| ROOMMATE_SEEKER = "roommate_seeker" # Looking for a room to share | |
| # Generic | |
| OWNER = "owner" | |
| BUYER = "buyer" | |
| SELLER = "seller" | |
| class UserRoleDetector: | |
| """Intelligently detect user role from context""" | |
| def __init__(self): | |
| # Keywords for role detection | |
| self.host_keywords = { | |
| "airbnb": ["host", "hosting", "list my property", "list my place", "rent out", "share"], | |
| "african": ["landlord", "owner", "property owner", "im renting out", "im listing"] | |
| } | |
| self.guest_keywords = { | |
| "airbnb": ["guest", "book", "looking for place", "need accommodation", "airbnb"], | |
| "african": ["renter", "tenant", "looking to rent", "seeking", "want to rent", "im looking for"] | |
| } | |
| self.buyer_keywords = ["buy", "purchase", "for sale", "selling", "acquire"] | |
| self.seller_keywords = ["sell", "selling", "sale", "list for sale"] | |
| # Roommate keywords | |
| self.homeowner_seeking_roommate_keywords = [ | |
| "looking for a roommate", "need a roommate", "seeking roommate", | |
| "want to share my", "have a spare room", "room available", | |
| "looking to share", "share my apartment", "share my house", | |
| "my place is too big", "extra room", "can share" | |
| ] | |
| self.roommate_seeker_keywords = [ | |
| "looking for a room", "seeking a room", "need a room", | |
| "looking for roommate", "want to share a place", "room for rent", | |
| "share accommodation", "shared apartment", "shared house", | |
| "need accommodation", "looking for a place to share" | |
| ] | |
| logger.info("π User Role Detector initialized") | |
| def detect_rental_model(self, user_message: str, location: str = None) -> RentalModel: | |
| """Detect which rental model user is in""" | |
| msg_lower = user_message.lower().strip() | |
| # Keywords indicating Airbnb model | |
| airbnb_indicators = ["airbnb", "short stay", "nightly", "daily", "vacation rental", "host"] | |
| # Keywords indicating African rental model | |
| african_indicators = ["landlord", "tenant", "renter", "monthly rent", "long term", "furnished room"] | |
| # Keywords indicating roommate model | |
| roommate_indicators = ["roommate", "share my", "spare room", "share apartment", "shared house", "share a place"] | |
| # Check for explicit indicators | |
| for indicator in roommate_indicators: | |
| if indicator in msg_lower: | |
| logger.info(f"ποΈ Detected roommate model: '{indicator}'") | |
| return RentalModel.ROOMMATE | |
| for indicator in airbnb_indicators: | |
| if indicator in msg_lower: | |
| logger.info(f"π¨ Detected Airbnb model: '{indicator}'") | |
| return RentalModel.AIRBNB | |
| for indicator in african_indicators: | |
| if indicator in msg_lower: | |
| logger.info(f"π’ Detected African rental model: '{indicator}'") | |
| return RentalModel.AFRICAN_RENTAL | |
| # Location-based inference (African locations more likely = African model) | |
| if location: | |
| african_countries = ["benin", "nigeria", "kenya", "ghana", "south africa", "uganda", "senegal"] | |
| if any(country in location.lower() for country in african_countries): | |
| logger.info(f"π African location detected: {location}") | |
| return RentalModel.AFRICAN_RENTAL | |
| # Default to mixed | |
| return RentalModel.MIXED | |
| def detect_user_role(self, user_message: str, rental_model: RentalModel = None) -> Tuple[str, float]: | |
| """ | |
| Detect user role from message | |
| Returns: (role, confidence) | |
| """ | |
| msg_lower = user_message.lower().strip() | |
| if rental_model is None: | |
| rental_model = self.detect_rental_model(user_message) | |
| # ==================== SELLER / LANDLORD ==================== | |
| # Check for explicit landlord/owner language | |
| landlord_explicit = ["im a landlord", "im the landlord", "i own", "i own this", "as a landlord"] | |
| for phrase in landlord_explicit: | |
| if phrase in msg_lower: | |
| logger.info(f"β Explicit landlord detected: '{phrase}'") | |
| return UserRole.LANDLORD, 0.99 | |
| # Check for listing/rental language | |
| if rental_model == RentalModel.AFRICAN_RENTAL: | |
| landlord_signals = [ | |
| "im listing", "list my", "im renting out", "property for rent", | |
| "available for rent", "i have a", "i own a" | |
| ] | |
| for signal in landlord_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π African landlord signal: '{signal}'") | |
| return UserRole.LANDLORD, 0.90 | |
| if rental_model == RentalModel.AIRBNB: | |
| host_signals = ["im hosting", "im a host", "list on airbnb", "airbnb host", "share my place"] | |
| for signal in host_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π¨ Airbnb host signal: '{signal}'") | |
| return UserRole.AIRBNB_HOST, 0.90 | |
| # ==================== BUYER / SELLER (SALE) ==================== | |
| # Explicit sale language | |
| seller_signals = ["im selling", "for sale", "sell my", "selling property", "list for sale"] | |
| for signal in seller_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π° Seller detected: '{signal}'") | |
| return UserRole.SELLER, 0.95 | |
| buyer_signals = ["want to buy", "looking to purchase", "im buying", "purchase property"] | |
| for signal in buyer_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π³ Buyer detected: '{signal}'") | |
| return UserRole.BUYER, 0.95 | |
| # ==================== RENTER / GUEST ==================== | |
| # Check for explicit renter language | |
| renter_explicit = ["im a tenant", "im a renter", "im looking to rent", "looking for a place to rent"] | |
| for phrase in renter_explicit: | |
| if phrase in msg_lower: | |
| logger.info(f"β Explicit renter/tenant detected: '{phrase}'") | |
| if rental_model == RentalModel.AFRICAN_RENTAL: | |
| return UserRole.TENANT, 0.99 | |
| else: | |
| return UserRole.AIRBNB_GUEST, 0.99 | |
| # ==================== ROOMMATE ROLES ==================== | |
| # Homeowner seeking roommate | |
| for keyword in self.homeowner_seeking_roommate_keywords: | |
| if keyword in msg_lower: | |
| logger.info(f"β Homeowner seeking roommate detected: '{keyword}'") | |
| return UserRole.HOMEOWNER_SEEKING_ROOMMATE, 0.90 | |
| # Roommate seeker | |
| for keyword in self.roommate_seeker_keywords: | |
| if keyword in msg_lower: | |
| logger.info(f"β Roommate seeker detected: '{keyword}'") | |
| return UserRole.ROOMMATE_SEEKER, 0.90 | |
| # Guest/renter signals | |
| if rental_model == RentalModel.AFRICAN_RENTAL: | |
| renter_signals = [ | |
| "looking for a", "need a", "seeking", "want to rent", | |
| "im looking for", "show me", "what do you have", "available rooms" | |
| ] | |
| for signal in renter_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π African renter signal: '{signal}'") | |
| return UserRole.RENTER, 0.80 | |
| if rental_model == RentalModel.AIRBNB: | |
| guest_signals = [ | |
| "looking for accommodation", "need a place", "book", | |
| "where can i stay", "available places", "show me listings" | |
| ] | |
| for signal in guest_signals: | |
| if signal in msg_lower: | |
| logger.info(f"π Airbnb guest signal: '{signal}'") | |
| return UserRole.AIRBNB_GUEST, 0.80 | |
| logger.warning(f"β οΈ Could not determine user role from: {user_message}") | |
| return None, 0.0 | |
| def validate_role_consistency(self, user_role: str, rental_model: RentalModel) -> bool: | |
| """Validate that role matches rental model""" | |
| valid_combinations = { | |
| RentalModel.AIRBNB: [UserRole.AIRBNB_HOST, UserRole.AIRBNB_GUEST], | |
| RentalModel.AFRICAN_RENTAL: [UserRole.LANDLORD, UserRole.RENTER, UserRole.TENANT], | |
| RentalModel.ROOMMATE: [UserRole.HOMEOWNER_SEEKING_ROOMMATE, UserRole.ROOMMATE_SEEKER], | |
| RentalModel.MIXED: [UserRole.LANDLORD, UserRole.RENTER, UserRole.TENANT, | |
| UserRole.AIRBNB_HOST, UserRole.AIRBNB_GUEST, | |
| UserRole.HOMEOWNER_SEEKING_ROOMMATE, UserRole.ROOMMATE_SEEKER], | |
| } | |
| valid = valid_combinations.get(rental_model, []) | |
| if user_role in valid: | |
| logger.info(f"β Role {user_role} valid for {rental_model.value}") | |
| return True | |
| logger.warning(f"β οΈ Role {user_role} may not match {rental_model.value}") | |
| return False | |
| class RoleBasedInferenceEngine: | |
| """Adapt inference based on user role and rental model""" | |
| def __init__(self): | |
| self.role_detector = UserRoleDetector() | |
| logger.info("π§ Role-based Inference Engine initialized") | |
| def infer_listing_type(self, state: Dict, user_message: str, rental_model: RentalModel = None) -> Tuple[str, float]: | |
| """ | |
| Infer listing type based on user role and rental model | |
| Returns: (listing_type, confidence) | |
| """ | |
| # Detect rental model | |
| if rental_model is None: | |
| rental_model = self.role_detector.detect_rental_model(user_message, state.get("location")) | |
| # Detect user role | |
| user_role, role_confidence = self.role_detector.detect_user_role(user_message, rental_model) | |
| logger.info(f"π Rental Model: {rental_model.value}") | |
| logger.info(f"π€ User Role: {user_role} (confidence: {role_confidence:.0%})") | |
| # Store in state for later use | |
| state["rental_model"] = rental_model.value | |
| state["user_role"] = user_role | |
| # ==================== AIRBNB MODEL ==================== | |
| if rental_model == RentalModel.AIRBNB: | |
| # Host listing = short-stay | |
| if user_role == UserRole.AIRBNB_HOST: | |
| logger.info("π Host β short-stay listing") | |
| return "short-stay", 0.98 | |
| # Guest searching = just needs to search | |
| if user_role == UserRole.AIRBNB_GUEST: | |
| logger.info("π Guest β searching for short-stay") | |
| return "short-stay", 0.95 | |
| # ==================== AFRICAN RENTAL MODEL ==================== | |
| elif rental_model == RentalModel.AFRICAN_RENTAL: | |
| # Landlord listing = rent listing | |
| if user_role in [UserRole.LANDLORD, UserRole.OWNER]: | |
| logger.info("π Landlord β rent listing") | |
| return "rent", 0.98 | |
| # Renter/tenant searching = rent listing | |
| if user_role in [UserRole.RENTER, UserRole.TENANT]: | |
| logger.info("π Tenant/Renter β searching for rent") | |
| return "rent", 0.95 | |
| # ==================== ROOMMATE MODEL ==================== | |
| elif rental_model == RentalModel.ROOMMATE: | |
| # Homeowner seeking roommate = roommate listing | |
| if user_role == UserRole.HOMEOWNER_SEEKING_ROOMMATE: | |
| logger.info("π Homeowner β roommate listing") | |
| return "roommate", 0.98 | |
| # Roommate seeker = searching roommate | |
| if user_role == UserRole.ROOMMATE_SEEKER: | |
| logger.info("π Roommate seeker β searching for roommate") | |
| return "roommate", 0.95 | |
| # ==================== SALE MODEL (both) ==================== | |
| if user_role == UserRole.SELLER: | |
| logger.info("π Seller β sale listing") | |
| return "sale", 0.98 | |
| if user_role == UserRole.BUYER: | |
| logger.info("π Buyer β searching for sale") | |
| return "sale", 0.95 | |
| # Fallback: check explicit listing_type | |
| explicit_type = state.get("listing_type") | |
| if explicit_type: | |
| logger.info(f"π Using explicit listing_type: {explicit_type}") | |
| return explicit_type, 0.85 | |
| logger.warning("β οΈ Could not infer listing_type, defaulting to rent") | |
| return "rent", 0.5 | |
| def adapt_field_extraction(self, state: Dict, user_message: str) -> Dict: | |
| """ | |
| Adapt field extraction based on user role and rental model | |
| """ | |
| rental_model = self.role_detector.detect_rental_model(user_message, state.get("location")) | |
| user_role, _ = self.role_detector.detect_user_role(user_message, rental_model) | |
| extraction_config = { | |
| "rental_model": rental_model.value, | |
| "user_role": user_role, | |
| "required_fields": [], | |
| "price_type_suggestions": [], | |
| "amenity_focus": [], | |
| "validation_rules": [] | |
| } | |
| # ==================== AIRBNB HOST ==================== | |
| if user_role == UserRole.AIRBNB_HOST: | |
| extraction_config["required_fields"] = [ | |
| "location", "bedrooms", "bathrooms", "price", "amenities" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["nightly", "daily", "weekly"] | |
| extraction_config["amenity_focus"] = ["wifi", "parking", "pool", "kitchen", "ac"] | |
| extraction_config["validation_rules"] = [ | |
| "price must be per night (nightly/daily)", | |
| "bedrooms minimum 1", | |
| "bathrooms can be shared" | |
| ] | |
| # ==================== AIRBNB GUEST ==================== | |
| elif user_role == UserRole.AIRBNB_GUEST: | |
| extraction_config["required_fields"] = ["location", "check_in", "check_out"] | |
| extraction_config["price_type_suggestions"] = ["nightly"] | |
| extraction_config["amenity_focus"] = ["wifi", "kitchen", "parking"] | |
| extraction_config["validation_rules"] = [ | |
| "check dates for availability", | |
| "show prices in nightly rates" | |
| ] | |
| # ==================== LANDLORD (African) ==================== | |
| elif user_role == UserRole.LANDLORD: | |
| extraction_config["required_fields"] = [ | |
| "location", "bedrooms", "bathrooms", "price", "price_type", "furnished" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["monthly", "yearly"] | |
| extraction_config["amenity_focus"] = [ | |
| "furnished", "kitchen", "water", "electricity", "security" | |
| ] | |
| extraction_config["validation_rules"] = [ | |
| "price must be monthly or yearly", | |
| "specify if furnished/unfurnished", | |
| "include utility info if available", | |
| "bedrooms and bathrooms required" | |
| ] | |
| # ==================== RENTER/TENANT (African) ==================== | |
| elif user_role in [UserRole.RENTER, UserRole.TENANT]: | |
| extraction_config["required_fields"] = [ | |
| "location", "budget", "bedrooms", "price_type" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["monthly", "yearly"] | |
| extraction_config["amenity_focus"] = [ | |
| "furnished", "security", "water", "electricity", "parking" | |
| ] | |
| extraction_config["validation_rules"] = [ | |
| "show monthly/yearly prices", | |
| "filter by budget", | |
| "highlight furnished options", | |
| "show security features" | |
| ] | |
| # ==================== HOMEOWNER SEEKING ROOMMATE ==================== | |
| elif user_role == UserRole.HOMEOWNER_SEEKING_ROOMMATE: | |
| extraction_config["required_fields"] = [ | |
| "location", "bedrooms_available", "bathrooms_available", "price", "price_type" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["monthly", "yearly"] | |
| extraction_config["amenity_focus"] = [ | |
| "furnished", "utilities_included", "kitchen_access", "laundry", | |
| "internet", "parking", "living_room_access" | |
| ] | |
| extraction_config["validation_rules"] = [ | |
| "price must be monthly or yearly", | |
| "specify which rooms are available", | |
| "describe house/apartment condition", | |
| "list utilities included", | |
| "mention house rules" | |
| ] | |
| # ==================== ROOMMATE SEEKER ==================== | |
| elif user_role == UserRole.ROOMMATE_SEEKER: | |
| extraction_config["required_fields"] = [ | |
| "location", "budget", "move_in_date" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["monthly", "yearly"] | |
| extraction_config["amenity_focus"] = [ | |
| "furnished", "utilities_included", "kitchen_access", "internet", | |
| "parking", "proximity_to_work" | |
| ] | |
| extraction_config["validation_rules"] = [ | |
| "show monthly/yearly prices", | |
| "filter by budget", | |
| "check roommate compatibility", | |
| "show lease terms" | |
| ] | |
| # ==================== SELLER ==================== | |
| elif user_role == UserRole.SELLER: | |
| extraction_config["required_fields"] = [ | |
| "location", "bedrooms", "bathrooms", "price", "property_type" | |
| ] | |
| extraction_config["price_type_suggestions"] = ["fixed"] | |
| extraction_config["amenity_focus"] = ["land size", "property type", "condition"] | |
| extraction_config["validation_rules"] = [ | |
| "price is total sale price", | |
| "property type required (apartment, house, etc)", | |
| "include land/property size if known" | |
| ] | |
| # ==================== BUYER ==================== | |
| elif user_role == UserRole.BUYER: | |
| extraction_config["required_fields"] = [ | |
| "location", "budget", "bedrooms", "property_type" | |
| ] | |
| extraction_config["price_type_suggestions"] = [] | |
| extraction_config["amenity_focus"] = ["property type", "land size", "condition"] | |
| extraction_config["validation_rules"] = [ | |
| "show total sale prices", | |
| "filter by budget range", | |
| "group by property type" | |
| ] | |
| logger.info(f"β Extraction config adapted for {user_role}") | |
| return extraction_config | |
| def get_role_context_prompt(self, user_role: str, rental_model: str) -> str: | |
| """Get AI prompt context based on role""" | |
| prompts = { | |
| UserRole.AIRBNB_HOST: """ | |
| You are helping an Airbnb host list their property. | |
| - Focus on: short-stay rental features, nightly rates, guest amenities | |
| - Price type: nightly/daily/weekly | |
| - Emphasize: WiFi, kitchen, parking, cleanliness | |
| """, | |
| UserRole.AIRBNB_GUEST: """ | |
| You are helping someone find an Airbnb accommodation. | |
| - Focus on: guest experience, amenities, location convenience | |
| - Price type: show nightly rates | |
| - Emphasize: cleanliness, safety, host responsiveness | |
| """, | |
| UserRole.LANDLORD: """ | |
| You are helping an African landlord/property owner list a rental. | |
| - Focus on: long-term rental (monthly/yearly), tenant features, property durability | |
| - Price type: monthly or yearly | |
| - Emphasize: furnished/unfurnished, utilities, security, maintenance | |
| - Include: lease terms, deposit requirements | |
| """, | |
| UserRole.RENTER: """ | |
| You are helping a tenant/renter find an apartment or room. | |
| - Focus on: long-term rental suitability, affordability, amenities for living | |
| - Price type: monthly or yearly budget | |
| - Emphasize: security, utilities included, furnished options, commute | |
| - Ask about: move-in date, lease length, budget | |
| """, | |
| UserRole.TENANT: """ | |
| You are helping a tenant/renter find an apartment or room. | |
| - Focus on: long-term rental suitability, affordability, amenities for living | |
| - Price type: monthly or yearly budget | |
| - Emphasize: security, utilities included, furnished options, commute | |
| - Ask about: move-in date, lease length, budget | |
| """, | |
| UserRole.SELLER: """ | |
| You are helping someone sell a property. | |
| - Focus on: property value, unique features, condition, potential | |
| - Price type: total sale price | |
| - Emphasize: location, size, renovations, investment potential | |
| - Include: property history, legal documents status | |
| """, | |
| UserRole.BUYER: """ | |
| You are helping someone find and purchase a property. | |
| - Focus on: property value, investment potential, location | |
| - Price type: show total purchase price | |
| - Emphasize: property condition, neighborhood, future value | |
| - Include: financing options, inspection recommendations | |
| """, | |
| UserRole.HOMEOWNER_SEEKING_ROOMMATE: """ | |
| You are helping someone find a roommate to share their home with. | |
| - Focus on: compatibility, house/apartment details, shared spaces | |
| - Price type: monthly or yearly | |
| - Emphasize: house rules, utilities included, available rooms, amenities | |
| - Include: lease terms, deposit, move-in date, roommate preferences | |
| - Ask about: their lifestyle, work schedule, cleanliness standards | |
| """, | |
| UserRole.ROOMMATE_SEEKER: """ | |
| You are helping someone find a room to share with a roommate. | |
| - Focus on: affordability, roommate compatibility, location, utilities | |
| - Price type: monthly or yearly budget | |
| - Emphasize: house rules, amenities, commute, lifestyle fit | |
| - Include: move-in date, lease length, deposit requirements | |
| - Ask about: budget, preferred location, work/study location, lifestyle | |
| """ | |
| } | |
| return prompts.get(user_role, "") | |
| # ==================== EXAMPLE USAGE ==================== | |
| if __name__ == "__main__": | |
| logging.basicConfig(level=logging.INFO) | |
| engine = RoleBasedInferenceEngine() | |
| # Test cases | |
| test_cases = [ | |
| # Airbnb host | |
| ("I'm a host on Airbnb and want to list my apartment in Lagos", "Lagos"), | |
| # Airbnb guest | |
| ("I'm looking for accommodation on Airbnb in Accra next week", "Accra"), | |
| # African landlord | |
| ("I'm a landlord in Cotonou with a 2-bedroom apartment for monthly rent", "Cotonou"), | |
| # African tenant | |
| ("I'm looking to rent a furnished room in Nairobi, my budget is 30000 KES per month", "Nairobi"), | |
| # Homeowner seeking roommate | |
| ("My house in Lagos is too big for just me. I have 2 extra bedrooms and want to share", "Lagos"), | |
| # Roommate seeker | |
| ("I'm looking for a room to share in Accra, somewhere near my workplace", "Accra"), | |
| # Seller | |
| ("I want to sell my house in Lagos for 50 million NGN", "Lagos"), | |
| # Buyer | |
| ("I'm looking to buy a 3-bedroom apartment in Cape Town", "Cape Town"), | |
| ] | |
| print("\n" + "="*70) | |
| print("π§ ROLE-BASED INFERENCE ENGINE TEST") | |
| print("="*70 + "\n") | |
| for message, location in test_cases: | |
| print(f"π Message: {message}") | |
| print(f"π Location: {location}\n") | |
| state = {"location": location} | |
| listing_type, confidence = engine.infer_listing_type(state, message) | |
| print(f"β Listing Type: {listing_type} (confidence: {confidence:.0%})") | |
| config = engine.adapt_field_extraction(state, message) | |
| print(f"π Required fields: {', '.join(config['required_fields'])}") | |
| print(f"π° Price types: {', '.join(config['price_type_suggestions'])}") | |
| prompt = engine.get_role_context_prompt(config['user_role'], config['rental_model']) | |
| print(f"π― AI Context:\n{prompt}") | |
| print("-" * 70 + "\n") |