cx_ai_agent_v1 / models /database.py
muzakkirhussain011's picture
Add application files (text files only)
8bab08d
"""
SQLAlchemy Database Models for Enterprise CX AI Agent
"""
from sqlalchemy import (
Column, Integer, String, Text, Float, Boolean, DateTime, ForeignKey,
UniqueConstraint, Index, Date
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
import json
Base = declarative_base()
class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
domain = Column(String, unique=True)
industry = Column(String)
size = Column(String)
revenue = Column(String)
location = Column(String)
description = Column(Text)
pain_points = Column(Text) # JSON
website = Column(String)
linkedin_url = Column(String)
summary = Column(Text) # AI-generated comprehensive summary
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
contacts = relationship("Contact", back_populates="company")
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'domain': self.domain,
'industry': self.industry,
'size': self.size,
'revenue': self.revenue,
'location': self.location,
'description': self.description,
'pain_points': json.loads(self.pain_points) if self.pain_points else [],
'website': self.website,
'linkedin_url': self.linkedin_url,
'summary': self.summary,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
class ClientProfile(Base):
"""Stores CLIENT company profiles for email personalization"""
__tablename__ = 'client_profiles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False, unique=True)
website = Column(String)
domain = Column(String)
description = Column(Text)
industry = Column(String)
# What they offer
offerings = Column(Text) # JSON list of offerings/products/services
value_propositions = Column(Text) # JSON list of value props/benefits
target_customers = Column(Text) # JSON list of who they serve
use_cases = Column(Text) # JSON list of use cases
differentiators = Column(Text) # JSON list of what makes them unique
summary = Column(Text) # AI-generated comprehensive summary for personalization
# Metadata
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
last_researched_at = Column(DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'website': self.website,
'domain': self.domain,
'description': self.description,
'industry': self.industry,
'offerings': json.loads(self.offerings) if self.offerings else [],
'value_propositions': json.loads(self.value_propositions) if self.value_propositions else [],
'target_customers': json.loads(self.target_customers) if self.target_customers else [],
'use_cases': json.loads(self.use_cases) if self.use_cases else [],
'differentiators': json.loads(self.differentiators) if self.differentiators else [],
'summary': self.summary,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'last_researched_at': self.last_researched_at.isoformat() if self.last_researched_at else None,
}
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True, autoincrement=True)
company_id = Column(Integer, ForeignKey('companies.id', ondelete='SET NULL'))
first_name = Column(String)
last_name = Column(String)
email = Column(String, unique=True, nullable=False)
phone = Column(String)
job_title = Column(String)
department = Column(String)
seniority_level = Column(String)
linkedin_url = Column(String)
twitter_url = Column(String)
location = Column(String)
timezone = Column(String)
# Scoring
fit_score = Column(Float, default=0.0)
engagement_score = Column(Float, default=0.0)
intent_score = Column(Float, default=0.0)
overall_score = Column(Float, default=0.0)
# Status & Lifecycle
status = Column(String, default='new')
lifecycle_stage = Column(String, default='lead')
# Tracking
source = Column(String)
first_contacted_at = Column(DateTime)
last_contacted_at = Column(DateTime)
last_activity_at = Column(DateTime)
# Metadata
tags = Column(Text) # JSON
notes = Column(Text)
custom_fields = Column(Text) # JSON
is_suppressed = Column(Boolean, default=False)
suppression_reason = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
company = relationship("Company", back_populates="contacts")
campaign_associations = relationship("CampaignContact", back_populates="contact")
email_activities = relationship("EmailActivity", back_populates="contact")
meetings = relationship("Meeting", back_populates="contact")
activities = relationship("Activity", back_populates="contact")
@property
def full_name(self):
return f"{self.first_name or ''} {self.last_name or ''}".strip()
def to_dict(self):
return {
'id': self.id,
'company_id': self.company_id,
'company_name': self.company.name if self.company else None,
'first_name': self.first_name,
'last_name': self.last_name,
'full_name': self.full_name,
'email': self.email,
'phone': self.phone,
'job_title': self.job_title,
'department': self.department,
'seniority_level': self.seniority_level,
'linkedin_url': self.linkedin_url,
'fit_score': self.fit_score,
'engagement_score': self.engagement_score,
'overall_score': self.overall_score,
'status': self.status,
'lifecycle_stage': self.lifecycle_stage,
'source': self.source,
'tags': json.loads(self.tags) if self.tags else [],
'created_at': self.created_at.isoformat() if self.created_at else None,
'last_activity_at': self.last_activity_at.isoformat() if self.last_activity_at else None,
}
class Campaign(Base):
__tablename__ = 'campaigns'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
description = Column(Text)
status = Column(String, default='draft')
# Targeting
target_industries = Column(Text) # JSON
target_company_sizes = Column(Text) # JSON
target_locations = Column(Text) # JSON
target_job_titles = Column(Text) # JSON
# Configuration
sequence_id = Column(Integer, ForeignKey('sequences.id', ondelete='SET NULL'))
goal_contacts = Column(Integer)
goal_response_rate = Column(Float)
goal_meetings = Column(Integer)
# Tracking
contacts_discovered = Column(Integer, default=0)
contacts_enriched = Column(Integer, default=0)
contacts_scored = Column(Integer, default=0)
contacts_contacted = Column(Integer, default=0)
contacts_responded = Column(Integer, default=0)
meetings_booked = Column(Integer, default=0)
# Dates
started_at = Column(DateTime)
completed_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_by = Column(String)
# Relationships
sequence = relationship("Sequence", back_populates="campaigns")
contact_associations = relationship("CampaignContact", back_populates="campaign")
email_activities = relationship("EmailActivity", back_populates="campaign")
meetings = relationship("Meeting", back_populates="campaign")
activities = relationship("Activity", back_populates="campaign")
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'status': self.status,
'sequence_id': self.sequence_id,
'sequence_name': self.sequence.name if self.sequence else None,
'goal_contacts': self.goal_contacts,
'goal_response_rate': self.goal_response_rate,
'goal_meetings': self.goal_meetings,
'contacts_discovered': self.contacts_discovered,
'contacts_enriched': self.contacts_enriched,
'contacts_contacted': self.contacts_contacted,
'contacts_responded': self.contacts_responded,
'meetings_booked': self.meetings_booked,
'started_at': self.started_at.isoformat() if self.started_at else None,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
class CampaignContact(Base):
__tablename__ = 'campaign_contacts'
id = Column(Integer, primary_key=True, autoincrement=True)
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='CASCADE'), nullable=False)
contact_id = Column(Integer, ForeignKey('contacts.id', ondelete='CASCADE'), nullable=False)
stage = Column(String, default='discovery')
stage_updated_at = Column(DateTime, default=datetime.utcnow)
added_at = Column(DateTime, default=datetime.utcnow)
notes = Column(Text)
# Relationships
campaign = relationship("Campaign", back_populates="contact_associations")
contact = relationship("Contact", back_populates="campaign_associations")
__table_args__ = (
UniqueConstraint('campaign_id', 'contact_id', name='uq_campaign_contact'),
)
class Sequence(Base):
__tablename__ = 'sequences'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
description = Column(Text)
category = Column(String, default='outbound')
is_active = Column(Boolean, default=True)
is_template = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_by = Column(String)
# Relationships
emails = relationship("SequenceEmail", back_populates="sequence", order_by="SequenceEmail.step_number")
campaigns = relationship("Campaign", back_populates="sequence")
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'category': self.category,
'is_active': self.is_active,
'email_count': len(self.emails) if self.emails else 0,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
class SequenceEmail(Base):
__tablename__ = 'sequence_emails'
id = Column(Integer, primary_key=True, autoincrement=True)
sequence_id = Column(Integer, ForeignKey('sequences.id', ondelete='CASCADE'), nullable=False)
step_number = Column(Integer, nullable=False)
wait_days = Column(Integer, default=0)
subject = Column(String, nullable=False)
body = Column(Text, nullable=False)
send_time_preference = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
sequence = relationship("Sequence", back_populates="emails")
email_activities = relationship("EmailActivity", back_populates="sequence_email")
__table_args__ = (
UniqueConstraint('sequence_id', 'step_number', name='uq_sequence_step'),
)
def to_dict(self):
return {
'id': self.id,
'sequence_id': self.sequence_id,
'step_number': self.step_number,
'wait_days': self.wait_days,
'subject': self.subject,
'body': self.body,
'send_time_preference': self.send_time_preference,
}
class EmailActivity(Base):
__tablename__ = 'email_activities'
id = Column(Integer, primary_key=True, autoincrement=True)
contact_id = Column(Integer, ForeignKey('contacts.id', ondelete='CASCADE'), nullable=False)
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='SET NULL'))
sequence_email_id = Column(Integer, ForeignKey('sequence_emails.id', ondelete='SET NULL'))
type = Column(String, nullable=False) # sent, opened, clicked, replied, etc.
subject = Column(String)
preview = Column(Text)
link_url = Column(String)
meta_data = Column(Text) # JSON
occurred_at = Column(DateTime, default=datetime.utcnow)
# Relationships
contact = relationship("Contact", back_populates="email_activities")
campaign = relationship("Campaign", back_populates="email_activities")
sequence_email = relationship("SequenceEmail", back_populates="email_activities")
class Meeting(Base):
__tablename__ = 'meetings'
id = Column(Integer, primary_key=True, autoincrement=True)
contact_id = Column(Integer, ForeignKey('contacts.id', ondelete='CASCADE'), nullable=False)
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='SET NULL'))
title = Column(String, nullable=False)
description = Column(Text)
scheduled_at = Column(DateTime, nullable=False)
duration_minutes = Column(Integer, default=30)
meeting_url = Column(String)
location = Column(String)
status = Column(String, default='scheduled')
outcome = Column(String)
notes = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
contact = relationship("Contact", back_populates="meetings")
campaign = relationship("Campaign", back_populates="meetings")
activities = relationship("Activity", back_populates="meeting")
def to_dict(self):
return {
'id': self.id,
'contact_id': self.contact_id,
'contact_name': self.contact.full_name if self.contact else None,
'campaign_id': self.campaign_id,
'title': self.title,
'scheduled_at': self.scheduled_at.isoformat() if self.scheduled_at else None,
'duration_minutes': self.duration_minutes,
'meeting_url': self.meeting_url,
'status': self.status,
'outcome': self.outcome,
}
class Activity(Base):
__tablename__ = 'activities'
id = Column(Integer, primary_key=True, autoincrement=True)
contact_id = Column(Integer, ForeignKey('contacts.id', ondelete='CASCADE'))
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='SET NULL'))
meeting_id = Column(Integer, ForeignKey('meetings.id', ondelete='SET NULL'))
type = Column(String, nullable=False)
description = Column(Text)
meta_data = Column(Text) # JSON
performed_by = Column(String)
occurred_at = Column(DateTime, default=datetime.utcnow)
# Relationships
contact = relationship("Contact", back_populates="activities")
campaign = relationship("Campaign", back_populates="activities")
meeting = relationship("Meeting", back_populates="activities")
class ABTest(Base):
__tablename__ = 'ab_tests'
id = Column(Integer, primary_key=True, autoincrement=True)
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='CASCADE'), nullable=False)
sequence_id = Column(Integer, ForeignKey('sequences.id', ondelete='CASCADE'), nullable=False)
name = Column(String, nullable=False)
description = Column(Text)
test_type = Column(String, nullable=False)
variant_a = Column(Text, nullable=False) # JSON
variant_b = Column(Text, nullable=False) # JSON
winner = Column(String)
status = Column(String, default='running')
started_at = Column(DateTime, default=datetime.utcnow)
completed_at = Column(DateTime)
# Relationships
results = relationship("ABTestResult", back_populates="ab_test")
class ABTestResult(Base):
__tablename__ = 'ab_test_results'
id = Column(Integer, primary_key=True, autoincrement=True)
ab_test_id = Column(Integer, ForeignKey('ab_tests.id', ondelete='CASCADE'), nullable=False)
variant = Column(String, nullable=False)
emails_sent = Column(Integer, default=0)
emails_delivered = Column(Integer, default=0)
emails_opened = Column(Integer, default=0)
emails_clicked = Column(Integer, default=0)
emails_replied = Column(Integer, default=0)
meetings_booked = Column(Integer, default=0)
# Relationships
ab_test = relationship("ABTest", back_populates="results")
__table_args__ = (
UniqueConstraint('ab_test_id', 'variant', name='uq_ab_test_variant'),
)
class Template(Base):
__tablename__ = 'templates'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
category = Column(String)
subject = Column(String, nullable=False)
body = Column(Text, nullable=False)
variables = Column(Text) # JSON
is_active = Column(Boolean, default=True)
usage_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class AnalyticsSnapshot(Base):
__tablename__ = 'analytics_snapshots'
id = Column(Integer, primary_key=True, autoincrement=True)
campaign_id = Column(Integer, ForeignKey('campaigns.id', ondelete='CASCADE'))
date = Column(Date, nullable=False)
hour = Column(Integer)
# Metrics
contacts_discovered = Column(Integer, default=0)
contacts_enriched = Column(Integer, default=0)
emails_sent = Column(Integer, default=0)
emails_opened = Column(Integer, default=0)
emails_clicked = Column(Integer, default=0)
emails_replied = Column(Integer, default=0)
meetings_booked = Column(Integer, default=0)
# Rates
open_rate = Column(Float, default=0.0)
click_rate = Column(Float, default=0.0)
response_rate = Column(Float, default=0.0)
meeting_rate = Column(Float, default=0.0)
created_at = Column(DateTime, default=datetime.utcnow)
__table_args__ = (
UniqueConstraint('campaign_id', 'date', 'hour', name='uq_analytics_snapshot'),
)
class Setting(Base):
__tablename__ = 'settings'
key = Column(String, primary_key=True)
value = Column(String, nullable=False)
description = Column(Text)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)