Files
HartOMat/backend/alembic/versions/037_workflow_definitions.py
T
Hartmut 7e47e4aca7 feat(C1+C2): workflow system — WorkflowDefinition + Celery Canvas builder
Migrations 037 (workflow tables + 3 seed definitions) + 038 (output_types.workflow_definition_id).
WorkflowDefinition/Run/NodeResult SQLAlchemy models in domains/rendering/models.py.
workflow_builder.py: dispatch_workflow() with Celery Canvas for still/turntable/multi_angle.
workflow_router.py: CRUD endpoints at /api/workflows (admin/PM guards).
dispatch_service.py: dispatch_render_with_workflow() prefers workflow path when
  OutputType.workflow_definition_id is set, falls back to legacy dispatch otherwise.
main.py: registers workflows_router.
models/__init__.py: re-exports WorkflowDefinition, WorkflowRun, WorkflowNodeResult.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 17:07:21 +01:00

65 lines
3.3 KiB
Python

"""Add workflow_definitions, workflow_runs, workflow_node_results tables.
Revision ID: 037
Revises: 036
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import UUID, JSONB
revision = '037'
down_revision = '036'
branch_labels = None
depends_on = None
def upgrade():
op.create_table('workflow_definitions',
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
sa.Column('name', sa.String(200), nullable=False),
sa.Column('output_type_id', UUID(as_uuid=True), sa.ForeignKey('output_types.id', ondelete='SET NULL'), nullable=True),
sa.Column('config', JSONB, nullable=False, server_default='{}'),
sa.Column('is_active', sa.Boolean, nullable=False, server_default='true'),
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
)
op.create_table('workflow_runs',
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
sa.Column('workflow_def_id', UUID(as_uuid=True), sa.ForeignKey('workflow_definitions.id', ondelete='SET NULL'), nullable=True),
sa.Column('order_line_id', UUID(as_uuid=True), sa.ForeignKey('order_lines.id', ondelete='CASCADE'), nullable=True),
sa.Column('celery_task_id', sa.String(500), nullable=True),
sa.Column('status', sa.String(50), nullable=False, server_default='pending'),
sa.Column('started_at', sa.DateTime, nullable=True),
sa.Column('completed_at', sa.DateTime, nullable=True),
sa.Column('error_message', sa.Text, nullable=True),
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
)
op.create_index('ix_workflow_runs_order_line', 'workflow_runs', ['order_line_id'])
op.create_index('ix_workflow_runs_status', 'workflow_runs', ['status'])
op.create_table('workflow_node_results',
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
sa.Column('run_id', UUID(as_uuid=True), sa.ForeignKey('workflow_runs.id', ondelete='CASCADE'), nullable=False),
sa.Column('node_name', sa.String(200), nullable=False),
sa.Column('status', sa.String(50), nullable=False, server_default='pending'),
sa.Column('output', JSONB, nullable=True),
sa.Column('log', sa.Text, nullable=True),
sa.Column('duration_s', sa.Float, nullable=True),
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
)
op.create_index('ix_workflow_node_results_run', 'workflow_node_results', ['run_id'])
# Seed standard workflow definitions
op.execute("""
INSERT INTO workflow_definitions (name, config, is_active) VALUES
('Still-Render', '{"type": "still", "params": {"render_engine": "cycles", "samples": 256, "resolution": [2048, 2048]}}', true),
('Turntable-Animation', '{"type": "turntable", "params": {"render_engine": "cycles", "samples": 64, "fps": 24, "duration_s": 5}}', true),
('Multi-Angle', '{"type": "multi_angle", "params": {"render_engine": "cycles", "samples": 128, "angles": [0, 45, 90, 135, 180]}}', true)
""")
def downgrade():
op.drop_table('workflow_node_results')
op.drop_table('workflow_runs')
op.drop_table('workflow_definitions')