96 lines
4.2 KiB
Python
96 lines
4.2 KiB
Python
"""Flow engine domain models (SQLModel tables)."""
|
|
|
|
from datetime import datetime, timezone
|
|
from uuid import UUID, uuid4
|
|
|
|
from sqlalchemy import JSON, Column, DateTime, UniqueConstraint
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
return datetime.now(timezone.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())
|