| import os
|
| from datetime import datetime, timedelta
|
| from typing import List, Optional, Dict, Any
|
| from dotenv import load_dotenv
|
| from supabase import create_client, Client
|
| from cache import cache
|
| import uuid
|
|
|
| load_dotenv()
|
|
|
| SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
|
|
| class Database:
|
| def __init__(self):
|
| self.client = None
|
| if SUPABASE_URL and SUPABASE_KEY:
|
| try:
|
| self.client: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| except Exception as e:
|
| print(f"Failed to initialize Supabase client: {e}")
|
|
|
|
|
| self.mock_users = [
|
| {"contact_number": "5550101", "name": "Alice Test", "created_at": datetime.now().isoformat()},
|
| {"contact_number": "9730102", "name": "Naresh", "created_at": datetime.now().isoformat()}
|
| ]
|
| self.mock_appointments = [
|
| {
|
| "id": "mock_apt_1",
|
| "contact_number": "555-0101",
|
| "appointment_time": "2026-01-22T10:00:00",
|
| "status": "confirmed",
|
| "purpose": "Checkup",
|
| "created_at": datetime.now().isoformat()
|
| }
|
| ]
|
| self.mock_summaries = []
|
| self.mock_chat_messages = []
|
| self.cache = cache
|
|
|
|
|
| self.mock_slots = []
|
| base_time = datetime.now().replace(minute=0, second=0, microsecond=0)
|
| for d in range(1, 11):
|
| day = base_time + timedelta(days=d)
|
| for h in [9, 10, 14, 16]:
|
| slot_time = day.replace(hour=h).isoformat()
|
| self.mock_slots.append({"slot_time": slot_time, "is_booked": False})
|
|
|
| def get_available_slots(self) -> List[str]:
|
| """Get list of available slot times."""
|
| if self.client:
|
| try:
|
| response = self.client.table("appointment_slots")\
|
| .select("slot_time")\
|
| .eq("is_booked", False)\
|
| .gt("slot_time", datetime.now().isoformat())\
|
| .order("slot_time")\
|
| .execute()
|
| return [row["slot_time"] for row in response.data]
|
| except Exception as e:
|
| print(f"Error fetching slots from DB: {e}")
|
|
|
|
|
|
|
| now_str = datetime.now().isoformat()
|
| return [s["slot_time"] for s in self.mock_slots if not s["is_booked"] and s["slot_time"] > now_str]
|
|
|
| def get_user(self, contact_number: str) -> Optional[Dict[str, Any]]:
|
| """Check if a user exists by contact number (with caching)."""
|
|
|
| contact_number = "".join(filter(str.isdigit, str(contact_number)))
|
|
|
|
|
| cache_key = f"user:{contact_number}"
|
| cached_user = self.cache.get(cache_key)
|
| if cached_user:
|
| return cached_user
|
|
|
| if self.client:
|
| try:
|
| response = self.client.table("users").select("*").eq("contact_number", contact_number).execute()
|
| if response.data:
|
| user = response.data[0]
|
|
|
| self.cache.set(cache_key, user, ttl=3600)
|
| return user
|
| except Exception as e:
|
| print(f"Error fetching user from DB (falling back to mock): {e}")
|
|
|
|
|
| for user in self.mock_users:
|
| if user["contact_number"] == contact_number:
|
| return user
|
| return None
|
|
|
| def create_user(self, contact_number: str, name: str = "Unknown") -> Optional[Dict[str, Any]]:
|
| """Create a new user."""
|
| if self.client:
|
| try:
|
| data = {"contact_number": contact_number, "name": name}
|
|
|
| response = self.client.table("users").upsert(data).execute()
|
| if response.data:
|
| return response.data[0]
|
| except Exception as e:
|
| print(f"Error creating user in DB (falling back to mock): {e}")
|
|
|
|
|
| new_user = {"contact_number": contact_number, "name": name, "created_at": datetime.now().isoformat()}
|
| self.mock_users.append(new_user)
|
| return new_user
|
|
|
| def get_user_appointments(self, contact_number: str) -> List[Dict[str, Any]]:
|
| """Fetch past and upcoming appointments for a user."""
|
| if self.client:
|
| try:
|
| response = self.client.table("appointments")\
|
| .select("*")\
|
| .eq("contact_number", contact_number)\
|
| .order("appointment_time", desc=True)\
|
| .execute()
|
| return response.data
|
| except Exception as e:
|
| print(f"Error fetching appointments from DB (falling back to mock): {e}")
|
|
|
|
|
| return [
|
| apt for apt in self.mock_appointments
|
| if apt["contact_number"] == contact_number and apt["status"] != "cancelled"
|
| ]
|
|
|
| def check_slot_availability(self, appointment_time: datetime) -> bool:
|
| """Check if a slot is valid and available."""
|
| time_str = appointment_time.isoformat()
|
|
|
| if self.client:
|
| try:
|
|
|
| response = self.client.table("appointment_slots")\
|
| .select("*")\
|
| .eq("slot_time", time_str)\
|
| .eq("is_booked", False)\
|
| .execute()
|
|
|
| return len(response.data) > 0
|
| except Exception as e:
|
| print(f"Error checking availability in DB (falling back to mock): {e}")
|
|
|
|
|
| for slot in self.mock_slots:
|
| if slot["slot_time"] == time_str:
|
| return not slot["is_booked"]
|
| return False
|
|
|
| def book_appointment(self, contact_number: str, appointment_time: str, purpose: str = "General") -> Optional[Dict[str, Any]]:
|
| """Book an appointment and mark slot as booked."""
|
| if self.client:
|
| try:
|
|
|
| data = {
|
| "contact_number": contact_number,
|
| "appointment_time": appointment_time,
|
| "status": "confirmed",
|
| "purpose": purpose
|
| }
|
| response = self.client.table("appointments").insert(data).execute()
|
|
|
|
|
| self.client.table("appointment_slots")\
|
| .update({"is_booked": True})\
|
| .eq("slot_time", appointment_time)\
|
| .execute()
|
|
|
| if response.data:
|
| return response.data[0]
|
| except Exception as e:
|
| print(f"Error booking appointment in DB (falling back to mock): {e}")
|
|
|
|
|
| import random
|
| apt_id = f"APT-{random.randint(1000, 9999)}"
|
| new_apt = {
|
| "id": apt_id,
|
| "contact_number": contact_number,
|
| "appointment_time": appointment_time,
|
| "status": "confirmed",
|
| "purpose": purpose,
|
| "created_at": datetime.now().isoformat()
|
| }
|
| self.mock_appointments.append(new_apt)
|
|
|
|
|
| for slot in self.mock_slots:
|
| if slot["slot_time"] == appointment_time:
|
| slot["is_booked"] = True
|
|
|
| return new_apt
|
|
|
| def cancel_appointment(self, appointment_id: str) -> bool:
|
| """Cancel an appointment."""
|
| if self.client:
|
| try:
|
| response = self.client.table("appointments")\
|
| .update({"status": "cancelled"})\
|
| .eq("id", appointment_id)\
|
| .execute()
|
| return True
|
| except Exception as e:
|
| print(f"Error cancelling appointment in DB (falling back to mock): {e}")
|
|
|
|
|
| for apt in self.mock_appointments:
|
| if apt["id"] == appointment_id:
|
| apt["status"] = "cancelled"
|
| return True
|
| return False
|
|
|
| def modify_appointment(self, appointment_id: str, new_time: str) -> bool:
|
| """Modify appointment time."""
|
| if self.client:
|
| try:
|
| response = self.client.table("appointments")\
|
| .update({"appointment_time": new_time})\
|
| .eq("id", appointment_id)\
|
| .execute()
|
| return True
|
| except Exception as e:
|
| print(f"Error modifying appointment in DB (falling back to mock): {e}")
|
|
|
|
|
| for apt in self.mock_appointments:
|
| if apt["id"] == appointment_id:
|
| apt["appointment_time"] = new_time
|
| return True
|
| return False
|
|
|
| def save_summary(self, contact_number: str, summary: str) -> bool:
|
| """Save the conversation summary."""
|
| if self.client:
|
| try:
|
| data = {
|
| "contact_number": contact_number,
|
| "summary": summary,
|
| "created_at": datetime.now().isoformat()
|
| }
|
|
|
| self.client.table("conversations").insert(data).execute()
|
| return True
|
| except Exception as e:
|
| print(f"Error saving summary in DB (falling back to mock): {e}")
|
|
|
|
|
| print(f"Mock saving summary for {contact_number}: {summary}")
|
| self.mock_summaries.append({
|
| "contact_number": contact_number,
|
| "summary": summary,
|
| "created_at": datetime.now().isoformat()
|
| })
|
| return True
|
|
|
| def save_chat_message(self, session_id: str, contact_number: str, role: str, content: str, tool_name: str = None, tool_args: dict = None) -> bool:
|
| """Save a single chat message to the database"""
|
| if self.client:
|
| try:
|
| data = {
|
| "session_id": session_id,
|
| "contact_number": contact_number,
|
| "role": role,
|
| "content": content,
|
| "tool_name": tool_name,
|
| "tool_args": tool_args,
|
| "created_at": datetime.now().isoformat()
|
| }
|
| self.client.table("chat_messages").insert(data).execute()
|
| return True
|
| except Exception as e:
|
| print(f"Error saving chat message to DB (falling back to mock): {e}")
|
|
|
|
|
| self.mock_chat_messages.append({
|
| "session_id": session_id,
|
| "contact_number": contact_number,
|
| "role": role,
|
| "content": content,
|
| "tool_name": tool_name,
|
| "tool_args": tool_args,
|
| "created_at": datetime.now().isoformat()
|
| })
|
| return True
|
|
|
| def save_chat_transcript(self, session_id: str, contact_number: str, messages: list) -> bool:
|
| """Save entire chat transcript (batch insert)"""
|
| if not messages:
|
| return False
|
|
|
| if self.client:
|
| try:
|
|
|
| data = []
|
| for msg in messages:
|
| data.append({
|
| "session_id": session_id,
|
| "contact_number": contact_number,
|
| "role": msg.get("role"),
|
| "content": msg.get("content"),
|
| "tool_name": msg.get("tool_name"),
|
| "tool_args": msg.get("tool_args"),
|
| "created_at": datetime.now().isoformat()
|
| })
|
|
|
|
|
| self.client.table("chat_messages").insert(data).execute()
|
| print(f"✅ Saved {len(data)} chat messages to database")
|
| return True
|
| except Exception as e:
|
| print(f"Error saving chat transcript to DB (falling back to mock): {e}")
|
|
|
|
|
| for msg in messages:
|
| self.mock_chat_messages.append({
|
| "session_id": session_id,
|
| "contact_number": contact_number,
|
| "role": msg.get("role"),
|
| "content": msg.get("content"),
|
| "tool_name": msg.get("tool_name"),
|
| "tool_args": msg.get("tool_args"),
|
| "created_at": datetime.now().isoformat()
|
| })
|
| print(f"Mock saved {len(messages)} chat messages")
|
| return True
|
|
|
| def get_chat_history(self, contact_number: str, limit: int = 100) -> list:
|
| """Get chat history for a user"""
|
| if self.client:
|
| try:
|
| response = self.client.table("chat_messages")\
|
| .select("*")\
|
| .eq("contact_number", contact_number)\
|
| .order("created_at", desc=True)\
|
| .limit(limit)\
|
| .execute()
|
| return response.data if response.data else []
|
| except Exception as e:
|
| print(f"Error fetching chat history: {e}")
|
|
|
|
|
| return [msg for msg in self.mock_chat_messages if msg["contact_number"] == contact_number][-limit:]
|
|
|
|
|
| AVAILABLE_SLOTS = [
|
| "2026-01-22T09:00:00",
|
| "2026-01-22T10:00:00",
|
| "2026-01-22T14:00:00",
|
| "2026-01-23T11:00:00",
|
| "2026-01-23T15:00:00"
|
| ]
|
|
|