Files
TieMeasureFlow/src/backend/models/api/production.py
T

105 lines
3.7 KiB
Python

"""Pydantic schemas for production runs and their events."""
from src.backend.models.api.tipi import UtcDateTime
from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
class ProductionRunCreate(BaseModel):
station_code: str = Field(..., min_length=1, max_length=100)
recipe_id: int = Field(..., gt=0)
version_id: Optional[int] = Field(default=None, gt=0)
lot_number: Optional[str] = Field(default=None, max_length=100)
serial_number: Optional[str] = Field(default=None, max_length=100)
class ProductionEventResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
event_type: str
user_id: int
supervisor_id: Optional[int]
task_id: Optional[int] = None
note: Optional[str]
created_at: UtcDateTime
class ProductionRunResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
station_id: int
recipe_id: int
version_id: int
operator_id: int
lot_number: Optional[str]
serial_number: Optional[str]
status: str
measurement_interval_minutes: Optional[int]
next_measurement_at: Optional[UtcDateTime]
cycle_count: int
started_at: UtcDateTime
paused_at: Optional[UtcDateTime]
closed_at: Optional[UtcDateTime]
closed_by: Optional[int]
statistics_path: Optional[str] = None
# Derived server-side so every client agrees on the countdown regardless of
# clock skew. Negative once the interval has elapsed: how long the run has been
# overdue is a fact the operator must see, not deduce.
seconds_to_next_measurement: Optional[int] = None
overdue: bool = False
server_time: Optional[UtcDateTime] = None
# The measurement tasks of this run's version, in execution order. The client
# needs them to bring the operator back to the measurement when the interval
# expires, whichever screen they are on, and to know which task closes a cycle.
measurement_task_ids: list[int] = Field(default_factory=list)
# #53: task 'note' con at_close, da compilare prima della modale del capoturno.
close_task_ids: list[int] = Field(default_factory=list)
class ProductionRunWithEventsResponse(ProductionRunResponse):
events: list[ProductionEventResponse] = Field(default_factory=list)
class SupervisorAction(BaseModel):
"""Credentials of the supervisor authorising a line stop or a close."""
# #59: o username+password, oppure supervisor_user_id+supervisor_pin
supervisor_username: Optional[str] = Field(None, min_length=1)
supervisor_password: Optional[str] = Field(None, min_length=1)
supervisor_user_id: Optional[int] = Field(None, gt=0)
supervisor_pin: Optional[str] = Field(None, pattern=r"^[0-9]{4}$")
@model_validator(mode="after")
def _one_credential_pair(self):
by_password = self.supervisor_username and self.supervisor_password
by_pin = self.supervisor_user_id and self.supervisor_pin
if not (by_password or by_pin):
raise ValueError(
"supervisor credentials required: username+password or user_id+pin"
)
return self
note: Optional[str] = None
class CycleCompletePayload(BaseModel):
# Which measurement task was just finished. The interval restarts only on the
# last one of the recipe; omitting it closes the cycle outright.
task_id: Optional[int] = Field(default=None, gt=0)
note: Optional[str] = None
class RemeasurePayload(BaseModel):
"""The piece was turned over: measure it again inside the same cycle."""
task_id: Optional[int] = Field(default=None, gt=0)
note: Optional[str] = None
ProductionEventType = Literal[
"start", "cycle_completed", "task_measured", "remeasure",
"line_stop", "resume", "close",
]