system-prompts-and-models-o.../salesflow-saas/backend/app/models/sequence.py
Claude a329957a3b
feat: Add AI engine, PDPL compliance, sequences, CPQ, and governance layers
Phase 1-6 implementation for Dealix AI Revenue OS:

- AI Arabic Engine: NLP (arabic_nlp.py), lead scoring (lead_scoring.py)
- PDPL Compliance: consent manager, data rights handler, consent model
- Sequence Engine: multi-channel sequences with WhatsApp/Email/SMS
- CPQ System: quote engine, AI proposal generator
- Security Gate: pre-release checks, PDPL message validation
- Tool Verification: agent action audit trail
- Project Operating Files: AGENTS.md, CLAUDE.md
- Project Memory: architecture, ADRs, provider routing, PDPL checklist
- Design System: IBM Plex Sans Arabic tokens, RTL-safe components
- Sequence/Consent models for database

https://claude.ai/code/session_01LsnvBa7HwF5hs99VZbgLGj
2026-04-11 07:40:39 +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))
metadata = Column(JSONB, default=dict)
enrollment = relationship("SequenceEnrollment", back_populates="events")
step = relationship("SequenceStep", back_populates="events")