letschatty 0.4.349__py3-none-any.whl → 0.4.351__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.
Potentially problematic release.
This version of letschatty might be problematic. Click here for more details.
- letschatty/models/ai_microservices/__init__.py +4 -4
- letschatty/models/ai_microservices/expected_output.py +29 -2
- letschatty/models/ai_microservices/lambda_events.py +155 -28
- letschatty/models/ai_microservices/lambda_invokation_types.py +4 -1
- letschatty/models/ai_microservices/n8n_ai_agents_payload.py +3 -1
- letschatty/models/analytics/events/__init__.py +3 -3
- letschatty/models/analytics/events/chat_based_events/ai_agent_execution_event.py +71 -0
- letschatty/models/analytics/events/chat_based_events/chat_funnel.py +13 -69
- letschatty/models/analytics/events/company_based_events/asset_events.py +2 -9
- letschatty/models/analytics/events/event_type_to_classes.py +3 -7
- letschatty/models/analytics/events/event_types.py +50 -11
- letschatty/models/chat/chat.py +2 -13
- letschatty/models/chat/chat_with_assets.py +1 -6
- letschatty/models/chat/client.py +2 -0
- letschatty/models/chat/continuous_conversation.py +1 -1
- letschatty/models/company/CRM/funnel.py +33 -365
- letschatty/models/company/__init__.py +1 -7
- letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py +1 -1
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +4 -0
- letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py +2 -2
- letschatty/models/company/assets/ai_agents_v2/get_chat_with_prompt_response.py +1 -0
- letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py +28 -1
- letschatty/models/company/assets/automation.py +19 -10
- letschatty/models/company/assets/chat_assets.py +2 -3
- letschatty/models/company/assets/company_assets.py +0 -2
- letschatty/models/company/assets/sale.py +3 -3
- letschatty/models/company/empresa.py +1 -2
- letschatty/models/data_base/collection_interface.py +101 -29
- letschatty/models/data_base/mongo_connection.py +92 -9
- letschatty/models/messages/chatty_messages/schema/chatty_content/content_document.py +2 -4
- letschatty/models/messages/chatty_messages/schema/chatty_content/content_media.py +3 -4
- letschatty/models/utils/custom_exceptions/custom_exceptions.py +14 -1
- letschatty/services/ai_agents/smart_follow_up_context_builder_v2.py +5 -2
- letschatty/services/chat/chat_service.py +11 -47
- letschatty/services/chatty_assets/__init__.py +12 -0
- letschatty/services/chatty_assets/asset_service.py +190 -13
- letschatty/services/chatty_assets/assets_collections.py +137 -0
- letschatty/services/chatty_assets/base_container.py +3 -2
- letschatty/services/chatty_assets/base_container_with_collection.py +35 -26
- letschatty/services/chatty_assets/collections/__init__.py +38 -0
- letschatty/services/chatty_assets/collections/ai_agent_collection.py +19 -0
- letschatty/services/chatty_assets/collections/ai_agent_in_chat_collection.py +32 -0
- letschatty/services/chatty_assets/collections/ai_component_collection.py +21 -0
- letschatty/services/chatty_assets/collections/chain_of_thought_collection.py +30 -0
- letschatty/services/chatty_assets/collections/chat_collection.py +21 -0
- letschatty/services/chatty_assets/collections/contact_point_collection.py +21 -0
- letschatty/services/chatty_assets/collections/fast_answer_collection.py +21 -0
- letschatty/services/chatty_assets/collections/filter_criteria_collection.py +18 -0
- letschatty/services/chatty_assets/collections/flow_collection.py +20 -0
- letschatty/services/chatty_assets/collections/product_collection.py +20 -0
- letschatty/services/chatty_assets/collections/sale_collection.py +20 -0
- letschatty/services/chatty_assets/collections/source_collection.py +21 -0
- letschatty/services/chatty_assets/collections/tag_collection.py +19 -0
- letschatty/services/chatty_assets/collections/topic_collection.py +21 -0
- letschatty/services/chatty_assets/collections/user_collection.py +20 -0
- letschatty/services/chatty_assets/example_usage.py +44 -0
- letschatty/services/chatty_assets/services/__init__.py +37 -0
- letschatty/services/chatty_assets/services/ai_agent_in_chat_service.py +73 -0
- letschatty/services/chatty_assets/services/ai_agent_service.py +23 -0
- letschatty/services/chatty_assets/services/chain_of_thought_service.py +70 -0
- letschatty/services/chatty_assets/services/chat_service.py +25 -0
- letschatty/services/chatty_assets/services/contact_point_service.py +29 -0
- letschatty/services/chatty_assets/services/fast_answer_service.py +32 -0
- letschatty/services/chatty_assets/services/filter_criteria_service.py +30 -0
- letschatty/services/chatty_assets/services/flow_service.py +25 -0
- letschatty/services/chatty_assets/services/product_service.py +30 -0
- letschatty/services/chatty_assets/services/sale_service.py +25 -0
- letschatty/services/chatty_assets/services/source_service.py +28 -0
- letschatty/services/chatty_assets/services/tag_service.py +32 -0
- letschatty/services/chatty_assets/services/topic_service.py +31 -0
- letschatty/services/chatty_assets/services/user_service.py +32 -0
- letschatty/services/continuous_conversation_service/continuous_conversation_helper.py +11 -0
- letschatty/services/events/__init__.py +6 -0
- letschatty/services/events/events_manager.py +218 -1
- letschatty/services/factories/analytics/ai_agent_event_factory.py +161 -0
- letschatty/services/factories/analytics/events_factory.py +66 -30
- letschatty/services/factories/lambda_ai_orchestrartor/lambda_events_factory.py +46 -8
- letschatty/services/messages_helpers/get_caption_or_body_or_preview.py +6 -4
- letschatty/services/validators/analytics_validator.py +0 -11
- {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/METADATA +1 -1
- {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/RECORD +83 -53
- letschatty/models/analytics/events/chat_based_events/chat_client.py +0 -19
- letschatty/models/company/integrations/product_sync_status.py +0 -28
- letschatty/models/company/integrations/shopify/company_shopify_integration.py +0 -62
- letschatty/models/company/integrations/shopify/shopify_product_sync_status.py +0 -18
- letschatty/models/company/integrations/shopify/shopify_webhook_topics.py +0 -40
- letschatty/models/company/integrations/sync_status_enum.py +0 -9
- {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/LICENSE +0 -0
- {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/WHEEL +0 -0
|
@@ -211,25 +211,6 @@ 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
|
-
|
|
233
214
|
@staticmethod
|
|
234
215
|
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:
|
|
235
216
|
"""
|
|
@@ -285,46 +266,36 @@ class ChatService:
|
|
|
285
266
|
return next((state for state in chat.flow_states if state.is_smart_follow_up), None)
|
|
286
267
|
|
|
287
268
|
@staticmethod
|
|
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:
|
|
269
|
+
def create_sale(chat : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> SaleAssignedToChat:
|
|
296
270
|
"""
|
|
297
271
|
Add a sale to the chat.
|
|
298
272
|
"""
|
|
299
273
|
if next((sale for sale in chat.client.sales if sale.asset_id == sale.id), None) is not None:
|
|
300
274
|
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 [])
|
|
303
275
|
assigned_asset = SaleAssignedToChat(
|
|
304
276
|
asset_type=ChatAssetType.SALE,
|
|
305
277
|
asset_id=sale.id,
|
|
306
278
|
assigned_at=sale.created_at,
|
|
307
279
|
assigned_by=execution_context.executor.id,
|
|
308
|
-
product_id=product.id
|
|
309
|
-
product_ids=assigned_product_ids
|
|
280
|
+
product_id=product.id
|
|
310
281
|
)
|
|
311
282
|
execution_context.set_event_time(assigned_asset.assigned_at)
|
|
312
283
|
bisect.insort(chat.client.sales, assigned_asset)
|
|
313
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta de {
|
|
314
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
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)
|
|
315
286
|
return assigned_asset
|
|
316
287
|
|
|
317
288
|
@staticmethod
|
|
318
|
-
def update_sale(chat: Chat, execution_context: ExecutionContext, sale: Sale,
|
|
289
|
+
def update_sale(chat : Chat, execution_context: ExecutionContext, sale : Sale, product : Product) -> Sale:
|
|
319
290
|
"""
|
|
320
291
|
Update a sale for the chat.
|
|
321
292
|
"""
|
|
322
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta actualizada de {
|
|
323
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
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)
|
|
324
295
|
return sale
|
|
325
296
|
|
|
326
297
|
@staticmethod
|
|
327
|
-
def delete_sale(chat: Chat, execution_context: ExecutionContext, sale_id: StrObjectId,
|
|
298
|
+
def delete_sale(chat : Chat, execution_context: ExecutionContext, sale_id : StrObjectId, product : Product) -> SaleAssignedToChat:
|
|
328
299
|
"""
|
|
329
300
|
Logically remove a sale from the chat.
|
|
330
301
|
"""
|
|
@@ -332,8 +303,8 @@ class ChatService:
|
|
|
332
303
|
assigned_asset_to_remove = next(sale for sale in chat.client.sales if sale.asset_id == sale_id)
|
|
333
304
|
chat.client.sales.remove(assigned_asset_to_remove)
|
|
334
305
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
335
|
-
ChatService.create_highlight(chat=chat, execution_context=execution_context, highlight_data=HighlightRequestData(title=f"🛍️ Venta eliminada de {
|
|
336
|
-
ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {
|
|
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)
|
|
337
308
|
return assigned_asset_to_remove
|
|
338
309
|
except StopIteration:
|
|
339
310
|
raise NotFoundError(message=f"Sale with id {sale_id} not found in chat {chat.id}")
|
|
@@ -902,16 +873,9 @@ class ChatService:
|
|
|
902
873
|
execution_context.set_event_time(datetime.now(tz=ZoneInfo("UTC")))
|
|
903
874
|
logger.info(f"Updated collected data for chat {chat.id}: {', '.join(updated_fields)}")
|
|
904
875
|
|
|
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]
|
|
912
876
|
ChatService.add_central_notification_from_text(
|
|
913
877
|
chat=chat,
|
|
914
|
-
body=f"
|
|
878
|
+
body=f"Collected customer data: {', '.join(updated_fields)}",
|
|
915
879
|
subtype=MessageSubtype.CLIENT_INFO_UPDATED,
|
|
916
880
|
context=ChattyContext(chain_of_thought_id=execution_context.chain_of_thought_id)
|
|
917
881
|
)
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
from .base_container import ChattyAssetBaseContainer
|
|
2
2
|
from .base_container_with_collection import ChattyAssetContainerWithCollection
|
|
3
|
+
from .assets_collections import AssetsCollections
|
|
4
|
+
from .services import (
|
|
5
|
+
ProductService,
|
|
6
|
+
TagService,
|
|
7
|
+
UserService,
|
|
8
|
+
ChatService,
|
|
9
|
+
SourceService,
|
|
10
|
+
FlowService,
|
|
11
|
+
SaleService,
|
|
12
|
+
ContactPointService,
|
|
13
|
+
AiAgentService
|
|
14
|
+
)
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import TypeVar, Generic, Type, Callable, Protocol, Optional
|
|
2
|
+
from typing import TypeVar, Generic, Type, Callable, Protocol, Optional, ClassVar, TYPE_CHECKING, List
|
|
3
|
+
|
|
4
|
+
from bson import ObjectId
|
|
5
|
+
from letschatty.models.utils.types import StrObjectId
|
|
3
6
|
from .base_container_with_collection import ChattyAssetCollectionInterface, ChattyAssetContainerWithCollection, CacheConfig
|
|
4
7
|
from ...models.base_models import ChattyAssetModel
|
|
5
8
|
from ...models.base_models.chatty_asset_model import ChattyAssetPreview
|
|
6
9
|
from ...models.data_base.mongo_connection import MongoConnection
|
|
7
10
|
import logging
|
|
8
11
|
import os
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ...models.analytics.events.base import EventType
|
|
15
|
+
from ...models.company.empresa import EmpresaModel
|
|
16
|
+
from ...models.execution.execution import ExecutionContext
|
|
17
|
+
from ...models.company.assets.company_assets import CompanyAssetType
|
|
18
|
+
from ...models.utils.types.deletion_type import DeletionType
|
|
19
|
+
|
|
9
20
|
logger = logging.getLogger("AssetService")
|
|
10
21
|
|
|
11
22
|
# Protocol for assets that specify their preview type
|
|
@@ -44,17 +55,75 @@ class AssetCollection(Generic[T, P], ChattyAssetCollectionInterface[T, P]):
|
|
|
44
55
|
raise ValueError(f"Data must be a dictionary, got {type(data)}: {data}")
|
|
45
56
|
return self._create_instance_method(data)
|
|
46
57
|
|
|
58
|
+
|
|
47
59
|
class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
48
|
-
"""
|
|
60
|
+
"""
|
|
61
|
+
Generic service for handling CRUD operations for any Chatty asset.
|
|
62
|
+
|
|
63
|
+
Supports optional automatic event handling for API implementations.
|
|
64
|
+
Set these class attributes to enable events:
|
|
65
|
+
- asset_type_enum: CompanyAssetType (e.g., CompanyAssetType.PRODUCTS)
|
|
66
|
+
- event_type_created: EventType (e.g., EventType.PRODUCT_CREATED)
|
|
67
|
+
- event_type_updated: EventType (e.g., EventType.PRODUCT_UPDATED)
|
|
68
|
+
- event_type_deleted: EventType (e.g., EventType.PRODUCT_DELETED)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# Optional: Set these in subclasses to enable automatic event handling
|
|
72
|
+
asset_type_enum: ClassVar[Optional['CompanyAssetType']] = None
|
|
73
|
+
event_type_created: ClassVar[Optional['EventType']] = None
|
|
74
|
+
event_type_updated: ClassVar[Optional['EventType']] = None
|
|
75
|
+
event_type_deleted: ClassVar[Optional['EventType']] = None
|
|
76
|
+
|
|
77
|
+
collection: AssetCollection[T, P] # Type annotation for better type checking
|
|
49
78
|
|
|
50
79
|
def __init__(self,
|
|
51
|
-
|
|
52
|
-
asset_type: Type[T],
|
|
53
|
-
connection: MongoConnection,
|
|
54
|
-
create_instance_method: Callable[[dict], T],
|
|
55
|
-
preview_type: Optional[Type[P]] = None,
|
|
80
|
+
collection: AssetCollection[T, P],
|
|
56
81
|
cache_config: CacheConfig = CacheConfig.default()):
|
|
57
|
-
|
|
82
|
+
"""
|
|
83
|
+
Initialize AssetService with a pre-configured collection.
|
|
84
|
+
|
|
85
|
+
The item_type and preview_type are automatically extracted from the collection,
|
|
86
|
+
eliminating redundancy and simplifying the API.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
collection: Pre-configured AssetCollection subclass
|
|
90
|
+
cache_config: Cache configuration
|
|
91
|
+
"""
|
|
92
|
+
logger.debug(f"AssetService {self.__class__.__name__} initializing with collection")
|
|
93
|
+
super().__init__(
|
|
94
|
+
item_type=collection.type,
|
|
95
|
+
preview_type=collection.preview_type,
|
|
96
|
+
collection=collection,
|
|
97
|
+
cache_config=cache_config,
|
|
98
|
+
)
|
|
99
|
+
logger.debug(f"AssetService {self.__class__.__name__} initialized")
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_config(cls,
|
|
103
|
+
collection_name: str,
|
|
104
|
+
asset_type: Type[T],
|
|
105
|
+
connection: MongoConnection,
|
|
106
|
+
create_instance_method: Callable[[dict], T],
|
|
107
|
+
preview_type: Optional[Type[P]] = None,
|
|
108
|
+
cache_config: CacheConfig = CacheConfig.default()) -> 'AssetService[T, P]':
|
|
109
|
+
"""
|
|
110
|
+
Create an AssetService using the legacy configuration pattern.
|
|
111
|
+
|
|
112
|
+
This class method is provided for backward compatibility.
|
|
113
|
+
New code should use pre-configured AssetCollection subclasses.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
collection_name: MongoDB collection name
|
|
117
|
+
asset_type: The asset model type
|
|
118
|
+
connection: MongoDB connection
|
|
119
|
+
create_instance_method: Factory method to create asset instances
|
|
120
|
+
preview_type: Optional preview type
|
|
121
|
+
cache_config: Cache configuration
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
AssetService instance
|
|
125
|
+
"""
|
|
126
|
+
logger.debug(f"AssetService creating from config for {collection_name}")
|
|
58
127
|
asset_collection = AssetCollection(
|
|
59
128
|
collection=collection_name,
|
|
60
129
|
asset_type=asset_type,
|
|
@@ -62,13 +131,102 @@ class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
|
62
131
|
create_instance_method=create_instance_method,
|
|
63
132
|
preview_type=preview_type
|
|
64
133
|
)
|
|
65
|
-
|
|
66
|
-
item_type=asset_type,
|
|
67
|
-
preview_type=preview_type,
|
|
134
|
+
return cls(
|
|
68
135
|
collection=asset_collection,
|
|
69
|
-
cache_config=cache_config
|
|
136
|
+
cache_config=cache_config
|
|
70
137
|
)
|
|
71
|
-
|
|
138
|
+
|
|
139
|
+
def _should_handle_events(self) -> bool:
|
|
140
|
+
"""Check if this service should handle events automatically"""
|
|
141
|
+
return (self.asset_type_enum is not None and
|
|
142
|
+
self.event_type_created is not None and
|
|
143
|
+
self.event_type_updated is not None and
|
|
144
|
+
self.event_type_deleted is not None)
|
|
145
|
+
|
|
146
|
+
def _queue_event(self, item: T, event_type: 'EventType', execution_context: 'ExecutionContext', company_info: 'EmpresaModel'):
|
|
147
|
+
"""Queue an event for this asset if event handling is enabled"""
|
|
148
|
+
if not self._should_handle_events() or not self.asset_type_enum:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
from ...services.factories.analytics.events_factory import EventFactory
|
|
153
|
+
from ...services.events import events_manager
|
|
154
|
+
|
|
155
|
+
# Type guard - company_id should exist on ChattyAssetModel
|
|
156
|
+
if not hasattr(item, 'company_id'):
|
|
157
|
+
logger.warning(f"Asset {type(item).__name__} missing company_id, skipping event")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
events = EventFactory.asset_events(
|
|
161
|
+
company_id=item.company_id, # type: ignore[attr-defined]
|
|
162
|
+
executor_id=execution_context.executor.id,
|
|
163
|
+
asset=item,
|
|
164
|
+
asset_type=self.asset_type_enum,
|
|
165
|
+
event_type=event_type,
|
|
166
|
+
time=execution_context.time,
|
|
167
|
+
trace_id=execution_context.trace_id,
|
|
168
|
+
executor_type=execution_context.executor.type,
|
|
169
|
+
company_info=company_info
|
|
170
|
+
)
|
|
171
|
+
events_manager.queue_events(events)
|
|
172
|
+
except ImportError:
|
|
173
|
+
# Events not available (microservice context) - skip
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# All methods are now async-only for better performance
|
|
177
|
+
async def insert(self, item: T, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
178
|
+
"""Insert with automatic event handling if configured"""
|
|
179
|
+
result = await super().insert(item, execution_context)
|
|
180
|
+
if company_info and self._should_handle_events() and self.event_type_created:
|
|
181
|
+
self._queue_event(result, self.event_type_created, execution_context, company_info)
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
async def update(self, id: str, new_item: T, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
185
|
+
"""Update with automatic event handling if configured"""
|
|
186
|
+
result = await super().update(id, new_item, execution_context)
|
|
187
|
+
if company_info and self._should_handle_events() and self.event_type_updated:
|
|
188
|
+
self._queue_event(result, self.event_type_updated, execution_context, company_info)
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
async def delete(self, id: str, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None, deletion_type: Optional['DeletionType'] = None) -> T:
|
|
192
|
+
"""Delete with automatic event handling if configured"""
|
|
193
|
+
from ...models.utils.types.deletion_type import DeletionType as DT
|
|
194
|
+
result = await super().delete(id, execution_context, deletion_type or DT.LOGICAL)
|
|
195
|
+
if company_info and self._should_handle_events() and self.event_type_deleted:
|
|
196
|
+
self._queue_event(result, self.event_type_deleted, execution_context, company_info)
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
async def restore(self, id: str, execution_context: 'ExecutionContext', company_info: Optional['EmpresaModel'] = None) -> T:
|
|
200
|
+
"""Restore with automatic event handling if configured"""
|
|
201
|
+
result = await super().restore(id, execution_context)
|
|
202
|
+
if company_info and self._should_handle_events() and self.event_type_updated:
|
|
203
|
+
self._queue_event(result, self.event_type_updated, execution_context, company_info)
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
# Generic convenience methods
|
|
207
|
+
async def create_asset(self, data: dict, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
208
|
+
"""
|
|
209
|
+
Generic create method - creates instance from dict and inserts with events.
|
|
210
|
+
Can be called as create_asset or aliased to create_product/create_tag/etc.
|
|
211
|
+
"""
|
|
212
|
+
data["company_id"] = execution_context.company_id
|
|
213
|
+
item = self.collection.create_instance(data)
|
|
214
|
+
return await self.insert(item, execution_context, company_info)
|
|
215
|
+
|
|
216
|
+
async def update_asset(self, id: str, data: dict, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
217
|
+
"""
|
|
218
|
+
Generic update method - creates instance from dict and updates with events.
|
|
219
|
+
Can be called as update_asset or aliased to update_product/update_tag/etc.
|
|
220
|
+
"""
|
|
221
|
+
new_item = self.collection.create_instance(data)
|
|
222
|
+
return await self.update(id, new_item, execution_context, company_info)
|
|
223
|
+
|
|
224
|
+
async def delete_asset(self, id: str, execution_context: 'ExecutionContext', company_info: 'EmpresaModel') -> T:
|
|
225
|
+
"""
|
|
226
|
+
Generic delete method - deletes with events.
|
|
227
|
+
Can be called as delete_asset or aliased to delete_product/delete_tag/etc.
|
|
228
|
+
"""
|
|
229
|
+
return await self.delete(id, execution_context, company_info)
|
|
72
230
|
|
|
73
231
|
def get_preview_type(self) -> Type[P]:
|
|
74
232
|
"""Get the preview type from the asset class if it has one"""
|
|
@@ -81,3 +239,22 @@ class AssetService(Generic[T, P], ChattyAssetContainerWithCollection[T, P]):
|
|
|
81
239
|
preview_type = self.get_preview_type()
|
|
82
240
|
return super().get_preview_by_id(id, company_id, preview_type)
|
|
83
241
|
|
|
242
|
+
# Additional async read methods (passthrough to base class)
|
|
243
|
+
async def get_by_id(self, id: str) -> T:
|
|
244
|
+
"""Get by ID"""
|
|
245
|
+
return await super().get_by_id(id)
|
|
246
|
+
|
|
247
|
+
async def get_all(self, company_id: str) -> List[T]:
|
|
248
|
+
"""Get all for company"""
|
|
249
|
+
return await super().get_all(company_id)
|
|
250
|
+
|
|
251
|
+
async def get_by_query(self, query: dict, company_id: Optional[str]) -> List[T]:
|
|
252
|
+
"""Get by query"""
|
|
253
|
+
return await super().get_by_query(query, company_id)
|
|
254
|
+
|
|
255
|
+
async def get_item_dumped(self, id: str) -> dict:
|
|
256
|
+
"""Get item by ID and return as JSON serialized dict for frontend"""
|
|
257
|
+
from ...models.utils.types.serializer_type import SerializerType
|
|
258
|
+
item = await self.get_by_id(id)
|
|
259
|
+
return item.model_dump_json(serializer=SerializerType.FRONTEND)
|
|
260
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Read-only Assets Collections Container
|
|
3
|
+
|
|
4
|
+
This module provides a singleton container class that gives microservices
|
|
5
|
+
read-only access to asset data using pre-configured AssetCollection subclasses.
|
|
6
|
+
Perfect for services that only need to read asset data without CRUD operations.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
# Import base components
|
|
13
|
+
from ...models.data_base.mongo_connection import MongoConnection
|
|
14
|
+
from ...models.base_models.singleton import SingletonMeta
|
|
15
|
+
|
|
16
|
+
# Import pre-configured collection subclasses
|
|
17
|
+
from .collections import (
|
|
18
|
+
ProductCollection,
|
|
19
|
+
TagCollection,
|
|
20
|
+
UserCollection,
|
|
21
|
+
ChatCollection,
|
|
22
|
+
SourceCollection,
|
|
23
|
+
FlowCollection,
|
|
24
|
+
SaleCollection,
|
|
25
|
+
ContactPointCollection,
|
|
26
|
+
AiAgentCollection,
|
|
27
|
+
FilterCriteriaCollection,
|
|
28
|
+
AiComponentCollection
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Import asset models for type hints
|
|
32
|
+
from ...models.company.assets.product import Product
|
|
33
|
+
from ...models.company.assets.tag import Tag
|
|
34
|
+
from ...models.company.assets.users.user import User
|
|
35
|
+
from ...models.chat.chat import Chat
|
|
36
|
+
from ...models.analytics.sources import SourceBase
|
|
37
|
+
from ...models.company.assets.flow import FlowPreview
|
|
38
|
+
from ...models.company.assets.sale import Sale
|
|
39
|
+
from ...models.company.assets.contact_point import ContactPoint
|
|
40
|
+
from ...models.company.assets.ai_agents_v2.chatty_ai_agent import ChattyAIAgent
|
|
41
|
+
from ...models.company.assets.filter_criteria import FilterCriteria
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger("AssetsCollections")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AssetsCollections(metaclass=SingletonMeta):
|
|
50
|
+
"""
|
|
51
|
+
Read-only singleton container for accessing asset collections across microservices.
|
|
52
|
+
|
|
53
|
+
This class provides simple read access to various asset types without the overhead
|
|
54
|
+
of the full AssetService (no caching, no events, no write operations).
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
assets = AssetsCollections(connection)
|
|
58
|
+
product = assets.get_product_by_id(product_id)
|
|
59
|
+
tag = assets.get_tag_by_id(tag_id)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, connection: MongoConnection):
|
|
63
|
+
"""
|
|
64
|
+
Initialize all asset collections using pre-configured collection subclasses.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
connection: MongoConnection instance to use for database access
|
|
68
|
+
"""
|
|
69
|
+
logger.debug("Initializing AssetsCollections")
|
|
70
|
+
|
|
71
|
+
# Initialize all collections using pre-configured subclasses
|
|
72
|
+
# Each collection subclass already knows its collection name, asset type,
|
|
73
|
+
# preview type, and create_instance_method
|
|
74
|
+
self.products = ProductCollection(connection)
|
|
75
|
+
self.tags = TagCollection(connection)
|
|
76
|
+
self.users = UserCollection(connection)
|
|
77
|
+
self.chats = ChatCollection(connection)
|
|
78
|
+
self.sources = SourceCollection(connection)
|
|
79
|
+
self.flows = FlowCollection(connection)
|
|
80
|
+
self.sales = SaleCollection(connection)
|
|
81
|
+
self.contact_points = ContactPointCollection(connection)
|
|
82
|
+
self.ai_agents = AiAgentCollection(connection)
|
|
83
|
+
self.filter_criterias = FilterCriteriaCollection(connection)
|
|
84
|
+
self.ai_components = AiComponentCollection(connection)
|
|
85
|
+
|
|
86
|
+
logger.debug("AssetsCollections initialized successfully")
|
|
87
|
+
|
|
88
|
+
# Convenience getter methods for easy access
|
|
89
|
+
|
|
90
|
+
async def get_product_by_id(self, id: str) -> Product:
|
|
91
|
+
"""Get a product by ID."""
|
|
92
|
+
return await self.products.get_by_id(id)
|
|
93
|
+
|
|
94
|
+
async def get_tag_by_id(self, id: str) -> Tag:
|
|
95
|
+
"""Get a tag by ID."""
|
|
96
|
+
return await self.tags.get_by_id(id)
|
|
97
|
+
|
|
98
|
+
async def get_user_by_id(self, id: str) -> User:
|
|
99
|
+
"""Get a user by ID."""
|
|
100
|
+
return await self.users.get_by_id(id)
|
|
101
|
+
|
|
102
|
+
async def get_chat_by_id(self, id: str) -> Chat:
|
|
103
|
+
"""Get a chat by ID."""
|
|
104
|
+
return await self.chats.get_by_id(id)
|
|
105
|
+
|
|
106
|
+
async def get_source_by_id(self, id: str) -> SourceBase:
|
|
107
|
+
"""Get a source by ID."""
|
|
108
|
+
return await self.sources.get_by_id(id)
|
|
109
|
+
|
|
110
|
+
async def get_flow_by_id(self, id: str) -> FlowPreview:
|
|
111
|
+
"""Get a flow by ID."""
|
|
112
|
+
return await self.flows.get_by_id(id)
|
|
113
|
+
|
|
114
|
+
async def get_sale_by_id(self, id: str) -> Sale:
|
|
115
|
+
"""Get a sale by ID."""
|
|
116
|
+
return await self.sales.get_by_id(id)
|
|
117
|
+
|
|
118
|
+
async def get_contact_point_by_id(self, id: str) -> ContactPoint:
|
|
119
|
+
"""Get a contact point by ID."""
|
|
120
|
+
return await self.contact_points.get_by_id(id)
|
|
121
|
+
|
|
122
|
+
async def get_ai_agent_by_id(self, id: str) -> ChattyAIAgent:
|
|
123
|
+
"""Get an AI agent by ID."""
|
|
124
|
+
return await self.ai_agents.get_by_id(id)
|
|
125
|
+
|
|
126
|
+
async def get_filter_criteria_by_id(self, id: str) -> FilterCriteria:
|
|
127
|
+
"""Get a filter criteria by ID."""
|
|
128
|
+
return await self.filter_criterias.get_by_id(id)
|
|
129
|
+
|
|
130
|
+
async def get_filter_criterias_by_ids(self, ids: list[str]) -> list[FilterCriteria]:
|
|
131
|
+
"""Get multiple filter criterias by their IDs in a single query."""
|
|
132
|
+
return await self.filter_criterias.get_by_ids(ids=ids)
|
|
133
|
+
|
|
134
|
+
async def get_ai_components_by_ids(self, ids: list[str]) -> list[Any]:
|
|
135
|
+
"""Get multiple AI components by their IDs in a single query."""
|
|
136
|
+
return await self.ai_components.get_by_ids(ids=ids)
|
|
137
|
+
|
|
@@ -123,8 +123,9 @@ class ChattyAssetBaseContainer(Generic[T, P], ABC):
|
|
|
123
123
|
else:
|
|
124
124
|
return list(self.items.values())
|
|
125
125
|
|
|
126
|
-
def get_all_dict_id_item(self, company_id:Optional[StrObjectId]) -> Dict[StrObjectId, T]:
|
|
127
|
-
|
|
126
|
+
async def get_all_dict_id_item(self, company_id:Optional[StrObjectId]) -> Dict[StrObjectId, T]:
|
|
127
|
+
items = await self.get_all(company_id)
|
|
128
|
+
return {item.id: item for item in items}
|
|
128
129
|
|
|
129
130
|
def get_all_previews(self, company_id:Optional[StrObjectId]) -> List[P]:
|
|
130
131
|
logger.debug(f"Getting all previews for {self.__class__.__name__}")
|