from __future__ import annotations import logging import os from typing import Any import httpx from realtime_voice_service.crm_client import _issue_service_token LOGGER = logging.getLogger("uvicorn.error") def _enabled() -> bool: return os.getenv("SALES_SYNC_ENABLED", "0").strip().lower() in {"1", "true", "yes", "on"} def _base_url() -> str: return str(os.getenv("SALES_SERVICE_URL", "http://sales-service:8000")).rstrip("/") def _timeout_seconds() -> float: raw = str(os.getenv("SALES_SYNC_TIMEOUT_SECONDS", "8")).strip() try: return max(float(raw), 1.0) except ValueError: return 8.0 def _secret() -> str: return str(os.getenv("CRM_APP_TOKEN_SECRET", "")).strip() def _service_headers() -> dict[str, str] | None: secret = _secret() if not secret: LOGGER.warning("sales sync: CRM_APP_TOKEN_SECRET not configured") return None token = _issue_service_token(secret) return {"Authorization": f"Bearer {token}"} def _queue_id() -> str | None: raw = str( os.getenv("REALTIME_VOICE_QUEUE_ID") or os.getenv("CRM_QUEUE_ID") or "" ).strip() return raw or None def _queue_code() -> str | None: raw = str(os.getenv("REALTIME_VOICE_QUEUE_CODE", "")).strip() return raw or None async def sync_voice_session( *, call_id: str, interaction_id: str | None = None, caller_number: str | None = None, caller_name: str | None = None, voice_session_id: str | None = None, ai_session_id: str | None = None, ai_state: str | None = None, handoff_reason: str | None = None, telephony_status: str | None = None, call_status: str | None = None, started_at: str | None = None, ended_at: str | None = None, summary: str | None = None, transcript_text: str | None = None, metadata: dict[str, Any] | None = None, ) -> dict[str, Any] | None: if not _enabled(): return None headers = _service_headers() if headers is None: return None payload: dict[str, Any] = { "call_id": call_id, "interaction_id": interaction_id, "queue_id": _queue_id(), "queue_code": _queue_code(), "caller_number": caller_number, "caller_name": caller_name, "voice_session_id": voice_session_id or call_id, "ai_session_id": ai_session_id, "ai_state": ai_state, "handoff_reason": handoff_reason, "telephony_status": telephony_status, "call_status": call_status, "started_at": started_at, "ended_at": ended_at, "summary": summary, "transcript_text": transcript_text, "metadata": { "source_service": "realtime_voice_service", **(metadata or {}), }, } try: async with httpx.AsyncClient(timeout=_timeout_seconds()) as client: response = await client.post( f"{_base_url()}/internal/sales-sync/voice", json=payload, headers=headers, ) response.raise_for_status() data = response.json() LOGGER.info( "sales sync: voice session synced call_id=%s voice_session_id=%s status=%s", call_id, voice_session_id or call_id, call_status, ) return data if isinstance(data, dict) else None except Exception: LOGGER.exception( "sales sync: failed to sync voice session call_id=%s voice_session_id=%s", call_id, voice_session_id or call_id, ) return None