letschatty 0.4.346__py3-none-any.whl → 0.4.347__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 +75 -13
- letschatty/services/validators/analytics_validator.py +11 -0
- {letschatty-0.4.346.dist-info → letschatty-0.4.347.dist-info}/METADATA +1 -1
- {letschatty-0.4.346.dist-info → letschatty-0.4.347.dist-info}/RECORD +14 -13
- {letschatty-0.4.346.dist-info → letschatty-0.4.347.dist-info}/LICENSE +0 -0
- {letschatty-0.4.346.dist-info → letschatty-0.4.347.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:
|
|
@@ -28,9 +28,10 @@ from ...models.chat.scheduled_messages import ScheduledMessageStatus
|
|
|
28
28
|
from ...models.utils.types.identifier import StrObjectId
|
|
29
29
|
from ...models.utils.custom_exceptions.custom_exceptions import AssetAlreadyAssigned, MessageNotFoundError, NotFoundError, MessageAlreadyInChat, MetaErrorNotification, ChatAlreadyAssigned, AlreadyCompleted, ErrorToMantainSafety
|
|
30
30
|
from ..factories.messages.central_notification_factory import CentralNotificationFactory
|
|
31
|
+
from ..factories.messages.chatty_message_factory import from_message_draft
|
|
31
32
|
from ...models.messages.chatty_messages.base.message_draft import ChattyContentAudio, MessageDraft
|
|
32
33
|
from ...models.messages.chatty_messages.schema.chatty_content.content_central import CentralNotificationStatus
|
|
33
|
-
from ...models.messages.chatty_messages.schema import ChattyContext
|
|
34
|
+
from ...models.messages.chatty_messages.schema import ChattyContext, ChattyContentText
|
|
34
35
|
from ...models.utils.types.message_types import MessageType
|
|
35
36
|
from .conversation_topics_service import ConversationTopicsService
|
|
36
37
|
import logging
|
|
@@ -211,6 +212,43 @@ class ChatService:
|
|
|
211
212
|
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
213
|
return chat.chatty_ai_agent
|
|
213
214
|
|
|
215
|
+
@staticmethod
|
|
216
|
+
def escalate_chatty_ai_agent(
|
|
217
|
+
chat: Chat,
|
|
218
|
+
execution_context: ExecutionContext,
|
|
219
|
+
message: Optional[str] = None,
|
|
220
|
+
reason: Optional[str] = None
|
|
221
|
+
) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Mark the chat's AI agent as requiring human intervention and add a central notification.
|
|
224
|
+
"""
|
|
225
|
+
if chat.chatty_ai_agent and not chat.chatty_ai_agent.requires_human_intervention:
|
|
226
|
+
chat.chatty_ai_agent.requires_human_intervention = True
|
|
227
|
+
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
228
|
+
body = "El chat fue escalado a un agente humano"
|
|
229
|
+
if reason:
|
|
230
|
+
body = f"{body}. Motivo: {reason}"
|
|
231
|
+
ChatService.add_central_notification_from_text(
|
|
232
|
+
chat=chat,
|
|
233
|
+
body=body,
|
|
234
|
+
subtype=MessageSubtype.CHATTY_AI_AGENT_NOTIFICATION,
|
|
235
|
+
content_status=CentralNotificationStatus.WARNING,
|
|
236
|
+
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
237
|
+
)
|
|
238
|
+
if message:
|
|
239
|
+
outgoing_context = ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
240
|
+
outgoing_message = from_message_draft(
|
|
241
|
+
MessageDraft(
|
|
242
|
+
type=MessageType.TEXT,
|
|
243
|
+
content=ChattyContentText(body=message),
|
|
244
|
+
context=outgoing_context,
|
|
245
|
+
subtype=MessageSubtype.NONE,
|
|
246
|
+
is_incoming_message=False
|
|
247
|
+
),
|
|
248
|
+
sent_by=execution_context.executor.id
|
|
249
|
+
)
|
|
250
|
+
ChatService.add_message(chat=chat, message=outgoing_message)
|
|
251
|
+
|
|
214
252
|
@staticmethod
|
|
215
253
|
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
254
|
"""
|
|
@@ -266,36 +304,46 @@ class ChatService:
|
|
|
266
304
|
return next((state for state in chat.flow_states if state.is_smart_follow_up), None)
|
|
267
305
|
|
|
268
306
|
@staticmethod
|
|
269
|
-
def create_sale(
|
|
307
|
+
def create_sale(
|
|
308
|
+
chat: Chat,
|
|
309
|
+
execution_context: ExecutionContext,
|
|
310
|
+
sale: Sale,
|
|
311
|
+
product: Optional[Product],
|
|
312
|
+
product_ids: Optional[List[StrObjectId]] = None,
|
|
313
|
+
product_label: Optional[str] = None
|
|
314
|
+
) -> SaleAssignedToChat:
|
|
270
315
|
"""
|
|
271
316
|
Add a sale to the chat.
|
|
272
317
|
"""
|
|
273
318
|
if next((sale for sale in chat.client.sales if sale.asset_id == sale.id), None) is not None:
|
|
274
319
|
raise AssetAlreadyAssigned(f"Sale with id {sale.id} already assigned to chat {chat.id}")
|
|
320
|
+
label = product_label or (product.name if product else "multiples productos")
|
|
321
|
+
assigned_product_ids = product_ids or ([product.id] if product else [])
|
|
275
322
|
assigned_asset = SaleAssignedToChat(
|
|
276
323
|
asset_type=ChatAssetType.SALE,
|
|
277
324
|
asset_id=sale.id,
|
|
278
325
|
assigned_at=sale.created_at,
|
|
279
326
|
assigned_by=execution_context.executor.id,
|
|
280
|
-
product_id=product.id
|
|
327
|
+
product_id=product.id if product else None,
|
|
328
|
+
product_ids=assigned_product_ids
|
|
281
329
|
)
|
|
282
330
|
execution_context.set_event_time(assigned_asset.assigned_at)
|
|
283
331
|
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 {
|
|
332
|
+
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))
|
|
333
|
+
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
334
|
return assigned_asset
|
|
287
335
|
|
|
288
336
|
@staticmethod
|
|
289
|
-
def update_sale(chat
|
|
337
|
+
def update_sale(chat: Chat, execution_context: ExecutionContext, sale: Sale, product_label: str) -> Sale:
|
|
290
338
|
"""
|
|
291
339
|
Update a sale for the chat.
|
|
292
340
|
"""
|
|
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 {
|
|
341
|
+
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))
|
|
342
|
+
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
343
|
return sale
|
|
296
344
|
|
|
297
345
|
@staticmethod
|
|
298
|
-
def delete_sale(chat
|
|
346
|
+
def delete_sale(chat: Chat, execution_context: ExecutionContext, sale_id: StrObjectId, product_label: str) -> SaleAssignedToChat:
|
|
299
347
|
"""
|
|
300
348
|
Logically remove a sale from the chat.
|
|
301
349
|
"""
|
|
@@ -303,8 +351,8 @@ class ChatService:
|
|
|
303
351
|
assigned_asset_to_remove = next(sale for sale in chat.client.sales if sale.asset_id == sale_id)
|
|
304
352
|
chat.client.sales.remove(assigned_asset_to_remove)
|
|
305
353
|
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 {
|
|
354
|
+
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))
|
|
355
|
+
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
356
|
return assigned_asset_to_remove
|
|
309
357
|
except StopIteration:
|
|
310
358
|
raise NotFoundError(message=f"Sale with id {sale_id} not found in chat {chat.id}")
|
|
@@ -849,6 +897,13 @@ class ChatService:
|
|
|
849
897
|
chat.client.email = collected_data.email
|
|
850
898
|
updated_fields.append("email")
|
|
851
899
|
|
|
900
|
+
if collected_data.phone:
|
|
901
|
+
if chat.client.lead_form_data is None:
|
|
902
|
+
chat.client.lead_form_data = {}
|
|
903
|
+
if chat.client.lead_form_data.get("phone") != collected_data.phone:
|
|
904
|
+
chat.client.lead_form_data["phone"] = collected_data.phone
|
|
905
|
+
updated_fields.append("phone")
|
|
906
|
+
|
|
852
907
|
if collected_data.document_id and chat.client.document_id != collected_data.document_id:
|
|
853
908
|
chat.client.document_id = collected_data.document_id
|
|
854
909
|
updated_fields.append("document_id")
|
|
@@ -866,11 +921,18 @@ class ChatService:
|
|
|
866
921
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
867
922
|
logger.info(f"Updated collected data for chat {chat.id}: {', '.join(updated_fields)}")
|
|
868
923
|
|
|
924
|
+
field_label_map = {
|
|
925
|
+
"name": "nombre",
|
|
926
|
+
"email": "email",
|
|
927
|
+
"phone": "telefono",
|
|
928
|
+
"document_id": "dni",
|
|
929
|
+
}
|
|
930
|
+
display_fields = [field_label_map.get(field, field) for field in updated_fields]
|
|
869
931
|
ChatService.add_central_notification_from_text(
|
|
870
932
|
chat=chat,
|
|
871
|
-
body=f"
|
|
933
|
+
body=f"Datos del cliente recopilados: {', '.join(display_fields)}",
|
|
872
934
|
subtype=MessageSubtype.CLIENT_INFO_UPDATED,
|
|
873
935
|
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
874
936
|
)
|
|
875
937
|
|
|
876
|
-
return chat.client.lead_form_data
|
|
938
|
+
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=UKmFpLJaoK7SHC2neNgGFnP9PYjUa03H2cVkplfSI9A,50209
|
|
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.347.dist-info/LICENSE,sha256=EClLu_bO2HBLDcThowIwfaIg5EOwIYhpRsBJjVEk92A,1197
|
|
280
|
+
letschatty-0.4.347.dist-info/METADATA,sha256=XBq4h1YtuUJqunaMbK96HeCgM3ZYMO7WeuRZXugTVYw,3283
|
|
281
|
+
letschatty-0.4.347.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
282
|
+
letschatty-0.4.347.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|