"""widen the production event trace for the measurement loop The loop needs to distinguish two things the trace could not say before: a measurement task finished while the cycle is still running (task_measured), and the piece turned over and measured again without closing the cycle (remeasure). Only the last measurement task of a recipe still produces cycle_completed, because only that one restarts the interval. Both values are added in one go. Widening a MySQL enum rewrites the table, so the values that are already planned go in now rather than one migration at a time - the same reasoning as the task types in 007. The events that happen inside a cycle also need to say which task they are about, so the column comes with them: a task_measured that does not name its task records nothing usable. Revision ID: 008_loop_events Revises: 007_task_type Create Date: 2026-07-28 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = '008_loop_events' down_revision: Union[str, None] = '007_task_type' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None OLD_TYPES = ('start', 'cycle_completed', 'line_stop', 'resume', 'close') NEW_TYPES = ( 'start', 'cycle_completed', 'task_measured', 'remeasure', 'line_stop', 'resume', 'close', ) def _alter(values: Sequence[str]) -> None: """Restate the column with a new set of allowed values. On MySQL this is the ALTER that rewrites the enum. On SQLite, where an enum is stored as a plain VARCHAR, batch mode rebuilds the table instead of emitting an ALTER the engine does not support - the tests run there. """ with op.batch_alter_table('production_events') as batch: batch.alter_column( 'event_type', existing_type=sa.Enum(*OLD_TYPES, name='production_event_type_enum'), type_=sa.Enum(*values, name='production_event_type_enum'), existing_nullable=False, ) def upgrade() -> None: _alter(NEW_TYPES) with op.batch_alter_table('production_events') as batch: batch.add_column(sa.Column('task_id', sa.Integer(), nullable=True)) batch.create_foreign_key( 'fk_production_events_task_id', 'recipe_tasks', ['task_id'], ['id'], ) def downgrade() -> None: with op.batch_alter_table('production_events') as batch: batch.drop_constraint('fk_production_events_task_id', type_='foreignkey') batch.drop_column('task_id') # The new kinds of event have no equivalent in the old vocabulary, and a row # left with a value the column no longer accepts would fail the rebuild. op.execute( "DELETE FROM production_events " "WHERE event_type IN ('task_measured', 'remeasure')" ) _alter(OLD_TYPES)