127 lines
3.6 KiB
Python
127 lines
3.6 KiB
Python
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
|