.
This commit is contained in:
@@ -1,20 +1,9 @@
|
||||
"""
|
||||
Copyright 2025 Google. This software is provided as-is, without warranty or
|
||||
representation for any use or purpose. Your use of it is subject to your
|
||||
agreement with Google.
|
||||
|
||||
Notification manager service for processing push notifications.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from ..config import Settings
|
||||
from ..models import DetectIntentResponseDTO
|
||||
from ..models.notification import ExternalNotRequestDTO, NotificationDTO
|
||||
from ..models.conversation import ConversationSessionDTO, ConversationEntryDTO
|
||||
from ..utils.session_id import generate_session_id
|
||||
from .dialogflow_client import DialogflowClientService
|
||||
from ..models.notification import ExternalNotificationRequest, Notification
|
||||
from .redis_service import RedisService
|
||||
from .firestore_service import FirestoreService
|
||||
from .dlp_service import DLPService
|
||||
@@ -36,7 +25,6 @@ class NotificationManagerService:
|
||||
def __init__(
|
||||
self,
|
||||
settings: Settings,
|
||||
dialogflow_client: DialogflowClientService,
|
||||
redis_service: RedisService,
|
||||
firestore_service: FirestoreService,
|
||||
dlp_service: DLPService,
|
||||
@@ -52,18 +40,17 @@ class NotificationManagerService:
|
||||
dlp_service: Data Loss Prevention service
|
||||
"""
|
||||
self.settings = settings
|
||||
self.dialogflow_client = dialogflow_client
|
||||
self.redis_service = redis_service
|
||||
self.firestore_service = firestore_service
|
||||
self.dlp_service = dlp_service
|
||||
self.default_language_code = settings.dialogflow_default_language
|
||||
self.event_name = "notificacion"
|
||||
self.default_language_code = "es"
|
||||
|
||||
logger.info("NotificationManagerService initialized")
|
||||
|
||||
async def process_notification(
|
||||
self, external_request: ExternalNotRequestDTO
|
||||
) -> DetectIntentResponseDTO:
|
||||
self, external_request: ExternalNotificationRequest
|
||||
) -> None:
|
||||
"""
|
||||
Process a push notification from external system.
|
||||
|
||||
@@ -87,10 +74,6 @@ class NotificationManagerService:
|
||||
"""
|
||||
telefono = external_request.telefono
|
||||
|
||||
if not telefono or not telefono.strip():
|
||||
logger.warning("No phone number provided in notification request")
|
||||
raise ValueError("Phone number is required")
|
||||
|
||||
# Obfuscate sensitive data using DLP
|
||||
obfuscated_text = await self.dlp_service.get_obfuscated_string(
|
||||
external_request.texto,
|
||||
@@ -104,14 +87,13 @@ class NotificationManagerService:
|
||||
parameters[f"{PREFIX_PO_PARAM}{key}"] = value
|
||||
|
||||
# Create notification entry
|
||||
new_notification_id = generate_session_id()
|
||||
new_notification_entry = NotificationDTO(
|
||||
idNotificacion=new_notification_id,
|
||||
new_notification_id = str(uuid4())
|
||||
new_notification_entry = Notification.create(
|
||||
id_notificacion=new_notification_id,
|
||||
telefono=telefono,
|
||||
timestampCreacion=datetime.now(),
|
||||
texto=obfuscated_text,
|
||||
nombreEventoDialogflow=self.event_name,
|
||||
codigoIdiomaDialogflow=self.default_language_code,
|
||||
nombre_evento_dialogflow=self.event_name,
|
||||
codigo_idioma_dialogflow=self.default_language_code,
|
||||
parametros=parameters,
|
||||
status="active",
|
||||
)
|
||||
@@ -122,138 +104,20 @@ class NotificationManagerService:
|
||||
f"Notification for phone {telefono} cached. Kicking off async Firestore write-back"
|
||||
)
|
||||
|
||||
# Fire-and-forget Firestore write
|
||||
# In production, consider using asyncio.create_task() with proper error handling
|
||||
try:
|
||||
await self.firestore_service.save_or_append_notification(
|
||||
new_notification_entry
|
||||
)
|
||||
logger.debug(
|
||||
f"Notification entry persisted to Firestore for phone {telefono}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Background: Error during notification persistence to Firestore for phone {telefono}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
# Fire-and-forget Firestore write (matching Java's .subscribe() behavior)
|
||||
async def save_notification_to_firestore():
|
||||
try:
|
||||
await self.firestore_service.save_or_append_notification(
|
||||
new_notification_entry
|
||||
)
|
||||
logger.debug(
|
||||
f"Notification entry persisted to Firestore for phone {telefono}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Background: Error during notification persistence to Firestore for phone {telefono}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
# Get or create conversation session
|
||||
session = await self._get_or_create_conversation_session(
|
||||
telefono, obfuscated_text, parameters
|
||||
)
|
||||
|
||||
# Send notification event to Dialogflow
|
||||
logger.info(
|
||||
f"Sending notification text to Dialogflow using conversation session: {session.sessionId}"
|
||||
)
|
||||
|
||||
response = await self.dialogflow_client.detect_intent_event(
|
||||
session_id=session.sessionId,
|
||||
event_name=self.event_name,
|
||||
parameters=parameters,
|
||||
language_code=self.default_language_code,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Finished processing notification. Dialogflow response received for phone {telefono}"
|
||||
)
|
||||
return response
|
||||
|
||||
async def _get_or_create_conversation_session(
|
||||
self, telefono: str, notification_text: str, parameters: dict
|
||||
) -> ConversationSessionDTO:
|
||||
"""
|
||||
Get existing conversation session or create a new one.
|
||||
|
||||
Also persists system entry for the notification.
|
||||
|
||||
Args:
|
||||
telefono: User phone number
|
||||
notification_text: Notification text content
|
||||
parameters: Notification parameters
|
||||
|
||||
Returns:
|
||||
Conversation session
|
||||
"""
|
||||
# Try to get existing session by phone
|
||||
# TODO: Need to implement get_session_by_telefono in Redis service
|
||||
# For now, we'll create a new session
|
||||
|
||||
new_session_id = generate_session_id()
|
||||
user_id = f"user_by_phone_{telefono}"
|
||||
|
||||
logger.info(
|
||||
f"Creating new conversation session {new_session_id} for notification (phone: {telefono})"
|
||||
)
|
||||
|
||||
# Create system entry for notification
|
||||
system_entry = ConversationEntryDTO(
|
||||
entity="SISTEMA",
|
||||
type="SISTEMA",
|
||||
timestamp=datetime.now(),
|
||||
text=notification_text,
|
||||
parameters=parameters,
|
||||
intent=None,
|
||||
)
|
||||
|
||||
# Create new session
|
||||
new_session = ConversationSessionDTO(
|
||||
sessionId=new_session_id,
|
||||
userId=user_id,
|
||||
telefono=telefono,
|
||||
createdAt=datetime.now(),
|
||||
lastModified=datetime.now(),
|
||||
lastMessage=notification_text,
|
||||
pantallaContexto=None,
|
||||
)
|
||||
|
||||
# Persist conversation turn (session + system entry)
|
||||
await self._persist_conversation_turn(new_session, system_entry)
|
||||
|
||||
return new_session
|
||||
|
||||
async def _persist_conversation_turn(
|
||||
self, session: ConversationSessionDTO, entry: ConversationEntryDTO
|
||||
) -> None:
|
||||
"""
|
||||
Persist conversation turn to Redis and Firestore.
|
||||
|
||||
Uses write-through caching: writes to Redis first, then async to Firestore.
|
||||
|
||||
Args:
|
||||
session: Conversation session
|
||||
entry: Conversation entry to persist
|
||||
"""
|
||||
logger.debug(
|
||||
f"Starting Write-Back persistence for notification session {session.sessionId}. "
|
||||
f"Type: {entry.type}. Writing to Redis first"
|
||||
)
|
||||
|
||||
# Update session with last message
|
||||
updated_session = ConversationSessionDTO(
|
||||
**session.model_dump(),
|
||||
lastMessage=entry.text,
|
||||
lastModified=datetime.now(),
|
||||
)
|
||||
|
||||
# Save to Redis
|
||||
await self.redis_service.save_session(updated_session)
|
||||
logger.info(
|
||||
f"Entry saved to Redis for notification session {session.sessionId}. "
|
||||
f"Type: {entry.type}. Kicking off async Firestore write-back"
|
||||
)
|
||||
|
||||
# Fire-and-forget Firestore writes
|
||||
try:
|
||||
await self.firestore_service.save_session(updated_session)
|
||||
await self.firestore_service.save_entry(session.sessionId, entry)
|
||||
logger.debug(
|
||||
f"Asynchronously (Write-Back): Entry successfully saved to Firestore "
|
||||
f"for notification session {session.sessionId}. Type: {entry.type}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Asynchronously (Write-Back): Failed to save entry to Firestore "
|
||||
f"for notification session {session.sessionId}. Type: {entry.type}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
# Fire and forget - don't await
|
||||
asyncio.create_task(save_notification_to_firestore())
|
||||
|
||||
Reference in New Issue
Block a user