Files

162 lines
5.7 KiB
Python
Raw Permalink Normal View History

import enum
import uuid
from datetime import datetime, timezone
from sqlalchemy import String, Text, Boolean, Integer, ForeignKey, JSON, Enum as SAEnum
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class ConversationStatus(str, enum.Enum):
active = "active"
completed = "completed"
abandoned = "abandoned"
class MessageRole(str, enum.Enum):
user = "user"
assistant = "assistant"
system = "system"
class ConversationSource(str, enum.Enum):
standalone = "standalone"
embedded = "embedded"
widget = "widget"
app = "app"
class IntentLevel(str, enum.Enum):
interested = "interested"
not_interested = "not_interested"
pending = "pending"
class Conversation(Base):
__tablename__ = "conversations"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
)
session_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
page_token: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
status: Mapped[ConversationStatus] = mapped_column(
SAEnum(ConversationStatus), default=ConversationStatus.active
)
source: Mapped[ConversationSource] = mapped_column(
SAEnum(ConversationSource), default=ConversationSource.standalone
)
client_info: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
messages: Mapped[list["Message"]] = relationship(
back_populates="conversation", order_by="Message.created_at"
)
profile: Mapped["CustomerProfile | None"] = relationship(
back_populates="conversation", uselist=False
)
evaluation: Mapped["Evaluation | None"] = relationship(
back_populates="conversation", uselist=False
)
class Message(Base):
__tablename__ = "messages"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
)
conversation_id: Mapped[str] = mapped_column(
ForeignKey("conversations.id", ondelete="CASCADE")
)
role: Mapped[MessageRole] = mapped_column(SAEnum(MessageRole))
content: Mapped[str] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
conversation: Mapped["Conversation"] = relationship(back_populates="messages")
class CustomerProfile(Base):
__tablename__ = "customer_profiles"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
)
conversation_id: Mapped[str] = mapped_column(
ForeignKey("conversations.id", ondelete="CASCADE"), unique=True
)
raw_info: Mapped[dict | None] = mapped_column(JSON, nullable=True, default=dict)
summary: Mapped[str | None] = mapped_column(Text, nullable=True)
updated_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
conversation: Mapped["Conversation"] = relationship(back_populates="profile")
class Evaluation(Base):
__tablename__ = "evaluations"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
)
conversation_id: Mapped[str] = mapped_column(
ForeignKey("conversations.id", ondelete="CASCADE"), unique=True
)
intent_level: Mapped[IntentLevel] = mapped_column(SAEnum(IntentLevel))
reasoning: Mapped[str | None] = mapped_column(Text, nullable=True)
contact_info: Mapped[dict | None] = mapped_column(JSON, nullable=True)
email_sent: Mapped[bool] = mapped_column(Boolean, default=False)
correction_count: Mapped[int] = mapped_column(Integer, default=0)
evaluated_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
conversation: Mapped["Conversation"] = relationship(back_populates="evaluation")
class LeadPushLog(Base):
"""询盘推送 UniService 日志(设计 2026-09-28-lead-push-to-uniservice)。
status: pending → success/failed;skipped = contact 三选一(phone/whatsapp/
email)不满足,对方必拒,预检后不发 HTTP 只留审计痕迹。
outcome: 200 时对方逐条结论 accepted/duplicated(duplicated 不是错误)。
"""
__tablename__ = "lead_push_logs"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
)
evaluation_id: Mapped[str] = mapped_column(
ForeignKey("evaluations.id", ondelete="CASCADE"), index=True
)
conversation_id: Mapped[str] = mapped_column(
ForeignKey("conversations.id", ondelete="CASCADE"), index=True
)
push_type: Mapped[str] = mapped_column(String(16)) # lead/returning/correction
status: Mapped[str] = mapped_column(String(16), default="pending", index=True)
attempts: Mapped[int] = mapped_column(Integer, default=0)
request_payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
response_status: Mapped[int | None] = mapped_column(Integer, nullable=True)
outcome: Mapped[str | None] = mapped_column(String(16), nullable=True)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)