system-prompts-and-models-o.../salesflow-saas/backend/app/models/sequence.py
Cursor Agent 8c3d91c070
fix(dealix): resolve Python deps, SQLAlchemy metadata, JWT, and frontend CI
- Align httpx, litellm, langchain, openai, mem0ai, crewai, numpy, requests, pydantic
- Rename SequenceEvent ORM attribute to event_metadata (DB column stays metadata)
- Use PyJWT instead of python-jose in security and auth service
- Mem0: MemoryConfig + graceful fallback when init fails (CI without keys)
- Frontend: I18nProvider in root layout, fix dashboard LeadScoreCard props, Section id,
  kpi-card useRef, en.json nameAr parity, e2e assertion for premium landing
- README: troubleshooting for connection refused and local E2E Playwright install

Co-authored-by: VoXc2 <VoXc2@users.noreply.github.com>
2026-04-12 10:32:05 +00:00

108 lines
4.2 KiB
Python

"""Sequence engine models for multi-channel outreach."""
import enum
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Index, Text
from sqlalchemy.orm import relationship
from datetime import datetime, timezone
from app.models.base import TenantModel, BaseModel
from app.models.compat import UUID, JSONB
class SequenceStatus(str, enum.Enum):
ACTIVE = "active"
PAUSED = "paused"
COMPLETED = "completed"
STOPPED = "stopped"
class SequenceEventStatus(str, enum.Enum):
SENT = "sent"
DELIVERED = "delivered"
OPENED = "opened"
REPLIED = "replied"
FAILED = "failed"
class Sequence(TenantModel):
"""A reusable multi-step outreach sequence."""
__tablename__ = "sequences"
__table_args__ = (
Index("ix_seq_tenant_active", "tenant_id", "is_active"),
)
name = Column(String(255), nullable=False)
name_ar = Column(String(255), nullable=True)
description = Column(Text, nullable=True)
trigger_event = Column(String(100), nullable=True) # lead_created, stage_changed, etc.
is_active = Column(Boolean, default=True)
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
steps = relationship("SequenceStep", back_populates="sequence", order_by="SequenceStep.step_order")
enrollments = relationship("SequenceEnrollment", back_populates="sequence")
creator = relationship("User", foreign_keys=[created_by])
class SequenceStep(BaseModel):
"""A single step within a sequence."""
__tablename__ = "sequence_steps"
__table_args__ = (
Index("ix_step_sequence_order", "sequence_id", "step_order"),
)
sequence_id = Column(UUID(as_uuid=True), ForeignKey("sequences.id", ondelete="CASCADE"), nullable=False)
step_order = Column(Integer, nullable=False)
channel = Column(String(50), nullable=False) # whatsapp, email, sms
delay_minutes = Column(Integer, nullable=False, default=0)
template_content = Column(Text, nullable=False)
template_content_ar = Column(Text, nullable=True)
variant = Column(String(1), nullable=True) # A or B for A/B testing
conditions = Column(JSONB, default=dict) # e.g. {"only_if_no_reply": true}
sequence = relationship("Sequence", back_populates="steps")
events = relationship("SequenceEvent", back_populates="step")
class SequenceEnrollment(BaseModel):
"""Tracks a single lead's progress through a sequence."""
__tablename__ = "sequence_enrollments"
__table_args__ = (
Index("ix_enroll_sequence_status", "sequence_id", "status"),
Index("ix_enroll_lead", "lead_id"),
)
sequence_id = Column(UUID(as_uuid=True), ForeignKey("sequences.id", ondelete="CASCADE"), nullable=False)
lead_id = Column(UUID(as_uuid=True), ForeignKey("leads.id"), nullable=False)
current_step = Column(Integer, default=0)
status = Column(String(50), nullable=False, default=SequenceStatus.ACTIVE.value)
enrolled_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
completed_at = Column(DateTime(timezone=True), nullable=True)
next_step_at = Column(DateTime(timezone=True), nullable=True)
sequence = relationship("Sequence", back_populates="enrollments")
lead = relationship("Lead", foreign_keys=[lead_id])
events = relationship("SequenceEvent", back_populates="enrollment")
class SequenceEvent(BaseModel):
"""Records every message sent or event within a sequence enrollment."""
__tablename__ = "sequence_events"
__table_args__ = (
Index("ix_sevent_enrollment", "enrollment_id"),
Index("ix_sevent_step", "step_id"),
)
enrollment_id = Column(UUID(as_uuid=True), ForeignKey("sequence_enrollments.id", ondelete="CASCADE"), nullable=False)
step_id = Column(UUID(as_uuid=True), ForeignKey("sequence_steps.id"), nullable=False)
channel = Column(String(50), nullable=False)
status = Column(String(50), nullable=False, default=SequenceEventStatus.SENT.value)
sent_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
event_metadata = Column("metadata", JSONB, default=dict)
enrollment = relationship("SequenceEnrollment", back_populates="events")
step = relationship("SequenceStep", back_populates="events")