zenaight commited on
Commit ·
729c393
1
Parent(s): e2c07e6
Refactor property search logic to support flexible ranges
Browse files- Updated the `search_properties` function to allow for flexible budget and size ranges, enabling searches within ±50% of the specified values.
- Removed the must-have features from the database search, shifting the responsibility to the chat response logic for better user interaction.
- Simplified the `extract_and_search_properties` function by eliminating multiple fallback passes, streamlining the property search process and improving response clarity.
- These changes aim to enhance the user experience by providing more relevant property listings while maintaining clear communication regarding search results.
- ai_chat.py +4 -49
- database.py +13 -9
ai_chat.py
CHANGED
|
@@ -348,64 +348,19 @@ async def extract_and_search_properties(state):
|
|
| 348 |
if intent.get("size_preference_sqm") is not None:
|
| 349 |
filters["size_preference_sqm"] = intent["size_preference_sqm"]
|
| 350 |
|
| 351 |
-
#
|
| 352 |
-
must_have = intent.get("must_have", [])
|
| 353 |
-
if must_have and len(must_have) > 0:
|
| 354 |
-
filters["must_have"] = must_have
|
| 355 |
-
|
| 356 |
-
# Search for properties
|
| 357 |
print(f"DEBUG - Searching with filters: {filters}")
|
| 358 |
properties = await search_properties(filters)
|
| 359 |
state["properties"] = properties
|
| 360 |
-
print(f"DEBUG - Found {len(properties)} properties with
|
| 361 |
|
| 362 |
-
# Pass 1: strict filters
|
| 363 |
if properties:
|
| 364 |
print("DEBUG - Properties found, returning properties to continue to chat")
|
| 365 |
return {"properties": properties}
|
| 366 |
|
| 367 |
-
#
|
| 368 |
-
if "must_have" in filters:
|
| 369 |
-
filters2 = {k: v for k, v in filters.items() if k != "must_have"}
|
| 370 |
-
props2 = await search_properties(filters2)
|
| 371 |
-
if props2:
|
| 372 |
-
return {
|
| 373 |
-
"properties": props2,
|
| 374 |
-
"search_status_message": (
|
| 375 |
-
"I don't have an exact match with those features, "
|
| 376 |
-
"but here are listings matching your location, budget and size."
|
| 377 |
-
)
|
| 378 |
-
}
|
| 379 |
-
|
| 380 |
-
# Pass 3: drop size
|
| 381 |
-
if "size_preference_sqm" in filters:
|
| 382 |
-
filters3 = {k: v for k, v in filters.items() if k not in ["must_have", "size_preference_sqm"]}
|
| 383 |
-
props3 = await search_properties(filters3)
|
| 384 |
-
if props3:
|
| 385 |
-
return {
|
| 386 |
-
"properties": props3,
|
| 387 |
-
"search_status_message": (
|
| 388 |
-
"I don't have that exact size, "
|
| 389 |
-
"but here are listings matching your location and budget."
|
| 390 |
-
)
|
| 391 |
-
}
|
| 392 |
-
|
| 393 |
-
# Pass 4: drop budget
|
| 394 |
-
if "budget" in filters:
|
| 395 |
-
filters4 = {k: v for k, v in filters.items() if k not in ["must_have", "size_preference_sqm", "budget"]}
|
| 396 |
-
props4 = await search_properties(filters4)
|
| 397 |
-
if props4:
|
| 398 |
-
return {
|
| 399 |
-
"properties": props4,
|
| 400 |
-
"search_status_message": (
|
| 401 |
-
f"I don't have listings within your budget in {filters4['location_preference']}, "
|
| 402 |
-
"but here are some options in that area."
|
| 403 |
-
)
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
# Final fallback: no listings at all
|
| 407 |
state["response"] = (
|
| 408 |
-
f"I don't have any listings right now
|
| 409 |
"I'll notify you as soon as something becomes available. "
|
| 410 |
"Feel free to reach out any time!"
|
| 411 |
)
|
|
|
|
| 348 |
if intent.get("size_preference_sqm") is not None:
|
| 349 |
filters["size_preference_sqm"] = intent["size_preference_sqm"]
|
| 350 |
|
| 351 |
+
# Search for properties with flexible ranges
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
print(f"DEBUG - Searching with filters: {filters}")
|
| 353 |
properties = await search_properties(filters)
|
| 354 |
state["properties"] = properties
|
| 355 |
+
print(f"DEBUG - Found {len(properties)} properties with flexible ranges")
|
| 356 |
|
|
|
|
| 357 |
if properties:
|
| 358 |
print("DEBUG - Properties found, returning properties to continue to chat")
|
| 359 |
return {"properties": properties}
|
| 360 |
|
| 361 |
+
# No properties found with any filters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
state["response"] = (
|
| 363 |
+
f"I don't have any listings right now in {location}. "
|
| 364 |
"I'll notify you as soon as something becomes available. "
|
| 365 |
"Feel free to reach out any time!"
|
| 366 |
)
|
database.py
CHANGED
|
@@ -288,7 +288,7 @@ async def update_user_intent(session_id: str, updates: dict):
|
|
| 288 |
# --- Property Search ---
|
| 289 |
async def search_properties(filters: dict) -> list:
|
| 290 |
"""
|
| 291 |
-
Returns up to 5 active properties matching the user's filters.
|
| 292 |
"""
|
| 293 |
if not supabase:
|
| 294 |
return []
|
|
@@ -310,20 +310,24 @@ async def search_properties(filters: dict) -> list:
|
|
| 310 |
# Fallback to location field
|
| 311 |
query = supabase.table("properties").select("*").eq("is_active", True).ilike("location", f"%{loc}%")
|
| 312 |
|
| 313 |
-
# b. Budget
|
| 314 |
budget = filters.get("budget")
|
| 315 |
if budget is not None:
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
| 317 |
|
| 318 |
-
# c.
|
| 319 |
size = filters.get("size_preference_sqm")
|
| 320 |
if size is not None:
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
-
#
|
| 324 |
-
|
| 325 |
-
for feat in features:
|
| 326 |
-
query = query.contains("features", [feat])
|
| 327 |
|
| 328 |
# e. Order & limit
|
| 329 |
query = query.order("is_featured", desc=True).order("price").limit(5)
|
|
|
|
| 288 |
# --- Property Search ---
|
| 289 |
async def search_properties(filters: dict) -> list:
|
| 290 |
"""
|
| 291 |
+
Returns up to 5 active properties matching the user's filters with flexible ranges.
|
| 292 |
"""
|
| 293 |
if not supabase:
|
| 294 |
return []
|
|
|
|
| 310 |
# Fallback to location field
|
| 311 |
query = supabase.table("properties").select("*").eq("is_active", True).ilike("location", f"%{loc}%")
|
| 312 |
|
| 313 |
+
# b. Budget range (target ± 50% tolerance)
|
| 314 |
budget = filters.get("budget")
|
| 315 |
if budget is not None:
|
| 316 |
+
# Allow properties within 50% of the target budget
|
| 317 |
+
min_budget = int(budget * 0.5) # 50% below target
|
| 318 |
+
max_budget = int(budget * 1.5) # 50% above target
|
| 319 |
+
query = query.gte("price", min_budget).lte("price", max_budget)
|
| 320 |
|
| 321 |
+
# c. Size range (target ± 50% tolerance)
|
| 322 |
size = filters.get("size_preference_sqm")
|
| 323 |
if size is not None:
|
| 324 |
+
# Allow properties within 50% of the target size
|
| 325 |
+
min_size = int(size * 0.5) # 50% below target
|
| 326 |
+
max_size = int(size * 1.5) # 50% above target
|
| 327 |
+
query = query.gte("size_sqm", min_size).lte("size_sqm", max_size)
|
| 328 |
|
| 329 |
+
# Note: Must-have features are removed from database search
|
| 330 |
+
# They will be handled by the LLM in the chat response
|
|
|
|
|
|
|
| 331 |
|
| 332 |
# e. Order & limit
|
| 333 |
query = query.order("is_featured", desc=True).order("price").limit(5)
|