"""Flow engine domain models (SQLModel tables).""" from datetime import UTC, datetime from uuid import UUID, uuid4 from sqlalchemy import JSON, Column, DateTime, UniqueConstraint from sqlmodel import Field, SQLModel def utcnow() -> datetime: return datetime.now(UTC) def _ts_column() -> Column: return Column(DateTime(timezone=True)) def _json_column() -> Column: # Generic JSON: JSONB on Postgres, JSON/TEXT elsewhere. return Column(JSON) class FlowDefinition(SQLModel, table=True): """A named decision flow (e.g. 'Bauvorhaben pruefen'). Versions live separately.""" __tablename__ = "flow_definitions" id: UUID = Field(default_factory=uuid4, primary_key=True) name: str = Field(index=True, unique=True, max_length=200) description: str = Field(default="", max_length=2000) status: str = Field(default="active", max_length=20) # active | archived # Filename under backend/flows/ this definition is synced from # (e.g. "simple_rule.json"). None for definitions created via the API. source_path: str | None = Field(default=None, max_length=255) # Flow to run after a human approval on this flow's runs (nullable). continuation_flow_id: UUID | None = Field(default=None, foreign_key="flow_definitions.id") created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column()) class FlowVersion(SQLModel, table=True): """An immutable version of a flow: the exact Langflow JSON that runs.""" __tablename__ = "flow_versions" __table_args__ = (UniqueConstraint("flow_id", "version_no"),) id: UUID = Field(default_factory=uuid4, primary_key=True) flow_id: UUID = Field(foreign_key="flow_definitions.id", index=True) version_no: int = Field(index=True) langflow_json: dict = Field(default_factory=dict, sa_column=_json_column()) # Flow id under which LFX serves this version (set on deploy). lfx_flow_id: str | None = Field(default=None, max_length=200) deployed_at: datetime | None = Field(default=None, sa_column=_ts_column()) author: str = Field(default="", max_length=200) created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column()) # Cached form schema for this exact JSON (see app/flows/input_schema.py). # Computed at version creation; nullable for pre-Phase-3 rows. input_schema: dict | None = Field(default=None, sa_column=_json_column()) class FlowRun(SQLModel, table=True): """One execution of a flow version.""" __tablename__ = "flow_runs" id: UUID = Field(default_factory=uuid4, primary_key=True) version_id: UUID = Field(foreign_key="flow_versions.id", index=True) tenant_id: str = Field(default="default", index=True, max_length=200) # running | completed | pending_approval | failed status: str = Field(default="running", index=True, max_length=30) input: dict | None = Field(default=None, sa_column=_json_column()) output: dict | None = Field(default=None, sa_column=_json_column()) session_id: str | None = Field(default=None, max_length=200) created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column()) finished_at: datetime | None = Field(default=None, sa_column=_ts_column()) class ApprovalTask(SQLModel, table=True): """Human task created by a flow that reached a terminal approval state.""" __tablename__ = "approval_tasks" id: UUID = Field(default_factory=uuid4, primary_key=True) run_id: UUID = Field(foreign_key="flow_runs.id", index=True) version_id: UUID = Field(foreign_key="flow_versions.id", index=True) tenant_id: str = Field(default="default", index=True, max_length=200) title: str = Field(default="", max_length=500) payload: dict = Field(default_factory=dict, sa_column=_json_column()) continuation_flow_id: UUID | None = Field(default=None) approver: str | None = Field(default=None, max_length=200) # open | approved | rejected status: str = Field(default="open", index=True, max_length=20) decision_note: str = Field(default="", max_length=2000) created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column()) decided_at: datetime | None = Field(default=None, sa_column=_ts_column())