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.

Files changed (89) hide show
  1. letschatty/models/ai_microservices/__init__.py +4 -4
  2. letschatty/models/ai_microservices/expected_output.py +29 -2
  3. letschatty/models/ai_microservices/lambda_events.py +155 -28
  4. letschatty/models/ai_microservices/lambda_invokation_types.py +4 -1
  5. letschatty/models/ai_microservices/n8n_ai_agents_payload.py +3 -1
  6. letschatty/models/analytics/events/__init__.py +3 -3
  7. letschatty/models/analytics/events/chat_based_events/ai_agent_execution_event.py +71 -0
  8. letschatty/models/analytics/events/chat_based_events/chat_funnel.py +13 -69
  9. letschatty/models/analytics/events/company_based_events/asset_events.py +2 -9
  10. letschatty/models/analytics/events/event_type_to_classes.py +3 -7
  11. letschatty/models/analytics/events/event_types.py +50 -11
  12. letschatty/models/chat/chat.py +2 -13
  13. letschatty/models/chat/chat_with_assets.py +1 -6
  14. letschatty/models/chat/client.py +2 -0
  15. letschatty/models/chat/continuous_conversation.py +1 -1
  16. letschatty/models/company/CRM/funnel.py +33 -365
  17. letschatty/models/company/__init__.py +1 -7
  18. letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py +1 -1
  19. letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +4 -0
  20. letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py +2 -2
  21. letschatty/models/company/assets/ai_agents_v2/get_chat_with_prompt_response.py +1 -0
  22. letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py +28 -1
  23. letschatty/models/company/assets/automation.py +19 -10
  24. letschatty/models/company/assets/chat_assets.py +2 -3
  25. letschatty/models/company/assets/company_assets.py +0 -2
  26. letschatty/models/company/assets/sale.py +3 -3
  27. letschatty/models/company/empresa.py +1 -2
  28. letschatty/models/data_base/collection_interface.py +101 -29
  29. letschatty/models/data_base/mongo_connection.py +92 -9
  30. letschatty/models/messages/chatty_messages/schema/chatty_content/content_document.py +2 -4
  31. letschatty/models/messages/chatty_messages/schema/chatty_content/content_media.py +3 -4
  32. letschatty/models/utils/custom_exceptions/custom_exceptions.py +14 -1
  33. letschatty/services/ai_agents/smart_follow_up_context_builder_v2.py +5 -2
  34. letschatty/services/chat/chat_service.py +11 -47
  35. letschatty/services/chatty_assets/__init__.py +12 -0
  36. letschatty/services/chatty_assets/asset_service.py +190 -13
  37. letschatty/services/chatty_assets/assets_collections.py +137 -0
  38. letschatty/services/chatty_assets/base_container.py +3 -2
  39. letschatty/services/chatty_assets/base_container_with_collection.py +35 -26
  40. letschatty/services/chatty_assets/collections/__init__.py +38 -0
  41. letschatty/services/chatty_assets/collections/ai_agent_collection.py +19 -0
  42. letschatty/services/chatty_assets/collections/ai_agent_in_chat_collection.py +32 -0
  43. letschatty/services/chatty_assets/collections/ai_component_collection.py +21 -0
  44. letschatty/services/chatty_assets/collections/chain_of_thought_collection.py +30 -0
  45. letschatty/services/chatty_assets/collections/chat_collection.py +21 -0
  46. letschatty/services/chatty_assets/collections/contact_point_collection.py +21 -0
  47. letschatty/services/chatty_assets/collections/fast_answer_collection.py +21 -0
  48. letschatty/services/chatty_assets/collections/filter_criteria_collection.py +18 -0
  49. letschatty/services/chatty_assets/collections/flow_collection.py +20 -0
  50. letschatty/services/chatty_assets/collections/product_collection.py +20 -0
  51. letschatty/services/chatty_assets/collections/sale_collection.py +20 -0
  52. letschatty/services/chatty_assets/collections/source_collection.py +21 -0
  53. letschatty/services/chatty_assets/collections/tag_collection.py +19 -0
  54. letschatty/services/chatty_assets/collections/topic_collection.py +21 -0
  55. letschatty/services/chatty_assets/collections/user_collection.py +20 -0
  56. letschatty/services/chatty_assets/example_usage.py +44 -0
  57. letschatty/services/chatty_assets/services/__init__.py +37 -0
  58. letschatty/services/chatty_assets/services/ai_agent_in_chat_service.py +73 -0
  59. letschatty/services/chatty_assets/services/ai_agent_service.py +23 -0
  60. letschatty/services/chatty_assets/services/chain_of_thought_service.py +70 -0
  61. letschatty/services/chatty_assets/services/chat_service.py +25 -0
  62. letschatty/services/chatty_assets/services/contact_point_service.py +29 -0
  63. letschatty/services/chatty_assets/services/fast_answer_service.py +32 -0
  64. letschatty/services/chatty_assets/services/filter_criteria_service.py +30 -0
  65. letschatty/services/chatty_assets/services/flow_service.py +25 -0
  66. letschatty/services/chatty_assets/services/product_service.py +30 -0
  67. letschatty/services/chatty_assets/services/sale_service.py +25 -0
  68. letschatty/services/chatty_assets/services/source_service.py +28 -0
  69. letschatty/services/chatty_assets/services/tag_service.py +32 -0
  70. letschatty/services/chatty_assets/services/topic_service.py +31 -0
  71. letschatty/services/chatty_assets/services/user_service.py +32 -0
  72. letschatty/services/continuous_conversation_service/continuous_conversation_helper.py +11 -0
  73. letschatty/services/events/__init__.py +6 -0
  74. letschatty/services/events/events_manager.py +218 -1
  75. letschatty/services/factories/analytics/ai_agent_event_factory.py +161 -0
  76. letschatty/services/factories/analytics/events_factory.py +66 -30
  77. letschatty/services/factories/lambda_ai_orchestrartor/lambda_events_factory.py +46 -8
  78. letschatty/services/messages_helpers/get_caption_or_body_or_preview.py +6 -4
  79. letschatty/services/validators/analytics_validator.py +0 -11
  80. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/METADATA +1 -1
  81. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/RECORD +83 -53
  82. letschatty/models/analytics/events/chat_based_events/chat_client.py +0 -19
  83. letschatty/models/company/integrations/product_sync_status.py +0 -28
  84. letschatty/models/company/integrations/shopify/company_shopify_integration.py +0 -62
  85. letschatty/models/company/integrations/shopify/shopify_product_sync_status.py +0 -18
  86. letschatty/models/company/integrations/shopify/shopify_webhook_topics.py +0 -40
  87. letschatty/models/company/integrations/sync_status_enum.py +0 -9
  88. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/LICENSE +0 -0
  89. {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 if product else None,
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 {label}", description=f"Venta de {label} creada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_ADDED))
314
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {label} agregada al chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_ADDED)
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, product_label: str) -> 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 {product_label}", description=f"Venta de {product_label} actualizada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_UPDATED))
323
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product_label} actualizada en el chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_UPDATED)
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, product_label: str) -> SaleAssignedToChat:
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 {product_label}", description=f"Venta de {product_label} eliminada por {execution_context.executor.name}", starred=False, subtype=MessageSubtype.SALE_DELETED))
336
- ChatService.add_central_notification_from_text(chat=chat, body=f"Venta de {product_label} eliminada del chat {chat.id} por {execution_context.executor.name}", subtype=MessageSubtype.SALE_DELETED)
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"Datos del cliente recopilados: {', '.join(display_fields)}",
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
- """Generic service for handling CRUD operations for any Chatty asset"""
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
- collection_name: str,
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
- logger.debug(f"AssetService {self.__class__.__name__} initializing for {collection_name}")
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
- super().__init__(
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
- logger.debug(f"AssetService {self.__class__.__name__} initialized for {collection_name}")
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
- return {item.id: item for item in self.get_all(company_id)}
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__}")