kirche-einfach/backend/app/models/flow.py

93 lines
3.9 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())
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())