AgentSkillsCN

Property Recommendation Engine

房产推荐引擎

SKILL.md

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.json via 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

  1. Start with explicit preferences - Don't over-rely on inference early
  2. Explain recommendations - Always provide reasoning for matches
  3. Ask for feedback - Actively request showing feedback
  4. Balance exploration - Include some stretch properties
  5. 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