Spaces:
Sleeping
Sleeping
Upload 18 files
Browse files- app/api/__pycache__/router.cpython-312.pyc +0 -0
- app/api/router.py +240 -0
- app/db/__pycache__/crud.cpython-312.pyc +0 -0
- app/db/__pycache__/database.cpython-312.pyc +0 -0
- app/db/__pycache__/models.cpython-312.pyc +0 -0
- app/db/__pycache__/schemas.cpython-312.pyc +0 -0
- app/db/crud.py +77 -0
- app/db/database.py +17 -0
- app/db/models.py +37 -0
- app/db/schemas.py +52 -0
- app/services/__pycache__/agent_service.cpython-312.pyc +0 -0
- app/services/__pycache__/properties_service.cpython-312.pyc +0 -0
- app/services/__pycache__/realtor_service.cpython-312.pyc +0 -0
- app/services/agent_service.py +8 -0
- app/services/properties_service.py +51 -0
- app/services/realtor_service.py +264 -0
- app/utils/__pycache__/log_utils.cpython-312.pyc +0 -0
- app/utils/log_utils.py +26 -0
app/api/__pycache__/router.cpython-312.pyc
ADDED
Binary file (15.3 kB). View file
|
|
app/api/router.py
ADDED
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.db import models
|
2 |
+
import os, json, random, requests
|
3 |
+
from sqlalchemy.orm import Session
|
4 |
+
from fastapi.responses import JSONResponse
|
5 |
+
from app.db.database import SessionLocal, engine
|
6 |
+
from app.db import crud
|
7 |
+
from app.services.realtor_service import RealtorService
|
8 |
+
from app.utils.log_utils import setup_logger
|
9 |
+
from fastapi import Request, Depends, APIRouter, HTTPException
|
10 |
+
from twilio.twiml.messaging_response import MessagingResponse
|
11 |
+
from app.services.properties_service import get_property_by_address_or_mls_number
|
12 |
+
from app.services.agent_service import verify_agent_by_agent_id_and_broker_name
|
13 |
+
from fastapi.responses import Response
|
14 |
+
from pydantic import BaseModel, Field
|
15 |
+
from twilio.rest import Client
|
16 |
+
from app.db.schemas import BuyerRealtorConfirmation, BuyerRealtorSignUP, CreatebookingRequest, GetBooking, ListingRealtorConfirmation, ListingRealtorSignUP, MessageRequestSignUP
|
17 |
+
|
18 |
+
from dotenv import load_dotenv
|
19 |
+
load_dotenv()
|
20 |
+
|
21 |
+
logger = setup_logger("router")
|
22 |
+
|
23 |
+
ACCOUNT_SID = os.getenv("ACCOUNT_SID")
|
24 |
+
AUTH_TOKEN = os.getenv("AUTH_TOKEN")
|
25 |
+
ai_agnet_number = os.getenv("AI_AGENT_NUMBER")
|
26 |
+
n8n_webhook_url = os.getenv("N8N_WEBHOOK_URL")
|
27 |
+
|
28 |
+
client = Client(ACCOUNT_SID, AUTH_TOKEN)
|
29 |
+
|
30 |
+
router = APIRouter()
|
31 |
+
models.Base.metadata.create_all(bind=engine)
|
32 |
+
|
33 |
+
def get_db():
|
34 |
+
db = SessionLocal()
|
35 |
+
try:
|
36 |
+
yield db
|
37 |
+
finally:
|
38 |
+
db.close()
|
39 |
+
|
40 |
+
|
41 |
+
@router.post('/realtor/get_properties')
|
42 |
+
async def get_properties(address: str=None, mls_number: str=None, db: Session = Depends(get_db)):
|
43 |
+
logger.warning(f"get_properties request: address: {address}, mls_number: {mls_number}")
|
44 |
+
response = get_property_by_address_or_mls_number(mls_number=mls_number, address=address)
|
45 |
+
return JSONResponse(content=response)
|
46 |
+
|
47 |
+
@router.post('/realtor/verify_agent')
|
48 |
+
async def verify_agent(agent_id: str, broker_name: str, db: Session = Depends(get_db)):
|
49 |
+
logger.warning(f"verify_agent request: agent_id: {agent_id}, broker_name: {broker_name}")
|
50 |
+
return verify_agent_by_agent_id_and_broker_name(agent_id=agent_id, broker_name=broker_name)
|
51 |
+
|
52 |
+
@router.post('/realtor/signup')
|
53 |
+
async def realtor_sign_up(request: MessageRequestSignUP, db: Session = Depends(get_db)):
|
54 |
+
logger.warning(f"singup request: {request}")
|
55 |
+
user = crud.create_user(db, full_name=request.full_name, phone_number=request.phone_number, type=request.type, agent_id=request.agent_id, broker_name=request.broker_name)
|
56 |
+
if not user:
|
57 |
+
raise HTTPException(status_code=400, detail="User creation failed")
|
58 |
+
return JSONResponse(content="user is signed up sucessfully.")
|
59 |
+
|
60 |
+
|
61 |
+
@router.post('/realtor/create_booking')
|
62 |
+
async def create_booking(request: CreatebookingRequest, db: Session = Depends(get_db)):
|
63 |
+
logger.warning(f"Create booking request: {request}")
|
64 |
+
try:
|
65 |
+
booking = crud.create_booking(db, buyer_agent_phone_number=request.buyer_agent_phone_number,
|
66 |
+
address=request.address, mls_number=request.mls_number, buyer_selected_date=request.buyer_selected_date,
|
67 |
+
buyer_selected_time=request.buyer_selected_time, listing_agent_phone_number=request.listing_agent_phone_number,
|
68 |
+
listing_agent_session_id=request.listing_agent_session_id, status=request.status)
|
69 |
+
except Exception as e:
|
70 |
+
db.rollback()
|
71 |
+
logger.error(f"Error in creating booking: {e}")
|
72 |
+
raise HTTPException(status_code=400, detail="booking creation failed")
|
73 |
+
|
74 |
+
return JSONResponse(content=booking)
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
@router.post('/realtor/book_showings')
|
79 |
+
async def buyer_book_showings(request: ListingRealtorSignUP, db: Session = Depends(get_db)):
|
80 |
+
logger.warning(f"Listing realtor singup request: {request}")
|
81 |
+
realtor_service = RealtorService()
|
82 |
+
response = realtor_service.book_showings(db, request.booking_address, request.mls_number, request.date, request.time, request.buyer_agent_phone_number)
|
83 |
+
logger.warning(f"Listing realtor sign up response: {response}")
|
84 |
+
return JSONResponse(content=response)
|
85 |
+
|
86 |
+
@router.post('/realtor/get_booking')
|
87 |
+
async def get_booking(request: GetBooking, db: Session = Depends(get_db)):
|
88 |
+
realtor_service = RealtorService()
|
89 |
+
response = realtor_service.get_booking(db, booking_id=request.booking_id, mls_number=request.mls_number, phone_number=request.phone_number)
|
90 |
+
logger.warning(f"Get booking response: {response}")
|
91 |
+
return JSONResponse(content=response)
|
92 |
+
|
93 |
+
@router.post('/buyer_realtor/signup')
|
94 |
+
async def buyer_realtor_sign_up(request: BuyerRealtorSignUP, db: Session = Depends(get_db)):
|
95 |
+
logger.warning(f"Buyer realtor singup request: {request}")
|
96 |
+
type = "buyer"
|
97 |
+
realtor_service = RealtorService()
|
98 |
+
response = realtor_service.buyer_realtor_sign_up(db, request.full_name, request.agent_id, request.broker_name, request.phone_number, type)
|
99 |
+
logger.warning(f"Buyer realtor sign up response: {response}")
|
100 |
+
return JSONResponse(content=response)
|
101 |
+
|
102 |
+
@router.post('/buyer_realtor/confirmation')
|
103 |
+
async def buyer_realtor_confirmation(request: BuyerRealtorConfirmation, db: Session = Depends(get_db)):
|
104 |
+
logger.warning(f"Buyer realtor confirmation request: {request}")
|
105 |
+
realtor_service = RealtorService()
|
106 |
+
response = realtor_service.buyer_realtor_confirmation(db, request.phone_number, request.booking_id, request.mls_number, request.date, request.time, request.confirmation)
|
107 |
+
logger.warning(f"Buyer realtor confirmation response: {response}")
|
108 |
+
return JSONResponse(content=response)
|
109 |
+
|
110 |
+
@router.post('/listing_realtor/confirmation')
|
111 |
+
async def listing_realtor_confirmation(request: ListingRealtorConfirmation, db: Session = Depends(get_db)):
|
112 |
+
logger.warning(f"Listing realtor confirmation request: {request}")
|
113 |
+
realtor_service = RealtorService()
|
114 |
+
response = realtor_service.listing_realtor_confirmation(db, request.session_id, request.date, request.time, request.confirmation)
|
115 |
+
logger.warning(f"Listing realtor confirmation response: {response}")
|
116 |
+
return JSONResponse(content=response)
|
117 |
+
|
118 |
+
|
119 |
+
|
120 |
+
# @router.post("/webhook/sms")
|
121 |
+
# async def receive_sms(request: Request):
|
122 |
+
# form_data = await request.form()
|
123 |
+
# logger.warning(f"Received SMS: {form_data}")
|
124 |
+
# sender = form_data.get("From")[9:]
|
125 |
+
# message_body = form_data.get("Body")
|
126 |
+
|
127 |
+
# logger.warning(f"Received SMS from {sender}: {message_body}")
|
128 |
+
# # # Reply to the sender
|
129 |
+
# # response = MessagingResponse()
|
130 |
+
# # response.message(f"Hello! You sent: {message_body}")
|
131 |
+
|
132 |
+
# # return str(response)
|
133 |
+
|
134 |
+
|
135 |
+
@router.get('/helth_check')
|
136 |
+
async def helth_check():
|
137 |
+
logger.warning(f"singup request: Comes")
|
138 |
+
return JSONResponse(content="Success")
|
139 |
+
|
140 |
+
@router.get('/')
|
141 |
+
async def test():
|
142 |
+
logger.warning(f"test request: Comes")
|
143 |
+
return JSONResponse(content="Go to /docs")
|
144 |
+
|
145 |
+
|
146 |
+
@router.post('/chatbot')
|
147 |
+
async def whatsapp_webhook(request: Request, db: Session = Depends(get_db)):
|
148 |
+
form_data = await request.form()
|
149 |
+
num_media = int(form_data.get('NumMedia', 0))
|
150 |
+
phone_number = form_data.get("From")[9:]
|
151 |
+
profile_name = form_data.get("ProfileName")
|
152 |
+
user_query = form_data.get("Body")
|
153 |
+
logger.warning(f"num_media: {num_media}, phone_number: {phone_number}, ProfileName: {profile_name}, user_query: {user_query}")
|
154 |
+
|
155 |
+
user = crud.get_user_by_phone_number(db, phone_number=phone_number)
|
156 |
+
if not user:
|
157 |
+
payload = {
|
158 |
+
"input_value": user_query,
|
159 |
+
"session_id": phone_number
|
160 |
+
}
|
161 |
+
logger.info(f"Payload: {payload}")
|
162 |
+
headers = {
|
163 |
+
'Content-Type': 'application/json'
|
164 |
+
}
|
165 |
+
url = "https://workflows.kickcall.ai/api/v1/run/067c7617-4758-4f07-8f4e-457b4d10dbb6?stream=false&user_id=1"
|
166 |
+
|
167 |
+
try:
|
168 |
+
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
169 |
+
logger.info(f"[Langflow][run] Response status code received: {response.status_code}")
|
170 |
+
|
171 |
+
if response.status_code == 200:
|
172 |
+
response_json = response.json()
|
173 |
+
logger.info(f"[Langflow][run] Response: {response_json}")
|
174 |
+
output_text = response_json["outputs"][0]["outputs"][0]["results"]["message"]["data"]["text"]
|
175 |
+
|
176 |
+
bot_resp = MessagingResponse()
|
177 |
+
msg = bot_resp.message()
|
178 |
+
msg.body(output_text)
|
179 |
+
|
180 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
181 |
+
else:
|
182 |
+
logger.info(f"Error: Received status code {response.status_code}")
|
183 |
+
bot_resp = MessagingResponse()
|
184 |
+
msg = bot_resp.message()
|
185 |
+
msg.body("I'm sorry, I couldn't process your request at the moment. Please try again later.")
|
186 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
187 |
+
|
188 |
+
except Exception as e:
|
189 |
+
logger.info(f"Error in processing user query: {e}")
|
190 |
+
bot_resp = MessagingResponse()
|
191 |
+
msg = bot_resp.message()
|
192 |
+
msg.body("I'm sorry, an unexpected error occurred. Please try again later.")
|
193 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
194 |
+
|
195 |
+
if user.type == "buyer":
|
196 |
+
payload = {
|
197 |
+
"input_value": user_query,
|
198 |
+
"session_id": phone_number
|
199 |
+
}
|
200 |
+
logger.info(f"Payload: {payload}")
|
201 |
+
headers = {
|
202 |
+
'Content-Type': 'application/json'
|
203 |
+
}
|
204 |
+
url = "https://workflows.kickcall.ai/api/v1/run/c94b14f5-d7a7-4fc8-89d8-eb672716c778?stream=false&user_id=1"
|
205 |
+
|
206 |
+
try:
|
207 |
+
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
208 |
+
logger.info(f"[Langflow][run] Response status code received: {response.status_code}")
|
209 |
+
|
210 |
+
if response.status_code == 200:
|
211 |
+
response_json = response.json()
|
212 |
+
logger.info(f"[Langflow][run] Response: {response_json}")
|
213 |
+
output_text = response_json["outputs"][0]["outputs"][0]["results"]["message"]["data"]["text"]
|
214 |
+
|
215 |
+
bot_resp = MessagingResponse()
|
216 |
+
msg = bot_resp.message()
|
217 |
+
msg.body(output_text)
|
218 |
+
|
219 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
220 |
+
else:
|
221 |
+
logger.info(f"Error: Received status code {response.status_code}")
|
222 |
+
bot_resp = MessagingResponse()
|
223 |
+
msg = bot_resp.message()
|
224 |
+
msg.body("I'm sorry, I couldn't process your request at the moment. Please try again later.")
|
225 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
226 |
+
|
227 |
+
except Exception as e:
|
228 |
+
logger.info(f"Error in processing user query: {e}")
|
229 |
+
bot_resp = MessagingResponse()
|
230 |
+
msg = bot_resp.message()
|
231 |
+
msg.body("I'm sorry, an unexpected error occurred. Please try again later.")
|
232 |
+
return Response(content=str(bot_resp), media_type="application/xml")
|
233 |
+
|
234 |
+
elif user.type == "listing":
|
235 |
+
logger.info(f"Message received by listing realto's ")
|
236 |
+
active_session_id = user.active_session_id
|
237 |
+
|
238 |
+
else:
|
239 |
+
logger.error(f"Message received by type : {user.type}")
|
240 |
+
|
app/db/__pycache__/crud.cpython-312.pyc
ADDED
Binary file (5.23 kB). View file
|
|
app/db/__pycache__/database.cpython-312.pyc
ADDED
Binary file (705 Bytes). View file
|
|
app/db/__pycache__/models.cpython-312.pyc
ADDED
Binary file (2.42 kB). View file
|
|
app/db/__pycache__/schemas.cpython-312.pyc
ADDED
Binary file (3.84 kB). View file
|
|
app/db/crud.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uuid
|
2 |
+
from app.db import models
|
3 |
+
from sqlalchemy.orm import Session
|
4 |
+
from app.utils.log_utils import setup_logger
|
5 |
+
|
6 |
+
logger = setup_logger("crud")
|
7 |
+
|
8 |
+
def get_booking_by_mls_and_buyer_phone(db: Session, mls_number: str, buyer_agent_phone_number: str):
|
9 |
+
return db.query(models.booking).filter(
|
10 |
+
models.booking.mls_number == mls_number,
|
11 |
+
models.booking.buyer_agent_phone_number == buyer_agent_phone_number
|
12 |
+
).first()
|
13 |
+
|
14 |
+
def get_user_by_phone_number(db: Session, phone_number: str):
|
15 |
+
return db.query(models.User).filter(models.User.phone_number == phone_number).first()
|
16 |
+
|
17 |
+
def create_user(db: Session, full_name: str, phone_number: str, type: str, agent_id=None, broker_name=None, active_session_id=None):
|
18 |
+
try:
|
19 |
+
new_user = models.User(full_name=full_name,
|
20 |
+
phone_number=phone_number,
|
21 |
+
type=type,
|
22 |
+
agent_id=agent_id,
|
23 |
+
broker_name=broker_name,
|
24 |
+
active_session_id=active_session_id)
|
25 |
+
db.add(new_user)
|
26 |
+
db.commit()
|
27 |
+
db.refresh(new_user)
|
28 |
+
return new_user
|
29 |
+
except Exception as e:
|
30 |
+
db.rollback()
|
31 |
+
logger.error(f"Error creating user: {e}")
|
32 |
+
return None
|
33 |
+
|
34 |
+
def get_booking_by_session_id(db: Session, session_id: str):
|
35 |
+
return db.query(models.booking).filter(models.booking.listing_agent_session_id == session_id).first()
|
36 |
+
|
37 |
+
def get_booking_by_id(db: Session, booking_id: int):
|
38 |
+
return db.query(models.booking).filter(models.booking.id == booking_id).first()
|
39 |
+
|
40 |
+
def get_booking_by_id_and_buyer_agent_phone_number(db: Session, booking_id: int, buyer_agent_phone_number: str):
|
41 |
+
return db.query(models.booking).filter(models.booking.id == booking_id,
|
42 |
+
models.booking.buyer_agent_phone_number == buyer_agent_phone_number).first()
|
43 |
+
|
44 |
+
def get_booking_by_mls_number_and_buyer_agent_phone_number(db: Session, mls_number: str, buyer_agent_phone_number: str):
|
45 |
+
return db.query(models.booking).filter(models.booking.mls_number == mls_number,
|
46 |
+
models.booking.buyer_agent_phone_number == buyer_agent_phone_number).first()
|
47 |
+
|
48 |
+
def create_booking(db: Session,
|
49 |
+
buyer_agent_phone_number: str,
|
50 |
+
address: str, mls_number: str,
|
51 |
+
buyer_selected_date: str,
|
52 |
+
buyer_selected_time: str,
|
53 |
+
listing_selected_date: str = None,
|
54 |
+
listing_selected_time: str = None,
|
55 |
+
listing_agent_phone_number: str = None,
|
56 |
+
listing_agent_session_id: str = None,
|
57 |
+
status: str = None):
|
58 |
+
try:
|
59 |
+
logger.info(f"Creating new booking with mls_number: {mls_number}")
|
60 |
+
new_booking = models.booking(buyer_agent_phone_number=buyer_agent_phone_number,
|
61 |
+
address=address,
|
62 |
+
mls_number=mls_number,
|
63 |
+
buyer_selected_date=buyer_selected_date,
|
64 |
+
buyer_selected_time=buyer_selected_time,
|
65 |
+
listing_selected_date=listing_selected_date,
|
66 |
+
listing_selected_time=listing_selected_time,
|
67 |
+
listing_agent_phone_number=listing_agent_phone_number,
|
68 |
+
listing_agent_session_id=listing_agent_session_id,
|
69 |
+
status=status)
|
70 |
+
db.add(new_booking)
|
71 |
+
db.commit()
|
72 |
+
db.refresh(new_booking)
|
73 |
+
return new_booking
|
74 |
+
except Exception as e:
|
75 |
+
db.rollback()
|
76 |
+
print(f"Error creating booking: {e}")
|
77 |
+
return None
|
app/db/database.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
from sqlalchemy import create_engine
|
4 |
+
from sqlalchemy.orm import sessionmaker
|
5 |
+
from sqlalchemy.ext.declarative import declarative_base
|
6 |
+
|
7 |
+
load_dotenv()
|
8 |
+
|
9 |
+
SQLALCHEMY_DATABASE_URL = os.getenv("POSTGRES_DATABASE_URL")
|
10 |
+
|
11 |
+
engine = create_engine(
|
12 |
+
SQLALCHEMY_DATABASE_URL,
|
13 |
+
pool_pre_ping=True
|
14 |
+
)
|
15 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
16 |
+
|
17 |
+
Base = declarative_base()
|
app/db/models.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, Integer, JSON,ForeignKey, String, DateTime, func, ARRAY
|
2 |
+
from sqlalchemy.orm import relationship
|
3 |
+
from .database import Base
|
4 |
+
|
5 |
+
class User(Base):
|
6 |
+
__tablename__ = "users"
|
7 |
+
|
8 |
+
id = Column(Integer, primary_key=True, index=True)
|
9 |
+
phone_number = Column(String, unique=True, index=True)
|
10 |
+
full_name = Column(String, default="Unknown")
|
11 |
+
agent_id = Column(String, default="")
|
12 |
+
type = Column(String, default="buyer")
|
13 |
+
broker_name = Column(String, default="")
|
14 |
+
active_session_id = Column(String, default="")
|
15 |
+
queued_session_ids = Column(ARRAY(String), default=[])
|
16 |
+
completed_session_ids = Column(ARRAY(String), default=[])
|
17 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
18 |
+
|
19 |
+
bookings = relationship("booking", back_populates="buyer_agent")
|
20 |
+
|
21 |
+
class booking(Base):
|
22 |
+
__tablename__ = "bookings"
|
23 |
+
|
24 |
+
id = Column(Integer, primary_key=True, index=True)
|
25 |
+
buyer_agent_phone_number = Column(String, ForeignKey("users.phone_number"), nullable=False)
|
26 |
+
address = Column(String, default="")
|
27 |
+
mls_number = Column(String, default="")
|
28 |
+
buyer_selected_date = Column(String, default="")
|
29 |
+
buyer_selected_time = Column(String, default="")
|
30 |
+
listing_selected_date = Column(String, default="")
|
31 |
+
listing_selected_time = Column(String, default="")
|
32 |
+
listing_agent_phone_number = Column(String, default="")
|
33 |
+
listing_agent_session_id = Column(String, default="")
|
34 |
+
status = Column(String, default="pending")
|
35 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
36 |
+
|
37 |
+
buyer_agent = relationship("User", back_populates="bookings")
|
app/db/schemas.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel, Field
|
2 |
+
|
3 |
+
class MessageRequestSignUP(BaseModel):
|
4 |
+
phone_number: str = Field(default="+917389058485")
|
5 |
+
agent_id: str = Field(default="")
|
6 |
+
full_name: str = Field(default="agent_full_name")
|
7 |
+
broker_name: str = Field(default="")
|
8 |
+
type: str = Field(default="buyer")
|
9 |
+
listing_realtor_msg: str = Field(default="")
|
10 |
+
|
11 |
+
|
12 |
+
class CreatebookingRequest(BaseModel):
|
13 |
+
buyer_agent_phone_number: str = Field(default="+917389058485")
|
14 |
+
address: str = Field(default="booking_address_test")
|
15 |
+
mls_number: str = Field(default="mls_number_test")
|
16 |
+
buyer_selected_date: str = Field(default="date_test")
|
17 |
+
buyer_selected_time: str = Field(default="time_test")
|
18 |
+
listing_agent_phone_number: str = Field(default="")
|
19 |
+
listing_agent_session_id: str = Field(default="")
|
20 |
+
status: str = Field(default="pending")
|
21 |
+
|
22 |
+
class BuyerRealtorSignUP(BaseModel):
|
23 |
+
full_name: str = Field(default="")
|
24 |
+
agent_id: str = Field(default="")
|
25 |
+
broker_name: str = Field(default="")
|
26 |
+
phone_number: str = Field(default="")
|
27 |
+
|
28 |
+
class ListingRealtorSignUP(BaseModel):
|
29 |
+
booking_address: str = Field(default="")
|
30 |
+
mls_number: str = Field(default="")
|
31 |
+
date: str = Field(default="")
|
32 |
+
time: str = Field(default="")
|
33 |
+
buyer_agent_phone_number: str = Field(default="")
|
34 |
+
|
35 |
+
class BuyerRealtorConfirmation(BaseModel):
|
36 |
+
phone_number: str = Field(default="")
|
37 |
+
mls_number: str = Field(default="")
|
38 |
+
booking_id: str = Field(default="")
|
39 |
+
date: str = Field(default="")
|
40 |
+
time: str = Field(default="")
|
41 |
+
confirmation: str = Field(default="")
|
42 |
+
|
43 |
+
class ListingRealtorConfirmation(BaseModel):
|
44 |
+
confirmation: str = Field(default="")
|
45 |
+
date: str = Field(default="")
|
46 |
+
time: str = Field(default="")
|
47 |
+
session_id: str = Field(default="")
|
48 |
+
|
49 |
+
class GetBooking(BaseModel):
|
50 |
+
phone_number: str = Field(default="")
|
51 |
+
booking_id: str = Field(default="")
|
52 |
+
mls_number: str = Field(default="")
|
app/services/__pycache__/agent_service.cpython-312.pyc
ADDED
Binary file (479 Bytes). View file
|
|
app/services/__pycache__/properties_service.cpython-312.pyc
ADDED
Binary file (1.82 kB). View file
|
|
app/services/__pycache__/realtor_service.cpython-312.pyc
ADDED
Binary file (18.2 kB). View file
|
|
app/services/agent_service.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
def verify_agent_by_agent_id_and_broker_name(agent_id, broker_name):
|
4 |
+
if agent_id == "agent_id_test" and broker_name == "broker_name_test":
|
5 |
+
return True
|
6 |
+
elif agent_id == "king_agent" and broker_name == "king_broker":
|
7 |
+
return True
|
8 |
+
return False
|
app/services/properties_service.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.utils.log_utils import setup_logger
|
2 |
+
|
3 |
+
logger = setup_logger("properties_service")
|
4 |
+
|
5 |
+
def get_property_by_address_or_mls_number(mls_number, address):
|
6 |
+
if mls_number in ["123", "456", "789", "123456"]:
|
7 |
+
response = {
|
8 |
+
"address": "123 Main St, San Francisco, CA 94105",
|
9 |
+
"mls_number": mls_number,
|
10 |
+
"price": "$1,000,000",
|
11 |
+
"bedrooms": "3",
|
12 |
+
"bathrooms": "2",
|
13 |
+
"sqft": "1,500",
|
14 |
+
"lot_size": "0.25",
|
15 |
+
"year_built": "2000",
|
16 |
+
"description": "This is a beautiful 3 bedroom, 2 bathroom home with 1,500 sqft of living space on a 0.25 acre lot. The home was built in 2000 and is listed for $1,000,000.",
|
17 |
+
"agent_name": "Bob",
|
18 |
+
"agent_phone": "+917347256305"
|
19 |
+
}
|
20 |
+
elif mls_number == "654321":
|
21 |
+
response = {
|
22 |
+
"address": "456 Elm St, San Francisco, CA 94105",
|
23 |
+
"mls_number": mls_number,
|
24 |
+
"price": "$1,500,000",
|
25 |
+
"bedrooms": "4",
|
26 |
+
"bathrooms": "3",
|
27 |
+
"sqft": "2,000",
|
28 |
+
"lot_size": "0.5",
|
29 |
+
"year_built": "2010",
|
30 |
+
"description": "This is a beautiful 4 bedroom, 3 bathroom home with 2,000 sqft of living space on a 0.5 acre lot. The home was built in 2010 and is listed for $1,500,000.",
|
31 |
+
"agent_name": "Bob1",
|
32 |
+
"agent_phone": "+917347256302"
|
33 |
+
}
|
34 |
+
elif address == "321 Oak St, San Francisco, CA 94105":
|
35 |
+
response = {
|
36 |
+
"address": address,
|
37 |
+
"mls_number": "789012",
|
38 |
+
"price": "$2,000,000",
|
39 |
+
"bedrooms": "5",
|
40 |
+
"bathrooms": "4",
|
41 |
+
"sqft": "2,500",
|
42 |
+
"lot_size": "0.75",
|
43 |
+
"year_built": "2020",
|
44 |
+
"description": "This is a beautiful 5 bedroom, 4 bathroom home with 2,500 sqft of living space on a 0.75 acre lot. The home was built in 2020 and is listed for $2,000,000.",
|
45 |
+
"agent_name": "Bob2",
|
46 |
+
"agent_phone": "+917347256303"
|
47 |
+
}
|
48 |
+
else:
|
49 |
+
logger.error(f"Property not found for address: {address}, mls_number: {mls_number}")
|
50 |
+
response = None
|
51 |
+
return response
|
app/services/realtor_service.py
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.db import crud
|
2 |
+
from app.services.agent_service import verify_agent_by_agent_id_and_broker_name
|
3 |
+
from app.services.properties_service import get_property_by_address_or_mls_number
|
4 |
+
from app.utils.log_utils import setup_logger
|
5 |
+
import requests, os, json, uuid
|
6 |
+
from dotenv import load_dotenv
|
7 |
+
load_dotenv()
|
8 |
+
|
9 |
+
ACCOUNT_SID = os.getenv("ACCOUNT_SID")
|
10 |
+
AUTH_TOKEN = os.getenv("AUTH_TOKEN")
|
11 |
+
ai_agnet_number = os.getenv("AI_AGENT_NUMBER")
|
12 |
+
n8n_webhook_url = os.getenv("N8N_WEBHOOK_URL")
|
13 |
+
|
14 |
+
logger = setup_logger("realtor_service")
|
15 |
+
|
16 |
+
def create_payload_for_whatsapp_message(from_number, to_number, body):
|
17 |
+
payload = [
|
18 |
+
{
|
19 |
+
"SmsMessageSid": "SM0b07afa5f1ede39427957b4dc127cab3",
|
20 |
+
"NumMedia": "0",
|
21 |
+
"ProfileName": "",
|
22 |
+
"MessageType": "text",
|
23 |
+
"SmsSid": "SM0b07afa5f1ede39427957b4dc127cab3",
|
24 |
+
"WaId": from_number,
|
25 |
+
"SmsStatus": "received",
|
26 |
+
"Body": f"paparaphrase this: {body}",
|
27 |
+
"To": f"whatsapp:{to_number}",
|
28 |
+
"NumSegments": "1",
|
29 |
+
"ReferralNumMedia": "0",
|
30 |
+
"MessageSid": "SM0b07afa5f1ede39427957b4dc127cab3",
|
31 |
+
"AccountSid": "",
|
32 |
+
"From": f"whatsapp:{from_number}",
|
33 |
+
"ApiVersion": "2010-04-01"
|
34 |
+
}
|
35 |
+
]
|
36 |
+
|
37 |
+
try:
|
38 |
+
|
39 |
+
headers = {"Content-Type": "application/json"}
|
40 |
+
response = requests.post(n8n_webhook_url, headers=headers, data=json.dumps(payload))
|
41 |
+
logger.info(f"Response for whatsapp webhook: {response.text}")
|
42 |
+
except Exception as e:
|
43 |
+
logger.error(f"Error in sending message to agent: {e}")
|
44 |
+
return f"Error in sending message to agent: {str(e)}"
|
45 |
+
return "Message sent successfully"
|
46 |
+
|
47 |
+
|
48 |
+
class RealtorService:
|
49 |
+
def __init__(self):
|
50 |
+
pass
|
51 |
+
|
52 |
+
def book_showings(self, db, booking_address, mls_number, date, time, buyer_agent_phone_number):
|
53 |
+
buyer_selected_date = date
|
54 |
+
buyer_selected_time = time
|
55 |
+
logger.warning(f"Listing realtore - booking_address: {booking_address}, mls_number: {mls_number}, date: {buyer_selected_date}, time: {buyer_selected_time}, buyer_agent_phone_number: {buyer_agent_phone_number}")
|
56 |
+
#TODO: Add logic to get booking details from mls_number
|
57 |
+
response = get_property_by_address_or_mls_number(mls_number=mls_number, address=booking_address)
|
58 |
+
if response is None:
|
59 |
+
return "Invalid mls_number or address"
|
60 |
+
listing_agent_full_name = response["agent_name"]
|
61 |
+
listing_agent_phone_number = response["agent_phone"]
|
62 |
+
type = "listing"
|
63 |
+
booking_address = response["address"]
|
64 |
+
try:
|
65 |
+
buyer_user = crud.get_user_by_phone_number(db, phone_number=buyer_agent_phone_number)
|
66 |
+
if not buyer_user:
|
67 |
+
return "Buyer agent not found"
|
68 |
+
|
69 |
+
new_session_id = uuid.uuid4()
|
70 |
+
listing_user = crud.get_user_by_phone_number(db, phone_number=listing_agent_phone_number)
|
71 |
+
if listing_user:
|
72 |
+
if listing_user.active_session_id:
|
73 |
+
listing_user.queued_session_ids = listing_user.queued_session_ids + [new_session_id]
|
74 |
+
logger.info(f"The user already exists with the provided phone number and has an active session. Therefore, the task is being added to the queue: {buyer_agent_phone_number}")
|
75 |
+
db.commit()
|
76 |
+
db.refresh(listing_user)
|
77 |
+
booking = crud.get_booking_by_mls_number_and_buyer_agent_phone_number(db, mls_number=mls_number, buyer_agent_phone_number=buyer_agent_phone_number)
|
78 |
+
if booking:
|
79 |
+
booking.buyer_selected_date = buyer_selected_date
|
80 |
+
booking.buyer_selected_time = buyer_selected_time
|
81 |
+
db.commit()
|
82 |
+
db.refresh(booking)
|
83 |
+
return f"A booking already exists with this MLS number. The rescheduling request has been successfully processed. Your booking ID is {booking.id} for future reference."
|
84 |
+
else:
|
85 |
+
booking = crud.create_booking(db, buyer_agent_phone_number=buyer_agent_phone_number,
|
86 |
+
address=booking_address, mls_number=mls_number, buyer_selected_date=buyer_selected_date,
|
87 |
+
buyer_selected_time=buyer_selected_time, listing_agent_phone_number=listing_agent_phone_number,
|
88 |
+
listing_agent_session_id=new_session_id)
|
89 |
+
else:
|
90 |
+
logger.info(f"The user already exists but no active session found : {buyer_agent_phone_number}")
|
91 |
+
listing_user.active_session_id = new_session_id
|
92 |
+
db.commit()
|
93 |
+
db.refresh(listing_user)
|
94 |
+
booking = crud.get_booking_by_mls_number_and_buyer_agent_phone_number(db, mls_number=mls_number, buyer_agent_phone_number=buyer_agent_phone_number)
|
95 |
+
if booking:
|
96 |
+
booking.buyer_selected_date = buyer_selected_date
|
97 |
+
booking.buyer_selected_time = buyer_selected_time
|
98 |
+
db.commit()
|
99 |
+
db.refresh(booking)
|
100 |
+
return f"Rescheduling request has been successfully processed. Your booking ID is {booking.id}."
|
101 |
+
else:
|
102 |
+
booking = crud.create_booking(db, buyer_agent_phone_number=buyer_agent_phone_number,
|
103 |
+
address=booking_address, mls_number=mls_number, buyer_selected_date=buyer_selected_date,
|
104 |
+
buyer_selected_time=buyer_selected_time, listing_agent_phone_number=listing_agent_phone_number,
|
105 |
+
listing_agent_session_id=listing_user.active_session_id)
|
106 |
+
else:
|
107 |
+
listing_user = crud.create_user(db, full_name=listing_agent_full_name, phone_number=listing_agent_phone_number, type=type, active_session_id=new_session_id)
|
108 |
+
if not listing_user:
|
109 |
+
logger.info(f"Error in creating listing agent.")
|
110 |
+
return "Error in creating listing agent."
|
111 |
+
booking = crud.create_booking(db, buyer_agent_phone_number=buyer_agent_phone_number,
|
112 |
+
address=booking_address, mls_number=mls_number, buyer_selected_date=buyer_selected_date,
|
113 |
+
buyer_selected_time=buyer_selected_time, listing_agent_phone_number=listing_agent_phone_number,
|
114 |
+
listing_agent_session_id=new_session_id)
|
115 |
+
|
116 |
+
except Exception as e:
|
117 |
+
db.rollback()
|
118 |
+
logger.error(f"Error in creating booking: {e}")
|
119 |
+
return "booking creation failed"
|
120 |
+
|
121 |
+
if int(buyer_selected_time[:2])>12:
|
122 |
+
buyer_selected_time = f"{int(buyer_selected_time[:2])-12}{buyer_selected_time[2:]} PM"
|
123 |
+
else:
|
124 |
+
buyer_selected_time = f"{buyer_selected_time} AM"
|
125 |
+
|
126 |
+
listing_agent_body = f"Hello {listing_agent_full_name}, this is Alice assistant of {buyer_user.full_name}. We would like to schedule a showing for {booking_address} (MLS # {mls_number}) at {buyer_selected_time} on {buyer_selected_date}. The booking ID for this request is #{booking.id} for future reference. Can you make that work?."
|
127 |
+
logger.info(f"Sending message to listing agent from: {ai_agnet_number}, body: {listing_agent_body}, to: {listing_agent_phone_number}")
|
128 |
+
response = create_payload_for_whatsapp_message(from_number=listing_agent_phone_number, to_number=ai_agnet_number, body=listing_agent_body)
|
129 |
+
if response == "Message sent successfully":
|
130 |
+
return f"Do not say like its booked successfully say like: Your showing has been pending with the listing agent {listing_user.full_name}. I will notify you once the listing agent confirms the showing appointment. Your booking ID is #{booking.id} for future reference."
|
131 |
+
return "Error in sending message to listing agent"
|
132 |
+
|
133 |
+
|
134 |
+
def listing_realtor_confirmation(self, db, session_id, listing_selected_date, listing_selected_time, confirmation):
|
135 |
+
logger.warning(f"Listing realtore - listing_selected_date: {listing_selected_date}, listing_selected_time: {listing_selected_time}, session_id: {session_id}, confirmation: {confirmation}.")
|
136 |
+
try:
|
137 |
+
booking = crud.get_booking_by_session_id(db, session_id=session_id)
|
138 |
+
listing_user = crud.get_user_by_phone_number(db, phone_number=booking.listing_agent_phone_number)
|
139 |
+
is_listing_user_session_queued = False
|
140 |
+
except Exception as e:
|
141 |
+
logger.error(f"Error in creating user: {e}")
|
142 |
+
return f"booking not found with this session id, Error: {str(e)}"
|
143 |
+
if not booking:
|
144 |
+
return "booking not found with this session id"
|
145 |
+
booking.listing_selected_date = listing_selected_date
|
146 |
+
booking.listing_selected_time = listing_selected_time
|
147 |
+
|
148 |
+
if listing_selected_date == booking.buyer_selected_date and listing_selected_time == booking.buyer_selected_time:
|
149 |
+
logger.info(f"listing_selected_date and listing_selected_time are same as buyer_selected_date and buyer_selected_time")
|
150 |
+
confirmation = "confirmed"
|
151 |
+
if int(listing_selected_time[:2])>12:
|
152 |
+
listing_selected_time = f"{int(listing_selected_time[:2])-12}{listing_selected_time[2:]} PM"
|
153 |
+
else:
|
154 |
+
listing_selected_time = f"{listing_selected_time} AM"
|
155 |
+
if confirmation == "confirmed":
|
156 |
+
logger.warning(f"booking Schudule is confirmed by listing agent.")
|
157 |
+
booking.status = "confirmed"
|
158 |
+
if listing_user.queued_session_ids:
|
159 |
+
logger.info(f"listing user has queued sessions Found.")
|
160 |
+
listing_user.active_session_id = listing_user.queued_session_ids.pop(0)
|
161 |
+
is_listing_user_session_queued = True
|
162 |
+
listing_agent_body = f"Your showing at {booking.address} (MLS # {booking.mls_number}) is confirmed on {listing_selected_date} at {listing_selected_time} . I'll send you a reminder the day before to ensure you're prepared."
|
163 |
+
buyer_agent_body = f"Your showing at {booking.address} (MLS # {booking.mls_number}) is confirmed by {listing_user.full_name} on {listing_selected_date} at {listing_selected_time} . I'll send you a reminder the day before to ensure you're prepared."
|
164 |
+
elif confirmation == "cancelled":
|
165 |
+
logger.warning(f"booking Schudule is cancelled by listing agent.")
|
166 |
+
if listing_user.queued_session_ids:
|
167 |
+
logger.info(f"listing user has queued sessions Found.")
|
168 |
+
listing_user.active_session_id = listing_user.queued_session_ids.pop(0)
|
169 |
+
is_listing_user_session_queued = True
|
170 |
+
booking.status = "cancelled"
|
171 |
+
listing_agent_body = f"Thank you for letting us know. We will inform the buyer that the showing has been cancelled."
|
172 |
+
buyer_agent_body = f"Unfortunately, the listing agent has cancelled the showing. We apologize for any inconvenience caused."
|
173 |
+
elif confirmation == "rescheduled":
|
174 |
+
logger.warning(f"booking Schudule is rescheduled by listing agent. because of listing_selected_date: {listing_selected_date}, listing_selected_time: {listing_selected_time} and buyer_selected_date: {booking.buyer_selected_date}, buyer_selected_time: {booking.buyer_selected_time}")
|
175 |
+
booking.status = "rescheduled"
|
176 |
+
listing_agent_body = f"paparaphrase this: Your reschedule request for the showing has been submitted and is waiting for confirmation from the buyer agent. I’ll update you once they confirm the showing appointment. Your booking ID is #{booking.id} for reference."
|
177 |
+
buyer_agent_body = f"The listing agent for the booking at {booking.address} (MLS # {booking.mls_number}) wants to rescheduled the showing on {listing_selected_date} at {listing_selected_time} . Your booking ID is #{booking.id} for future reference, Please confirm if this new time works for you."
|
178 |
+
else:
|
179 |
+
logger.warning(f"Invalid confirmation status")
|
180 |
+
return "Invalid confirmation status"
|
181 |
+
|
182 |
+
logger.info(f"Sending message to listing agent from: {ai_agnet_number}, body: {buyer_agent_body}, to: {booking.buyer_agent_phone_number}")
|
183 |
+
response = create_payload_for_whatsapp_message(from_number=booking.buyer_agent_phone_number, to_number=ai_agnet_number, body=buyer_agent_body)
|
184 |
+
if response != "Message sent successfully":
|
185 |
+
logger.error(f"Error in sending message to listing agent")
|
186 |
+
return "Error in sending message to buyer agent"
|
187 |
+
db.commit()
|
188 |
+
return listing_agent_body
|
189 |
+
|
190 |
+
def buyer_realtor_confirmation(self, db, buyer_agent_phone_number, booking_id, mls_number, buyer_selected_date, buyer_selected_time, confirmation):
|
191 |
+
logger.warning(f"Buyer realtore - booking_id: {booking_id}, mls_number: {mls_number}, buyer_selected_date: {buyer_selected_date}, buyer_selected_time: {buyer_selected_time}, confirmation: {confirmation}.")
|
192 |
+
try:
|
193 |
+
if booking_id:
|
194 |
+
booking = crud.get_booking_by_id_and_buyer_agent_phone_number(db, booking_id=booking_id, buyer_agent_phone_number=buyer_agent_phone_number)
|
195 |
+
elif mls_number:
|
196 |
+
booking = crud.get_booking_by_mls_number_and_buyer_agent_phone_number(db, mls_number=mls_number, buyer_agent_phone_number=buyer_agent_phone_number)
|
197 |
+
else:
|
198 |
+
return "Invalid booking id or mls number"
|
199 |
+
except Exception as e:
|
200 |
+
logger.error(f"Error in creating user: {e}")
|
201 |
+
return f"booking not found with this booking id, Error: {str(e)}"
|
202 |
+
if not booking:
|
203 |
+
return "booking not found with this booking id"
|
204 |
+
booking.buyer_selected_date = buyer_selected_date
|
205 |
+
booking.buyer_selected_time = buyer_selected_time
|
206 |
+
if buyer_selected_date == booking.listing_selected_date and buyer_selected_time == booking.listing_selected_time:
|
207 |
+
logger.info(f"buyer_selected_date and buyer_selected_time are same as listing_selected_date and listing_selected_time")
|
208 |
+
confirmation = "confirmed"
|
209 |
+
if int(buyer_selected_time[:2])>12:
|
210 |
+
buyer_selected_time = f"{int(buyer_selected_time[:2])-12}{buyer_selected_time[2:]} PM"
|
211 |
+
else:
|
212 |
+
buyer_selected_time = f"{buyer_selected_time} AM"
|
213 |
+
if confirmation == "confirmed":
|
214 |
+
logger.warning(f"booking Schudule is confirmed by buyer agent.")
|
215 |
+
booking.status = "confirmed"
|
216 |
+
buyer_agent_body = f"Your showing at {booking.address} (MLS # {booking.mls_number}) is confirmed on {buyer_selected_date} at {buyer_selected_time}. Your booking ID is {booking.id} for future reference. I'll send you a reminder the day before to ensure you're prepared."
|
217 |
+
listing_agent_body = f"Your showing at {booking.address} (MLS # {booking.mls_number}) is confirmed on {buyer_selected_date} at {buyer_selected_time} . Your booking ID is {booking.id} for future reference. I'll send you a reminder the day before to ensure you're prepared."
|
218 |
+
elif confirmation == "cancelled":
|
219 |
+
logger.warning(f"booking Schudule is cancelled by buyer agent.")
|
220 |
+
booking.status = "cancelled"
|
221 |
+
buyer_agent_body = f"Thank you for letting us know. We will inform the listing Agent that the showing has been cancelled."
|
222 |
+
listing_agent_body = f"Unfortunately, the Buyer agent has cancelled the showing. We apologize for any inconvenience caused."
|
223 |
+
elif confirmation == "rescheduled":
|
224 |
+
logger.warning(f"booking Schudule is rescheduled by Buyer agent. because of listing_selected_date: {buyer_selected_date}, listing_selected_time: {buyer_selected_time} and buyer_selected_date: {booking.buyer_selected_date}, buyer_selected_time: {booking.buyer_selected_time}")
|
225 |
+
booking.status = "rescheduled"
|
226 |
+
buyer_agent_body = f"paparaphrase this: Your reschedule request for the showing has been submitted and is waiting for confirmation from the buyer agent. I’ll update you once they confirm the showing appointment. Your booking ID is #{booking.id} for reference."
|
227 |
+
listing_agent_body = f"The Buyer agent for the booking at {booking.address} (MLS # {booking.mls_number}) wants to rescheduled the showing on {buyer_selected_date} at {buyer_selected_time}. Your booking ID is #{booking.id} for future reference. Please confirm if this new time works for you."
|
228 |
+
else:
|
229 |
+
logger.warning(f"Invalid confirmation status")
|
230 |
+
return "Invalid confirmation status"
|
231 |
+
|
232 |
+
logger.info(f"Sending message to listing agent from: {ai_agnet_number}, body: {listing_agent_body}, to: {booking.listing_agent_phone_number}")
|
233 |
+
response = create_payload_for_whatsapp_message(from_number=booking.listing_agent_phone_number, to_number=ai_agnet_number, body=listing_agent_body)
|
234 |
+
if response != "Message sent successfully":
|
235 |
+
logger.error(f"Error in sending message to listing agent")
|
236 |
+
return "Error in sending message to Listing agent"
|
237 |
+
db.commit()
|
238 |
+
return buyer_agent_body
|
239 |
+
|
240 |
+
|
241 |
+
def buyer_realtor_sign_up(self, db, full_name, agent_id, broker_name, phone_number, type):
|
242 |
+
logger.warning(f"Buyer realtor singup request: {full_name}, {agent_id}, {broker_name}, {phone_number}, {type}")
|
243 |
+
#TODO: Add logic to verify agent_id and broker_name
|
244 |
+
if not verify_agent_by_agent_id_and_broker_name(agent_id=agent_id, broker_name=broker_name):
|
245 |
+
logger.warning(f"Invalid agent_id or broker_name")
|
246 |
+
return "Wrong agent id or broker name"
|
247 |
+
user = crud.create_user(db, full_name=full_name, phone_number=phone_number, type=type, agent_id=agent_id, broker_name=broker_name)
|
248 |
+
if not user:
|
249 |
+
"User creation failed"
|
250 |
+
return "user is signed up sucessfully. Now you can book the showing with the listing agent. would you like to book a showing now?"
|
251 |
+
|
252 |
+
|
253 |
+
def get_booking(self, db, booking_id, mls_number, phone_number):
|
254 |
+
logger.warning(f"Get booking request: {booking_id}, {mls_number}, {phone_number}")
|
255 |
+
if booking_id:
|
256 |
+
booking = crud.get_booking_by_id_and_buyer_agent_phone_number(db, booking_id=booking_id, buyer_agent_phone_number=phone_number)
|
257 |
+
elif mls_number:
|
258 |
+
booking = crud.get_booking_by_mls_number_and_buyer_agent_phone_number(db, mls_number=mls_number, buyer_agent_phone_number=phone_number)
|
259 |
+
else:
|
260 |
+
return "Invalid booking_id or mls_number or phone_number"
|
261 |
+
if not booking:
|
262 |
+
return "booking not found"
|
263 |
+
logger.info(f"booking details: {str(vars(booking))}")
|
264 |
+
return str(vars(booking))
|
app/utils/__pycache__/log_utils.cpython-312.pyc
ADDED
Binary file (1.28 kB). View file
|
|
app/utils/log_utils.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import colorlog
|
3 |
+
|
4 |
+
def setup_logger(logger_name: str, debug_color="white", info_color="green", propagate=False):
|
5 |
+
color_handler = colorlog.StreamHandler()
|
6 |
+
log_colors = {
|
7 |
+
"DEBUG": debug_color,
|
8 |
+
"INFO": info_color,
|
9 |
+
"WARNING": "yellow",
|
10 |
+
"ERROR": "red",
|
11 |
+
"CRITICAL": "bold_red",
|
12 |
+
}
|
13 |
+
color_handler.setFormatter(
|
14 |
+
colorlog.ColoredFormatter(
|
15 |
+
"%(log_color)s%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
16 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
17 |
+
log_colors=log_colors
|
18 |
+
)
|
19 |
+
)
|
20 |
+
logger = logging.getLogger(logger_name)
|
21 |
+
if logger.hasHandlers():
|
22 |
+
logger.handlers.clear()
|
23 |
+
logger.setLevel(logging.DEBUG)
|
24 |
+
logger.addHandler(color_handler)
|
25 |
+
logger.propagate = propagate
|
26 |
+
return logger
|