from datetime import datetime, timezone from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from db.base import Base def _utcnow() -> datetime: return datetime.now(timezone.utc) class IntentStep(Base): """Шаг state machine внутри ветки (Спринт 6a, версионирование — Спринт 7.7). Шаги живут в БД, а не в коде: оператор редактирует промпт шага и список допустимых переходов через UI «Настройки → Шаги». `allowed_next` и `guards` хранятся как JSON-строки (парсим в сервисе), чтобы не городить отдельные таблицы. Со Спринта 7.7 каждый шаг принадлежит конкретной версии графа (`graph_id` → `intent_step_graphs.id`); UNIQUE по `(graph_id, code)`. Старая запись без графа допустима только в окне data-migration через `intent_step_graph_service.ensure_seed_graphs`. """ __tablename__ = "intent_steps" __table_args__ = (UniqueConstraint("graph_id", "code", name="uq_intent_step_graph_code"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) intent_id: Mapped[int] = mapped_column( ForeignKey("intents.id", ondelete="CASCADE"), nullable=False, index=True ) graph_id: Mapped[int | None] = mapped_column( ForeignKey("intent_step_graphs.id", ondelete="CASCADE"), nullable=True, index=True ) code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(200), nullable=False) order_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0) system_prompt: Mapped[str] = mapped_column(Text, nullable=False, default="") allowed_next_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]") guards_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow, nullable=False )