Files
hold-slayer/models/device.py
Robert Helewka 63f1a270bb feat: add call history API endpoints and TTS service client
Adds read-only access to persisted call records for the dashboard
and implements a client for the Rhema text-to-speech service.

- api/call_history.py: New router providing paged call lists
  and detailed call records with transcript metadata.
- services/tts.py: Async client for OpenAI-compatible TTS
  endpoints (Rhema/Kokoro) used for call-flow steps.
2026-05-22 06:28:33 -04:00

85 lines
2.3 KiB
Python

"""
Device models — SIP phones, softphones, cell phones.
Devices register with the gateway and can receive transferred calls.
"""
from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
class DeviceType(str, Enum):
"""Types of devices that can connect to the gateway."""
SIP_PHONE = "sip_phone" # Hardware SIP phone
SOFTPHONE = "softphone" # Software SIP client
CELL = "cell" # Cell phone (reached via PSTN trunk)
TABLET = "tablet" # Tablet with SIP client
WEBRTC = "webrtc" # Browser-based WebRTC client
class DeviceBase(BaseModel):
"""Shared device fields."""
name: str # "Office SIP Phone"
type: DeviceType
extension: Optional[int] = None # 221-299, auto-assigned if omitted
sip_uri: Optional[str] = None # sip:robert@gateway.helu.ca
phone_number: Optional[str] = None # For PSTN devices (E.164)
priority: int = 10 # Routing priority (lower = higher priority)
capabilities: list[str] = Field(default_factory=lambda: ["voice"])
class Device(DeviceBase):
"""Full device model."""
id: str
is_online: bool = False
dnd: bool = False
last_seen: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
@property
def can_receive_call(self) -> bool:
"""Can this device receive a call right now?"""
if self.dnd:
return False
if self.type in (DeviceType.SIP_PHONE, DeviceType.SOFTPHONE, DeviceType.WEBRTC):
return self.is_online and self.sip_uri is not None
if self.type == DeviceType.CELL:
return self.phone_number is not None
return False
class DeviceCreate(DeviceBase):
"""Request model for registering a new device."""
pass
class DeviceUpdate(BaseModel):
"""Request model for updating a device."""
name: Optional[str] = None
type: Optional[DeviceType] = None
extension: Optional[int] = None
sip_uri: Optional[str] = None
phone_number: Optional[str] = None
priority: Optional[int] = None
capabilities: Optional[list[str]] = None
class DeviceStatus(BaseModel):
"""Lightweight device status for list views."""
id: str
name: str
type: DeviceType
is_online: bool
last_seen: Optional[datetime] = None
can_receive_call: bool