Files
ai-kefu-admin/app/engine/models.py
T

162 lines
5.7 KiB
Python

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),
)