letschatty 0.4.346__py3-none-any.whl → 0.4.348__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letschatty/models/ai_microservices/lambda_events.py +17 -4
- letschatty/models/chat/chat.py +11 -2
- letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py +5 -3
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +2 -34
- letschatty/models/company/assets/ai_agents_v2/statuses.py +33 -0
- letschatty/models/company/assets/chat_assets.py +12 -2
- letschatty/models/company/assets/sale.py +3 -3
- letschatty/models/company/form_field.py +9 -2
- letschatty/services/chat/chat_service.py +55 -12
- letschatty/services/validators/analytics_validator.py +11 -0
- {letschatty-0.4.346.dist-info → letschatty-0.4.348.dist-info}/METADATA +1 -1
- {letschatty-0.4.346.dist-info → letschatty-0.4.348.dist-info}/RECORD +14 -13
- {letschatty-0.4.346.dist-info → letschatty-0.4.348.dist-info}/LICENSE +0 -0
- {letschatty-0.4.346.dist-info → letschatty-0.4.348.dist-info}/WHEEL +0 -0
|
@@ -61,10 +61,23 @@ class SmartTaggingCallbackEvent(LambdaAiEvent):
|
|
|
61
61
|
callback_metadata: SmartTaggingCallbackMetadata
|
|
62
62
|
|
|
63
63
|
@model_validator(mode="before")
|
|
64
|
-
def
|
|
65
|
-
if isinstance(
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
def normalize_data(cls, values):
|
|
65
|
+
if not isinstance(values, dict):
|
|
66
|
+
return values
|
|
67
|
+
|
|
68
|
+
data = values.get("data")
|
|
69
|
+
if isinstance(data, str):
|
|
70
|
+
import json
|
|
71
|
+
try:
|
|
72
|
+
data = json.loads(data)
|
|
73
|
+
values["data"] = data
|
|
74
|
+
except (json.JSONDecodeError, TypeError):
|
|
75
|
+
return values
|
|
76
|
+
|
|
77
|
+
if isinstance(data, dict) and "chain_of_thought" in data:
|
|
78
|
+
data["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
|
|
79
|
+
|
|
80
|
+
return values
|
|
68
81
|
|
|
69
82
|
class ChatData(BaseModel):
|
|
70
83
|
chat_id: StrObjectId
|
letschatty/models/chat/chat.py
CHANGED
|
@@ -269,7 +269,17 @@ class Chat(CompanyAssetModel):
|
|
|
269
269
|
@property
|
|
270
270
|
def bought_product_ids(self) -> List[StrObjectId]:
|
|
271
271
|
"""Get all sale ids in the chat"""
|
|
272
|
-
|
|
272
|
+
product_ids: List[StrObjectId] = []
|
|
273
|
+
for sale in self.sales:
|
|
274
|
+
sale_product_ids = getattr(sale, "product_ids", None)
|
|
275
|
+
if sale_product_ids:
|
|
276
|
+
for product_id in sale_product_ids:
|
|
277
|
+
if product_id not in product_ids:
|
|
278
|
+
product_ids.append(product_id)
|
|
279
|
+
elif sale.product_id:
|
|
280
|
+
if sale.product_id not in product_ids:
|
|
281
|
+
product_ids.append(sale.product_id)
|
|
282
|
+
return product_ids
|
|
273
283
|
|
|
274
284
|
@property
|
|
275
285
|
def products(self) -> List[AssignedAssetToChat]:
|
|
@@ -366,4 +376,3 @@ class Chat(CompanyAssetModel):
|
|
|
366
376
|
dump["last_message"] = self.last_message.model_dump_json(serializer=SerializerType.DATABASE) if self.last_message else None
|
|
367
377
|
dump["flow_states"] = [flow_state.model_dump_json(serializer=SerializerType.DATABASE) for flow_state in self.flow_states]
|
|
368
378
|
return dump
|
|
369
|
-
|
|
@@ -84,12 +84,13 @@ class ChainOfThoughtInChatPreview(ChattyAssetPreview):
|
|
|
84
84
|
# trigger_id removed - trigger info is in: incoming_message_ids for user_message,
|
|
85
85
|
# triggered_by_user_id for manual_trigger, smart_follow_up_id for follow_up
|
|
86
86
|
chain_of_thought : Optional[str] = Field(default=None, description="The chain of thought")
|
|
87
|
+
confidence: Optional[int] = Field(default=None, ge=0, le=100, description="Confidence 0-100")
|
|
87
88
|
status: Optional[ChainOfThoughtInChatStatus] = Field(default=None, description="The status of the chain of thought")
|
|
88
89
|
name: str = Field(description="A title for the chain of thought")
|
|
89
90
|
|
|
90
91
|
@classmethod
|
|
91
92
|
def get_projection(cls) -> dict[str, Any]:
|
|
92
|
-
return super().get_projection() | {"chat_id": 1, "trigger": 1, "chain_of_thought": 1, "name": 1, "status": 1}
|
|
93
|
+
return super().get_projection() | {"chat_id": 1, "trigger": 1, "chain_of_thought": 1, "confidence": 1, "name": 1, "status": 1}
|
|
93
94
|
|
|
94
95
|
@classmethod
|
|
95
96
|
def from_dict(cls, data: dict) -> 'ChainOfThoughtInChatPreview':
|
|
@@ -103,6 +104,7 @@ class ChainOfThoughtInChatPreview(ChattyAssetPreview):
|
|
|
103
104
|
chat_id=asset.chat_id,
|
|
104
105
|
trigger=asset.trigger,
|
|
105
106
|
chain_of_thought=asset.chain_of_thought,
|
|
107
|
+
confidence=asset.confidence,
|
|
106
108
|
status=asset.status,
|
|
107
109
|
company_id=asset.company_id,
|
|
108
110
|
created_at=asset.created_at,
|
|
@@ -121,6 +123,7 @@ class ChainOfThoughtInChat(CompanyAssetModel):
|
|
|
121
123
|
chatty_ai_agent_id : StrObjectId = Field(description="The chatty ai agent id")
|
|
122
124
|
chatty_ai_agent : Dict[str, Any] = Field(description="The chatty ai agent at the moment of triggering the chain of thought", exclude=True,default_factory=dict)
|
|
123
125
|
chain_of_thought : Optional[str] = Field(default=None, description="The chain of thought")
|
|
126
|
+
confidence: Optional[int] = Field(default=None, ge=0, le=100, description="Confidence 0-100")
|
|
124
127
|
name: str = Field(description="A title for the chain of thought", alias="title")
|
|
125
128
|
preview_class: ClassVar[Type[ChattyAssetPreview]] = ChainOfThoughtInChatPreview
|
|
126
129
|
|
|
@@ -128,5 +131,4 @@ class ChainOfThoughtInChat(CompanyAssetModel):
|
|
|
128
131
|
if self.chain_of_thought is None:
|
|
129
132
|
self.chain_of_thought = cot
|
|
130
133
|
else:
|
|
131
|
-
self.chain_of_thought += f"\n{cot}"
|
|
132
|
-
|
|
134
|
+
self.chain_of_thought += f"\n{cot}"
|
|
@@ -7,15 +7,16 @@ independently without loading entire chat documents.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from letschatty.models.analytics.events.base import EventType
|
|
10
|
+
from enum import StrEnum
|
|
10
11
|
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
11
12
|
from datetime import datetime
|
|
12
13
|
from zoneinfo import ZoneInfo
|
|
13
14
|
from typing import Optional, ClassVar, List, Dict, Any
|
|
14
|
-
from enum import StrEnum
|
|
15
15
|
from letschatty.models.base_models.chatty_asset_model import CompanyAssetModel, ChattyAssetPreview
|
|
16
16
|
from letschatty.models.utils.types.identifier import StrObjectId
|
|
17
17
|
from .chain_of_thought_in_chat import ChainOfThoughtInChatTrigger
|
|
18
18
|
from .chatty_ai_mode import ChattyAIMode
|
|
19
|
+
from .statuses import DataCollectionStatus, PreQualifyStatus
|
|
19
20
|
import logging
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
@@ -45,38 +46,6 @@ class HumanInterventionReason(StrEnum):
|
|
|
45
46
|
SOMETHING_WENT_WRONG = "something_went_wrong"
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
class DataCollectionStatus(StrEnum):
|
|
49
|
-
"""
|
|
50
|
-
Status of data collection for the AI agent in this chat.
|
|
51
|
-
Only tracks field collection, not qualification status.
|
|
52
|
-
|
|
53
|
-
- COLLECTING: Still collecting data from the user
|
|
54
|
-
- MANDATORY_COMPLETED: All mandatory fields have been collected
|
|
55
|
-
- ALL_COMPLETED: All fields (mandatory + optional) have been collected
|
|
56
|
-
- CANCELLED: Data collection was cancelled
|
|
57
|
-
"""
|
|
58
|
-
COLLECTING = "collecting"
|
|
59
|
-
MANDATORY_COMPLETED = "mandatory_completed"
|
|
60
|
-
ALL_COMPLETED = "all_completed"
|
|
61
|
-
CANCELLED = "cancelled"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class PreQualifyStatus(StrEnum):
|
|
65
|
-
"""
|
|
66
|
-
Status of pre-qualification for the AI agent in this chat.
|
|
67
|
-
Separate from data collection - tracks qualification evaluation.
|
|
68
|
-
|
|
69
|
-
- PENDING: Waiting for data collection to complete
|
|
70
|
-
- EVALUATING: Data collected, evaluating acceptance criteria
|
|
71
|
-
- QUALIFIED: User met acceptance criteria
|
|
72
|
-
- UNQUALIFIED: User did NOT meet acceptance criteria
|
|
73
|
-
"""
|
|
74
|
-
PENDING = "pending"
|
|
75
|
-
EVALUATING = "evaluating"
|
|
76
|
-
QUALIFIED = "qualified"
|
|
77
|
-
UNQUALIFIED = "unqualified"
|
|
78
|
-
|
|
79
|
-
|
|
80
49
|
class HumanIntervention(BaseModel):
|
|
81
50
|
"""
|
|
82
51
|
Reason for human intervention
|
|
@@ -352,4 +321,3 @@ class ChattyAIAgentInChat(CompanyAssetModel):
|
|
|
352
321
|
"""Cancel data collection"""
|
|
353
322
|
self.data_collection_status = DataCollectionStatus.CANCELLED
|
|
354
323
|
self.updated_at = datetime.now(ZoneInfo("UTC"))
|
|
355
|
-
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataCollectionStatus(StrEnum):
|
|
5
|
+
"""
|
|
6
|
+
Status of data collection for the AI agent in this chat.
|
|
7
|
+
Only tracks field collection, not qualification status.
|
|
8
|
+
|
|
9
|
+
- COLLECTING: Still collecting data from the user
|
|
10
|
+
- MANDATORY_COMPLETED: All mandatory fields have been collected
|
|
11
|
+
- ALL_COMPLETED: All fields (mandatory + optional) have been collected
|
|
12
|
+
- CANCELLED: Data collection was cancelled
|
|
13
|
+
"""
|
|
14
|
+
COLLECTING = "collecting"
|
|
15
|
+
MANDATORY_COMPLETED = "mandatory_completed"
|
|
16
|
+
ALL_COMPLETED = "all_completed"
|
|
17
|
+
CANCELLED = "cancelled"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PreQualifyStatus(StrEnum):
|
|
21
|
+
"""
|
|
22
|
+
Status of pre-qualification for the AI agent in this chat.
|
|
23
|
+
Separate from data collection - tracks qualification evaluation.
|
|
24
|
+
|
|
25
|
+
- PENDING: Waiting for data collection to complete
|
|
26
|
+
- EVALUATING: Data collected, evaluating acceptance criteria
|
|
27
|
+
- QUALIFIED: User met acceptance criteria
|
|
28
|
+
- UNQUALIFIED: User did NOT meet acceptance criteria
|
|
29
|
+
"""
|
|
30
|
+
PENDING = "pending"
|
|
31
|
+
EVALUATING = "evaluating"
|
|
32
|
+
QUALIFIED = "qualified"
|
|
33
|
+
UNQUALIFIED = "unqualified"
|
|
@@ -4,12 +4,13 @@ from enum import StrEnum
|
|
|
4
4
|
from pydantic_core.core_schema import str_schema
|
|
5
5
|
|
|
6
6
|
from letschatty.models.company.assets.ai_agents_v2.chain_of_thought_in_chat import ChainOfThoughtInChatTrigger
|
|
7
|
+
from letschatty.models.company.assets.ai_agents_v2.statuses import DataCollectionStatus, PreQualifyStatus
|
|
7
8
|
from ...utils.types.identifier import StrObjectId
|
|
8
9
|
from datetime import datetime
|
|
9
10
|
from zoneinfo import ZoneInfo
|
|
10
11
|
from bson import ObjectId
|
|
11
12
|
import json
|
|
12
|
-
from typing import Dict, Any, Optional
|
|
13
|
+
from typing import Dict, Any, Optional, List
|
|
13
14
|
from letschatty.models.utils.types.serializer_type import SerializerType
|
|
14
15
|
from letschatty.models.company.assets.ai_agents_v2.chatty_ai_mode import ChattyAIMode
|
|
15
16
|
|
|
@@ -66,7 +67,8 @@ class AssignedAssetToChat(BaseModel):
|
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
class SaleAssignedToChat(AssignedAssetToChat):
|
|
69
|
-
product_id: StrObjectId = Field(
|
|
70
|
+
product_id: Optional[StrObjectId] = Field(default=None)
|
|
71
|
+
product_ids: List[StrObjectId] = Field(default_factory=list)
|
|
70
72
|
|
|
71
73
|
class ContactPointAssignedToChat(AssignedAssetToChat):
|
|
72
74
|
source_id: StrObjectId = Field(frozen=True)
|
|
@@ -75,6 +77,14 @@ class ChattyAIAgentAssignedToChat(AssignedAssetToChat):
|
|
|
75
77
|
mode: ChattyAIMode = Field(default=ChattyAIMode.OFF)
|
|
76
78
|
requires_human_intervention: bool = Field(default=False)
|
|
77
79
|
is_processing: bool = Field(default=False)
|
|
80
|
+
data_collection_status: Optional[DataCollectionStatus] = Field(
|
|
81
|
+
default=None,
|
|
82
|
+
description="Status of data collection for pre-qualification"
|
|
83
|
+
)
|
|
84
|
+
pre_qualify_status: Optional[PreQualifyStatus] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Status of pre-qualification"
|
|
87
|
+
)
|
|
78
88
|
last_call_started_at: Optional[datetime] = Field(default=None, description="The timestamp of the get chat with prompt (the moment n8n started processing the call)")
|
|
79
89
|
trigger_timestamp: Optional[datetime] = Field(default=None, description="The timestamp of the trigger that started the call, if it's a manual trigger, it will be the timestamp of the manual trigger, if it's a follow up, it will be the timestamp of the follow up, if it's a user message, it will be the timestamp of the user message")
|
|
80
90
|
last_call_cot_id: Optional[StrObjectId] = Field(default=None)
|
|
@@ -5,7 +5,7 @@ from ...utils.types.identifier import StrObjectId
|
|
|
5
5
|
|
|
6
6
|
class Sale(CompanyAssetModel):
|
|
7
7
|
chat_id: StrObjectId
|
|
8
|
-
product_id: StrObjectId
|
|
8
|
+
product_id: Optional[StrObjectId] = Field(default=None)
|
|
9
9
|
quantity: int
|
|
10
10
|
total_amount: float
|
|
11
11
|
currency: str
|
|
@@ -16,7 +16,7 @@ class Sale(CompanyAssetModel):
|
|
|
16
16
|
creator_id: StrObjectId
|
|
17
17
|
|
|
18
18
|
class SaleRequest(BaseModel):
|
|
19
|
-
product_id: StrObjectId
|
|
19
|
+
product_id: Optional[StrObjectId] = Field(default=None)
|
|
20
20
|
quantity: int
|
|
21
21
|
creator_id: StrObjectId
|
|
22
22
|
total_amount: float
|
|
@@ -36,4 +36,4 @@ class SaleRequest(BaseModel):
|
|
|
36
36
|
"paid_amount": 100,
|
|
37
37
|
"installments": 1,
|
|
38
38
|
"details": {}
|
|
39
|
-
}
|
|
39
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field, field_validator
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
2
2
|
from typing import Optional, Any, ClassVar, List
|
|
3
3
|
from letschatty.models.base_models.chatty_asset_model import CompanyAssetModel, ChattyAssetPreview
|
|
4
4
|
import re
|
|
@@ -212,7 +212,11 @@ class CollectedData(BaseModel):
|
|
|
212
212
|
name: Optional[str] = Field(default=None, description="Customer's name")
|
|
213
213
|
email: Optional[str] = Field(default=None, description="Customer's email address")
|
|
214
214
|
phone: Optional[str] = Field(default=None, description="Customer's phone number")
|
|
215
|
-
document_id: Optional[str] = Field(
|
|
215
|
+
document_id: Optional[str] = Field(
|
|
216
|
+
default=None,
|
|
217
|
+
alias="dni",
|
|
218
|
+
description="Customer's DNI/ID number"
|
|
219
|
+
)
|
|
216
220
|
|
|
217
221
|
# Generic key-value store for any other collected fields
|
|
218
222
|
additional_fields: dict[str, Any] = Field(
|
|
@@ -220,6 +224,9 @@ class CollectedData(BaseModel):
|
|
|
220
224
|
description="Additional collected fields as key-value pairs"
|
|
221
225
|
)
|
|
222
226
|
|
|
227
|
+
model_config = ConfigDict(
|
|
228
|
+
populate_by_name=True
|
|
229
|
+
)
|
|
223
230
|
|
|
224
231
|
@classmethod
|
|
225
232
|
def example(cls) -> dict:
|
|
@@ -211,6 +211,25 @@ class ChatService:
|
|
|
211
211
|
ChatService.add_central_notification_from_text(chat=chat, body=f"Agente de IA {chatty_ai_agent.name} actualizado en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.CHATTY_AI_AGENT_UPDATED)
|
|
212
212
|
return chat.chatty_ai_agent
|
|
213
213
|
|
|
214
|
+
@staticmethod
|
|
215
|
+
def escalate_chatty_ai_agent(chat: Chat, execution_context: ExecutionContext, message: Optional[str] = None) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Mark the chat's AI agent as requiring human intervention and add a central notification.
|
|
218
|
+
"""
|
|
219
|
+
if chat.chatty_ai_agent and not chat.chatty_ai_agent.requires_human_intervention:
|
|
220
|
+
chat.chatty_ai_agent.requires_human_intervention = True
|
|
221
|
+
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
222
|
+
body = "El chat fue escalado a un agente humano"
|
|
223
|
+
if message:
|
|
224
|
+
body = f"{body}. Motivo: {message}"
|
|
225
|
+
ChatService.add_central_notification_from_text(
|
|
226
|
+
chat=chat,
|
|
227
|
+
body=body,
|
|
228
|
+
subtype=MessageSubtype.CHATTY_AI_AGENT_NOTIFICATION,
|
|
229
|
+
content_status=CentralNotificationStatus.WARNING,
|
|
230
|
+
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
231
|
+
)
|
|
232
|
+
|
|
214
233
|
@staticmethod
|
|
215
234
|
def add_workflow_link(chat : Chat, link : LinkItem, flow:FlowPreview, execution_context: ExecutionContext, description: str, last_incoming_message_id: Optional[str] = None, next_call: Optional[datetime] = None) -> FlowStateAssignedToChat:
|
|
216
235
|
"""
|
|
@@ -266,36 +285,46 @@ class ChatService:
|
|
|
266
285
|
return next((state for state in chat.flow_states if state.is_smart_follow_up), None)
|
|
267
286
|
|
|
268
287
|
@staticmethod
|
|
269
|
-
def create_sale(
|
|
288
|
+
def create_sale(
|
|
289
|
+
chat: Chat,
|
|
290
|
+
execution_context: ExecutionContext,
|
|
291
|
+
sale: Sale,
|
|
292
|
+
product: Optional[Product],
|
|
293
|
+
product_ids: Optional[List[StrObjectId]] = None,
|
|
294
|
+
product_label: Optional[str] = None
|
|
295
|
+
) -> SaleAssignedToChat:
|
|
270
296
|
"""
|
|
271
297
|
Add a sale to the chat.
|
|
272
298
|
"""
|
|
273
299
|
if next((sale for sale in chat.client.sales if sale.asset_id == sale.id), None) is not None:
|
|
274
300
|
raise AssetAlreadyAssigned(f"Sale with id {sale.id} already assigned to chat {chat.id}")
|
|
301
|
+
label = product_label or (product.name if product else "multiples productos")
|
|
302
|
+
assigned_product_ids = product_ids or ([product.id] if product else [])
|
|
275
303
|
assigned_asset = SaleAssignedToChat(
|
|
276
304
|
asset_type=ChatAssetType.SALE,
|
|
277
305
|
asset_id=sale.id,
|
|
278
306
|
assigned_at=sale.created_at,
|
|
279
307
|
assigned_by=execution_context.executor.id,
|
|
280
|
-
product_id=product.id
|
|
308
|
+
product_id=product.id if product else None,
|
|
309
|
+
product_ids=assigned_product_ids
|
|
281
310
|
)
|
|
282
311
|
execution_context.set_event_time(assigned_asset.assigned_at)
|
|
283
312
|
bisect.insort(chat.client.sales, assigned_asset)
|
|
284
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta de {
|
|
285
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
313
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta de {label}", description=f"Venta de {label} creada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_ADDED))
|
|
314
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {label} agregada al chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_ADDED)
|
|
286
315
|
return assigned_asset
|
|
287
316
|
|
|
288
317
|
@staticmethod
|
|
289
|
-
def update_sale(chat
|
|
318
|
+
def update_sale(chat: Chat, execution_context: ExecutionContext, sale: Sale, product_label: str) -> Sale:
|
|
290
319
|
"""
|
|
291
320
|
Update a sale for the chat.
|
|
292
321
|
"""
|
|
293
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta actualizada de {
|
|
294
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
322
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta actualizada de {product_label}", description=f"Venta de {product_label} actualizada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_UPDATED))
|
|
323
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product_label} actualizada en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_UPDATED)
|
|
295
324
|
return sale
|
|
296
325
|
|
|
297
326
|
@staticmethod
|
|
298
|
-
def delete_sale(chat
|
|
327
|
+
def delete_sale(chat: Chat, execution_context: ExecutionContext, sale_id: StrObjectId, product_label: str) -> SaleAssignedToChat:
|
|
299
328
|
"""
|
|
300
329
|
Logically remove a sale from the chat.
|
|
301
330
|
"""
|
|
@@ -303,8 +332,8 @@ class ChatService:
|
|
|
303
332
|
assigned_asset_to_remove = next(sale for sale in chat.client.sales if sale.asset_id == sale_id)
|
|
304
333
|
chat.client.sales.remove(assigned_asset_to_remove)
|
|
305
334
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
306
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta eliminada de {
|
|
307
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
335
|
+
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta eliminada de {product_label}", description=f"Venta de {product_label} eliminada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_DELETED))
|
|
336
|
+
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product_label} eliminada del chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_DELETED)
|
|
308
337
|
return assigned_asset_to_remove
|
|
309
338
|
except StopIteration:
|
|
310
339
|
raise NotFoundError(message=f"Sale with id {sale_id} not found in chat {chat.id}")
|
|
@@ -849,6 +878,13 @@ class ChatService:
|
|
|
849
878
|
chat.client.email = collected_data.email
|
|
850
879
|
updated_fields.append("email")
|
|
851
880
|
|
|
881
|
+
if collected_data.phone:
|
|
882
|
+
if chat.client.lead_form_data is None:
|
|
883
|
+
chat.client.lead_form_data = {}
|
|
884
|
+
if chat.client.lead_form_data.get("phone") != collected_data.phone:
|
|
885
|
+
chat.client.lead_form_data["phone"] = collected_data.phone
|
|
886
|
+
updated_fields.append("phone")
|
|
887
|
+
|
|
852
888
|
if collected_data.document_id and chat.client.document_id != collected_data.document_id:
|
|
853
889
|
chat.client.document_id = collected_data.document_id
|
|
854
890
|
updated_fields.append("document_id")
|
|
@@ -866,11 +902,18 @@ class ChatService:
|
|
|
866
902
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
867
903
|
logger.info(f"Updated collected data for chat {chat.id}: {', '.join(updated_fields)}")
|
|
868
904
|
|
|
905
|
+
field_label_map = {
|
|
906
|
+
"name": "nombre",
|
|
907
|
+
"email": "email",
|
|
908
|
+
"phone": "telefono",
|
|
909
|
+
"document_id": "dni",
|
|
910
|
+
}
|
|
911
|
+
display_fields = [field_label_map.get(field, field) for field in updated_fields]
|
|
869
912
|
ChatService.add_central_notification_from_text(
|
|
870
913
|
chat=chat,
|
|
871
|
-
body=f"
|
|
914
|
+
body=f"Datos del cliente recopilados: {', '.join(display_fields)}",
|
|
872
915
|
subtype=MessageSubtype.CLIENT_INFO_UPDATED,
|
|
873
916
|
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
874
917
|
)
|
|
875
918
|
|
|
876
|
-
return chat.client.lead_form_data
|
|
919
|
+
return chat.client.lead_form_data
|
|
@@ -10,6 +10,10 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
class SourcesAndTopicsValidator:
|
|
12
12
|
"""This class provides methods to validate sources and topics."""
|
|
13
|
+
@staticmethod
|
|
14
|
+
def normalize_source_name(name: str) -> str:
|
|
15
|
+
return name.strip().lower()
|
|
16
|
+
|
|
13
17
|
@staticmethod
|
|
14
18
|
def source_validation_check(sources : Dict[str,Source], topics : Dict[str,Topic], source : Source, existing_source_id : str | None = None) -> None:
|
|
15
19
|
"""Checks validation of a source against other sources and topics."""
|
|
@@ -28,9 +32,16 @@ class SourcesAndTopicsValidator:
|
|
|
28
32
|
"""This method checks that no duplicated source is created.
|
|
29
33
|
For Pure Ads, it checks that the ad_id is unique and doesn't exist already.
|
|
30
34
|
For Other Sources, it checks that exact trigger doesn't exist already. """
|
|
35
|
+
normalized_name = SourcesAndTopicsValidator.normalize_source_name(source.name)
|
|
36
|
+
existing_source_name = None
|
|
37
|
+
if existing_source_id and existing_source_id in sources:
|
|
38
|
+
existing_source_name = SourcesAndTopicsValidator.normalize_source_name(sources[existing_source_id].name)
|
|
39
|
+
should_check_name = existing_source_name is None or normalized_name != existing_source_name
|
|
31
40
|
for source_id, source_to_check in sources.items():
|
|
32
41
|
if source_id == existing_source_id:
|
|
33
42
|
continue
|
|
43
|
+
if should_check_name and SourcesAndTopicsValidator.normalize_source_name(source_to_check.name) == normalized_name:
|
|
44
|
+
raise ConflictedSource(f":warning: *Conflict while adding source: Name already exists* \n New source '{source.name}' \n- Id {source.id} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id}", conflicting_source_id=source_to_check.id)
|
|
34
45
|
if source == source_to_check: #type: ignore
|
|
35
46
|
if isinstance(source, OtherSource):
|
|
36
47
|
raise ConflictedSource(f":warning: *Conflict while adding source: Trigger already exists* \n New source '{source.name}' \n- Id {source.id} \n- Trigger {source.trigger[:30]} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id} \n- Trigger '{source_to_check.trigger[:30]}'", conflicting_source_id=source_to_check.id)
|
|
@@ -2,7 +2,7 @@ letschatty/__init__.py,sha256=6dGYdy5edB1dHdgvpqUpENZ347CpIyWgR1CsGYwPSk8,45
|
|
|
2
2
|
letschatty/models/__init__.py,sha256=obWHa796C-O1mqo_lk22KhUC2ODVvG7cICY3Lo-AqJg,126
|
|
3
3
|
letschatty/models/ai_microservices/__init__.py,sha256=h0xLiuU1wfHpDl1oKCzVKUef3ROYU7qIyVxBsdcdZe0,641
|
|
4
4
|
letschatty/models/ai_microservices/expected_output.py,sha256=PWXvmGiOhqAuOVCZHhfvUMzb8BwSnAoys4CReJ9dIK4,9441
|
|
5
|
-
letschatty/models/ai_microservices/lambda_events.py,sha256=
|
|
5
|
+
letschatty/models/ai_microservices/lambda_events.py,sha256=ELIjD95MM4t1EdeHUTxCT8qkUWJFwqu9Q9dwGnQQxWE,14626
|
|
6
6
|
letschatty/models/ai_microservices/lambda_invokation_types.py,sha256=KWvXFvHhwYMaAIHbGpdW8AYE0eqrfCgZ_28jIyGXoME,1918
|
|
7
7
|
letschatty/models/ai_microservices/n8n_ai_agents_payload.py,sha256=E5tu2UcSJCybMSvxfZG3JIxSxHbTBbjdRE74cnRtlGY,696
|
|
8
8
|
letschatty/models/ai_microservices/openai_payloads.py,sha256=HauUGf4c37khJJsKyH4ikxzHKboGWnerjDQgDOnMFrY,271
|
|
@@ -64,7 +64,7 @@ letschatty/models/base_models/update_model_interface.py,sha256=80YuvkEM5Rd3Ycysq
|
|
|
64
64
|
letschatty/models/base_models/validate_timestamps_and_id.py,sha256=WRtaoW2EYtItgZjnNOYAffxy9K7M_NMNJ5imRN8Iu80,1971
|
|
65
65
|
letschatty/models/channels/channel.py,sha256=Mgqigm-3uRDozxBIZZYj62PQwtnB0rAKoY2JS4477n8,2029
|
|
66
66
|
letschatty/models/chat/assets_assigned_to_chat.py,sha256=nAdSzRvUSWa8-Plw6KZKnpUxxjCdWs_grml0bhskSis,192
|
|
67
|
-
letschatty/models/chat/chat.py,sha256=
|
|
67
|
+
letschatty/models/chat/chat.py,sha256=P6c8JGutMsk5GF526s0RxnT_F6oBclofPtTzjcnNUrc,16749
|
|
68
68
|
letschatty/models/chat/chat_status_modifications.py,sha256=LcwXNl4WRllPI_ZYKcg5ANRjmSk4Cykkra0naayMAt4,317
|
|
69
69
|
letschatty/models/chat/chat_with_assets.py,sha256=K4UUwdfLw_Q0ZNHdeoqavqyHkZwisAQOi1OPMBYszOU,814
|
|
70
70
|
letschatty/models/chat/client.py,sha256=oa7PM76V-OiBJL4T-IMytPW0WZdtvh9vFZhNtRQvt6Q,2196
|
|
@@ -83,12 +83,12 @@ letschatty/models/company/__init__.py,sha256=N_I-kRMHrtnp00c_bbFSiYYM47HJRS-d5sl
|
|
|
83
83
|
letschatty/models/company/assets/__init__.py,sha256=z0xN_lt1D76bXt8DXVdbH3GkofUPQPZU9NioRSLt_lI,244
|
|
84
84
|
letschatty/models/company/assets/ai_agents_v2/ai_agent_message_draft.py,sha256=xbshA34RSjHm2g8J7hW2FkWo-Qm8MH2HTbcRcoYmyvc,2076
|
|
85
85
|
letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py,sha256=aEKZCOsGiFFPSx23fkS5Khfsxo-r8JGk3O0sxiGs8T0,5876
|
|
86
|
-
letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py,sha256=
|
|
86
|
+
letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py,sha256=xK7IbM_sEN7_-SZXcgiRyALq6IjYaJ-ivq9BE8B8Bdg,6129
|
|
87
87
|
letschatty/models/company/assets/ai_agents_v2/chat_example.py,sha256=yCKt6ifNYch3C78zAvj8To0_Sb9CPAZ8sC-hyoBPa4s,1816
|
|
88
88
|
letschatty/models/company/assets/ai_agents_v2/chat_example_test.py,sha256=vChD-TkX1ORRD9LMbd2HlpbK4QyrsfhDiJd-LDjIqlk,4618
|
|
89
89
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent.py,sha256=R2rCANri8ZiqnmPP10PWoanGddMImCMxrl78Y6d-194,8204
|
|
90
90
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_config_for_automation.py,sha256=W8orpgp-yG8OHNWGQ16jMv-VgM_Z-Hk0q8PtC0KT2KU,388
|
|
91
|
-
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py,sha256=
|
|
91
|
+
letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py,sha256=akCa10TtiJ1wsL33z4uxSwhZyyinnJZmdlYJ9csLak8,14470
|
|
92
92
|
letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py,sha256=CKlKI3t_D5eWJPn6hRvN4_V8Wu6W8c0x_EFQlpA50F4,2521
|
|
93
93
|
letschatty/models/company/assets/ai_agents_v2/context_item.py,sha256=9629_sJ4vjfF6--KR44E6lqVpc0uOGSlcorKS5ooSXU,438
|
|
94
94
|
letschatty/models/company/assets/ai_agents_v2/faq.py,sha256=qL-sr17ALvJ9PEzpaXpYL91-bQ9Np3C4jCYe-zfot6o,457
|
|
@@ -99,10 +99,11 @@ letschatty/models/company/assets/ai_agents_v2/n8n_agents_info.py,sha256=UNsYXznX
|
|
|
99
99
|
letschatty/models/company/assets/ai_agents_v2/n8n_schema_incoming_message_output.json,sha256=_NERM9gJ4Ry8NFVb8mHxRmdv0GfBrHdtmKxVJWmTUrY,2102
|
|
100
100
|
letschatty/models/company/assets/ai_agents_v2/n8n_schema_smart_follow_up_output.json,sha256=QAPRJXin-sE9NxQBHmmErSYHL6dQzmu4i0HEFSSXOi4,2350
|
|
101
101
|
letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py,sha256=o9kKywbnm1lEliTTV0G7oSCFroyqp_w0GkypL--sSk4,4181
|
|
102
|
+
letschatty/models/company/assets/ai_agents_v2/statuses.py,sha256=nhIGfeKednLNFZCIMavbZptT_9L-HDDVshAyCLdwPe8,1109
|
|
102
103
|
letschatty/models/company/assets/assignment/__init__.py,sha256=eWYZfaDQde5OJNIDed8D-LNRXOa5O_O4mGMToNFtaW8,239
|
|
103
104
|
letschatty/models/company/assets/assignment/assignment_assets.py,sha256=phIJqNh4UGTU-Hux_kaOYhJm2GCqv37AnCGePSDVKmM,2245
|
|
104
105
|
letschatty/models/company/assets/automation.py,sha256=RQFOvM-lipBdalfsRSZ8K5opytVgq_GS6YSR0Mz5Qs8,1639
|
|
105
|
-
letschatty/models/company/assets/chat_assets.py,sha256=
|
|
106
|
+
letschatty/models/company/assets/chat_assets.py,sha256=kUnV1ngN1OA7AgED85ue91TOiFT1Fmf3LDOPbHAf460,8016
|
|
106
107
|
letschatty/models/company/assets/chatty_fast_answers/__init__.py,sha256=tbgWS0n1i0nVi9f79U44GPRnrW25NKcjl6Port92alk,48
|
|
107
108
|
letschatty/models/company/assets/chatty_fast_answers/chatty_fast_answer.py,sha256=PxW3eStHXo0BY7Z9hMDqBTHmgO7XcwtOvEpwXi-rA5g,1076
|
|
108
109
|
letschatty/models/company/assets/company_assets.py,sha256=9Or3hdUsWO0YCLqlwUb0Zcm_c5OerJqDx7rrrUK4_-E,800
|
|
@@ -116,7 +117,7 @@ letschatty/models/company/assets/launch/subscription.py,sha256=BnZuNK0jMOzn0s9zg
|
|
|
116
117
|
letschatty/models/company/assets/media/__init__.py,sha256=o22MHvoqGBUeG1P383KfBfsu-Fg0YdVh9FYeNB1j14s,38
|
|
117
118
|
letschatty/models/company/assets/media/file.py,sha256=MFhTNe8zsgjR1zMm2UIAznp4vtD4U12UMfu7mjFIGfE,1902
|
|
118
119
|
letschatty/models/company/assets/product.py,sha256=02eAZlE74nRzwOPAmbCHkQj7czVNgEtJQNzWxSBPP_w,2406
|
|
119
|
-
letschatty/models/company/assets/sale.py,sha256=
|
|
120
|
+
letschatty/models/company/assets/sale.py,sha256=8YiZoRHA0l6-JHX4N0jDe8PDu5Kkw1O2tZLDNOGL8p8,1143
|
|
120
121
|
letschatty/models/company/assets/tag.py,sha256=P1jRAvaebEtdAUs-07sRbCK25vmS6Q5NNtH7UX7bK-0,1008
|
|
121
122
|
letschatty/models/company/assets/users/agent_chats_snapshot.py,sha256=CTNRN8p9HE57bvxI8MbO2krg8KD3yIo3PmFjIV0iVWg,568
|
|
122
123
|
letschatty/models/company/assets/users/user.py,sha256=JWABDnvc9qhFnFcqBWSH2OAmVny37pUMBxo2CJGOMtA,9019
|
|
@@ -126,7 +127,7 @@ letschatty/models/company/company_chats_snapshot.py,sha256=Mg9Wmu3pfE-t5Sf57FCj-
|
|
|
126
127
|
letschatty/models/company/company_messaging_settgins.py,sha256=7isXt7gmqw-FKSUSZwdctdXDFMfPi2OyPuWqB7WpC6c,957
|
|
127
128
|
letschatty/models/company/conversation_topic.py,sha256=__IinJ3YcUsiApF4Em5uAgd01ydRUrlCVxaat-pCCRg,1704
|
|
128
129
|
letschatty/models/company/empresa.py,sha256=2INPC8YSZJcT10Gny_zXV_PujeqUGccsZHV31ZSywyw,5961
|
|
129
|
-
letschatty/models/company/form_field.py,sha256=
|
|
130
|
+
letschatty/models/company/form_field.py,sha256=LGyMLybwvfEPG3hMmwsoP_4wnMaPJbVFpG6c1HdVC8E,9802
|
|
130
131
|
letschatty/models/company/insight.py,sha256=B7BL07E4Z1b9aJHi3PXC1atln4-7fSo9JeqgQoeB_ao,7459
|
|
131
132
|
letschatty/models/company/notifications/notification.py,sha256=wE7rIi21nZno6jjIxajMz4e7OJbzrDHjH1KdkNzJiF8,1907
|
|
132
133
|
letschatty/models/copilot/links.py,sha256=mcddNR6WdWOoOr3NgDl_FElxF15SiZZXw9wmIV08HRw,185
|
|
@@ -230,7 +231,7 @@ letschatty/services/ai_agents/tool_descriptions/human_handover.json,sha256=-b3l_
|
|
|
230
231
|
letschatty/services/ai_agents/tool_descriptions.py,sha256=MMFhcgEl6Z7k8BUv3Yx1pCxHzIjc8HD93S0uM5OPj8Y,105
|
|
231
232
|
letschatty/services/ai_components_service.py,sha256=QUb-NdzHj4nPmnceVSQ9Pe3swFWUrkUFKzDZeuP0b5o,3762
|
|
232
233
|
letschatty/services/chat/chat_extractors.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
233
|
-
letschatty/services/chat/chat_service.py,sha256=
|
|
234
|
+
letschatty/services/chat/chat_service.py,sha256=75ebdL9C1k4NU_zfTsGqSi31H0Gv1yqB-IOHJK6GyQY,49439
|
|
234
235
|
letschatty/services/chat/client_service.py,sha256=TtlG0GJDqx6YemfueiJzAtMNk_udnPhg0MJG0bKwnk8,1097
|
|
235
236
|
letschatty/services/chat/conversation_topics_service.py,sha256=jvIkpHTCz0JKXui3n_BRs6UQ-TB3x7DrQsnX2zUVFVw,2461
|
|
236
237
|
letschatty/services/chatty_assets/__init__.py,sha256=quOXMGvcDVRkbeaGfCQliFfZzis5m1wrfJkHqDkWRx8,132
|
|
@@ -274,8 +275,8 @@ letschatty/services/scheduled_messages_service/scheduled_messages_helpers.py,sha
|
|
|
274
275
|
letschatty/services/template_campaigns/template_campaign_service.py,sha256=jORgDncuXJ5ZV4fr_KsfKBHOz_lypIy6lcNMWMw8OPw,4038
|
|
275
276
|
letschatty/services/users/agent_service.py,sha256=hIkUUJ1SpkKbh5_uo4i2CeqGtuMTjU7tSV8k5J7WPG4,279
|
|
276
277
|
letschatty/services/users/user_factory.py,sha256=FCB9uiAfjMeYfh4kMdx5h8VDHJ8MCsD-uaxW3X3KaWM,6681
|
|
277
|
-
letschatty/services/validators/analytics_validator.py,sha256
|
|
278
|
-
letschatty-0.4.
|
|
279
|
-
letschatty-0.4.
|
|
280
|
-
letschatty-0.4.
|
|
281
|
-
letschatty-0.4.
|
|
278
|
+
letschatty/services/validators/analytics_validator.py,sha256=6ejecLcif2i1C5trUo1qQgp8vKr9WchdljFZ5GzB2i4,7239
|
|
279
|
+
letschatty-0.4.348.dist-info/LICENSE,sha256=EClLu_bO2HBLDcThowIwfaIg5EOwIYhpRsBJjVEk92A,1197
|
|
280
|
+
letschatty-0.4.348.dist-info/METADATA,sha256=v718GGEk56rt4dRJa4zpho6WWar_mileYNkNKqDArzk,3283
|
|
281
|
+
letschatty-0.4.348.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
282
|
+
letschatty-0.4.348.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|