zenaight commited on
Commit
ab1535a
·
1 Parent(s): ba64859

propety search

Browse files
Files changed (3) hide show
  1. .DS_Store +0 -0
  2. ai_chat.py +59 -4
  3. database.py +49 -1
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
ai_chat.py CHANGED
@@ -2,7 +2,7 @@ from langgraph.graph import StateGraph, END
2
  from langchain_core.runnables import RunnableLambda
3
  from typing import TypedDict
4
  from config import llm, OPENAI_API_KEY
5
- from database import get_session_messages, save_message, update_user_persona, update_user_intent
6
 
7
  def chat_with_session_memory(state):
8
  """Chat function with session-based memory"""
@@ -37,6 +37,22 @@ def chat_with_session_memory(state):
37
  f"and must-haves: {', '.join(must_have_list) if must_have_list else '[none]'}. "
38
  )
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  # Build messages array with history
41
  messages = [{"role": "system", "content": system_message}]
42
 
@@ -77,6 +93,7 @@ class ChatState(TypedDict):
77
  session_messages: list
78
  persona: dict
79
  intent: dict
 
80
 
81
  async def extract_and_update_persona(state):
82
  # a. Define which persona fields to track
@@ -243,18 +260,55 @@ async def extract_and_update_intent(state):
243
 
244
  return {"response": None}
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  # --- Build LangGraph ---
247
  graph = StateGraph(ChatState)
248
  graph.add_node("persona_update", RunnableLambda(extract_and_update_persona))
249
  graph.add_node("intent_update", RunnableLambda(extract_and_update_intent))
 
250
  graph.add_node("chat", RunnableLambda(chat_with_session_memory))
251
  graph.set_entry_point("persona_update")
252
  graph.add_edge("persona_update", "intent_update")
253
- graph.add_edge("intent_update", "chat")
 
254
  graph.add_edge("chat", END)
255
  chat_graph = graph.compile()
256
 
257
- async def process_message(user_message: str, user_info: dict = None, session_id: str = None, wa_id: str = None, wamid: str = None, persona: dict = None, intent: dict = None):
258
  """Process a message through the AI chat system with session memory"""
259
  if user_info is None:
260
  user_info = {}
@@ -273,7 +327,8 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
273
  "wamid": wamid,
274
  "session_messages": session_messages,
275
  "persona": persona or {},
276
- "intent": intent or {}
 
277
  })
278
 
279
  # Save messages to database
 
2
  from langchain_core.runnables import RunnableLambda
3
  from typing import TypedDict
4
  from config import llm, OPENAI_API_KEY
5
+ from database import get_session_messages, save_message, update_user_persona, update_user_intent, search_properties
6
 
7
  def chat_with_session_memory(state):
8
  """Chat function with session-based memory"""
 
37
  f"and must-haves: {', '.join(must_have_list) if must_have_list else '[none]'}. "
38
  )
39
 
40
+ props = state.get("properties", [])
41
+ if props:
42
+ system_message += "\nHere are some listings I found:\n"
43
+ for p in props[:5]:
44
+ title = p.get("title", "[no title]")
45
+ loc = p.get("location", "")
46
+ city = p.get("city", "")
47
+ size = p.get("size_sqm", "")
48
+ price = p.get("price", "")
49
+ ptype = p.get("price_type", "")
50
+ features = ", ".join(p.get("features", [])) or "[no features]"
51
+ system_message += (
52
+ f"- {title} in {loc}, {city}: {size} sqm, "
53
+ f"{price} ({ptype}); features: {features}\n"
54
+ )
55
+
56
  # Build messages array with history
57
  messages = [{"role": "system", "content": system_message}]
58
 
 
93
  session_messages: list
94
  persona: dict
95
  intent: dict
96
+ properties: list
97
 
98
  async def extract_and_update_persona(state):
99
  # a. Define which persona fields to track
 
260
 
261
  return {"response": None}
262
 
263
+ async def extract_and_search_properties(state):
264
+ """
265
+ Search for properties based on user intent and store results in state.
266
+ """
267
+ intent = state.get("intent", {})
268
+
269
+ # Check if we have the minimum required fields for property search
270
+ location = intent.get("location_preference")
271
+ budget = intent.get("budget")
272
+ size = intent.get("size_preference_sqm")
273
+
274
+ if not location or budget is None or size is None:
275
+ # Missing required fields, skip property search
276
+ return {"response": None}
277
+
278
+ # Prepare filters for property search
279
+ filters = {
280
+ "location_preference": location,
281
+ "budget": budget,
282
+ "size_preference_sqm": size,
283
+ "must_have": intent.get("must_have", [])
284
+ }
285
+
286
+ # Search for properties
287
+ properties = await search_properties(filters)
288
+ state["properties"] = properties
289
+
290
+ if not properties:
291
+ # No properties found, provide fallback suggestion
292
+ state["response"] = f"I don't see any listings in {location} that match your criteria. Would you like to look in Johannesburg instead, or adjust your requirements?"
293
+ return state
294
+
295
+ # Properties found, continue to chat
296
+ return {"response": None}
297
+
298
  # --- Build LangGraph ---
299
  graph = StateGraph(ChatState)
300
  graph.add_node("persona_update", RunnableLambda(extract_and_update_persona))
301
  graph.add_node("intent_update", RunnableLambda(extract_and_update_intent))
302
+ graph.add_node("property_search", RunnableLambda(extract_and_search_properties))
303
  graph.add_node("chat", RunnableLambda(chat_with_session_memory))
304
  graph.set_entry_point("persona_update")
305
  graph.add_edge("persona_update", "intent_update")
306
+ graph.add_edge("intent_update", "property_search")
307
+ graph.add_edge("property_search", "chat")
308
  graph.add_edge("chat", END)
309
  chat_graph = graph.compile()
310
 
311
+ async def process_message(user_message: str, user_info: dict = None, session_id: str = None, wa_id: str = None, wamid: str = None, persona: dict = None, intent: dict = None, properties: list = None):
312
  """Process a message through the AI chat system with session memory"""
313
  if user_info is None:
314
  user_info = {}
 
327
  "wamid": wamid,
328
  "session_messages": session_messages,
329
  "persona": persona or {},
330
+ "intent": intent or {},
331
+ "properties": properties or []
332
  })
333
 
334
  # Save messages to database
database.py CHANGED
@@ -286,4 +286,52 @@ async def update_user_intent(session_id: str, updates: dict):
286
  .execute()
287
  print(f"Successfully updated intent for session {session_id}")
288
  except Exception as e:
289
- print(f"Error updating user intent: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  .execute()
287
  print(f"Successfully updated intent for session {session_id}")
288
  except Exception as e:
289
+ print(f"Error updating user intent: {e}")
290
+
291
+ # --- Property Search ---
292
+ async def search_properties(filters: dict) -> list:
293
+ """
294
+ Returns up to 5 active properties matching the user's filters.
295
+ """
296
+ if not supabase:
297
+ return []
298
+
299
+ try:
300
+ query = supabase.table("properties").select("*").eq("is_active", True)
301
+
302
+ # a. Location: try city first, then location field
303
+ loc = filters.get("location_preference")
304
+ if loc:
305
+ query = query.ilike("city", f"%{loc}%")
306
+ resp = query.execute()
307
+ if not resp.data:
308
+ # fallback to suburb/area match
309
+ query = supabase.table("properties").select("*")\
310
+ .eq("is_active", True)\
311
+ .ilike("location", f"%{loc}%")
312
+
313
+ # b. Budget cap
314
+ budget = filters.get("budget")
315
+ if budget is not None:
316
+ query = query.lte("price", budget)
317
+
318
+ # c. Minimum size
319
+ size = filters.get("size_preference_sqm")
320
+ if size is not None:
321
+ query = query.gte("size_sqm", size)
322
+
323
+ # d. Must-have features
324
+ features = filters.get("must_have") or []
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", asc=True).limit(5)
330
+
331
+ # f. Execute and return
332
+ resp = query.execute()
333
+ return resp.data or []
334
+
335
+ except Exception as e:
336
+ print(f"Error searching properties: {e}")
337
+ return []