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.
This commit is contained in:
2026-05-22 06:28:33 -04:00
parent dbdb03beb9
commit 63f1a270bb
28 changed files with 2275 additions and 11 deletions

View File

@@ -8,6 +8,7 @@ from datetime import datetime
from sqlalchemy import (
JSON,
Boolean,
Column,
DateTime,
Float,
@@ -111,6 +112,7 @@ class Device(Base):
priority = Column(Integer, default=10) # Routing priority (lower = higher priority)
is_online = Column(String, default="false")
capabilities = Column(JSON, default=list) # ["voice", "video", "sms"]
dnd = Column(Boolean, default=False, nullable=False)
last_seen = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
@@ -119,6 +121,55 @@ class Device(Base):
return f"<Device {self.id} {self.name} ({self.type})>"
class RoutingRuleRecord(Base):
__tablename__ = "routing_rules"
id = Column(String, primary_key=True)
name = Column(String, nullable=False)
priority = Column(Integer, default=100, nullable=False) # lower runs first
enabled = Column(Boolean, default=True, nullable=False)
match = Column(JSON, nullable=False) # caller_pattern, dnis, time_range, days
action = Column(JSON, nullable=False) # {type, ...}
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
def __repr__(self) -> str:
return f"<RoutingRule {self.id} {self.name} p={self.priority}>"
class TranscriptChunk(Base):
__tablename__ = "transcript_chunks"
id = Column(String, primary_key=True)
call_id = Column(String, index=True, nullable=False)
seq = Column(Integer, nullable=False)
t_offset_ms = Column(Integer, default=0) # offset from call start
speaker = Column(String, default="unknown") # caller / agent / receptionist / unknown
text = Column(Text, nullable=False)
confidence = Column(Float, nullable=True)
created_at = Column(DateTime, default=func.now())
def __repr__(self) -> str:
return f"<TranscriptChunk {self.call_id}#{self.seq}>"
class RecordingRecord(Base):
__tablename__ = "recordings"
id = Column(String, primary_key=True)
call_id = Column(String, index=True, nullable=False)
path = Column(String, nullable=False)
format = Column(String, default="wav")
duration_s = Column(Float, default=0.0)
size_bytes = Column(Integer, default=0)
channels = Column(Integer, default=1)
started_at = Column(DateTime, default=func.now())
ended_at = Column(DateTime, nullable=True)
def __repr__(self) -> str:
return f"<Recording {self.id} call={self.call_id} {self.path}>"
# ============================================================
# Engine & Session
# ============================================================