bde8fafd77
Il conto alla rovescia non si ferma piu' a zero. Oltre la scadenza continua nell'altro senso e dice da quanto la linea e' in ritardo, in rosso, su tutte le schermate del percorso di misura: lista task, riepilogo, scelta ricetta. Un ritardo va letto, non dedotto. Allo scadere dell'intervallo la misura si ripropone da sola: cicalino, un avviso di cinque secondi, e l'operatore torna al primo task di misura da qualunque schermata si trovi. Un ciclo copre tutti i task di misura della ricetta, quindi solo l'ultimo lo chiude e fa ripartire l'intervallo. Farlo ripartire al primo avrebbe fatto misurare una ricetta con tre task di misura tre volte piu' spesso di come e' configurata. E' il server a decidere quale task chiude il ciclo, perche' e' lui a conoscere la sequenza: la lista dei task di misura viaggia con la produzione (measurement_task_ids), che e' anche cio' che permette a una schermata qualsiasi di sapere dove riportare l'operatore. Aggiunta la rimisura: si gira il pezzo e si misura di nuovo dentro lo stesso ciclo, senza chiudere niente e senza guadagnare tempo sulla scadenza. Le due letture restano entrambe in statistica, che e' il motivo per cui si prendono. Chi sta gia' misurando quando l'intervallo scade e' in ritardo, non perso: la banda diventa rossa e lo si lascia lavorare. Portarlo altrove a meta' ciclo cancellerebbe quote che ha davanti agli occhi senza guadagnare nulla. Sistemato anche il pulsante "Conferma ciclo" dell'overlay: alzava una bandierina locale e basta, quindi il ciclo non veniva mai registrato sul server da quella strada. Ora passa da confirmCycle come il pulsante della barra. Migrazione 008: il registro eventi della produzione impara task_measured e remeasure, e la colonna task_id — un task_measured che non dice quale task non registra niente di utile. I valori nuovi entrano tutti insieme perche' allargare una enum MySQL riscrive la tabella, stesso ragionamento dei tipi di task in 007. Verificata su SQLite (batch mode) e in MySQL con --sql. La logica dell'orologio — come si legge un ritardo, quando suona, dove sta la misura — vive in un solo posto (production-clock.js) e la schermata di misura la usa invece di riscriverla. Test: +10 (265). Coprono il ciclo che non riparte a meta', la rimisura che non sposta la scadenza, il rifiuto a linea ferma, la sequenza dei task di misura esposta dalla produzione, i proxy Flask e la validita' JS della lista task. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""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)
|