letschatty 0.4.345__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.
@@ -61,10 +61,23 @@ class SmartTaggingCallbackEvent(LambdaAiEvent):
61
61
  callback_metadata: SmartTaggingCallbackMetadata
62
62
 
63
63
  @model_validator(mode="before")
64
- def validate_data(cls, data):
65
- if isinstance(data, dict) and "data" in data and "chain_of_thought" in data["data"]:
66
- data["data"]["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
67
- return data
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
@@ -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
- return [sale.product_id for sale in self.sales]
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(frozen=True)
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(default=None, description="Customer's DNI/ID number")
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(chat : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> SaleAssignedToChat:
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 {product.name}", description=f"Venta de {product.name} creada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_ADDED))
285
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} agregada al chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_ADDED)
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 : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> Sale:
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 {product.name}", description=f"Venta de {product.name} actualizada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_UPDATED))
294
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} actualizada en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_UPDATED)
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 : Chat, execution_context: ExecutionContext, sale_id : StrObjectId, product : Product) -> SaleAssignedToChat:
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 {product.name}", description=f"Venta de {product.name} eliminada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_DELETED))
307
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product.name} eliminada del chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_DELETED)
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"Collected customer data: {', '.join(updated_fields)}",
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: letschatty
3
- Version: 0.4.345
3
+ Version: 0.4.347
4
4
  Summary: Models and custom classes to work across the Chattyverse
5
5
  License: MIT
6
6
  Author: Axel
@@ -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=aazW8g6M7b7Lk-KZbx0R4AoV5oMjgXEpAcnsIwQ7Jf4,14319
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=8kAQvtH_ccrhk6lnPDdmRvBwvV_iBYsA2h7Q9NcdNOA,16298
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=LJqtSl1A1V0frPLYwW-5cMZxaGx7-0x1HvujRXsZsNU,5877
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=uVmD4FqGP5Tc8noxMBuRYGbkOXrtYlH2w6SAYwQZlLo,15494
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=OV33LPOBOaK02Va0yH7rCH5ufmEyOploQPTEefjlsdk,7524
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=OG9tGesyajvhXNyjSgOPc8EMcAVlqoaqAVflMKBzWa4,1078
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=Twzpieo0yia_kJ8uEXJq7pX9drsKuBYEslAMa3iO0Mg,9680
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=h1AkzLDA7PSKXIuol18a5-lXq48ODfBPPLtHog0fzzE,47496
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=-QBR6XIqEv2qw3stcBQehkwui1EcfWUM6M9DRQODykY,6335
278
- letschatty-0.4.345.dist-info/LICENSE,sha256=EClLu_bO2HBLDcThowIwfaIg5EOwIYhpRsBJjVEk92A,1197
279
- letschatty-0.4.345.dist-info/METADATA,sha256=69SG0X--n-8x8UeYDl_s_pMMDarN9KqQmVTI3W0N9n8,3283
280
- letschatty-0.4.345.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
281
- letschatty-0.4.345.dist-info/RECORD,,
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,,