callvala / app /database.py
jarvisemitra
Deploy LeadAI voice agent to Hugging Face Spaces
3750487
Raw
History Blame Contribute Delete
3.85 kB
"""
LeadAI — Database module.
Async SQLAlchemy with SQLite for leads, calls, transcripts, and campaigns.
"""
import datetime
from sqlalchemy import (
Column, Integer, String, Text, DateTime, Float,
ForeignKey, Enum, Boolean, create_engine
)
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import declarative_base, relationship
from app.config import DATABASE_URL
# --- Engine & Session ---
engine = create_async_engine(DATABASE_URL, echo=False)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
Base = declarative_base()
# --- Models ---
class Campaign(Base):
"""A calling campaign with a specific purpose and AI script."""
__tablename__ = "campaigns"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), nullable=False)
description = Column(Text, default="")
system_prompt = Column(Text, default="")
voice = Column(String(100), default="en-US-GuyNeural")
status = Column(String(50), default="draft") # draft, active, paused, completed
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
# Relationships
leads = relationship("Lead", back_populates="campaign", lazy="selectin")
class Lead(Base):
"""A lead/contact to be called."""
__tablename__ = "leads"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), nullable=False)
phone = Column(String(50), nullable=False)
company = Column(String(255), default="")
purpose = Column(Text, default="")
status = Column(String(50), default="pending") # pending, calling, completed, failed, no_answer
notes = Column(Text, default="")
campaign_id = Column(Integer, ForeignKey("campaigns.id"), nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
# Relationships
campaign = relationship("Campaign", back_populates="leads")
calls = relationship("Call", back_populates="lead", lazy="selectin")
class Call(Base):
"""A phone call record."""
__tablename__ = "calls"
id = Column(Integer, primary_key=True, autoincrement=True)
lead_id = Column(Integer, ForeignKey("leads.id"), nullable=False)
twilio_call_sid = Column(String(100), default="")
status = Column(String(50), default="initiated") # initiated, ringing, in_progress, completed, failed, no_answer
outcome = Column(String(100), default="") # interested, not_interested, callback, voicemail, error
duration_seconds = Column(Float, default=0.0)
started_at = Column(DateTime, default=datetime.datetime.utcnow)
ended_at = Column(DateTime, nullable=True)
error_message = Column(Text, default="")
# Relationships
lead = relationship("Lead", back_populates="calls")
transcripts = relationship("Transcript", back_populates="call", lazy="selectin")
class Transcript(Base):
"""Individual message in a call transcript."""
__tablename__ = "transcripts"
id = Column(Integer, primary_key=True, autoincrement=True)
call_id = Column(Integer, ForeignKey("calls.id"), nullable=False)
speaker = Column(String(20), nullable=False) # "agent" or "human"
text = Column(Text, nullable=False)
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
# Relationships
call = relationship("Call", back_populates="transcripts")
# --- Database Lifecycle ---
async def init_db():
"""Create all tables."""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_session() -> AsyncSession:
"""Get an async database session."""
async with async_session() as session:
yield session