-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqueue_worker.py
More file actions
234 lines (206 loc) · 8.67 KB
/
Copy pathqueue_worker.py
File metadata and controls
234 lines (206 loc) · 8.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import logging
import os
from datetime import datetime, timezone
from typing import Any
import openai
import agent
import copywriter
import db
import whatsapp
from models import ConversationMessage, IncomingMessage, Intent, LeadData, Tenant
logger = logging.getLogger("propbot")
LEAD_NOTIFICATION_THRESHOLD = 75
AGENT_PLACEHOLDER_REPLIES: dict[Intent, str] = {
Intent.CMA_REQUEST: "קיבלתי — ניתוח שוק (CMA) יהיה זמין בגרסה הבאה.",
Intent.PROPERTY_UPDATE: "קיבלתי — עדכון מלאי נכסים יהיה זמין בגרסה הבאה.",
Intent.STATS_REQUEST: "קיבלתי — דוחות וסטטיסטיקות יהיו זמינים בגרסה הבאה.",
Intent.LEAD_QUALIFICATION: (
"קיבלתי — ההודעה הזו נראית כמו שיחה עם לקוח, "
"לא ממך. אם זו טעות תכתוב לי שוב."
),
}
def process_message(tenant_id: str, role: str, incoming_dict: dict[str, Any]) -> None:
try:
incoming = IncomingMessage(**incoming_dict)
tenant = db.get_tenant_by_id(tenant_id)
if tenant is None:
logger.warning("tenant לא נמצא: %s — מדלג", tenant_id)
return
if incoming.audio_id is not None:
try:
audio_bytes = whatsapp.download_media(incoming.audio_id)
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.audio.transcriptions.create(
model="whisper-1",
language="he",
file=("voice.ogg", audio_bytes, "audio/ogg"),
)
transcript = response.text
logger.info("🎤 תמלול קולי: %s", transcript)
incoming.body = f"[הודעה קולית] {transcript}"
except Exception:
logger.warning(
"תמלול הודעה קולית נכשל (audio_id=%s)",
incoming.audio_id,
exc_info=True,
)
incoming.body = (
"[הודעה קולית — לא הצלחתי לתמלל. "
"אנא שלח כהודעת טקסט]"
)
intent, intent_usage = agent.classify_intent(incoming.body)
db.log_usage(tenant_id, intent_usage, intent.value)
logger.info(
"📩 מעבד הודעה (tenant=%s, role=%s, intent=%s)",
tenant_id,
role,
intent.value,
)
if role == "customer":
_handle_customer(tenant, incoming, intent)
elif role == "agent":
_handle_agent(tenant, incoming, intent)
else:
logger.warning("תפקיד לא ידוע: %s", role)
except Exception:
logger.exception(
"שגיאה בעיבוד הודעה (tenant=%s, role=%s)", tenant_id, role
)
_notify_agent_of_error(tenant_id)
def _handle_customer(
tenant: Tenant, incoming: IncomingMessage, intent: Intent
) -> None:
tenant_id = str(tenant.id)
conversation = db.get_or_create_conversation(tenant_id, incoming.from_phone)
properties_summary = db.get_active_properties_summary(tenant_id)
existing_lead = conversation.lead_data
if agent.is_bant_complete(existing_lead) and not existing_lead.meeting_proposed:
bant_hint = (
"הלקוח השלים את כל פרטי החיפוש. שלב בסוף התשובה הצעה חמה וטבעית "
"לקבוע פגישה או ביקור בנכס — קצר, לא לחוץ."
)
else:
bant_hint = agent.get_bant_gap_question(existing_lead)
reply, resp_usage = agent.get_response(
tenant=tenant,
history=conversation.messages,
new_message=incoming.body,
properties_summary=properties_summary,
bant_gap_hint=bant_hint,
)
db.log_usage(tenant_id, resp_usage, intent.value)
whatsapp.send_text_message(incoming.from_phone, reply)
now = datetime.now(timezone.utc)
updated_messages = [
*conversation.messages,
ConversationMessage(
role="user", content=incoming.body, timestamp=incoming.timestamp
),
ConversationMessage(role="assistant", content=reply, timestamp=now),
]
lead_data, lead_usage = agent.extract_lead_data(updated_messages)
db.log_usage(tenant_id, lead_usage, intent.value)
# extract_lead_data לא מחלץ meeting_proposed (state פנימי) —
# שומרים את הדגל כדי לא להציע פגישה שוב בטורן הבא.
if existing_lead.meeting_proposed:
lead_data.meeting_proposed = True
elif agent.is_bant_complete(existing_lead):
# BANT היה שלם בכניסה לטורן → bant_hint כלל את הצעת הפגישה → כבר הוצע.
lead_data.meeting_proposed = True
score = agent.compute_lead_score(lead_data)
notified = conversation.notified_agent
if score >= LEAD_NOTIFICATION_THRESHOLD and not notified:
_notify_agent_of_lead(tenant, incoming.from_phone, lead_data, score)
from crm import send_crm_webhook
send_crm_webhook(tenant, lead_data, score, customer_phone=incoming.from_phone)
notified = True
db.save_conversation(
conversation_id=conversation.id,
messages=updated_messages,
lead_data=lead_data,
lead_score=score,
notified_agent=notified,
)
logger.info(
"✅ תגובה ללקוח נשלחה (tenant=%s, customer=%s, score=%d, notified=%s)",
tenant_id,
incoming.from_phone,
score,
notified,
)
def _handle_agent(
tenant: Tenant, incoming: IncomingMessage, intent: Intent
) -> None:
tenant_id = str(tenant.id)
if intent == Intent.AD_REQUEST:
try:
ad = copywriter.generate_ad(incoming.body, tenant_id)
reply = (
"✍️ *מודעת פייסבוק:*\n"
f"{ad['facebook']}\n\n"
"🏠 *מודעת יד2:*\n"
f"{ad['yad2']}"
)
except Exception:
logger.exception("generate_ad נכשל (tenant=%s)", tenant_id)
reply = (
"מצטער, לא הצלחתי לנסח את המודעה. "
"נסה שוב או פנה לתמיכה."
)
elif intent == Intent.GENERAL_QUESTION:
properties_summary = db.get_active_properties_summary(tenant_id)
reply, usage = agent.get_response(
tenant=tenant,
history=[],
new_message=incoming.body,
properties_summary=properties_summary,
)
db.log_usage(tenant_id, usage, intent.value)
else:
reply = AGENT_PLACEHOLDER_REPLIES.get(
intent, "קיבלתי — זה יגיע בגרסה הבאה."
)
whatsapp.send_text_message(incoming.from_phone, reply)
logger.info(
"✅ תגובה למתווך נשלחה (tenant=%s, intent=%s)", tenant_id, intent.value
)
def _notify_agent_of_lead(
tenant: Tenant, customer_phone: str, lead: LeadData, score: int
) -> None:
lines = [
f"🔥 ליד חם מ-{customer_phone}",
f"ציון: {score}/100",
"",
]
if lead.customer_name:
lines.append(f"שם: {lead.customer_name}")
if lead.budget:
lines.append(f"תקציב: {lead.budget}")
if lead.area_or_asset:
lines.append(f"אזור/נכס: {lead.area_or_asset}")
if lead.rooms:
lines.append(f"חדרים: {lead.rooms}")
if lead.condition_preference:
pref_map = {"renovated": "משופצת", "needs_work": "מוכן לשיפוץ", "both": "לא אכפת"}
lines.append(f"מצב דירה: {pref_map.get(lead.condition_preference, lead.condition_preference)}")
if lead.meeting_ready:
lines.append("מוכן לפגישה: כן")
if lead.gender:
gender_map = {"male": "זכר", "female": "נקבה", "couple": "זוג"}
lines.append(f"מגדר: {gender_map.get(lead.gender, lead.gender)}")
try:
whatsapp.send_text_message(tenant.phone, "\n".join(lines))
except Exception:
logger.exception(
"שליחת התראת ליד למתווך נכשלה (tenant=%s)", tenant.id
)
def _notify_agent_of_error(tenant_id: str) -> None:
try:
tenant = db.get_tenant_by_id(tenant_id)
if tenant is None:
return
whatsapp.send_text_message(
tenant.phone, "⚠️ שגיאה בטיפול בהודעה האחרונה. צוות התמיכה יבדוק."
)
except Exception:
logger.exception("גם הודעת השגיאה למתווך נכשלה (tenant=%s)", tenant_id)