Property Recommendation Engine
ML-powered property matching with continuous learning from user interactions and feedback.
Description
Learn from lead behavior to recommend perfectly matched properties using preference extraction, behavioral matching, and feedback loop learning. This skill integrates with the property matcher service to provide intelligent, personalized property recommendations.
Triggers
- •"recommend properties"
- •"match buyer preferences"
- •"optimize showing schedule"
- •"personalize property list"
- •"property recommendations"
- •"find matching homes"
- •"property matching"
Model Configuration
- •Model: opus (complex reasoning for preference learning)
- •Thinking: enabled for preference analysis
- •Temperature: 0.5 (balanced exploration/consistency)
Workflow
Phase 1: Preference Extraction
code
1. Parse conversation history for explicit preferences - Budget range and flexibility - Location preferences and priorities - Property features (beds, baths, sqft) - Lifestyle requirements (schools, commute, amenities) 2. Infer implicit preferences from behavior - Properties viewed multiple times - Time spent on specific listings - Features mentioned in questions - Comparison patterns 3. Weight preferences by recency and frequency
Phase 2: Property Scoring
code
1. Match properties against weighted preferences - Hard constraints (must-haves) - Soft preferences (nice-to-haves) - Stretch opportunities (above budget but valuable) 2. Calculate multi-dimensional match scores - Feature alignment score - Budget fit score - Location score - Lifestyle match score 3. Apply behavioral boost factors - Similar to previously liked properties - Addresses stated concerns - New on market / price reduced
Phase 3: Schedule Optimization
code
1. Group recommended properties by location 2. Calculate optimal showing route 3. Consider property availability 4. Factor in lead's schedule preferences
Phase 4: Feedback Learning
code
1. Track showing outcomes 2. Analyze accept/reject patterns 3. Update preference weights 4. Improve future recommendations
Scripts (Zero-Context Execution)
scripts/calculate-property-scores.py
Scores properties against lead preferences.
bash
python scripts/calculate-property-scores.py --lead-id <id> --limit 10
scripts/optimize-showing-schedule.py
Optimizes property showing schedule and route.
bash
python scripts/optimize-showing-schedule.py --properties <ids.json> --date 2026-01-20
scripts/extract-preferences.py
Extracts preferences from conversation history.
bash
python scripts/extract-preferences.py --lead-id <id> --output json
scripts/update-preference-weights.py
Updates preference weights based on feedback.
bash
python scripts/update-preference-weights.py --lead-id <id> --feedback <feedback.json>
References
- •@reference/preference-extraction.md - How to extract preferences from conversations
- •@reference/scoring-algorithm.md - Property scoring methodology
- •@reference/feedback-learning.md - Continuous improvement patterns
- •@reference/showing-optimization.md - Route optimization strategies
Integration Points
Primary Services
python
from ghl_real_estate_ai.services.property_matcher import PropertyMatcher from ghl_real_estate_ai.services.claude_assistant import ClaudeAssistant from ghl_real_estate_ai.services.memory_service import MemoryService
Data Sources
- •Property Listings:
property_listings.jsonvia PropertyMatcher - •Lead Preferences: Memory service and conversation history
- •Feedback Data: GHL contact notes and showing outcomes
Output Destinations
- •GHL Tasks: Schedule showing appointments
- •Email/SMS: Send curated property lists
- •Dashboard: Display recommendations with explanations
Example Usage
Generate Recommendations
python
from ghl_real_estate_ai.services.property_matcher import PropertyMatcher
matcher = PropertyMatcher()
# Find matches based on preferences
preferences = {
"budget": 750000,
"location": ["Teravista", "Round Rock"],
"bedrooms": 4,
"bathrooms": 3,
"sqft_min": 2500,
"school_rating_min": 8,
"commute_to": "Downtown Austin",
"commute_max_minutes": 45
}
matches = matcher.find_matches(preferences, limit=5, min_score=0.6)
for match in matches:
print(f"{match['address']}: {match['match_score']:.0%} match")
print(f" Reason: {match['match_reasoning']}")
Get AI-Enhanced Explanation
python
# Get Claude's strategic explanation for a match
explanation = await matcher.agentic_explain_match(
property=matches[0],
preferences=preferences
)
print(f"Why this property: {explanation}")
# Output: "This South Lamar home is a strategic capture; it sits 5%
# below your budget while offering the modern renovation you prioritized.
# The high walkability score aligns with your request for an active
# neighborhood lifestyle."
Optimize Showing Schedule
python
from ghl_real_estate_ai.services.property_recommendation import ShowingOptimizer
optimizer = ShowingOptimizer()
schedule = await optimizer.create_showing_schedule(
properties=[p["property_id"] for p in matches[:5]],
date="2026-01-20",
start_time="10:00",
max_duration_hours=4
)
print(f"Optimized Route: {schedule.total_drive_time} min driving")
for showing in schedule.showings:
print(f" {showing.time}: {showing.address}")
Learn from Feedback
python
# After showing, record feedback
await matcher.record_feedback(
lead_id="lead_123",
property_id="prop_456",
feedback={
"viewed": True,
"liked": True,
"offer_made": False,
"objections": ["backyard too small"],
"interests": ["open floor plan", "updated kitchen"]
}
)
# Preferences are automatically updated for better future matches
Output Schema
Recommendation Result
json
{
"lead_id": "string",
"generated_at": "ISO8601",
"preferences_used": {
"explicit": {...},
"inferred": {...},
"weights": {...}
},
"recommendations": [
{
"property_id": "string",
"address": "string",
"price": 0,
"match_score": 0.0-1.0,
"score_breakdown": {
"budget_fit": 0.0-1.0,
"location_match": 0.0-1.0,
"features_match": 0.0-1.0,
"lifestyle_match": 0.0-1.0
},
"match_reasoning": "string",
"strategic_explanation": "string",
"highlights": ["string"],
"potential_concerns": ["string"],
"similar_to_liked": ["property_ids"],
"listing_url": "string"
}
],
"showing_schedule": {
"optimized": true,
"total_drive_time_minutes": 0,
"route": ["property_ids"]
}
}
Preference Profile
json
{
"lead_id": "string",
"explicit_preferences": {
"budget": {"min": 0, "max": 0, "flexibility": "firm|flexible|stretch"},
"locations": [{"area": "string", "priority": 1-5}],
"features": {
"bedrooms": {"min": 0, "ideal": 0},
"bathrooms": {"min": 0, "ideal": 0},
"sqft": {"min": 0, "ideal": 0},
"garage": 0,
"pool": false,
"yard_size": "string"
}
},
"inferred_preferences": {
"style": "modern|traditional|ranch|colonial",
"neighborhood_type": "suburban|urban|rural",
"investment_focus": 0.0-1.0,
"school_importance": 0.0-1.0,
"commute_sensitivity": 0.0-1.0
},
"behavioral_patterns": {
"properties_viewed": 0,
"avg_view_time_seconds": 0,
"most_viewed_features": ["string"],
"price_range_viewed": {"min": 0, "max": 0}
},
"feedback_history": [
{
"property_id": "string",
"liked": true,
"feedback": "string"
}
],
"last_updated": "ISO8601"
}
Scoring Algorithm
Multi-Dimensional Match Score
python
def calculate_match_score(property: Dict, preferences: Dict) -> float:
"""
Calculate comprehensive match score.
Components:
- Budget fit (25%): How well price matches budget
- Location match (25%): Distance from preferred areas
- Features match (30%): Bedroom, bathroom, sqft alignment
- Lifestyle match (20%): Schools, commute, amenities
"""
# Budget score (0-1)
budget = preferences.get("budget", float("inf"))
price = property.get("price", 0)
if price <= budget:
budget_score = 1.0 - (price / budget) * 0.2 # Under budget is good
else:
over_percent = (price - budget) / budget
budget_score = max(0, 1.0 - over_percent * 2) # Penalize over budget
# Location score (0-1)
preferred_locations = preferences.get("locations", [])
property_location = property.get("address", {}).get("neighborhood", "")
if property_location in preferred_locations:
location_score = 1.0
else:
location_score = 0.5 # Needs distance calculation in production
# Features score (0-1)
feature_scores = []
# Bedrooms
min_beds = preferences.get("bedrooms", 0)
prop_beds = property.get("bedrooms", 0)
if prop_beds >= min_beds:
feature_scores.append(1.0)
else:
feature_scores.append(prop_beds / min_beds if min_beds > 0 else 0)
# Bathrooms
min_baths = preferences.get("bathrooms", 0)
prop_baths = property.get("bathrooms", 0)
if prop_baths >= min_baths:
feature_scores.append(1.0)
else:
feature_scores.append(prop_baths / min_baths if min_baths > 0 else 0)
# Square footage
min_sqft = preferences.get("sqft_min", 0)
prop_sqft = property.get("sqft", 0)
if prop_sqft >= min_sqft:
feature_scores.append(1.0)
else:
feature_scores.append(prop_sqft / min_sqft if min_sqft > 0 else 0)
features_score = sum(feature_scores) / len(feature_scores) if feature_scores else 0.5
# Lifestyle score (0-1) - simplified
lifestyle_score = 0.7 # Default, needs full implementation
# Weighted combination
final_score = (
budget_score * 0.25 +
location_score * 0.25 +
features_score * 0.30 +
lifestyle_score * 0.20
)
return round(final_score, 3)
Feedback Learning
Weight Adjustment Algorithm
python
def update_preference_weights(lead_id: str, feedback: Dict) -> None:
"""
Update preference weights based on showing feedback.
Learning Rules:
- If liked: boost weight of matching features
- If rejected: reduce weight or add new constraint
- If offer made: strongly boost matching features
"""
current_weights = load_preference_weights(lead_id)
property_features = feedback.get("property_features", {})
liked = feedback.get("liked", False)
offer_made = feedback.get("offer_made", False)
# Learning rate based on signal strength
lr = 0.1 if liked else 0.05
if offer_made:
lr = 0.2 # Strong signal
for feature, value in property_features.items():
if feature in current_weights:
if liked:
# Increase weight for liked features
current_weights[feature] *= (1 + lr)
else:
# Check if this feature was an objection
objections = feedback.get("objections", [])
if any(feature in obj.lower() for obj in objections):
current_weights[feature] *= (1 - lr * 2)
# Normalize weights
total = sum(current_weights.values())
for key in current_weights:
current_weights[key] /= total
save_preference_weights(lead_id, current_weights)
Success Metrics
- •Match Quality: 40% better match satisfaction
- •Showing Efficiency: 30% fewer showings to offer
- •Time Savings: 2+ hours per week on property research
- •Conversion Improvement: 25% higher showing-to-offer rate
Best Practices
- •Start with explicit preferences - Don't over-rely on inference early
- •Explain recommendations - Always provide reasoning for matches
- •Ask for feedback - Actively request showing feedback
- •Balance exploration - Include some stretch properties
- •Monitor accuracy - Track match satisfaction over time
Error Handling
python
# Graceful degradation for insufficient preferences
try:
recommendations = await engine.get_recommendations(lead_id)
except InsufficientPreferencesError as e:
# Return broader matches with lower confidence
recommendations = await engine.get_broad_recommendations(
location=e.available_preferences.get("location"),
budget=e.available_preferences.get("budget")
)
recommendations.confidence = "low"
recommendations.message = "Need more preferences for better matches"
Version History
- •1.0.0 (2026-01-16): Initial implementation
- •Preference extraction from conversations
- •Multi-dimensional property scoring
- •Showing schedule optimization
- •Feedback learning system